From b831aa747f0c28d3ec4ab07e6b0808f10ba01bde Mon Sep 17 00:00:00 2001 From: oxmc <67136658+oxmc@users.noreply.github.com> Date: Fri, 28 Mar 2025 11:39:20 -0400 Subject: [PATCH] Upload Base --- .gitignore | 22 + LICENSE | 32 + README.md | 36 + build.gradle | 122 ++ gradle.properties | 5 + gradle/wrapper/gradle-wrapper.jar | Bin 0 -> 54708 bytes gradle/wrapper/gradle-wrapper.properties | 5 + gradlew | 172 ++ gradlew.bat | 84 + src/main/java/net/montoyo/mcef/BaseProxy.java | 224 +++ .../net/montoyo/mcef/LetsEncryptAdder.java | 102 ++ src/main/java/net/montoyo/mcef/MCEF.java | 98 + src/main/java/net/montoyo/mcef/api/API.java | 96 + .../java/net/montoyo/mcef/api/IBrowser.java | 138 ++ .../net/montoyo/mcef/api/IDisplayHandler.java | 35 + .../montoyo/mcef/api/IJSQueryCallback.java | 24 + .../net/montoyo/mcef/api/IJSQueryHandler.java | 39 + .../java/net/montoyo/mcef/api/IScheme.java | 9 + .../montoyo/mcef/api/ISchemeResponseData.java | 9 + .../mcef/api/ISchemeResponseHeaders.java | 11 + .../net/montoyo/mcef/api/IStringVisitor.java | 7 + .../java/net/montoyo/mcef/api/MCEFApi.java | 29 + .../montoyo/mcef/api/SchemePreResponse.java | 9 + .../net/montoyo/mcef/client/AppHandler.java | 103 ++ .../net/montoyo/mcef/client/ClientProxy.java | 374 ++++ .../montoyo/mcef/client/DisplayHandler.java | 116 ++ .../montoyo/mcef/client/MessageRouter.java | 29 + .../montoyo/mcef/client/QueryCallback.java | 24 + .../mcef/client/SchemeResourceHandler.java | 48 + .../mcef/client/SchemeResponseData.java | 33 + .../mcef/client/SchemeResponseHeaders.java | 45 + .../montoyo/mcef/client/ShutdownThread.java | 69 + .../montoyo/mcef/client/StringVisitor.java | 20 + .../net/montoyo/mcef/client/UpdateFrame.java | 79 + .../montoyo/mcef/coremod/ShutdownPatcher.java | 114 ++ .../montoyo/mcef/example/BrowserScreen.java | 250 +++ .../net/montoyo/mcef/example/ExampleMod.java | 167 ++ .../net/montoyo/mcef/example/ModScheme.java | 78 + .../net/montoyo/mcef/example/ScreenCfg.java | 120 ++ .../java/net/montoyo/mcef/remote/Mirror.java | 104 ++ .../montoyo/mcef/remote/MirrorManager.java | 92 + .../net/montoyo/mcef/remote/RemoteConfig.java | 354 ++++ .../net/montoyo/mcef/setup/CfgParser.java | 348 ++++ .../net/montoyo/mcef/setup/ConfigForm.java | 223 +++ .../montoyo/mcef/setup/DefaultComparator.java | 17 + .../java/net/montoyo/mcef/setup/Deleter.java | 47 + .../net/montoyo/mcef/setup/FileListing.java | 120 ++ .../montoyo/mcef/setup/McLocationPrompt.java | 200 +++ .../net/montoyo/mcef/setup/Processes.java | 125 ++ .../java/net/montoyo/mcef/setup/SetupUI.java | 199 +++ .../net/montoyo/mcef/setup/SetupUtil.java | 94 + .../montoyo/mcef/setup/SlashComparator.java | 50 + .../mcef/utilities/DummyProgressListener.java | 23 + .../mcef/utilities/ForgeProgressListener.java | 44 + .../mcef/utilities/ForgeVersionParser.java | 42 + .../mcef/utilities/IProgressListener.java | 27 + .../java/net/montoyo/mcef/utilities/Log.java | 29 + .../net/montoyo/mcef/utilities/Platform.java | 97 + .../java/net/montoyo/mcef/utilities/Util.java | 347 ++++ .../net/montoyo/mcef/utilities/Version.java | 107 ++ .../montoyo/mcef/virtual/VirtualBrowser.java | 80 + src/main/java/org/cef/CefApp.java | 546 ++++++ src/main/java/org/cef/CefClient.java | 869 +++++++++ src/main/java/org/cef/CefSettings.java | 259 +++ src/main/java/org/cef/OS.java | 44 + src/main/java/org/cef/SystemBootstrap.java | 38 + src/main/java/org/cef/browser/CefBrowser.java | 383 ++++ .../org/cef/browser/CefBrowserFactory.java | 18 + .../java/org/cef/browser/CefBrowserOsr.java | 346 ++++ .../org/cef/browser/CefBrowserWindow.java | 20 + .../java/org/cef/browser/CefBrowserWr.java | 424 +++++ .../java/org/cef/browser/CefBrowser_N.java | 817 +++++++++ .../cef/browser/CefDropTargetListener.java | 127 ++ src/main/java/org/cef/browser/CefFrame.java | 101 ++ src/main/java/org/cef/browser/CefFrame_N.java | 165 ++ .../org/cef/browser/CefMessageRouter.java | 261 +++ .../org/cef/browser/CefMessageRouter_N.java | 82 + .../java/org/cef/browser/CefRenderer.java | 209 +++ .../org/cef/browser/CefRequestContext.java | 53 + .../org/cef/browser/CefRequestContext_N.java | 86 + .../cef/browser/mac/CefBrowserWindowMac.java | 16 + .../org/cef/callback/CefAuthCallback.java | 21 + .../org/cef/callback/CefAuthCallback_N.java | 36 + .../callback/CefBeforeDownloadCallback.java | 20 + .../callback/CefBeforeDownloadCallback_N.java | 26 + .../java/org/cef/callback/CefCallback.java | 20 + .../java/org/cef/callback/CefCallback_N.java | 36 + .../java/org/cef/callback/CefCommandLine.java | 95 + .../org/cef/callback/CefCommandLine_N.java | 154 ++ .../cef/callback/CefCompletionCallback.java | 15 + .../cef/callback/CefContextMenuParams.java | 180 ++ .../cef/callback/CefContextMenuParams_N.java | 210 +++ .../org/cef/callback/CefCookieVisitor.java | 23 + .../org/cef/callback/CefDownloadItem.java | 94 + .../cef/callback/CefDownloadItemCallback.java | 25 + .../callback/CefDownloadItemCallback_N.java | 51 + .../org/cef/callback/CefDownloadItem_N.java | 188 ++ .../java/org/cef/callback/CefDragData.java | 189 ++ .../java/org/cef/callback/CefDragData_N.java | 287 +++ .../cef/callback/CefFileDialogCallback.java | 28 + .../cef/callback/CefFileDialogCallback_N.java | 39 + .../org/cef/callback/CefJSDialogCallback.java | 19 + .../cef/callback/CefJSDialogCallback_N.java | 26 + .../java/org/cef/callback/CefMenuModel.java | 338 ++++ .../java/org/cef/callback/CefMenuModel_N.java | 568 ++++++ src/main/java/org/cef/callback/CefNative.java | 29 + .../org/cef/callback/CefNativeAdapter.java | 16 + .../org/cef/callback/CefPdfPrintCallback.java | 22 + .../cef/callback/CefPrintDialogCallback.java | 22 + .../callback/CefPrintDialogCallback_N.java | 38 + .../org/cef/callback/CefPrintJobCallback.java | 15 + .../cef/callback/CefPrintJobCallback_N.java | 26 + .../org/cef/callback/CefQueryCallback.java | 25 + .../org/cef/callback/CefQueryCallback_N.java | 36 + .../callback/CefRunFileDialogCallback.java | 26 + .../cef/callback/CefSchemeHandlerFactory.java | 32 + .../org/cef/callback/CefSchemeRegistrar.java | 75 + .../cef/callback/CefSchemeRegistrar_N.java | 24 + .../org/cef/callback/CefStringVisitor.java | 16 + .../org/cef/callback/CefURLRequestClient.java | 54 + .../java/org/cef/handler/CefAppHandler.java | 75 + .../org/cef/handler/CefAppHandlerAdapter.java | 96 + .../org/cef/handler/CefClientHandler.java | 317 ++++ .../cef/handler/CefContextMenuHandler.java | 59 + .../handler/CefContextMenuHandlerAdapter.java | 30 + .../cef/handler/CefCookieAccessFilter.java | 52 + .../handler/CefCookieAccessFilterAdapter.java | 30 + .../org/cef/handler/CefDialogHandler.java | 51 + .../org/cef/handler/CefDisplayHandler.java | 65 + .../cef/handler/CefDisplayHandlerAdapter.java | 47 + .../org/cef/handler/CefDownloadHandler.java | 39 + .../handler/CefDownloadHandlerAdapter.java | 25 + .../java/org/cef/handler/CefDragHandler.java | 39 + .../java/org/cef/handler/CefFocusHandler.java | 48 + .../cef/handler/CefFocusHandlerAdapter.java | 27 + .../org/cef/handler/CefJSDialogHandler.java | 81 + .../handler/CefJSDialogHandlerAdapter.java | 35 + .../org/cef/handler/CefKeyboardHandler.java | 140 ++ .../handler/CefKeyboardHandlerAdapter.java | 26 + .../org/cef/handler/CefLifeSpanHandler.java | 65 + .../handler/CefLifeSpanHandlerAdapter.java | 35 + .../java/org/cef/handler/CefLoadHandler.java | 341 ++++ .../cef/handler/CefLoadHandlerAdapter.java | 30 + .../cef/handler/CefMessageRouterHandler.java | 43 + .../CefMessageRouterHandlerAdapter.java | 29 + .../java/org/cef/handler/CefPrintHandler.java | 77 + .../cef/handler/CefPrintHandlerAdapter.java | 60 + .../org/cef/handler/CefRenderHandler.java | 106 ++ .../cef/handler/CefRenderHandlerAdapter.java | 57 + .../cef/handler/CefRequestContextHandler.java | 40 + .../CefRequestContextHandlerAdapter.java | 24 + .../org/cef/handler/CefRequestHandler.java | 142 ++ .../cef/handler/CefRequestHandlerAdapter.java | 61 + .../org/cef/handler/CefResourceHandler.java | 59 + .../handler/CefResourceHandlerAdapter.java | 37 + .../handler/CefResourceRequestHandler.java | 144 ++ .../CefResourceRequestHandlerAdapter.java | 56 + .../java/org/cef/handler/CefScreenInfo.java | 36 + .../org/cef/handler/CefWindowHandler.java | 42 + .../cef/handler/CefWindowHandlerAdapter.java | 25 + src/main/java/org/cef/misc/BoolRef.java | 26 + src/main/java/org/cef/misc/CefPageRange.java | 20 + .../org/cef/misc/CefPdfPrintSettings.java | 111 ++ .../java/org/cef/misc/CefPrintSettings.java | 188 ++ .../java/org/cef/misc/CefPrintSettings_N.java | 286 +++ src/main/java/org/cef/misc/EventFlags.java | 21 + src/main/java/org/cef/misc/IntRef.java | 26 + src/main/java/org/cef/misc/StringRef.java | 26 + src/main/java/org/cef/network/CefCookie.java | 77 + .../org/cef/network/CefCookieManager.java | 88 + .../org/cef/network/CefCookieManager_N.java | 116 ++ .../java/org/cef/network/CefPostData.java | 81 + .../org/cef/network/CefPostDataElement.java | 127 ++ .../org/cef/network/CefPostDataElement_N.java | 132 ++ .../java/org/cef/network/CefPostData_N.java | 116 ++ src/main/java/org/cef/network/CefRequest.java | 505 ++++++ .../java/org/cef/network/CefRequest_N.java | 284 +++ .../java/org/cef/network/CefResponse.java | 130 ++ .../java/org/cef/network/CefResponse_N.java | 187 ++ .../java/org/cef/network/CefURLRequest.java | 89 + .../java/org/cef/network/CefURLRequest_N.java | 109 ++ .../assets/letsencrypt/isrgrootx1.der | Bin 0 -> 1391 bytes src/main/resources/assets/mcef/html/home.html | 56 + src/main/resources/assets/mcef/html/jquery.js | 6 + src/main/resources/assets/mcef/mime.types | 1588 +++++++++++++++++ src/main/resources/mcmod.info | 16 + src/main/resources/pack.mcmeta | 6 + 187 files changed, 20962 insertions(+) create mode 100644 .gitignore create mode 100644 LICENSE create mode 100644 README.md create mode 100644 build.gradle create mode 100644 gradle.properties create mode 100644 gradle/wrapper/gradle-wrapper.jar create mode 100644 gradle/wrapper/gradle-wrapper.properties create mode 100644 gradlew create mode 100644 gradlew.bat create mode 100644 src/main/java/net/montoyo/mcef/BaseProxy.java create mode 100644 src/main/java/net/montoyo/mcef/LetsEncryptAdder.java create mode 100644 src/main/java/net/montoyo/mcef/MCEF.java create mode 100644 src/main/java/net/montoyo/mcef/api/API.java create mode 100644 src/main/java/net/montoyo/mcef/api/IBrowser.java create mode 100644 src/main/java/net/montoyo/mcef/api/IDisplayHandler.java create mode 100644 src/main/java/net/montoyo/mcef/api/IJSQueryCallback.java create mode 100644 src/main/java/net/montoyo/mcef/api/IJSQueryHandler.java create mode 100644 src/main/java/net/montoyo/mcef/api/IScheme.java create mode 100644 src/main/java/net/montoyo/mcef/api/ISchemeResponseData.java create mode 100644 src/main/java/net/montoyo/mcef/api/ISchemeResponseHeaders.java create mode 100644 src/main/java/net/montoyo/mcef/api/IStringVisitor.java create mode 100644 src/main/java/net/montoyo/mcef/api/MCEFApi.java create mode 100644 src/main/java/net/montoyo/mcef/api/SchemePreResponse.java create mode 100644 src/main/java/net/montoyo/mcef/client/AppHandler.java create mode 100644 src/main/java/net/montoyo/mcef/client/ClientProxy.java create mode 100644 src/main/java/net/montoyo/mcef/client/DisplayHandler.java create mode 100644 src/main/java/net/montoyo/mcef/client/MessageRouter.java create mode 100644 src/main/java/net/montoyo/mcef/client/QueryCallback.java create mode 100644 src/main/java/net/montoyo/mcef/client/SchemeResourceHandler.java create mode 100644 src/main/java/net/montoyo/mcef/client/SchemeResponseData.java create mode 100644 src/main/java/net/montoyo/mcef/client/SchemeResponseHeaders.java create mode 100644 src/main/java/net/montoyo/mcef/client/ShutdownThread.java create mode 100644 src/main/java/net/montoyo/mcef/client/StringVisitor.java create mode 100644 src/main/java/net/montoyo/mcef/client/UpdateFrame.java create mode 100644 src/main/java/net/montoyo/mcef/coremod/ShutdownPatcher.java create mode 100644 src/main/java/net/montoyo/mcef/example/BrowserScreen.java create mode 100644 src/main/java/net/montoyo/mcef/example/ExampleMod.java create mode 100644 src/main/java/net/montoyo/mcef/example/ModScheme.java create mode 100644 src/main/java/net/montoyo/mcef/example/ScreenCfg.java create mode 100644 src/main/java/net/montoyo/mcef/remote/Mirror.java create mode 100644 src/main/java/net/montoyo/mcef/remote/MirrorManager.java create mode 100644 src/main/java/net/montoyo/mcef/remote/RemoteConfig.java create mode 100644 src/main/java/net/montoyo/mcef/setup/CfgParser.java create mode 100644 src/main/java/net/montoyo/mcef/setup/ConfigForm.java create mode 100644 src/main/java/net/montoyo/mcef/setup/DefaultComparator.java create mode 100644 src/main/java/net/montoyo/mcef/setup/Deleter.java create mode 100644 src/main/java/net/montoyo/mcef/setup/FileListing.java create mode 100644 src/main/java/net/montoyo/mcef/setup/McLocationPrompt.java create mode 100644 src/main/java/net/montoyo/mcef/setup/Processes.java create mode 100644 src/main/java/net/montoyo/mcef/setup/SetupUI.java create mode 100644 src/main/java/net/montoyo/mcef/setup/SetupUtil.java create mode 100644 src/main/java/net/montoyo/mcef/setup/SlashComparator.java create mode 100644 src/main/java/net/montoyo/mcef/utilities/DummyProgressListener.java create mode 100644 src/main/java/net/montoyo/mcef/utilities/ForgeProgressListener.java create mode 100644 src/main/java/net/montoyo/mcef/utilities/ForgeVersionParser.java create mode 100644 src/main/java/net/montoyo/mcef/utilities/IProgressListener.java create mode 100644 src/main/java/net/montoyo/mcef/utilities/Log.java create mode 100644 src/main/java/net/montoyo/mcef/utilities/Platform.java create mode 100644 src/main/java/net/montoyo/mcef/utilities/Util.java create mode 100644 src/main/java/net/montoyo/mcef/utilities/Version.java create mode 100644 src/main/java/net/montoyo/mcef/virtual/VirtualBrowser.java create mode 100644 src/main/java/org/cef/CefApp.java create mode 100644 src/main/java/org/cef/CefClient.java create mode 100644 src/main/java/org/cef/CefSettings.java create mode 100644 src/main/java/org/cef/OS.java create mode 100644 src/main/java/org/cef/SystemBootstrap.java create mode 100644 src/main/java/org/cef/browser/CefBrowser.java create mode 100644 src/main/java/org/cef/browser/CefBrowserFactory.java create mode 100644 src/main/java/org/cef/browser/CefBrowserOsr.java create mode 100644 src/main/java/org/cef/browser/CefBrowserWindow.java create mode 100644 src/main/java/org/cef/browser/CefBrowserWr.java create mode 100644 src/main/java/org/cef/browser/CefBrowser_N.java create mode 100644 src/main/java/org/cef/browser/CefDropTargetListener.java create mode 100644 src/main/java/org/cef/browser/CefFrame.java create mode 100644 src/main/java/org/cef/browser/CefFrame_N.java create mode 100644 src/main/java/org/cef/browser/CefMessageRouter.java create mode 100644 src/main/java/org/cef/browser/CefMessageRouter_N.java create mode 100644 src/main/java/org/cef/browser/CefRenderer.java create mode 100644 src/main/java/org/cef/browser/CefRequestContext.java create mode 100644 src/main/java/org/cef/browser/CefRequestContext_N.java create mode 100644 src/main/java/org/cef/browser/mac/CefBrowserWindowMac.java create mode 100644 src/main/java/org/cef/callback/CefAuthCallback.java create mode 100644 src/main/java/org/cef/callback/CefAuthCallback_N.java create mode 100644 src/main/java/org/cef/callback/CefBeforeDownloadCallback.java create mode 100644 src/main/java/org/cef/callback/CefBeforeDownloadCallback_N.java create mode 100644 src/main/java/org/cef/callback/CefCallback.java create mode 100644 src/main/java/org/cef/callback/CefCallback_N.java create mode 100644 src/main/java/org/cef/callback/CefCommandLine.java create mode 100644 src/main/java/org/cef/callback/CefCommandLine_N.java create mode 100644 src/main/java/org/cef/callback/CefCompletionCallback.java create mode 100644 src/main/java/org/cef/callback/CefContextMenuParams.java create mode 100644 src/main/java/org/cef/callback/CefContextMenuParams_N.java create mode 100644 src/main/java/org/cef/callback/CefCookieVisitor.java create mode 100644 src/main/java/org/cef/callback/CefDownloadItem.java create mode 100644 src/main/java/org/cef/callback/CefDownloadItemCallback.java create mode 100644 src/main/java/org/cef/callback/CefDownloadItemCallback_N.java create mode 100644 src/main/java/org/cef/callback/CefDownloadItem_N.java create mode 100644 src/main/java/org/cef/callback/CefDragData.java create mode 100644 src/main/java/org/cef/callback/CefDragData_N.java create mode 100644 src/main/java/org/cef/callback/CefFileDialogCallback.java create mode 100644 src/main/java/org/cef/callback/CefFileDialogCallback_N.java create mode 100644 src/main/java/org/cef/callback/CefJSDialogCallback.java create mode 100644 src/main/java/org/cef/callback/CefJSDialogCallback_N.java create mode 100644 src/main/java/org/cef/callback/CefMenuModel.java create mode 100644 src/main/java/org/cef/callback/CefMenuModel_N.java create mode 100644 src/main/java/org/cef/callback/CefNative.java create mode 100644 src/main/java/org/cef/callback/CefNativeAdapter.java create mode 100644 src/main/java/org/cef/callback/CefPdfPrintCallback.java create mode 100644 src/main/java/org/cef/callback/CefPrintDialogCallback.java create mode 100644 src/main/java/org/cef/callback/CefPrintDialogCallback_N.java create mode 100644 src/main/java/org/cef/callback/CefPrintJobCallback.java create mode 100644 src/main/java/org/cef/callback/CefPrintJobCallback_N.java create mode 100644 src/main/java/org/cef/callback/CefQueryCallback.java create mode 100644 src/main/java/org/cef/callback/CefQueryCallback_N.java create mode 100644 src/main/java/org/cef/callback/CefRunFileDialogCallback.java create mode 100644 src/main/java/org/cef/callback/CefSchemeHandlerFactory.java create mode 100644 src/main/java/org/cef/callback/CefSchemeRegistrar.java create mode 100644 src/main/java/org/cef/callback/CefSchemeRegistrar_N.java create mode 100644 src/main/java/org/cef/callback/CefStringVisitor.java create mode 100644 src/main/java/org/cef/callback/CefURLRequestClient.java create mode 100644 src/main/java/org/cef/handler/CefAppHandler.java create mode 100644 src/main/java/org/cef/handler/CefAppHandlerAdapter.java create mode 100644 src/main/java/org/cef/handler/CefClientHandler.java create mode 100644 src/main/java/org/cef/handler/CefContextMenuHandler.java create mode 100644 src/main/java/org/cef/handler/CefContextMenuHandlerAdapter.java create mode 100644 src/main/java/org/cef/handler/CefCookieAccessFilter.java create mode 100644 src/main/java/org/cef/handler/CefCookieAccessFilterAdapter.java create mode 100644 src/main/java/org/cef/handler/CefDialogHandler.java create mode 100644 src/main/java/org/cef/handler/CefDisplayHandler.java create mode 100644 src/main/java/org/cef/handler/CefDisplayHandlerAdapter.java create mode 100644 src/main/java/org/cef/handler/CefDownloadHandler.java create mode 100644 src/main/java/org/cef/handler/CefDownloadHandlerAdapter.java create mode 100644 src/main/java/org/cef/handler/CefDragHandler.java create mode 100644 src/main/java/org/cef/handler/CefFocusHandler.java create mode 100644 src/main/java/org/cef/handler/CefFocusHandlerAdapter.java create mode 100644 src/main/java/org/cef/handler/CefJSDialogHandler.java create mode 100644 src/main/java/org/cef/handler/CefJSDialogHandlerAdapter.java create mode 100644 src/main/java/org/cef/handler/CefKeyboardHandler.java create mode 100644 src/main/java/org/cef/handler/CefKeyboardHandlerAdapter.java create mode 100644 src/main/java/org/cef/handler/CefLifeSpanHandler.java create mode 100644 src/main/java/org/cef/handler/CefLifeSpanHandlerAdapter.java create mode 100644 src/main/java/org/cef/handler/CefLoadHandler.java create mode 100644 src/main/java/org/cef/handler/CefLoadHandlerAdapter.java create mode 100644 src/main/java/org/cef/handler/CefMessageRouterHandler.java create mode 100644 src/main/java/org/cef/handler/CefMessageRouterHandlerAdapter.java create mode 100644 src/main/java/org/cef/handler/CefPrintHandler.java create mode 100644 src/main/java/org/cef/handler/CefPrintHandlerAdapter.java create mode 100644 src/main/java/org/cef/handler/CefRenderHandler.java create mode 100644 src/main/java/org/cef/handler/CefRenderHandlerAdapter.java create mode 100644 src/main/java/org/cef/handler/CefRequestContextHandler.java create mode 100644 src/main/java/org/cef/handler/CefRequestContextHandlerAdapter.java create mode 100644 src/main/java/org/cef/handler/CefRequestHandler.java create mode 100644 src/main/java/org/cef/handler/CefRequestHandlerAdapter.java create mode 100644 src/main/java/org/cef/handler/CefResourceHandler.java create mode 100644 src/main/java/org/cef/handler/CefResourceHandlerAdapter.java create mode 100644 src/main/java/org/cef/handler/CefResourceRequestHandler.java create mode 100644 src/main/java/org/cef/handler/CefResourceRequestHandlerAdapter.java create mode 100644 src/main/java/org/cef/handler/CefScreenInfo.java create mode 100644 src/main/java/org/cef/handler/CefWindowHandler.java create mode 100644 src/main/java/org/cef/handler/CefWindowHandlerAdapter.java create mode 100644 src/main/java/org/cef/misc/BoolRef.java create mode 100644 src/main/java/org/cef/misc/CefPageRange.java create mode 100644 src/main/java/org/cef/misc/CefPdfPrintSettings.java create mode 100644 src/main/java/org/cef/misc/CefPrintSettings.java create mode 100644 src/main/java/org/cef/misc/CefPrintSettings_N.java create mode 100644 src/main/java/org/cef/misc/EventFlags.java create mode 100644 src/main/java/org/cef/misc/IntRef.java create mode 100644 src/main/java/org/cef/misc/StringRef.java create mode 100644 src/main/java/org/cef/network/CefCookie.java create mode 100644 src/main/java/org/cef/network/CefCookieManager.java create mode 100644 src/main/java/org/cef/network/CefCookieManager_N.java create mode 100644 src/main/java/org/cef/network/CefPostData.java create mode 100644 src/main/java/org/cef/network/CefPostDataElement.java create mode 100644 src/main/java/org/cef/network/CefPostDataElement_N.java create mode 100644 src/main/java/org/cef/network/CefPostData_N.java create mode 100644 src/main/java/org/cef/network/CefRequest.java create mode 100644 src/main/java/org/cef/network/CefRequest_N.java create mode 100644 src/main/java/org/cef/network/CefResponse.java create mode 100644 src/main/java/org/cef/network/CefResponse_N.java create mode 100644 src/main/java/org/cef/network/CefURLRequest.java create mode 100644 src/main/java/org/cef/network/CefURLRequest_N.java create mode 100644 src/main/resources/assets/letsencrypt/isrgrootx1.der create mode 100644 src/main/resources/assets/mcef/html/home.html create mode 100644 src/main/resources/assets/mcef/html/jquery.js create mode 100644 src/main/resources/assets/mcef/mime.types create mode 100644 src/main/resources/mcmod.info create mode 100644 src/main/resources/pack.mcmeta diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..2c770e0 --- /dev/null +++ b/.gitignore @@ -0,0 +1,22 @@ +# eclipse +bin +*.launch +.settings +.metadata +.classpath +.project + +# idea +out +*.ipr +*.iws +*.iml +.idea + +# gradle +build +.gradle + +# other +eclipse +run diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..cdedd19 --- /dev/null +++ b/LICENSE @@ -0,0 +1,32 @@ +This project contains modified and unmodified source files from the JCEF project https://code.google.com/p/javachromiumembedded/ +The following license applies to all file in the org.cef package, as well as all files in the native folder. + +Copyright (c) 2008-2013 Marshall A. Greenblatt. Portions Copyright (c) +2006-2009 Google Inc. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google Inc. nor the name Chromium Embedded +Framework nor the names of its contributors may be used to endorse +or promote products derived from this software without specific prior +written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..ebbb6d8 --- /dev/null +++ b/README.md @@ -0,0 +1,36 @@ +# MCEF +Minecraft Chromium Embedded Framework (MCEF) is an API to allow Minecraft Modders to add custom web browsers into Minecraft. +The project was initialy made for WebDisplays (www.minecraftforum.net/forums/mapping-and-modding/minecraft-mods/1291044-web-displays-browse-on-the-internet-in-minecraft). +It is based on JCEF (https://code.google.com/p/javachromiumembedded/), which is based on CEF (https://bitbucket.org/chromiumembedded/java-cef) which is based on chromium (http://www.chromium.org). + +# Features +- 2D & 3D web view rendering (not only in GUIs) +- Java -> JavaScript (IBrowser.runJavaScript) +- JavaScript -> Java (IJSQueryHandler) +- Embedded files (mod://modname/file.html => /assets/modname/html/file.html) +- HTML5, CSS3 are supported. + +# What can I do with this? +- If you're tired of Minecraft's GuiScreen, you can make a HTML/JS/CSS GUI. +- Open a link to your wiki. +- Whatever you want, but please not a WebDisplays clone. + +# Currently supported platforms +- Windows 10 x64 +- macOS (Intel-based Macs only) - needs work, frequent crashes +- Linux x64 (tested on Fedora 34 and Ubuntu 20.04) + +# For players +This is the Github project of MCEF; here you can only read the source code of it. +You can download the mod in its latest version from here: http://www.minecraftforum.net/forums/mapping-and-modding/minecraft-mods/2324969-minecraft-chromium-embedded-framework-mcef + +# For modders +**DONT** copy the net.montoyo.mcef.api package into your project. Instead, download the latest API release from https://github.com/montoyo/mcef/releases and put it in the libs folder. Users will have to download the mod from the MinecraftForum thread. +To understand how it works, you may look at the net.montoyo.mcef.example package, which demos: +* The IBrowser interface, how to draw it and control it +* The IJSQueryHandler interface, how to handle JavaScript queries +* The IDisplayHandler interface, how to handle browser URL changes +* How to use the mod:// scheme + +# For forkers +Don't forget to add "-Dfml.coreMods.load=net.montoyo.mcef.coremod.ShutdownPatcher" to the VM options, otherwise the Java process will hang forever! diff --git a/build.gradle b/build.gradle new file mode 100644 index 0000000..0b1c4fb --- /dev/null +++ b/build.gradle @@ -0,0 +1,122 @@ +buildscript { + repositories { + maven { url = 'https://files.minecraftforge.net/maven' } + jcenter() + mavenCentral() + } + dependencies { + classpath 'net.minecraftforge.gradle:ForgeGradle:3.+' + } +} + +apply plugin: 'net.minecraftforge.gradle' +// Only edit below this line, the above code adds and enables the necessary things for Forge to be setup. +apply plugin: 'eclipse' +apply plugin: 'maven-publish' + +version = '1.12.2-1.41' +group = 'net.montoyo.mcef' +archivesBaseName = 'mcef' + +sourceCompatibility = targetCompatibility = compileJava.sourceCompatibility = compileJava.targetCompatibility = '1.8' // Need this here so eclipse task generates correctly. + +minecraft { + // The mappings can be changed at any time, and must be in the following format. + // snapshot_YYYYMMDD Snapshot are built nightly. + // stable_# Stables are built at the discretion of the MCP team. + // Use non-default mappings at your own risk. they may not always work. + // Simply re-run your setup task after changing the mappings to update your workspace. + //mappings channel: 'snapshot', version: '20171003-1.12' + mappings channel: 'snapshot', version: '20171003-1.12' + // makeObfSourceJar = false // an Srg named sources jar is made by default. uncomment this to disable. + + // accessTransformer = file('src/main/resources/META-INF/accesstransformer.cfg') + + // Default run configurations. + // These can be tweaked, removed, or duplicated as needed. + runs { + client { + workingDirectory project.file('run') + + // Recommended logging data for a userdev environment + property 'forge.logging.markers', 'SCAN,REGISTRIES,REGISTRYDUMP' + + // Recommended logging level for the console + property 'forge.logging.console.level', 'debug' + } + + server { + + // Recommended logging data for a userdev environment + property 'forge.logging.markers', 'SCAN,REGISTRIES,REGISTRYDUMP' + + // Recommended logging level for the console + property 'forge.logging.console.level', 'debug' + } + } +} + +dependencies { + // Specify the version of Minecraft to use, If this is any group other then 'net.minecraft' it is assumed + // that the dep is a ForgeGradle 'patcher' dependency. And it's patches will be applied. + // The userdev artifact is a special name and will get all sorts of transformations applied to it. + minecraft 'net.minecraftforge:forge:1.12.2-14.23.5.2855' + + // You may put jars on which you depend on in ./libs or you may define them like so.. + // compile "some.group:artifact:version:classifier" + // compile "some.group:artifact:version" + + // Real examples + // compile 'com.mod-buildcraft:buildcraft:6.0.8:dev' // adds buildcraft to the dev env + // compile 'com.googlecode.efficient-java-matrix-library:ejml:0.24' // adds ejml to the dev env + + // The 'provided' configuration is for optional dependencies that exist at compile-time but might not at runtime. + // provided 'com.mod-buildcraft:buildcraft:6.0.8:dev' + + // These dependencies get remapped to your current MCP mappings + // deobf 'com.mod-buildcraft:buildcraft:6.0.8:dev' + + // For more info... + // http://www.gradle.org/docs/current/userguide/artifact_dependencies_tutorial.html + // http://www.gradle.org/docs/current/userguide/dependency_management.html +} + +// Example for how to get properties into the manifest for reading by the runtime.. +jar { + manifest { + attributes([ + "Specification-Title" : "mcef", + "Specification-Vendor" : "mcef", + "Specification-Version" : "3", // We are version 3 of ourselves + "Implementation-Title" : project.name, + "Implementation-Version" : project.version, + "Implementation-Vendor" : "mcef", + "Implementation-Timestamp": new Date().format("yyyy-MM-dd'T'HH:mm:ssZ") + ]) + } +} + +// Example configuration to allow publishing using the maven-publish task +// This is the preferred method to reobfuscate your jar file +jar.finalizedBy('reobfJar') +// However if you are in a multi-project build, dev time needs unobfed jar files, so you can delay the obfuscation until publishing by doing +//publish.dependsOn('reobfJar') + +processResources { + // this will ensure that this task is redone when the versions change. + inputs.property "version", project.version + inputs.property "mcversion", "${mc_version}" + + // replace stuff in mcmod.info, nothing else + from(sourceSets.main.resources.srcDirs) { + include 'mcmod.info' + + // replace version and mcversion + expand 'version': project.version, 'mcversion': "${mc_version}" + } + + // copy everything else except the mcmod.info + from(sourceSets.main.resources.srcDirs) { + exclude 'mcmod.info' + } +} diff --git a/gradle.properties b/gradle.properties new file mode 100644 index 0000000..89cbdda --- /dev/null +++ b/gradle.properties @@ -0,0 +1,5 @@ +# Sets default memory used for gradle commands. Can be overridden by user or command line properties. +# This is required to provide enough memory for the Minecraft decompilation process. +org.gradle.jvmargs=-Xmx3G +org.gradle.daemon=false +mc_version=1.12.2-14.23.5.2855 \ No newline at end of file diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000000000000000000000000000000000000..7a3265ee94c0ab25cf079ac8ccdf87f41d455d42 GIT binary patch literal 54708 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

_x$+l{{cH8$W#CT literal 0 HcmV?d00001 diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..949819d --- /dev/null +++ b/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,5 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-4.9-bin.zip diff --git a/gradlew b/gradlew new file mode 100644 index 0000000..cccdd3d --- /dev/null +++ b/gradlew @@ -0,0 +1,172 @@ +#!/usr/bin/env sh + +############################################################################## +## +## Gradle start up script for UN*X +## +############################################################################## + +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >/dev/null +APP_HOME="`pwd -P`" +cd "$SAVED" >/dev/null + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS="" + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn () { + echo "$*" +} + +die () { + echo + echo "$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MINGW* ) + msys=true + ;; + NONSTOP* ) + nonstop=true + ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin, switch paths to Windows format before running java +if $cygwin ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + JAVACMD=`cygpath --unix "$JAVACMD"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=$((i+1)) + done + case $i in + (0) set -- ;; + (1) set -- "$args0" ;; + (2) set -- "$args0" "$args1" ;; + (3) set -- "$args0" "$args1" "$args2" ;; + (4) set -- "$args0" "$args1" "$args2" "$args3" ;; + (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Escape application args +save () { + for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done + echo " " +} +APP_ARGS=$(save "$@") + +# Collect all arguments for the java command, following the shell quoting and substitution rules +eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" + +# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong +if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then + cd "$(dirname "$0")" +fi + +exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat new file mode 100644 index 0000000..f955316 --- /dev/null +++ b/gradlew.bat @@ -0,0 +1,84 @@ +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS= + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto init + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto init + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:init +@rem Get command-line arguments, handling Windows variants + +if not "%OS%" == "Windows_NT" goto win9xME_args + +:win9xME_args +@rem Slurp the command line arguments. +set CMD_LINE_ARGS= +set _SKIP=2 + +:win9xME_args_slurp +if "x%~1" == "x" goto execute + +set CMD_LINE_ARGS=%* + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/src/main/java/net/montoyo/mcef/BaseProxy.java b/src/main/java/net/montoyo/mcef/BaseProxy.java new file mode 100644 index 0000000..6c4aa72 --- /dev/null +++ b/src/main/java/net/montoyo/mcef/BaseProxy.java @@ -0,0 +1,224 @@ +package net.montoyo.mcef; + +import net.montoyo.mcef.api.*; +import net.montoyo.mcef.utilities.Log; + +public class BaseProxy implements API { + + public void onPreInit() { + } + + public void onInit() { + Log.info("MCEF is running on server. Nothing to do."); + } + + @Override + public IBrowser createBrowser(String url, boolean transparent) { + Log.warning("A mod called API.createBrowser() from server! Returning null..."); + return null; + } + + @Override + public IBrowser createBrowser(String url) { + return createBrowser(url, false); + } + + @Override + public void registerDisplayHandler(IDisplayHandler idh) { + Log.warning("A mod called API.registerDisplayHandler() from server!"); + } + + @Override + public boolean isVirtual() { + return true; + } + + @Override + public void openExampleBrowser(String url) { + Log.warning("A mod called API.openExampleBrowser() from server! URL: %s", url); + } + + @Override + public void registerJSQueryHandler(IJSQueryHandler iqh) { + Log.warning("A mod called API.registerJSQueryHandler() from server!"); + } + + @Override + public String mimeTypeFromExtension(String ext) { + Log.warning("A mod called API.mimeTypeFromExtension() from server!"); + return null; + } + + @Override + public void registerScheme(String name, Class schemeClass, boolean std, boolean local, boolean displayIsolated, boolean secure, boolean corsEnabled, boolean cspBypassing, boolean fetchEnabled) { + Log.warning("A mod called API.registerScheme() from server!"); + } + + @Override + public boolean isSchemeRegistered(String name) { + Log.warning("A mod called API.isSchemeRegistered() from server!"); + return false; + } + + public void onShutdown() { + } + + private static final int PUNYCODE_TMIN = 1; + private static final int PUNYCODE_TMAX = 26; + private static final int PUNYCODE_SKEW = 38; + private static final int PUNYCODE_DAMP = 700; + private static final int PUNYCODE_INITIAL_BIAS = 72; + private static final int PUNYCODE_INITIAL_N = 128; + + private static int punycodeBiasAdapt(int delta, int numPoints, boolean firstTime) { + if(firstTime) + delta /= PUNYCODE_DAMP; + else + delta /= 2; + + int k = 0; + delta = delta + delta / numPoints; + + while(delta > ((36 - PUNYCODE_TMIN) * PUNYCODE_TMAX) / 2) { + delta /= 36 - PUNYCODE_TMIN; + k += 36; + } + + return k + ((36 - PUNYCODE_TMIN + 1) * delta) / (delta + PUNYCODE_SKEW); + } + + private static void punycodeEncodeNumber(StringBuilder dst, int q, int bias) { + boolean keepGoing = true; + + for(int k = 36; keepGoing; k += 36) { + //Compute & clamp threshold + int t = k - bias; + if(t < PUNYCODE_TMIN) + t = PUNYCODE_TMIN; + else if(t > PUNYCODE_TMAX) + t = PUNYCODE_TMAX; + + //Compute digit + int digit; + if(q < t) { + digit = q; + keepGoing = false; + } else { + digit = t + (q - t) % (36 - t); + q = (q - t) / (36 - t); + } + + //Encode digit + if(digit < 26) + dst.append((char) ('a' + digit)); + else + dst.append((char) ('0' + digit - 26)); + } + } + + private static String punycodeEncodeString(int[] input) { + StringBuilder output = new StringBuilder(); + + for(int i = 0; i < input.length; i++) { + if(input[i] < 128) + output.append((char) input[i]); + } + + int n = PUNYCODE_INITIAL_N; + int delta = 0; + int bias = PUNYCODE_INITIAL_BIAS; + int h = output.length(); + int b = h; + + if(b > 0) + output.append('-'); + + while(h < input.length) { + int m = Integer.MAX_VALUE; + for(int i = 0; i < input.length; i++) { + if(input[i] >= n && input[i] < m) + m = input[i]; + } + + delta = delta + (m - n) * (h + 1); + n = m; + + for(int i = 0; i < input.length; i++) { + int c = input[i]; + + if(c < n) + delta++; + + if(c == n) { + punycodeEncodeNumber(output, delta, bias); + bias = punycodeBiasAdapt(delta, h + 1, h == b); + delta = 0; + h++; + } + } + + delta++; + n++; + } + + return "xn--" + output.toString(); + } + + @Override + public String punycode(String url) { + int protoEnd = url.indexOf("://"); + + if(protoEnd < 0) + protoEnd = 0; + else + protoEnd += 3; + + int hostEnd = url.indexOf('/', protoEnd); + if(hostEnd < 0) + hostEnd = url.length(); + + String hostname = url.substring(protoEnd, hostEnd); + boolean doTransform = false; + + for(int i = 0; i < hostname.length(); i++) { + if(hostname.charAt(i) >= 128) { + doTransform = true; + break; + } + } + + if(!doTransform) + return url; + + String[] parts = hostname.split("\\."); + StringBuilder sb = new StringBuilder(); + boolean first = true; + + sb.append(url, 0, protoEnd); + + for(String p: parts) { + doTransform = false; + + for(int i = 0; i < p.length(); i++) { + if(p.charAt(i) >= 128) { + doTransform = true; + break; + } + } + + if(first) + first = false; + else + sb.append('.'); + + if(doTransform) + sb.append(punycodeEncodeString(p.codePoints().toArray())); + else + sb.append(p); + } + + sb.append(url, hostEnd, url.length()); + return sb.toString(); + } + +} diff --git a/src/main/java/net/montoyo/mcef/LetsEncryptAdder.java b/src/main/java/net/montoyo/mcef/LetsEncryptAdder.java new file mode 100644 index 0000000..911dec6 --- /dev/null +++ b/src/main/java/net/montoyo/mcef/LetsEncryptAdder.java @@ -0,0 +1,102 @@ +package net.montoyo.mcef; + +import net.montoyo.mcef.utilities.Log; + +import org.apache.commons.io.IOUtils; +import javax.net.ssl.SSLContext; +import javax.net.ssl.TrustManagerFactory; +import java.io.*; +import java.net.URL; +import java.net.URLConnection; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.security.KeyStore; +import java.security.cert.*; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class LetsEncryptAdder { + private static final String CERT_ALIAS = "lets-encrypt-x3-cross-signed"; + private static final String KEYSTORE_PASSWORD = "changeit"; + + public static void addLetsEncryptCertificate() throws Exception { + try (InputStream certStream = LetsEncryptAdder.class + .getResourceAsStream("/assets/letsencrypt/isrgrootx1.der")) { + if (certStream == null) { + throw new FileNotFoundException("Let's Encrypt root certificate file not found."); + } + + Path ksPath = Paths.get(System.getProperty("java.home"), "lib", "security", "cacerts"); + KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType()); + + try (InputStream ksInputStream = Files.newInputStream(ksPath)) { + keyStore.load(ksInputStream, KEYSTORE_PASSWORD.toCharArray()); + } + + CertificateFactory cf = CertificateFactory.getInstance("X.509"); + Certificate certificate; + + try (BufferedInputStream caInput = new BufferedInputStream(certStream)) { + certificate = cf.generateCertificate(caInput); + } + + keyStore.setCertificateEntry(CERT_ALIAS, certificate); + + TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); + tmf.init(keyStore); + + SSLContext sslContext = SSLContext.getInstance("TLS"); + sslContext.init(null, tmf.getTrustManagers(), null); + SSLContext.setDefault(sslContext); + } + } + + public static void doStuff() { + String version = System.getProperty("java.version"); + Pattern versionPattern = Pattern.compile("^(\\d+)(?:\\.(\\d+))?.*?_?(\\d+)?"); + Matcher matcher = versionPattern.matcher(version); + + int majorVersion = 7; + int minorVersion = 110; // Default fallback + + if (matcher.matches()) { + majorVersion = Integer.parseInt(matcher.group(1)); + if (matcher.group(3) != null) { + minorVersion = Integer.parseInt(matcher.group(3)); + } + } else { + Log.info("[Let'sEncrypt SSL] Failed to parse Java version. Applying fix anyway."); + } + + if ((majorVersion == 7 && minorVersion >= 111) || (majorVersion == 8 && minorVersion >= 101)) { + Log.info("[Let'sEncrypt SSL] Not running as Java version is at least Java " + majorVersion + "u" + + minorVersion); + return; + } + + try { + Log.info("[Let'sEncrypt SSL] Adding Let's Encrypt certificate..."); + addLetsEncryptCertificate(); + Log.info("[Let'sEncrypt SSL] Done! Attempting to connect to https://helloworld.letsencrypt.org..."); + + URL url = new URL("https://helloworld.letsencrypt.org"); + URLConnection conn = url.openConnection(); + conn.setConnectTimeout(5000); + conn.setReadTimeout(5000); + + String response; + try (InputStream inputStream = conn.getInputStream()) { + response = IOUtils.toString(inputStream); + } + + if (response.isEmpty()) { + Log.error("[Let'sEncrypt SSL] Unknown error: Unable to access Let's Encrypt resources."); + } else { + Log.info("[Let'sEncrypt SSL] Success! You can now access Let's Encrypt-secured resources."); + } + } catch (Exception e) { + Log.error("[Let'sEncrypt SSL] Failed to add the Let's Encrypt root certificate.", e); + } + } +} diff --git a/src/main/java/net/montoyo/mcef/MCEF.java b/src/main/java/net/montoyo/mcef/MCEF.java new file mode 100644 index 0000000..7798805 --- /dev/null +++ b/src/main/java/net/montoyo/mcef/MCEF.java @@ -0,0 +1,98 @@ +package net.montoyo.mcef; + +import net.minecraftforge.common.config.Configuration; +import net.minecraftforge.fml.common.Mod; +import net.minecraftforge.fml.common.SidedProxy; +import net.minecraftforge.fml.common.event.FMLInitializationEvent; +import net.minecraftforge.fml.common.event.FMLPreInitializationEvent; +import net.montoyo.mcef.utilities.Log; +import net.montoyo.mcef.LetsEncryptAdder; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +@Mod(modid = "mcef", name = "MCEF", version = MCEF.VERSION) +public class MCEF { + + public static final String VERSION = "1.41"; + public static boolean ENABLE_EXAMPLE; + public static boolean SKIP_UPDATES; + public static boolean WARN_UPDATES; + public static boolean USE_FORGE_SPLASH; + public static String FORCE_MIRROR = null; + public static String HOME_PAGE; + private static String DEF_CEF_ARGS; + public static String[] CEF_ARGS = new String[0]; + public static boolean CHECK_VRAM_LEAK; + public static boolean SHUTDOWN_JCEF; + public static boolean SECURE_MIRRORS_ONLY; + + @Mod.Instance(owner = "mcef") + public static MCEF INSTANCE; + + @SidedProxy(serverSide = "net.montoyo.mcef.BaseProxy", clientSide = "net.montoyo.mcef.client.ClientProxy") + public static BaseProxy PROXY; + + @Mod.EventHandler + public void onPreInit(FMLPreInitializationEvent ev) { + Log.info("PREINT, Loading MCEF config..."); + Configuration cfg = new Configuration(ev.getSuggestedConfigurationFile()); + + // Default CEF arguments + DEF_CEF_ARGS = "--enable-widevine --disable-gpu --disable-gpu-compositing"; + + //Config: main + SKIP_UPDATES = cfg.getBoolean("skipUpdates", "main", false, "Do not update binaries."); + WARN_UPDATES = cfg.getBoolean("warnUpdates", "main", true, "Tells in the chat if a new version of MCEF is available."); + USE_FORGE_SPLASH = cfg.getBoolean("useForgeSplash", "main", true, "Use Forge's splash screen to display resource download progress (may be unstable)."); + SHUTDOWN_JCEF = cfg.getBoolean("shutdownJcef", "main", false, "Set this to true if your Java process hangs after closing Minecraft. This is disabled by default because it makes the launcher think Minecraft crashed..."); + SECURE_MIRRORS_ONLY = cfg.getBoolean("secureMirrorsOnly", "main", true, "Only enable secure (HTTPS) mirror. This should be kept to true unless you know what you're doing."); + + // Merge default and user CEF arguments: + String[] defaultArgs = DEF_CEF_ARGS.split("\\s+"); + String[] userArgs = cfg.getString("cefArgs", "main", DEF_CEF_ARGS, "Command line arguments passed to CEF. For advanced users.").split("\\s+"); + + List combinedArgs = new ArrayList<>(Arrays.asList(defaultArgs)); // Start with the default args + combinedArgs.addAll(Arrays.asList(userArgs)); // Add the user-provided args + + // Convert back to array + CEF_ARGS = combinedArgs.toArray(new String[0]); + + String mirror = cfg.getString("forcedMirror", "main", "", "A URL that contains every MCEF resources; for instance https://montoyo.net/jcef.").trim(); + if (mirror.length() > 0) + FORCE_MIRROR = mirror; + + //Config: exampleBrowser + ENABLE_EXAMPLE = cfg.getBoolean("enable", "exampleBrowser", true, "Set this to false if you don't want to enable the F10 browser."); + HOME_PAGE = cfg.getString("home", "exampleBrowser", "mod://mcef/home.html", "The home page of the F10 browser."); + + //Config: debug + CHECK_VRAM_LEAK = cfg.getBoolean("checkForVRAMLeak", "debug", false, "Track allocated OpenGL textures to make sure there's no leak"); + + // Save the configuration + cfg.save(); + + // Add Let's Encrypt certificate + try { + LetsEncryptAdder.addLetsEncryptCertificate(); + } catch (Exception e) { + Log.error("Failed to add Let's Encrypt certificate: " + e.getMessage()); + } + + PROXY.onPreInit(); + } + + @Mod.EventHandler + public void onInit(FMLInitializationEvent ev) { + Log.info("Now initializing MCEF v%s...", VERSION); + PROXY.onInit(); + } + + //Called by Minecraft.run() if the ShutdownPatcher succeeded + public static void onMinecraftShutdown() { + Log.info("Minecraft shutdown hook called!"); + PROXY.onShutdown(); + } + +} diff --git a/src/main/java/net/montoyo/mcef/api/API.java b/src/main/java/net/montoyo/mcef/api/API.java new file mode 100644 index 0000000..0af2f33 --- /dev/null +++ b/src/main/java/net/montoyo/mcef/api/API.java @@ -0,0 +1,96 @@ +package net.montoyo.mcef.api; + +public interface API { + + /** + * Creates a web view and loads the specified URL. + * + * @param url The URL to start from. + * @param transp True is the web view can be transparent + * @return The created web view, or null if this is run on server. + */ + IBrowser createBrowser(String url, boolean transp); + + /** + * Same as {@link #createBrowser(String, boolean) createBrowser} but with transp set to false. + * + * @param url The URL to start from. + * @return The created web view, or null if this is run on server. + */ + IBrowser createBrowser(String url); + + /** + * Registers a display handler. + * @param idh The display handler to register. + * @see IDisplayHandler + */ + void registerDisplayHandler(IDisplayHandler idh); + + /** + * Registers a JavaScript query handler. + * @param iqh The JavaScript query handler to register. + * @see IJSQueryHandler + */ + void registerJSQueryHandler(IJSQueryHandler iqh); + + /** + * Call this to know if MCEF is in virtual mode. + * MCEF switches in virtual mode if something failed to load. + * When in virtual mode, {@link #createBrowser(String, boolean) createBrowser} will generate fake browsers that does nothing. + * + * @return true if MCEF is in virtual mode. + */ + boolean isVirtual(); + + /** + * Opens the example browser UI. + * @param url The URL to load. + * @see net.montoyo.mcef.example.ExampleMod + */ + void openExampleBrowser(String url); + + /** + * Returns a mime type from a file extension, + * or null, if there is no mapping for this extension. + * + * @param ext File extension, without the '.' + * @return A mime type corresponding to this extension, or null if none could be found. + */ + String mimeTypeFromExtension(String ext); + + /** + * Registers a scheme (custom URLs). + * This has to be done in preInit, init happens too late. + * + * @param name The name of the scheme + * @param schemeClass The class that will be instantiated for request + * @param std Whether the scheme has standards URLs, like "protocol://host/path". Non standard is just "procotol:" + * @param local If the scheme is local, some special security rules are applied, just like the "file:///" scheme. + * @param displayIsolated iframes (and such things) from an external scheme cannot access pages from this scheme. + * @param secure Whether this scheme is considered as secure (like https) + * @param corsEnabled To allow this scheme to send CORS requests + * @param cspBypassing true if this scheme can bypass Content-Security-Policy (CSP) checks + * @param fetchEnabled true if this scheme can perform Fetch API requests + * + * @see org.cef.callback.CefSchemeRegistrar + */ + void registerScheme(String name, Class schemeClass, boolean std, boolean local, boolean displayIsolated, boolean secure, boolean corsEnabled, boolean cspBypassing, boolean fetchEnabled); + + /** + * Checks whether the scheme with name 'name' is already registered. + * + * @param name The name of the scheme + * @return true if it is registered, false otherwise. + */ + boolean isSchemeRegistered(String name); + + /** + * If the hostname of the passed URL contains some illegal characters, it converts the hostname to punnycode. + * If it fails to parse the URL or if the specified URL contains a valid hostname, returns the input string. + * + * @param url The URL to transform + * @return The transformed URL + */ + String punycode(String url); + +} diff --git a/src/main/java/net/montoyo/mcef/api/IBrowser.java b/src/main/java/net/montoyo/mcef/api/IBrowser.java new file mode 100644 index 0000000..07bd9fd --- /dev/null +++ b/src/main/java/net/montoyo/mcef/api/IBrowser.java @@ -0,0 +1,138 @@ +package net.montoyo.mcef.api; + +public interface IBrowser { + + /** + * Destroys the web view. + */ + void close(); + + /** + * Resizes the web view. + * + * @param width The new width in pixels. + * @param height The new height in pixels. + */ + void resize(int width, int height); + + /** + * Renders the web view into Minecraft. + * + * @param x1 The first X coord of the rectangle to render the web view on (left). + * @param y1 The first Y coord of the rectangle to render the web view on (top). + * @param x2 The second X coord of the rectangle to render the web view on (right). + * @param y2 The second Y coord of the rectangle to render the web view on (bottom). + */ + void draw(double x1, double y1, double x2, double y2); + + /** + * Gets the OpenGL texture ID of the web view. + * @return the OpenGL texture ID of the web view. + */ + int getTextureID(); + + /** + * Simulates a mouse move. + * + * @param x The X coord of the mouse. + * @param y The Y coord of the mouse. + * @param mods The key modifiers (shift, ctrl, alt) + * @param left true if the mouse is out of (left) the web view. + */ + void injectMouseMove(int x, int y, int mods, boolean left); + + /** + * Simulates a mouse click. + * + * @param x The X coord of the mouse. + * @param y The Y coord of the mouse. + * @param mods The key modifiers (shift, ctrl, alt) + * @param btn The mouse button to press. See {@link java.awt.event.MouseEvent} + * @param pressed true if the button is pressed, false if it is released. + * @param ccnt The click count. You probably want this to be 1. + */ + void injectMouseButton(int x, int y, int mods, int btn, boolean pressed, int ccnt); + + /** + * Simulates a keyboard type. + * + * @param c The typed character. + * @param mods The key modifiers (shift, ctrl, alt) + */ + void injectKeyTyped(char c, int mods); + + /** + * Simulates a key press. + * + * @param keyCode LWJGL key code of the pressed key. + * @param c The resulting character + * @param mods The key modifiers (shift, ctrl, alt) + */ + void injectKeyPressedByKeyCode(int keyCode, char c, int mods); + + /** + * Simulates a key release. + * + * @param keyCode LWJGL key code of the pressed key. + * @param c The resulting character + * @param mods The key modifiers (shift, ctrl, alt) + */ + void injectKeyReleasedByKeyCode(int keyCode, char c, int mods); + + /** + * Simulates a mouse wheel. + * + * @param x The X coord of the mouse. + * @param y The Y coord of the mouse. + * @param mods The key modifiers (shift, ctrl, alt) + * @param amount The amount to scroll. + * @param rot The number of "clicks" by which the mouse wheel was rotated. + */ + void injectMouseWheel(int x, int y, int mods, int amount, int rot); + + /** + * Runs JavaScript code on the web view. + * + * @param script The script to run. + * @param frame The URL of the frame to run the script on. Let this EMPTY (!= null) for the default frame. + */ + void runJS(String script, String frame); + + /** + * Changes the current page's address. + * @param url The URL to load. + */ + void loadURL(String url); + + /** + * Returns to the previous address. + */ + void goBack(); + + /** + * Undoes {@link #goBack()} + */ + void goForward(); + + /** + * Retrieves the current browser's location. + * @return The current browser's URL. + */ + String getURL(); + + /** + * Asynchronously retrieves the current page's source code. + * FIXME: This is asynchronous; so you won't get your source right after calling this method! + * + * @param isv An object that handles strings. + */ + void visitSource(IStringVisitor isv); + + /** + * Checks if the page is currently being loaded. + * + * @return true if the page is still being loaded, false otherwise. + */ + boolean isPageLoading(); + +} diff --git a/src/main/java/net/montoyo/mcef/api/IDisplayHandler.java b/src/main/java/net/montoyo/mcef/api/IDisplayHandler.java new file mode 100644 index 0000000..2bc93ac --- /dev/null +++ b/src/main/java/net/montoyo/mcef/api/IDisplayHandler.java @@ -0,0 +1,35 @@ +package net.montoyo.mcef.api; + +public interface IDisplayHandler { + + /** + * Handle address changes. + * @param browser The browser generating the event. + * @param url The new URL. + */ + void onAddressChange(IBrowser browser, String url); + + /** + * Handle title changes. + * @param browser The browser generating the event. + * @param title The new title. + */ + void onTitleChange(IBrowser browser, String title); + + /** + * Called when the browser is about to display a tooltip. + * + * @param browser The browser generating the event. + * @param text Contains the text that will be displayed in the tooltip. + */ + void onTooltip(IBrowser browser, String text); + + /** + * Called when the browser receives a status message. + * + * @param browser The browser generating the event. + * @param value Contains the text that will be displayed in the status message. + */ + void onStatusMessage(IBrowser browser, String value); + +} diff --git a/src/main/java/net/montoyo/mcef/api/IJSQueryCallback.java b/src/main/java/net/montoyo/mcef/api/IJSQueryCallback.java new file mode 100644 index 0000000..26e55d6 --- /dev/null +++ b/src/main/java/net/montoyo/mcef/api/IJSQueryCallback.java @@ -0,0 +1,24 @@ +package net.montoyo.mcef.api; + +/** + * Use this interface to answer to JavaScript queries. + * @author montoyo + * + */ +public interface IJSQueryCallback { + + /** + * If the query succeeded, call this. + * @param response Whatever you want. + */ + void success(String response); + + /** + * If the query failed, call this. + * + * @param errId Whatever you want. + * @param errMsg Whatever you want. + */ + void failure(int errId, String errMsg); + +} diff --git a/src/main/java/net/montoyo/mcef/api/IJSQueryHandler.java b/src/main/java/net/montoyo/mcef/api/IJSQueryHandler.java new file mode 100644 index 0000000..e98ea6c --- /dev/null +++ b/src/main/java/net/montoyo/mcef/api/IJSQueryHandler.java @@ -0,0 +1,39 @@ +package net.montoyo.mcef.api; + +/** + * Use this to handle asynchronous JavaScript queries. + * Queries are sent to Java using var request_id = window.mcefQuery({ request: 'my_request', persistent: false, onSuccess: function(response) {}, onFailure: function(error_code, error_message) {} }) + * Queries may be cancelled using window.mcefCancel(request_id) + * + * @author montoyo + * + */ +public interface IJSQueryHandler { + + /** + * Handles a JavaScript query. Queries are created using the following JavaScript code: + * var request_id = window.mcefQuery({ request: 'my_request', persistent: false, onSuccess: function(response) {}, onFailure: function(error_code, error_message) {} }) + * + * Be aware that this method is NOT called from the main Minecraft thread, and you have to handle synchronisation yourself. + * + * @param b The browser the query has been created from. + * @param queryId The unique query identifier. + * @param query The "request" field. + * @param persistent If the query is persistent or not. If it is, then cb will remain valid when leaving this method. + * @param cb Use this to answer the query; this means call the JS onSuccess and onFailure functions. + * @return true if the query was handled. + */ + boolean handleQuery(IBrowser b, long queryId, String query, boolean persistent, IJSQueryCallback cb); + + /** + * Handles a JavaScript query cancellation. Queries are cancelled using the following JavaScript code: + * window.mcefCancel(request_id) + * + * Be aware that this method is NOT called from the main Minecraft thread, and you have to handle synchronisation yourself. + * + * @param b The browser the query has been cancelled from. + * @param queryId The unique query identifier given in {@link #handleQuery(IBrowser, long, String, boolean, IJSQueryCallback)} + */ + void cancelQuery(IBrowser b, long queryId); + +} diff --git a/src/main/java/net/montoyo/mcef/api/IScheme.java b/src/main/java/net/montoyo/mcef/api/IScheme.java new file mode 100644 index 0000000..3ec2a9d --- /dev/null +++ b/src/main/java/net/montoyo/mcef/api/IScheme.java @@ -0,0 +1,9 @@ +package net.montoyo.mcef.api; + +public interface IScheme { + + SchemePreResponse processRequest(String url); + void getResponseHeaders(ISchemeResponseHeaders resp); + boolean readResponse(ISchemeResponseData data); + +} diff --git a/src/main/java/net/montoyo/mcef/api/ISchemeResponseData.java b/src/main/java/net/montoyo/mcef/api/ISchemeResponseData.java new file mode 100644 index 0000000..30bd767 --- /dev/null +++ b/src/main/java/net/montoyo/mcef/api/ISchemeResponseData.java @@ -0,0 +1,9 @@ +package net.montoyo.mcef.api; + +public interface ISchemeResponseData { + + byte[] getDataArray(); + int getBytesToRead(); + void setAmountRead(int rd); + +} diff --git a/src/main/java/net/montoyo/mcef/api/ISchemeResponseHeaders.java b/src/main/java/net/montoyo/mcef/api/ISchemeResponseHeaders.java new file mode 100644 index 0000000..5e98673 --- /dev/null +++ b/src/main/java/net/montoyo/mcef/api/ISchemeResponseHeaders.java @@ -0,0 +1,11 @@ +package net.montoyo.mcef.api; + +public interface ISchemeResponseHeaders { + + void setMimeType(String mt); + void setStatus(int status); + void setStatusText(String st); + void setResponseLength(int len); + void setRedirectURL(String redirURL); + +} diff --git a/src/main/java/net/montoyo/mcef/api/IStringVisitor.java b/src/main/java/net/montoyo/mcef/api/IStringVisitor.java new file mode 100644 index 0000000..06e65dc --- /dev/null +++ b/src/main/java/net/montoyo/mcef/api/IStringVisitor.java @@ -0,0 +1,7 @@ +package net.montoyo.mcef.api; + +public interface IStringVisitor { + + void visit(String str); + +} diff --git a/src/main/java/net/montoyo/mcef/api/MCEFApi.java b/src/main/java/net/montoyo/mcef/api/MCEFApi.java new file mode 100644 index 0000000..780d06f --- /dev/null +++ b/src/main/java/net/montoyo/mcef/api/MCEFApi.java @@ -0,0 +1,29 @@ +package net.montoyo.mcef.api; + +import net.minecraftforge.fml.common.Loader; + +public class MCEFApi { + + /** + * Call this to get the API instance. + * @return the MCEF API or null if something failed. + */ + public static API getAPI() { + try { + Class cls = Class.forName("net.montoyo.mcef.MCEF"); + return (API) cls.getField("PROXY").get(null); + } catch(Throwable t) { + t.printStackTrace(); + return null; + } + } + + /** + * Checks if MCEF was loaded by forge. + * @return true if it is loaded. false otherwise. + */ + public static boolean isMCEFLoaded() { + return Loader.isModLoaded("mcef"); + } + +} diff --git a/src/main/java/net/montoyo/mcef/api/SchemePreResponse.java b/src/main/java/net/montoyo/mcef/api/SchemePreResponse.java new file mode 100644 index 0000000..2293ac0 --- /dev/null +++ b/src/main/java/net/montoyo/mcef/api/SchemePreResponse.java @@ -0,0 +1,9 @@ +package net.montoyo.mcef.api; + +public enum SchemePreResponse { + + NOT_HANDLED, + HANDLED_CONTINUE, + HANDLED_CANCEL + +} diff --git a/src/main/java/net/montoyo/mcef/client/AppHandler.java b/src/main/java/net/montoyo/mcef/client/AppHandler.java new file mode 100644 index 0000000..a4f35fa --- /dev/null +++ b/src/main/java/net/montoyo/mcef/client/AppHandler.java @@ -0,0 +1,103 @@ +package net.montoyo.mcef.client; + +import net.montoyo.mcef.MCEF; +import net.montoyo.mcef.api.IScheme; +import net.montoyo.mcef.utilities.Log; + +import org.cef.CefApp; +import org.cef.browser.CefBrowser; +import org.cef.browser.CefFrame; +import org.cef.callback.CefSchemeHandlerFactory; +import org.cef.callback.CefSchemeRegistrar; +import org.cef.handler.CefAppHandlerAdapter; +import org.cef.handler.CefResourceHandler; +import org.cef.network.CefRequest; + +import java.util.HashMap; +import java.util.Map; + +public class AppHandler extends CefAppHandlerAdapter { + + public AppHandler() { + super(new String[] {}); + } + + private static class SchemeData { + + private Class cls; + private boolean std; + private boolean local; + private boolean dispIsolated; + private boolean secure; + private boolean corsEnabled; + private boolean cspBypassing; + private boolean fetchEnabled; + + private SchemeData(Class cls, boolean std, boolean local, boolean dispIsolated, boolean secure, boolean corsEnabled, boolean cspBypassing, boolean fetchEnabled) { + this.cls = cls; + this.std = std; + this.local = local; + this.dispIsolated = dispIsolated; + this.secure = secure; + this.corsEnabled = corsEnabled; + this.cspBypassing = cspBypassing; + this.fetchEnabled = fetchEnabled; + } + + } + + private final HashMap schemeMap = new HashMap<>(); + + public void registerScheme(String name, Class cls, boolean std, boolean local, boolean dispIsolated, boolean secure, boolean corsEnabled, boolean cspBypassing, boolean fetchEnabled) { + schemeMap.put(name, new SchemeData(cls, std, local, dispIsolated, secure, corsEnabled, cspBypassing, fetchEnabled)); + } + + public boolean isSchemeRegistered(String name) { + return schemeMap.containsKey(name); + } + + @Override + public void onRegisterCustomSchemes(CefSchemeRegistrar reg) { + int cnt = 0; + + for(Map.Entry entry : schemeMap.entrySet()) { + SchemeData v = entry.getValue(); + + if(reg.addCustomScheme(entry.getKey(), v.std, v.local, v.dispIsolated, v.secure, v.corsEnabled, v.cspBypassing, v.fetchEnabled)) + cnt++; + else + Log.error("Could not register scheme %s", entry.getKey()); + } + + Log.info("%d schemes registered", cnt); + } + + @Override + public void onContextInitialized() { + CefApp app = ((ClientProxy) MCEF.PROXY).getCefApp(); + + for(Map.Entry entry : schemeMap.entrySet()) + app.registerSchemeHandlerFactory(entry.getKey(), "", new SchemeHandlerFactory(entry.getValue().cls)); + } + + private static class SchemeHandlerFactory implements CefSchemeHandlerFactory { + + private Class cls; + + private SchemeHandlerFactory(Class cls) { + this.cls = cls; + } + + @Override + public CefResourceHandler create(CefBrowser browser, CefFrame frame, String schemeName, CefRequest request) { + try { + return new SchemeResourceHandler(cls.newInstance()); + } catch(Throwable t) { + t.printStackTrace(); + return null; + } + } + + } + +} diff --git a/src/main/java/net/montoyo/mcef/client/ClientProxy.java b/src/main/java/net/montoyo/mcef/client/ClientProxy.java new file mode 100644 index 0000000..98a5e9d --- /dev/null +++ b/src/main/java/net/montoyo/mcef/client/ClientProxy.java @@ -0,0 +1,374 @@ +package net.montoyo.mcef.client; + +import net.minecraft.client.Minecraft; +import net.minecraft.util.text.Style; +import net.minecraft.util.text.TextComponentString; +import net.minecraft.util.text.TextFormatting; +import net.minecraftforge.common.MinecraftForge; +import net.minecraftforge.fml.client.SplashProgress; +import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; +import net.minecraftforge.fml.common.gameevent.PlayerEvent; +import net.minecraftforge.fml.common.gameevent.TickEvent; +import net.montoyo.mcef.BaseProxy; +import net.montoyo.mcef.MCEF; +import net.montoyo.mcef.api.IBrowser; +import net.montoyo.mcef.api.IDisplayHandler; +import net.montoyo.mcef.api.IJSQueryHandler; +import net.montoyo.mcef.api.IScheme; +import net.montoyo.mcef.coremod.ShutdownPatcher; +import net.montoyo.mcef.example.ExampleMod; +import net.montoyo.mcef.remote.RemoteConfig; +import net.montoyo.mcef.utilities.ForgeProgressListener; +import net.montoyo.mcef.utilities.IProgressListener; +import net.montoyo.mcef.utilities.Log; +import net.montoyo.mcef.utilities.Util; +import net.montoyo.mcef.virtual.VirtualBrowser; +import org.cef.CefApp; +import org.cef.CefClient; +import org.cef.CefSettings; +import org.cef.OS; +import org.cef.browser.CefBrowser; +import org.cef.browser.CefBrowserOsr; +import org.cef.browser.CefMessageRouter; +import org.cef.browser.CefMessageRouter.CefMessageRouterConfig; +import org.cef.browser.CefRenderer; +import org.cef.handler.CefLifeSpanHandlerAdapter; + +import java.io.BufferedReader; +import java.io.File; +import java.io.InputStreamReader; +import java.lang.reflect.Field; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class ClientProxy extends BaseProxy { + + public static String ROOT = "."; + public static String JCEF_ROOT = "."; + public static boolean VIRTUAL = false; + + private CefApp cefApp; + private CefClient cefClient; + private CefMessageRouter cefRouter; + private final ArrayList browsers = new ArrayList<>(); + private String updateStr; + private final Minecraft mc = Minecraft.getMinecraft(); + private final DisplayHandler displayHandler = new DisplayHandler(); + private final HashMap mimeTypeMap = new HashMap<>(); + private final AppHandler appHandler = new AppHandler(); + private ExampleMod exampleMod; + + @Override + public void onPreInit() { + exampleMod = new ExampleMod(); + exampleMod.onPreInit(); //Do it even if example mod is disabled because it registers the "mod://" scheme + } + + @Override + public void onInit() { + appHandler.setArgs(MCEF.CEF_ARGS); + + boolean enableForgeSplash = false; + try { + Field f = SplashProgress.class.getDeclaredField("enabled"); + f.setAccessible(true); + enableForgeSplash = f.getBoolean(null); + } catch (Throwable t) { + t.printStackTrace(); + } + + ROOT = mc.mcDataDir.getAbsolutePath().replaceAll("\\\\", "/"); + if (ROOT.endsWith(".")) + ROOT = ROOT.substring(0, ROOT.length() - 1); + + if (ROOT.endsWith("/")) + ROOT = ROOT.substring(0, ROOT.length() - 1); + + JCEF_ROOT = ROOT + "/jcef"; + + File fileListing = new File(new File(ROOT), "config"); + + IProgressListener ipl; + RemoteConfig cfg = new RemoteConfig(); + if (MCEF.USE_FORGE_SPLASH && enableForgeSplash) + ipl = new ForgeProgressListener(); + else + ipl = new UpdateFrame(); + + cfg.load(); + + if (!cfg.downloadMCEF(ipl)) { + Log.warning("Going in virtual mode; couldn't download resources."); + VIRTUAL = true; + return; + } + + updateStr = cfg.getUpdateString(); + ipl.onProgressEnd(); + + if (OS.isLinux()) { + File subproc = new File(JCEF_ROOT, "jcef_helper"); + + // Attempt to make the CEF subprocess executable if not + if (!subproc.canExecute()) { + try { + int retCode = Runtime.getRuntime().exec(new String[]{"/usr/bin/chmod", "+x", subproc.getAbsolutePath()}).waitFor(); + + if (retCode != 0) + throw new RuntimeException("chmod exited with code " + retCode); + } catch (Throwable t) { + Log.errorEx("Error while giving execution rights to jcef_helper. MCEF will enter virtual mode. You can fix this by chmoding jcef_helper manually.", t); + VIRTUAL = true; + } + } + } + + if (VIRTUAL) + return; + + CefSettings settings = new CefSettings(); + settings.windowless_rendering_enabled = true; + settings.background_color = settings.new ColorType(0, 255, 255, 255); + settings.cache_path = (new File(JCEF_ROOT, "cache")).getAbsolutePath(); + + CefApp.startup(MCEF.CEF_ARGS); + cefApp = CefApp.getInstance(settings); + + // Custom scheme broken on Linux, for now + if (!OS.isLinux()) { + CefApp.addAppHandler(appHandler); + } + + loadMimeTypeMapping(); + + cefClient = cefApp.createClient(); + + Log.info(cefApp.getVersion().toString()); + cefRouter = CefMessageRouter.create(new CefMessageRouterConfig("mcefQuery", "mcefCancel")); + cefClient.addMessageRouter(cefRouter); + cefClient.addDisplayHandler(displayHandler); + cefClient.addLifeSpanHandler(new CefLifeSpanHandlerAdapter() { + @Override + public boolean doClose(CefBrowser browser) { + browser.close(true); + return false; + } + }); + + if (!ShutdownPatcher.didPatchSucceed()) { + Log.warning("ShutdownPatcher failed to patch Minecraft.run() method; starting ShutdownThread..."); + (new ShutdownThread()).start(); + } + + MinecraftForge.EVENT_BUS.register(this); + if (MCEF.ENABLE_EXAMPLE) + exampleMod.onInit(); + + Log.info("MCEF loaded successfuly."); + } + + public CefApp getCefApp() { + return cefApp; + } + + @Override + public IBrowser createBrowser(String url, boolean transp) { + if (VIRTUAL) + return new VirtualBrowser(); + + CefBrowserOsr ret = (CefBrowserOsr) cefClient.createBrowser(url, true, transp); + ret.setCloseAllowed(); + ret.createImmediately(); + + browsers.add(ret); + return ret; + } + + @Override + public void registerDisplayHandler(IDisplayHandler idh) { + displayHandler.addHandler(idh); + } + + @Override + public boolean isVirtual() { + return VIRTUAL; + } + + @Override + public void openExampleBrowser(String url) { + if (MCEF.ENABLE_EXAMPLE) + exampleMod.showScreen(url); + } + + @Override + public void registerJSQueryHandler(IJSQueryHandler iqh) { + if (!VIRTUAL) + cefRouter.addHandler(new MessageRouter(iqh), false); + } + + @Override + public void registerScheme(String name, Class schemeClass, boolean std, boolean local, boolean displayIsolated, boolean secure, boolean corsEnabled, boolean cspBypassing, boolean fetchEnabled) { + appHandler.registerScheme(name, schemeClass, std, local, displayIsolated, secure, corsEnabled, cspBypassing, fetchEnabled); + } + + @Override + public boolean isSchemeRegistered(String name) { + return appHandler.isSchemeRegistered(name); + } + + @SubscribeEvent + public void onTick(TickEvent.RenderTickEvent ev) { + if (ev.phase == TickEvent.Phase.START) { + mc.mcProfiler.startSection("MCEF"); + + if (cefApp != null) + cefApp.N_DoMessageLoopWork(); + + for (CefBrowserOsr b : browsers) + b.mcefUpdate(); + + displayHandler.update(); + mc.mcProfiler.endSection(); + } + } + + @SubscribeEvent + public void onLogin(PlayerEvent.PlayerLoggedInEvent ev) { + if (updateStr == null || !MCEF.WARN_UPDATES) + return; + + Style cs = new Style(); + cs.setColor(TextFormatting.LIGHT_PURPLE); + + TextComponentString cct = new TextComponentString(updateStr); + cct.setStyle(cs); + + ev.player.sendMessage(cct); + } + + public void removeBrowser(CefBrowserOsr b) { + browsers.remove(b); + } + + @Override + public IBrowser createBrowser(String url) { + return createBrowser(url, false); + } + + private void runMessageLoopFor(long ms) { + final long start = System.currentTimeMillis(); + + do { + cefApp.N_DoMessageLoopWork(); + } while (System.currentTimeMillis() - start < ms); + } + + @Override + public void onShutdown() { + if (VIRTUAL) + return; + + Log.info("Shutting down JCEF..."); + CefBrowserOsr.CLEANUP = false; //Workaround + + for (CefBrowserOsr b : browsers) + b.close(); + + browsers.clear(); + + if (MCEF.CHECK_VRAM_LEAK) + CefRenderer.dumpVRAMLeak(); + + runMessageLoopFor(100); + CefApp.forceShutdownState(); + cefClient.dispose(); + + if (MCEF.SHUTDOWN_JCEF) + cefApp.N_Shutdown(); + } + + public void loadMimeTypeMapping() { + Pattern p = Pattern.compile("^(\\S+)\\s+(\\S+)\\s*(\\S*)\\s*(\\S*)$"); + String line = ""; + int cLine = 0; + mimeTypeMap.clear(); + + try { + BufferedReader br = new BufferedReader(new InputStreamReader(ClientProxy.class.getResourceAsStream("/assets/mcef/mime.types"))); + + while (true) { + cLine++; + line = br.readLine(); + if (line == null) + break; + + line = line.trim(); + if (!line.startsWith("#")) { + Matcher m = p.matcher(line); + if (!m.matches()) + continue; + + mimeTypeMap.put(m.group(2), m.group(1)); + if (m.groupCount() >= 4 && !m.group(3).isEmpty()) { + mimeTypeMap.put(m.group(3), m.group(1)); + + if (m.groupCount() >= 5 && !m.group(4).isEmpty()) + mimeTypeMap.put(m.group(4), m.group(1)); + } + } + } + + Util.close(br); + } catch (Throwable e) { + Log.error("[Mime Types] Error while parsing \"%s\" at line %d:", line, cLine); + e.printStackTrace(); + } + + Log.info("Loaded %d mime types", mimeTypeMap.size()); + } + + @Override + public String mimeTypeFromExtension(String ext) { + ext = ext.toLowerCase(); + String ret = mimeTypeMap.get(ext); + if (ret != null) + return ret; + + //If the mimeTypeMap couldn't be loaded, fall back to common things + switch (ext) { + case "htm": + case "html": + return "text/html"; + + case "css": + return "text/css"; + + case "js": + return "text/javascript"; + + case "png": + return "image/png"; + + case "jpg": + case "jpeg": + return "image/jpeg"; + + case "gif": + return "image/gif"; + + case "svg": + return "image/svg+xml"; + + case "xml": + return "text/xml"; + + case "txt": + return "text/plain"; + + default: + return null; + } + } +} diff --git a/src/main/java/net/montoyo/mcef/client/DisplayHandler.java b/src/main/java/net/montoyo/mcef/client/DisplayHandler.java new file mode 100644 index 0000000..2d3653c --- /dev/null +++ b/src/main/java/net/montoyo/mcef/client/DisplayHandler.java @@ -0,0 +1,116 @@ +package net.montoyo.mcef.client; + +import net.montoyo.mcef.api.IDisplayHandler; + +import org.cef.CefSettings; +import org.cef.browser.CefBrowser; +import org.cef.browser.CefBrowserOsr; +import org.cef.browser.CefFrame; +import org.cef.handler.CefDisplayHandler; + +import java.util.ArrayList; + +public class DisplayHandler implements CefDisplayHandler { + + private final ArrayList list = new ArrayList<>(); + private final ArrayList queue = new ArrayList<>(); + + private enum EventType { + + ADDRESS_CHANGE, + TITLE_CHANGE, + TOOLTIP, + STATUS_MESSAGE; + + } + + private static final class EventData { + + private final CefBrowser browser; + private final String data; + private final EventType type; + + private EventData(CefBrowser b, String d, EventType t) { + browser = b; + data = d; + type = t; + } + + private void execute(IDisplayHandler idh) { + switch(type) { + case ADDRESS_CHANGE: + idh.onAddressChange((CefBrowserOsr) browser, data); + break; + + case TITLE_CHANGE: + idh.onTitleChange((CefBrowserOsr) browser, data); + break; + + case TOOLTIP: + idh.onTooltip((CefBrowserOsr) browser, data); + break; + + case STATUS_MESSAGE: + idh.onStatusMessage((CefBrowserOsr) browser, data); + break; + } + } + + } + + @Override + public void onAddressChange(CefBrowser browser, CefFrame frame, String url) { + synchronized(queue) { + queue.add(new EventData(browser, url, EventType.ADDRESS_CHANGE)); + } + } + + @Override + public void onTitleChange(CefBrowser browser, String title) { + synchronized(queue) { + queue.add(new EventData(browser, title, EventType.TITLE_CHANGE)); + } + } + + @Override + public boolean onTooltip(CefBrowser browser, String text) { + synchronized(queue) { + queue.add(new EventData(browser, text, EventType.TOOLTIP)); + } + + return false; + } + + @Override + public void onStatusMessage(CefBrowser browser, String value) { + synchronized(queue) { + queue.add(new EventData(browser, value, EventType.STATUS_MESSAGE)); + } + } + + @Override + public boolean onConsoleMessage(CefBrowser browser, CefSettings.LogSeverity level, String message, String source, int line) { + return false; + } + + @Override + public boolean onCursorChange(CefBrowser browser, int cursorType) { + return false; + } + + public void addHandler(IDisplayHandler h) { + list.add(h); + } + + public void update() { + synchronized(queue) { + while(!queue.isEmpty()) { + EventData ed = queue.remove(0); + + for(IDisplayHandler idh : list) + ed.execute(idh); + } + } + } + +} diff --git a/src/main/java/net/montoyo/mcef/client/MessageRouter.java b/src/main/java/net/montoyo/mcef/client/MessageRouter.java new file mode 100644 index 0000000..9c34ac2 --- /dev/null +++ b/src/main/java/net/montoyo/mcef/client/MessageRouter.java @@ -0,0 +1,29 @@ +package net.montoyo.mcef.client; + +import net.montoyo.mcef.api.IJSQueryHandler; + +import org.cef.browser.CefBrowser; +import org.cef.browser.CefBrowserOsr; +import org.cef.browser.CefFrame; +import org.cef.callback.CefQueryCallback; +import org.cef.handler.CefMessageRouterHandlerAdapter; + +public class MessageRouter extends CefMessageRouterHandlerAdapter { + + private IJSQueryHandler handler; + + public MessageRouter(IJSQueryHandler h) { + handler = h; + } + + @Override + public boolean onQuery(CefBrowser browser, CefFrame frame, long query_id, String request, boolean persistent, CefQueryCallback callback) { + return handler.handleQuery((CefBrowserOsr) browser, query_id, request, persistent, new QueryCallback(callback)); + } + + @Override + public void onQueryCanceled(CefBrowser browser, CefFrame frame, long query_id) { + handler.cancelQuery((CefBrowserOsr) browser, query_id); + } + +} diff --git a/src/main/java/net/montoyo/mcef/client/QueryCallback.java b/src/main/java/net/montoyo/mcef/client/QueryCallback.java new file mode 100644 index 0000000..3b370ee --- /dev/null +++ b/src/main/java/net/montoyo/mcef/client/QueryCallback.java @@ -0,0 +1,24 @@ +package net.montoyo.mcef.client; + +import org.cef.callback.CefQueryCallback; +import net.montoyo.mcef.api.IJSQueryCallback; + +public class QueryCallback implements IJSQueryCallback { + + private CefQueryCallback cb; + + public QueryCallback(CefQueryCallback cb) { + this.cb = cb; + } + + @Override + public void success(String response) { + cb.success(response); + } + + @Override + public void failure(int errId, String errMsg) { + cb.failure(errId, errMsg); + } + +} diff --git a/src/main/java/net/montoyo/mcef/client/SchemeResourceHandler.java b/src/main/java/net/montoyo/mcef/client/SchemeResourceHandler.java new file mode 100644 index 0000000..e3f30ec --- /dev/null +++ b/src/main/java/net/montoyo/mcef/client/SchemeResourceHandler.java @@ -0,0 +1,48 @@ +package net.montoyo.mcef.client; + +import net.montoyo.mcef.api.IScheme; +import net.montoyo.mcef.api.SchemePreResponse; +import org.cef.callback.CefCallback; +import org.cef.handler.CefResourceHandlerAdapter; +import org.cef.misc.IntRef; +import org.cef.misc.StringRef; +import org.cef.network.CefRequest; +import org.cef.network.CefResponse; + +public class SchemeResourceHandler extends CefResourceHandlerAdapter { + + private final IScheme scheme; + + public SchemeResourceHandler(IScheme scm) { + scheme = scm; + } + + @Override + public boolean processRequest(CefRequest request, CefCallback callback) { + SchemePreResponse resp = scheme.processRequest(request.getURL()); + + switch(resp) { + case HANDLED_CONTINUE: + callback.Continue(); + return true; + + case HANDLED_CANCEL: + callback.cancel(); + return true; + + default: + return false; + } + } + + @Override + public void getResponseHeaders(CefResponse response, IntRef response_length, StringRef redirectUrl) { + scheme.getResponseHeaders(new SchemeResponseHeaders(response, response_length, redirectUrl)); + } + + @Override + public boolean readResponse(byte[] data_out, int bytes_to_read, IntRef bytes_read, CefCallback callback) { + return scheme.readResponse(new SchemeResponseData(data_out, bytes_to_read, bytes_read)); + } + +} diff --git a/src/main/java/net/montoyo/mcef/client/SchemeResponseData.java b/src/main/java/net/montoyo/mcef/client/SchemeResponseData.java new file mode 100644 index 0000000..ebf3ac0 --- /dev/null +++ b/src/main/java/net/montoyo/mcef/client/SchemeResponseData.java @@ -0,0 +1,33 @@ +package net.montoyo.mcef.client; + +import net.montoyo.mcef.api.ISchemeResponseData; +import org.cef.misc.IntRef; + +public class SchemeResponseData implements ISchemeResponseData { + + private final byte[] data; + private final int toRead; + private final IntRef read; + + public SchemeResponseData(byte[] data, int toRead, IntRef read) { + this.data = data; + this.toRead = toRead; + this.read = read; + } + + @Override + public byte[] getDataArray() { + return data; + } + + @Override + public int getBytesToRead() { + return toRead; + } + + @Override + public void setAmountRead(int rd) { + read.set(rd); + } + +} diff --git a/src/main/java/net/montoyo/mcef/client/SchemeResponseHeaders.java b/src/main/java/net/montoyo/mcef/client/SchemeResponseHeaders.java new file mode 100644 index 0000000..f07125f --- /dev/null +++ b/src/main/java/net/montoyo/mcef/client/SchemeResponseHeaders.java @@ -0,0 +1,45 @@ +package net.montoyo.mcef.client; + +import net.montoyo.mcef.api.ISchemeResponseHeaders; +import org.cef.misc.IntRef; +import org.cef.misc.StringRef; +import org.cef.network.CefResponse; + +public class SchemeResponseHeaders implements ISchemeResponseHeaders { + + private final CefResponse response; + private final IntRef length; + private final StringRef redirURL; + + public SchemeResponseHeaders(CefResponse r, IntRef l, StringRef url) { + response = r; + length = l; + redirURL = url; + } + + @Override + public void setMimeType(String mt) { + response.setMimeType(mt); + } + + @Override + public void setStatus(int status) { + response.setStatus(status); + } + + @Override + public void setStatusText(String st) { + response.setStatusText(st); + } + + @Override + public void setResponseLength(int len) { + length.set(len); + } + + @Override + public void setRedirectURL(String r) { + redirURL.set(r); + } + +} diff --git a/src/main/java/net/montoyo/mcef/client/ShutdownThread.java b/src/main/java/net/montoyo/mcef/client/ShutdownThread.java new file mode 100644 index 0000000..5e6212c --- /dev/null +++ b/src/main/java/net/montoyo/mcef/client/ShutdownThread.java @@ -0,0 +1,69 @@ +package net.montoyo.mcef.client; + +import java.lang.reflect.Field; +import java.lang.reflect.Modifier; +import java.util.ArrayList; + +import net.montoyo.mcef.MCEF; +import org.cef.CefApp; +import org.cef.browser.CefBrowserOsr; + +import net.minecraft.client.Minecraft; +import net.montoyo.mcef.utilities.Log; + +public class ShutdownThread extends Thread { + + private Field running = null; + private Minecraft mc = Minecraft.getMinecraft(); + + public ShutdownThread() { + super("MCEF-Shutdown"); + setDaemon(false); + + try { + Field[] fields = Minecraft.class.getDeclaredFields(); + + for(Field f: fields) { + if(f.getType().equals(Boolean.TYPE)) { + //Log.info("Minecraft.%s: %s", f.getName(), Modifier.toString(f.getModifiers())); + + if(f.getModifiers() == Modifier.VOLATILE) { + f.setAccessible(true); + running = f; + Log.info("volatile boolean Minecraft.running => %s", f.getName()); + break; + } + } + } + } catch(Throwable t) { + Log.warning("Can't detect Minecraft shutdown:"); + t.printStackTrace(); + } + } + + @Override + public void run() { + if(running == null) + return; + + Log.info("Minecraft shutdown detection thread started."); + + while(true) { + try { + if(!running.getBoolean(mc)) + break; + } catch(Throwable t) { + Log.warning("Can't detect Minecraft shutdown:"); + t.printStackTrace(); + return; + } + + try { + sleep(100); + } catch(Throwable t) {} + } + + MCEF.PROXY.onShutdown(); + } + +} diff --git a/src/main/java/net/montoyo/mcef/client/StringVisitor.java b/src/main/java/net/montoyo/mcef/client/StringVisitor.java new file mode 100644 index 0000000..b154567 --- /dev/null +++ b/src/main/java/net/montoyo/mcef/client/StringVisitor.java @@ -0,0 +1,20 @@ +package net.montoyo.mcef.client; + +import net.montoyo.mcef.api.IStringVisitor; + +import org.cef.callback.CefStringVisitor; + +public class StringVisitor implements CefStringVisitor { + + IStringVisitor isv; + + public StringVisitor(IStringVisitor isv) { + this.isv = isv; + } + + @Override + public void visit(String string) { + isv.visit(string); + } + +} diff --git a/src/main/java/net/montoyo/mcef/client/UpdateFrame.java b/src/main/java/net/montoyo/mcef/client/UpdateFrame.java new file mode 100644 index 0000000..ae4b2f5 --- /dev/null +++ b/src/main/java/net/montoyo/mcef/client/UpdateFrame.java @@ -0,0 +1,79 @@ +package net.montoyo.mcef.client; + +import java.awt.Dimension; + +import javax.swing.BorderFactory; +import javax.swing.Box; +import javax.swing.BoxLayout; +import javax.swing.JFrame; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JProgressBar; +import javax.swing.SwingUtilities; +import javax.swing.UIManager; + +import net.montoyo.mcef.utilities.IProgressListener; +import net.montoyo.mcef.utilities.Log; +import net.montoyo.mcef.utilities.Util; + +public class UpdateFrame extends JFrame implements IProgressListener { + + private JLabel label = new JLabel("Preparing..."); + private JProgressBar pbar = new JProgressBar(); + + public UpdateFrame() { + setTitle("Minecraft ChromiumEF"); + setDefaultCloseOperation(DO_NOTHING_ON_CLOSE); + setLocationRelativeTo(null); + + JPanel lpane = new JPanel(); + lpane.setLayout(new BoxLayout(lpane, BoxLayout.LINE_AXIS)); + lpane.add(label); + lpane.add(Box.createHorizontalGlue()); + + Dimension dim = new Dimension(5, 5); + JPanel pane = new JPanel(); + + pane.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5)); + pane.setLayout(new BoxLayout(pane, BoxLayout.PAGE_AXIS)); + pane.add(lpane); + pane.add(new Box.Filler(dim, dim, dim)); + pane.add(pbar); + + setContentPane(pane); + + try { + UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName()); + SwingUtilities.updateComponentTreeUI(this); + } catch(Throwable t) { + Log.info("Note: couldn't set system look & feel."); + } + + setVisible(true); + + dim = new Dimension(50, 26); + pbar.setMinimumSize(dim); + pbar.setPreferredSize(dim); + + setMinimumSize(new Dimension(540, 90)); + pack(); + } + + @Override + public void onProgressed(double d) { + int val = (int) Util.clamp(d, 0.d, 100.d); + pbar.setValue(val); + } + + @Override + public void onTaskChanged(String name) { + Log.info("Task changed to \"%s\"", name); + label.setText(name); + } + + @Override + public void onProgressEnd() { + dispose(); + } + +} diff --git a/src/main/java/net/montoyo/mcef/coremod/ShutdownPatcher.java b/src/main/java/net/montoyo/mcef/coremod/ShutdownPatcher.java new file mode 100644 index 0000000..7be79bc --- /dev/null +++ b/src/main/java/net/montoyo/mcef/coremod/ShutdownPatcher.java @@ -0,0 +1,114 @@ +package net.montoyo.mcef.coremod; + +import net.minecraftforge.fml.relauncher.IFMLLoadingPlugin; +import net.minecraft.launchwrapper.IClassTransformer; +import org.apache.logging.log4j.Level; +import org.apache.logging.log4j.LogManager; +import org.objectweb.asm.*; + +import java.util.Map; + +@IFMLLoadingPlugin.Name(value = "ShutdownPatcher") +@IFMLLoadingPlugin.TransformerExclusions(value = "net.montoyo.mcef.") +@IFMLLoadingPlugin.SortingIndex(value = 90007531) //It has to run after the searge-name transformation +@IFMLLoadingPlugin.MCVersion("1.12.2") +public class ShutdownPatcher implements IFMLLoadingPlugin, IClassTransformer { + + private static boolean PATCH_OK = false; + private static final String OBF_SHUTDOWN_METHOD = "func_71405_e"; //The "searge-obfuscated" name of the Minecraft.shutdownMinecraftApplet() method + + public static boolean didPatchSucceed() { + return PATCH_OK; + } + + @Override + public String[] getASMTransformerClass() { + return new String[] { "net.montoyo.mcef.coremod.ShutdownPatcher" }; + } + + @Override + public String getModContainerClass() { + return null; + } + + @Override + public String getSetupClass() { + return null; + } + + @Override + public void injectData(Map data) { + } + + @Override + public String getAccessTransformerClass() { + return null; + } + + @Override + public byte[] transform(String name, String deobfName, byte[] cls) { + if(!deobfName.equals("net.minecraft.client.Minecraft")) + return cls; + + boolean envObf = !name.equals(deobfName); //If the current environment is obfuscated + log("Now transforming %s, aka %s (obfuscated: %s)", name, deobfName, envObf ? "yes" : "no"); + + try { + ClassReader cr = new ClassReader(cls); + ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS); + McVisitor cv = new McVisitor(cw, envObf); + cr.accept(cv, 0); + + return cw.toByteArray(); + } catch(Throwable t) { + t.printStackTrace(); + log("Failed to setup Minecraft shutdown detector."); + } + + return cls; //Abort class transforming + } + + private static class ShutdownMCAppletVisitor extends MethodVisitor { + + public ShutdownMCAppletVisitor(MethodVisitor mv) { + super(Opcodes.ASM5, mv); + } + + @Override + public void visitCode() { + mv.visitCode(); + mv.visitMethodInsn(Opcodes.INVOKESTATIC, "net/montoyo/mcef/MCEF", "onMinecraftShutdown", "()V", false); + PATCH_OK = true; + log("Target section has been patched."); + } + } + + private static class McVisitor extends ClassVisitor { + + private final boolean envObf; + + public McVisitor(ClassVisitor cv, boolean obf) { + super(Opcodes.ASM5, cv); + envObf = obf; + } + + @Override + public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) { + MethodVisitor mv = cv.visitMethod(access, name, desc, signature, exceptions); + if(access == Opcodes.ACC_PUBLIC && desc.equals("()V")) { //void shutdownMinecraftApplet() + if((envObf && name.equals(OBF_SHUTDOWN_METHOD)) || name.equals("shutdownMinecraftApplet")) { + log("shutdownMinecraftApplet() method found; transforming..."); + return new ShutdownMCAppletVisitor(mv); + } + } + + return mv; + } + + } + + private static void log(String str, Object ... args) { + LogManager.getLogger("MCEF").log(Level.INFO, String.format(str, args)); + } + +} diff --git a/src/main/java/net/montoyo/mcef/example/BrowserScreen.java b/src/main/java/net/montoyo/mcef/example/BrowserScreen.java new file mode 100644 index 0000000..450f92a --- /dev/null +++ b/src/main/java/net/montoyo/mcef/example/BrowserScreen.java @@ -0,0 +1,250 @@ +package net.montoyo.mcef.example; + +import net.minecraft.client.renderer.GlStateManager; +import net.montoyo.mcef.MCEF; +import net.montoyo.mcef.utilities.Log; +import org.lwjgl.opengl.GL11; +import org.lwjgl.input.Keyboard; +import org.lwjgl.input.Mouse; + +import net.minecraft.client.gui.GuiButton; +import net.minecraft.client.gui.GuiScreen; +import net.minecraft.client.gui.GuiTextField; +import net.montoyo.mcef.api.API; +import net.montoyo.mcef.api.IBrowser; +import net.montoyo.mcef.api.MCEFApi; + +import java.lang.reflect.Field; +import java.lang.reflect.Modifier; +import java.util.HashMap; + +public class BrowserScreen extends GuiScreen { + + IBrowser browser = null; + private GuiButton back = null; + private GuiButton fwd = null; + private GuiButton go = null; + private GuiButton min = null; + private GuiButton vidMode = null; + private GuiTextField url = null; + private String urlToLoad = null; + + private static final String YT_REGEX1 = "^https?://(?:www\\.)?youtube\\.com/watch\\?v=([a-zA-Z0-9_\\-]+)$"; + private static final String YT_REGEX2 = "^https?://(?:www\\.)?youtu\\.be/([a-zA-Z0-9_\\-]+)$"; + private static final String YT_REGEX3 = "^https?://(?:www\\.)?youtube\\.com/embed/([a-zA-Z0-9_\\-]+)(\\?.+)?$"; + + public BrowserScreen() { + urlToLoad = MCEF.HOME_PAGE; + } + + public BrowserScreen(String url) { + urlToLoad = (url == null) ? MCEF.HOME_PAGE : url; + } + + @Override + public void initGui() { + ExampleMod.INSTANCE.hudBrowser = null; + + if(browser == null) { + //Grab the API and make sure it isn't null. + API api = MCEFApi.getAPI(); + if(api == null) + return; + + //Create a browser and resize it to fit the screen + browser = api.createBrowser((urlToLoad == null) ? MCEF.HOME_PAGE : urlToLoad, false); + urlToLoad = null; + } + + //Resize the browser if window size changed + if(browser != null) + browser.resize(mc.displayWidth, mc.displayHeight - scaleY(20)); + + //Create GUI + Keyboard.enableRepeatEvents(true); + buttonList.clear(); + + if(url == null) { + buttonList.add(back = (new GuiButton(0, 0, 0, 20, 20, "<"))); + buttonList.add(fwd = (new GuiButton(1, 20, 0, 20, 20, ">"))); + buttonList.add(go = (new GuiButton(2, width - 60, 0, 20, 20, "Go"))); + buttonList.add(min = (new GuiButton(3, width - 20, 0, 20, 20, "_"))); + buttonList.add(vidMode = (new GuiButton(4, width - 40, 0, 20, 20, "YT"))); + vidMode.enabled = false; + + url = new GuiTextField(5, fontRenderer, 40, 0, width - 100, 20); + url.setMaxStringLength(65535); + //url.setText("mod://mcef/home.html"); + } else { + buttonList.add(back); + buttonList.add(fwd); + buttonList.add(go); + buttonList.add(min); + buttonList.add(vidMode); + + //Handle resizing + vidMode.x = width - 40; + go.x = width - 60; + min.x = width - 20; + + String old = url.getText(); + url = new GuiTextField(5, fontRenderer, 40, 0, width - 100, 20); + url.setMaxStringLength(65535); + url.setText(old); + } + } + + public int scaleY(int y) { + double sy = ((double) y) / ((double) height) * ((double) mc.displayHeight); + return (int) sy; + } + + public void loadURL(String url) { + if(browser == null) + urlToLoad = url; + else + browser.loadURL(url); + } + + @Override + public void updateScreen() { + if(urlToLoad != null && browser != null) { + browser.loadURL(urlToLoad); + urlToLoad = null; + } + } + + @Override + public void drawScreen(int i1, int i2, float f) { + //Render the URL box first because it overflows a bit + url.drawTextBox(); + + //Render buttons + super.drawScreen(i1, i2, f); + + //Renders the browser if itsn't null + if(browser != null) { + GlStateManager.disableDepth(); + GlStateManager.enableTexture2D(); + GlStateManager.color(1.0f, 1.0f, 1.0f, 1.0f); + browser.draw(.0d, height, width, 20.d); //Don't forget to flip Y axis. + GlStateManager.enableDepth(); + } + } + + @Override + public void onGuiClosed() { + //Make sure to close the browser when you don't need it anymore. + if(!ExampleMod.INSTANCE.hasBackup() && browser != null) + browser.close(); + + Keyboard.enableRepeatEvents(false); + } + + @Override + public void handleInput() { + while(Keyboard.next()) { + if(Keyboard.getEventKey() == Keyboard.KEY_ESCAPE) { + mc.displayGuiScreen(null); + return; + } + + boolean pressed = Keyboard.getEventKeyState(); + boolean focused = url.isFocused(); + char key = Keyboard.getEventCharacter(); + int num = Keyboard.getEventKey(); + + if(browser != null && !focused) { //Inject events into browser + if(pressed) + browser.injectKeyPressedByKeyCode(num, key, 0); + else + browser.injectKeyReleasedByKeyCode(num, key, 0); + + if(key != 0) + browser.injectKeyTyped(key, 0); + } + + //Forward event to text box. + if(!pressed && focused && num == Keyboard.KEY_RETURN) + actionPerformed(go); + else if(pressed) + url.textboxKeyTyped(key, num); + } + + while(Mouse.next()) { + int btn = Mouse.getEventButton(); + boolean pressed = Mouse.getEventButtonState(); + int sx = Mouse.getEventX(); + int sy = Mouse.getEventY(); + int wheel = Mouse.getEventDWheel(); + + if(browser != null) { //Inject events into browser. TODO: Handle mods & leaving. + int y = mc.displayHeight - sy - scaleY(20); //Don't forget to flip Y axis. + + if(wheel != 0) + browser.injectMouseWheel(sx, y, 0, 1, wheel); + else if(btn == -1) + browser.injectMouseMove(sx, y, 0, y < 0); + else + browser.injectMouseButton(sx, y, 0, btn + 1, pressed, 1); + } + + if(pressed) { //Forward events to GUI. + int x = sx * width / mc.displayWidth; + int y = height - (sy * height / mc.displayHeight) - 1; + + try { + mouseClicked(x, y, btn); + } catch(Throwable t) { + t.printStackTrace(); + } + + url.mouseClicked(x, y, btn); + } + } + } + + //Called by ExampleMod when the current browser's URL changes. + public void onUrlChanged(IBrowser b, String nurl) { + if(b == browser && url != null) { + url.setText(nurl); + vidMode.enabled = nurl.matches(YT_REGEX1) || nurl.matches(YT_REGEX2) || nurl.matches(YT_REGEX3); + } + } + + //Handle button clicks + @Override + protected void actionPerformed(GuiButton src) { + if(browser == null) + return; + + if(src.id == 0) + browser.goBack(); + else if(src.id == 1) + browser.goForward(); + else if(src.id == 2) { + String fixedURL = ExampleMod.INSTANCE.getAPI().punycode(url.getText()); + browser.loadURL(fixedURL); + } else if(src.id == 3) { + ExampleMod.INSTANCE.setBackup(this); + mc.displayGuiScreen(null); + } else if(src.id == 4) { + String loc = browser.getURL(); + String vId = null; + boolean redo = false; + + if(loc.matches(YT_REGEX1)) + vId = loc.replaceFirst(YT_REGEX1, "$1"); + else if(loc.matches(YT_REGEX2)) + vId = loc.replaceFirst(YT_REGEX2, "$1"); + else if(loc.matches(YT_REGEX3)) + redo = true; + + if(vId != null || redo) { + ExampleMod.INSTANCE.setBackup(this); + mc.displayGuiScreen(new ScreenCfg(browser, vId)); + } + } + } + +} diff --git a/src/main/java/net/montoyo/mcef/example/ExampleMod.java b/src/main/java/net/montoyo/mcef/example/ExampleMod.java new file mode 100644 index 0000000..29e7e51 --- /dev/null +++ b/src/main/java/net/montoyo/mcef/example/ExampleMod.java @@ -0,0 +1,167 @@ +package net.montoyo.mcef.example; + +import net.minecraftforge.common.MinecraftForge; +import net.montoyo.mcef.utilities.Log; +import org.lwjgl.input.Keyboard; + +import net.minecraft.client.Minecraft; +import net.minecraft.client.settings.KeyBinding; +import net.minecraftforge.client.event.RenderGameOverlayEvent; + +import net.montoyo.mcef.api.API; +import net.montoyo.mcef.api.IBrowser; +import net.montoyo.mcef.api.IDisplayHandler; +import net.montoyo.mcef.api.IJSQueryCallback; +import net.montoyo.mcef.api.IJSQueryHandler; +import net.montoyo.mcef.api.MCEFApi; + +import net.minecraftforge.fml.client.registry.ClientRegistry; +import net.minecraftforge.fml.common.FMLCommonHandler; +import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; +import net.minecraftforge.fml.common.gameevent.TickEvent; +import net.minecraftforge.fml.relauncher.Side; + +/** + * An example mod that shows you how to use MCEF. + * Assuming that it is client-side only and that onInit() is called on initialization. + * This example shows a simple 2D web browser when pressing F6. + * + * @author montoyo + * + */ +public class ExampleMod implements IDisplayHandler, IJSQueryHandler { + + public static ExampleMod INSTANCE; + + public ScreenCfg hudBrowser = null; + private KeyBinding key = new KeyBinding("Open Browser", Keyboard.KEY_F10, "key.categories.misc"); + private Minecraft mc = Minecraft.getMinecraft(); + private BrowserScreen backup = null; + private API api; + + public API getAPI() { + return api; + } + + public void onPreInit() { + //Grab the API and make sure it isn't null. + api = MCEFApi.getAPI(); + if(api == null) + return; + + api.registerScheme("mod", ModScheme.class, true, false, false, true, true, false, false); + } + + public void onInit() { + INSTANCE = this; + + //Register key binding and listen to the FML event bus for ticks. + ClientRegistry.registerKeyBinding(key); + MinecraftForge.EVENT_BUS.register(this); + + if(api != null) { + //Register this class to handle onAddressChange and onQuery events + api.registerDisplayHandler(this); + api.registerJSQueryHandler(this); + } + } + + public void setBackup(BrowserScreen bu) { + backup = bu; + } + + public boolean hasBackup() { + return (backup != null); + } + + public void showScreen(String url) { + if(mc.currentScreen instanceof BrowserScreen) + ((BrowserScreen) mc.currentScreen).loadURL(url); + else if(hasBackup()) { + mc.displayGuiScreen(backup); + backup.loadURL(url); + backup = null; + } else + mc.displayGuiScreen(new BrowserScreen(url)); + } + + public IBrowser getBrowser() { + if(mc.currentScreen instanceof BrowserScreen) + return ((BrowserScreen) mc.currentScreen).browser; + else if(backup != null) + return backup.browser; + else + return null; + } + + @SubscribeEvent + public void onTick(TickEvent ev) { + if(ev.phase == TickEvent.Phase.START && ev.side == Side.CLIENT && ev.type == TickEvent.Type.CLIENT) { + //Check if our key was pressed + if(key.isPressed() && !(mc.currentScreen instanceof BrowserScreen)) { + //Display the web browser UI. + mc.displayGuiScreen(hasBackup() ? backup : new BrowserScreen()); + backup = null; + } + } + } + + @Override + public void onAddressChange(IBrowser browser, String url) { + //Called by MCEF if a browser's URL changes. Forward this event to the screen. + if(mc.currentScreen instanceof BrowserScreen) + ((BrowserScreen) mc.currentScreen).onUrlChanged(browser, url); + else if(hasBackup()) + backup.onUrlChanged(browser, url); + } + + @Override + public void onTitleChange(IBrowser browser, String title) { + } + + @Override + public void onTooltip(IBrowser browser, String text) { + } + + @Override + public void onStatusMessage(IBrowser browser, String value) { + } + + @Override + public boolean handleQuery(IBrowser b, long queryId, String query, boolean persistent, IJSQueryCallback cb) { + if(b != null && query.equalsIgnoreCase("username")) { + if(b.getURL().startsWith("mod://")) { + //Only allow MCEF URLs to get the player's username to keep his identity secret + + mc.addScheduledTask(() -> { + //Add this to a scheduled task because this is NOT called from the main Minecraft thread... + + try { + String name = mc.getSession().getUsername(); + cb.success(name); + } catch(Throwable t) { + cb.failure(500, "Internal error."); + Log.warning("Could not get username from JavaScript:"); + t.printStackTrace(); + } + }); + } else + cb.failure(403, "Can't access username from external page"); + + return true; + } + + return false; + } + + @Override + public void cancelQuery(IBrowser b, long queryId) { + } + + @SubscribeEvent + public void onDrawHUD(RenderGameOverlayEvent.Post ev) { + if(hudBrowser != null) + hudBrowser.drawScreen(0, 0, 0.f); + } + +} diff --git a/src/main/java/net/montoyo/mcef/example/ModScheme.java b/src/main/java/net/montoyo/mcef/example/ModScheme.java new file mode 100644 index 0000000..dd51179 --- /dev/null +++ b/src/main/java/net/montoyo/mcef/example/ModScheme.java @@ -0,0 +1,78 @@ +package net.montoyo.mcef.example; + +import java.io.IOException; +import java.io.InputStream; + +import net.montoyo.mcef.MCEF; +import net.montoyo.mcef.api.*; +import net.montoyo.mcef.utilities.Log; + +public class ModScheme implements IScheme { + + private String contentType = null; + private InputStream is = null; + + @Override + public SchemePreResponse processRequest(String url) { + url = url.substring("mod://".length()); + + int pos = url.indexOf('/'); + if(pos < 0) + return SchemePreResponse.NOT_HANDLED; + + String mod = removeSlashes(url.substring(0, pos)); + String loc = removeSlashes(url.substring(pos + 1)); + + if(mod.length() <= 0 || loc.length() <= 0 || mod.charAt(0) == '.' || loc.charAt(0) == '.') { + Log.warning("Invalid URL " + url); + return SchemePreResponse.NOT_HANDLED; + } + + is = ModScheme.class.getResourceAsStream("/assets/" + mod.toLowerCase() + "/html/" + loc.toLowerCase()); + if(is == null) { + Log.warning("Resource " + url + " NOT found!"); + return SchemePreResponse.NOT_HANDLED; //Mhhhhh... 404? + } + + contentType = null; + pos = loc.lastIndexOf('.'); + if(pos >= 0 && pos < loc.length() - 2) + contentType = MCEF.PROXY.mimeTypeFromExtension(loc.substring(pos + 1)); + + return SchemePreResponse.HANDLED_CONTINUE; + } + + private String removeSlashes(String loc) { + int i = 0; + while(i < loc.length() && loc.charAt(i) == '/') + i++; + + return loc.substring(i); + } + + @Override + public void getResponseHeaders(ISchemeResponseHeaders rep) { + if(contentType != null) + rep.setMimeType(contentType); + + rep.setStatus(200); + rep.setStatusText("OK"); + rep.setResponseLength(-1); + } + + @Override + public boolean readResponse(ISchemeResponseData data) { + try { + int ret = is.read(data.getDataArray(), 0, data.getBytesToRead()); + if(ret <= 0) + is.close(); + + data.setAmountRead(Math.max(ret, 0)); + return ret > 0; + } catch(IOException e) { + e.printStackTrace(); + return false; + } + } + +} diff --git a/src/main/java/net/montoyo/mcef/example/ScreenCfg.java b/src/main/java/net/montoyo/mcef/example/ScreenCfg.java new file mode 100644 index 0000000..2922e28 --- /dev/null +++ b/src/main/java/net/montoyo/mcef/example/ScreenCfg.java @@ -0,0 +1,120 @@ +package net.montoyo.mcef.example; + +import net.minecraft.client.gui.GuiScreen; +import net.minecraft.client.renderer.BufferBuilder; +import net.minecraft.client.renderer.Tessellator; +import net.minecraft.client.renderer.vertex.DefaultVertexFormats; +import net.montoyo.mcef.api.IBrowser; +import org.lwjgl.input.Keyboard; +import org.lwjgl.input.Mouse; +import org.lwjgl.opengl.GL11; + +public class ScreenCfg extends GuiScreen { + + private IBrowser browser; + private int width = 320; + private int height = 180; + private int x = 10; + private int y = 10; + private int offsetX = 0; + private int offsetY = 0; + private boolean dragging = false; + private boolean resizing = false; + private boolean drawSquare = true; + + public ScreenCfg(IBrowser b, String vId) { + browser = b; + if(vId != null) + b.loadURL("https://www.youtube.com/embed/" + vId + "?autoplay=1"); + + b.resize(width, height); + } + + @Override + public void handleInput() { + while(Keyboard.next()) { + if(Keyboard.getEventKey() == Keyboard.KEY_ESCAPE) { + drawSquare = false; + ExampleMod.INSTANCE.hudBrowser = this; + browser.injectMouseMove(-10, -10, 0, true); + mc.displayGuiScreen(null); + return; + } + } + + while(Mouse.next()) { + int btn = Mouse.getEventButton(); + boolean pressed = Mouse.getEventButtonState(); + int sx = Mouse.getEventX(); + int sy = mc.displayHeight - Mouse.getEventY(); + + if(btn == 1 && pressed && sx >= x && sy >= y && sx < x + width && sy < y + height) { + browser.injectMouseMove(sx - x, sy - y, 0, false); + browser.injectMouseButton(sx - x, sy - y, 0, 1, true, 1); + browser.injectMouseButton(sx - x, sy - y, 0, 1, false, 1); + } else if(dragging) { + if(btn == 0 && !pressed) + dragging = false; + else { + x = sx + offsetX; + y = sy + offsetY; + } + } else if(resizing) { + if(btn == 0 && !pressed) { + resizing = false; + browser.resize(width, height); + } else { + int w = sx - x; + int h = sy - y; + + if(w >= 32 && h >= 18) { + if(h >= w) { + double dw = ((double) h) * (16.0 / 9.0); + width = (int) dw; + height = h; + } else { + double dh = ((double) w) * (9.0 / 16.0); + width = w; + height = (int) dh; + } + } + } + } else if(pressed && btn == 0 && sx >= x && sy >= y && sx < x + width && sy < y + height) { //In browser rect + dragging = true; + offsetX = x - sx; + offsetY = y - sy; + } else if(pressed && btn == 0 && sx >= x + width && sy >= y + height && sx < x + width + 10 && sy < y + height + 10) //In resize rect + resizing = true; + } + } + + @Override + public void drawScreen(int i1, int i2, float f) { + GL11.glDisable(GL11.GL_DEPTH_TEST); + GL11.glEnable(GL11.GL_TEXTURE_2D); + browser.draw(unscaleX(x), unscaleY(height + y), unscaleX(width + x), unscaleY(y)); + + if(drawSquare) { + Tessellator t = Tessellator.getInstance(); + BufferBuilder vb = t.getBuffer(); + + vb.begin(GL11.GL_LINE_LOOP, DefaultVertexFormats.POSITION_COLOR); + vb.pos(unscaleX(x + width), unscaleY(y + height), 0.0).color(255, 255, 255, 255).endVertex(); + vb.pos(unscaleX(x + width + 10), unscaleY(y + height), 0.0).color(255, 255, 255, 255).endVertex(); + vb.pos(unscaleX(x + width + 10), unscaleY(y + height + 10), 0.0).color(255, 255, 255, 255).endVertex(); + vb.pos(unscaleX(x + width), unscaleY(y + height + 10), 0.0).color(255, 255, 255, 255).endVertex(); + t.draw(); + } + + GL11.glEnable(GL11.GL_DEPTH_TEST); + } + + public double unscaleX(int x) { + return ((double) x) / ((double) mc.displayWidth) * ((double) super.width); + } + + public double unscaleY(int y) { + return ((double) y) / ((double) mc.displayHeight) * ((double) super.height); + } + +} diff --git a/src/main/java/net/montoyo/mcef/remote/Mirror.java b/src/main/java/net/montoyo/mcef/remote/Mirror.java new file mode 100644 index 0000000..16b97bf --- /dev/null +++ b/src/main/java/net/montoyo/mcef/remote/Mirror.java @@ -0,0 +1,104 @@ +package net.montoyo.mcef.remote; + +import java.io.IOException; +import java.net.HttpURLConnection; +import java.net.MalformedURLException; +import java.net.URL; + +/** + * An object representing an HTTP(S) mirror to download the resources from. + * + * @author montoyo + * @see {@link net.montoyo.mcef.remote.MirrorManager} + */ +public final class Mirror { + + /** + * Whether the mirror is HTTPS or not + */ + public static final int FLAG_SECURE = 1; + + /** + * Whether this mirror has been forced by the user in the MCEF configuration file + */ + public static final int FLAG_FORCED = 4; + + private final String name; + private final String url; + private final int flags; + + /** + * Constructs a Mirror from its name, URL, and flags. + * + * @param name The name of the mirror + * @param url The corresponding URL + * @param flags Its flags + */ + public Mirror(String name, String url, int flags) { + this.name = name; + this.url = url; + this.flags = flags; + } + + /** + * @return The name of the mirror + */ + public String getName() { + return name; + } + + /** + * @return The URL of the mirror + */ + public String getURL() { + return url; + } + + /** + * @return The flags of this mirror + */ + public int getFlags() { + return flags; + } + + /** + * @return Whether the secure flag is set + * @see #FLAG_SECURE + */ + public boolean isSecure() { + return (flags & FLAG_SECURE) != 0; + } + + /** + * @return Whether this mirror has been forced by the user + * @see #FLAG_FORCED + */ + public boolean isForced() { + return (flags & FLAG_FORCED) != 0; + } + + /** + * @return A string informing the user of which mirror was selected + */ + public String getInformationString() { + return isForced() ? ("Mirror location forced by user to: " + url) : ("Selected mirror: " + name); + } + + /** + * Opens a connection to the mirror's resource corresponding to the URL. + * + * @param name The URL of the resource, relative to the root of the mirror website. + * @return A connection to this resource, with timeout set up. + * @throws MalformedURLException if the mirror's URL is invalid or if name is invalid. + * @throws IOException if an I/O exception occurs. + */ + public HttpURLConnection getResource(String name) throws MalformedURLException, IOException { + HttpURLConnection ret = (HttpURLConnection) (new URL(url + '/' + name)).openConnection(); + ret.setConnectTimeout(30000); + ret.setReadTimeout(15000); + ret.setRequestProperty("User-Agent", "MCEF"); + + return ret; + } + +} diff --git a/src/main/java/net/montoyo/mcef/remote/MirrorManager.java b/src/main/java/net/montoyo/mcef/remote/MirrorManager.java new file mode 100644 index 0000000..9ec9397 --- /dev/null +++ b/src/main/java/net/montoyo/mcef/remote/MirrorManager.java @@ -0,0 +1,92 @@ +package net.montoyo.mcef.remote; + +import net.montoyo.mcef.MCEF; +import net.montoyo.mcef.utilities.Log; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Random; + +/** + * MirrorManager keeps track of valid & broken mirrors that should be used to download MCEF resources. + * It also makes sure only HTTPS mirrors are used, depending on the user's choices. + * + * @author montoyo + */ +public class MirrorManager { + + private static final Mirror[] defaultMirrors = new Mirror[] { + // HTTPS mirrors + new Mirror("oxmc-mcef-mirror", "https://cdn.oxmc.me/internal/mcef", Mirror.FLAG_SECURE), + //new Mirror("ds58-mcef-mirror", "https://ds58-mcef-mirror.ewr1.vultrobjects.com", Mirror.FLAG_SECURE), + }; + + /** + * The unique instance of the MirrorManager + */ + public static final MirrorManager INSTANCE = new MirrorManager(); + + private final ArrayList mirrors = new ArrayList<>(); + private final Random r = new Random(); + private Mirror current; + + private MirrorManager() { + markCurrentMirrorAsBroken(); + } + + private void reset() { + mirrors.clear(); + + if(MCEF.FORCE_MIRROR != null) + mirrors.add(new Mirror("user-forced", MCEF.FORCE_MIRROR, Mirror.FLAG_FORCED)); + else { + ArrayList lst = new ArrayList<>(Arrays.asList(defaultMirrors)); + + //Begin by adding all HTTPS mirrors in a random fashion + while(!lst.isEmpty()) { + Mirror m = lst.remove(r.nextInt(lst.size())); + + if(m.isSecure()) + mirrors.add(m); + } + + //Then add all non-secure mirrors, if user didn't disable them + if(!MCEF.SECURE_MIRRORS_ONLY) { + lst.addAll(Arrays.asList(defaultMirrors)); + + while(!lst.isEmpty()) { + Mirror m = lst.remove(r.nextInt(lst.size())); + + if(!m.isSecure()) + mirrors.add(m); + } + } + } + } + + /** + * @return The active mirror to be used + */ + public Mirror getCurrent() { + return current; + } + + /** + * Marks the active mirror as broken and chooses another one + * + * @return false if all mirrors were tested and the list was reset + */ + public boolean markCurrentMirrorAsBroken() { + boolean ret = true; + + if(mirrors.isEmpty()) { + reset(); + ret = false; + } + + current = mirrors.remove(0); + Log.info(current.getInformationString()); + return ret; + } + +} diff --git a/src/main/java/net/montoyo/mcef/remote/RemoteConfig.java b/src/main/java/net/montoyo/mcef/remote/RemoteConfig.java new file mode 100644 index 0000000..fa3afd6 --- /dev/null +++ b/src/main/java/net/montoyo/mcef/remote/RemoteConfig.java @@ -0,0 +1,354 @@ +package net.montoyo.mcef.remote; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.OutputStream; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.FileReader; +import java.util.ArrayList; +import java.util.Map.Entry; +import java.util.Set; +import org.apache.commons.io.FileUtils; + +import net.minecraft.client.Minecraft; +import net.montoyo.mcef.setup.FileListing; +import org.cef.OS; + +import net.montoyo.mcef.MCEF; +import net.montoyo.mcef.client.ClientProxy; +import net.montoyo.mcef.utilities.IProgressListener; +import net.montoyo.mcef.utilities.Log; +import net.montoyo.mcef.utilities.Platform; +import net.montoyo.mcef.utilities.Util; +import net.montoyo.mcef.utilities.Version; +import net.montoyo.mcef.utilities.ForgeVersionParser; + +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonIOException; +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; + +import static net.montoyo.mcef.client.ClientProxy.JCEF_ROOT; + +/** + * A class for updating and parsing the remote configuration file. + * @author montoyo + * + */ +public class RemoteConfig { + + private static String jcefCommit; + private boolean useLegacyJcef = false; + private String version = null; + private static final String PLATFORM = Platform.getPlatform(); + private static final String PLATFORMFULL = Platform.getPlatformFull(); + + public RemoteConfig() { + } + + /** + * Parses the MCEF configuration file. + * + * @param f The configuration file. + * @return The parsed configuration file. + */ + private JsonObject readConfig(File f) { + try { + return (new JsonParser()).parse(new FileReader(f)).getAsJsonObject(); + } catch(JsonIOException e) { + Log.error("IOException while reading remote config."); + e.printStackTrace(); + return null; + } catch(FileNotFoundException e) { + Log.error("Couldn't find remote config."); + e.printStackTrace(); + return null; + } catch(Exception e) { + Log.error("Syntax error in remote config."); + e.printStackTrace(); + return null; + } + } + + /** + * Updates the MCEF configuration file and parses it. + * @return The parsed configuration file. + */ + private JsonObject readConfig() { + File newCfg = new File(JCEF_ROOT, "mcef.new"); + File cfgFle = new File(JCEF_ROOT, "mcef.json"); + + // Get the mirror URL + Mirror m = MirrorManager.INSTANCE.getCurrent(); + String mirrorHost = m.getURL(); + String cfgUrl = mirrorHost + "/mcef.json"; + + boolean ok = Util.downloadFileSafe(cfgUrl, newCfg, null); + + if(ok) { + Util.delete(cfgFle); + + if(newCfg.renameTo(cfgFle)) + return readConfig(cfgFle); + else { + Log.warning("Couldn't rename mcef.new to mcef.json."); + return readConfig(newCfg); + } + + } else { + Log.warning("Couldn't read remote config. Using local configuration file."); + return readConfig(cfgFle); + } + } + + /** + * Updates the MCEF configuration file and parses it. + * Fills the resources, extract and version fields from it. + */ + public void load() { + JsonObject json = readConfig(); + if(json == null) { + Log.error("Could NOT read either remote and local configuration files. Entering virtual mode."); + ClientProxy.VIRTUAL = true; + return; + } + + Log.info("Detected platform: %s", PLATFORM); + + // Check if the config file contains the MCEF version + JsonElement ver = json.get(MCEF.VERSION); + if(ver == null || !ver.isJsonObject()) { + Log.error("Config file does NOT contain this MCEF jar's version (wtf??). Entering virtual mode."); + ClientProxy.VIRTUAL = true; + return; + } + + // Get the version data + JsonObject vData = ver.getAsJsonObject(); + + // Create an instance of ForgeVersionParser + ForgeVersionParser forgeVersionParser = new ForgeVersionParser(); + + // Get the Minecraft version + String mcVersion = forgeVersionParser.parse(Minecraft.getMinecraft().getVersion()); + + // Otherwise we fallback to hardcoded version + if (mcVersion == null || mcVersion.isEmpty() || mcVersion.contains("forge")) { + mcVersion = "1.12.2"; + } + + // Check for old_versions first + JsonElement oldVersions = vData.get("old_versions"); + if (oldVersions != null && oldVersions.isJsonObject()) { + JsonObject oldVersionsObj = oldVersions.getAsJsonObject(); + + // Check if the current Minecraft version exists in old_versions + if (oldVersionsObj.has(mcVersion)) { + JsonElement versionData = oldVersionsObj.get(mcVersion); + + // Parse the options for the current Minecraft version + if (versionData != null && versionData.isJsonObject()) { + JsonObject versionDataObj = versionData.getAsJsonObject(); + vData = versionDataObj; + + // Read the "use_legacy_jcef" option, which uses the old JCEF version + JsonElement useLegacyJcefOpt = versionDataObj.get("use_legacy_jcef"); + if (useLegacyJcefOpt != null && useLegacyJcefOpt.isJsonPrimitive()) { + this.useLegacyJcef = useLegacyJcefOpt.getAsBoolean(); + Log.info("Use Legacy JCEF: %s", useLegacyJcef); + + // get the jcef_version object + JsonElement jcefVersion = versionDataObj.get("jcef_version"); + if (jcefVersion != null && jcefVersion.isJsonPrimitive()) { + String commitHash = jcefVersion.getAsString(); + //Log.info("JCEF Commit: %s", commitHash); + jcefCommit = commitHash; + } else { + Log.error("Config file is missing \"jcef_version\" object. Entering virtual mode."); + ClientProxy.VIRTUAL = true; + return; + } + } + } + } else { + Log.info("Minecraft version not found in old_versions. Using the latest version."); + } + } + + // if the "use_legacy_jcef" option is not true we check for git_commit + if (!useLegacyJcef) { + // Check for the git_commit object + JsonElement gitCommit = vData.get("git_commit"); + if (gitCommit != null && gitCommit.isJsonPrimitive()) { + String commitHash = gitCommit.getAsString(); + //Log.info("Git Commit: %s", commitHash); + jcefCommit = commitHash; + } else { + Log.error("Config file is missing \"git_commit\" object. Entering virtual mode."); + ClientProxy.VIRTUAL = true; + return; + } + } + + // Check for the supported_platforms object + JsonElement cat = vData.get("supported_platforms"); + if(cat == null || !cat.isJsonArray()) { + Log.error("Config file is missing \"supported_platforms\" object. Entering virtual mode."); + ClientProxy.VIRTUAL = true; + return; + } + + // Check if the current platform is supported + JsonArray catArr = cat.getAsJsonArray(); + boolean platformSupported = false; + for (JsonElement element : catArr) { + if (element.getAsString().equals(PLATFORM)) { + platformSupported = true; + break; + } + } + + if (!platformSupported) { + Log.error("Your platform isn't supported by MCEF yet. Entering virtual mode."); + ClientProxy.VIRTUAL = true; + return; + } + + // Check for the latestVersions object + JsonElement mcVersions = json.get("latestVersions"); + if(mcVersions != null && mcVersions.isJsonObject()) { + JsonElement cVer = mcVersions.getAsJsonObject().get(mcVersion); + + if(cVer != null && cVer.isJsonPrimitive()) + version = cVer.getAsString(); + } + } + + /** + * Detects missing files, download them, and extracts them. + * + * @param ipl The progress listener. + * @return true if the operation was successful. + */ + public boolean downloadMCEF(IProgressListener ipl) { + Mirror m = MirrorManager.INSTANCE.getCurrent(); + String mirrorHost = m.getURL(); + String baseURL; + if (this.useLegacyJcef) { + baseURL = mirrorHost+"/legacy/"+jcefCommit+"/"; + } else { + baseURL = mirrorHost+"/java-cef-builds/"+jcefCommit+"/"; + } + String targz = PLATFORMFULL+".tar.gz"; + //https://mcef-download.cinemamod.com/java-cef-builds/eaeb3d4370aa3526ee237ad1981ad59af3de4dd1/windows_amd64.tar.gz + // or + //https://mcef-download.cinemamod.com/legacy/1.33/windows_amd64.tar.gz + String JAVA_CEF_DOWNLOAD_URL = baseURL+targz; + String JAVA_CEF_CHECKSUM_DOWNLOAD_URL = baseURL+targz+".sha256"; + boolean shouldDownload = false; + try { + ipl = Util.secure(ipl); + + // Prepare the file paths and URLs + File mcefLibrariesPath = new File(JCEF_ROOT); + File cefRuntimeFolder = new File(mcefLibrariesPath, "cef_runtime"); + String downloadUrl = JAVA_CEF_DOWNLOAD_URL; + String checksumUrl = JAVA_CEF_CHECKSUM_DOWNLOAD_URL; + + File tarGzFile = new File(mcefLibrariesPath, PLATFORMFULL + ".tar.gz"); + File checksumFile = new File(mcefLibrariesPath, PLATFORMFULL + ".tar.gz.sha256"); + File checksumTempFile = new File(mcefLibrariesPath, PLATFORMFULL + ".tar.gz.sha256.temp"); + + // Log the file paths + //Log.info("JCEF libraries path: %s", mcefLibrariesPath.getAbsolutePath()); + //Log.info("JCEF tar.gz file: %s", tarGzFile.getAbsolutePath()); + //Log.info("JCEF checksum file: %s", checksumFile.getAbsolutePath()); + //Log.info("JCEF checksum temp file: %s", checksumTempFile.getAbsolutePath()); + + // Download the checksum file + Util.downloadFile(checksumUrl, checksumTempFile, ipl); + + // Step 3: Verify checksum + if (checksumFile.exists()) { + boolean sameContent = FileUtils.contentEquals(checksumFile, checksumTempFile); + if (sameContent) { + // Before we delete the temp file, we need to check if the JCEF build is already downloaded + if (cefRuntimeFolder.exists()) { + Log.info("JCEF build already downloaded, Skipping download."); + checksumTempFile.delete(); + shouldDownload = false; + } else { + Log.info("JCEF build not downloaded yet and or missing, Downloading JCEF build."); + checksumTempFile.renameTo(checksumFile); + shouldDownload = true; + } + } else { + Log.warning("Checksum file doesn't match. Redownloading JCEF build."); + checksumTempFile.renameTo(checksumFile); + shouldDownload = true; + } + } else { + // If the checksum file doesn't exist, then we need to download the JCEF build + shouldDownload = true; + checksumTempFile.renameTo(checksumFile); + } + + // Step 4: Download the JCEF build if necessary + if (shouldDownload && !MCEF.SKIP_UPDATES) { + // Download the JCEF build + Util.downloadFile(downloadUrl, tarGzFile, ipl); + + // Extract the tar.gz file + Util.extractTarGz(tarGzFile, mcefLibrariesPath, ipl); + + // Define extracted folder (platform_arch) and target folder (cef_runtime) + File extractedFolder = new File(mcefLibrariesPath, PLATFORMFULL); + + // Ensure the old cef_runtime folder is removed before renaming + if (cefRuntimeFolder.exists()) { + FileUtils.deleteDirectory(cefRuntimeFolder); + } + + // Rename extracted folder to cef_runtime + if (extractedFolder.exists()) { + boolean renamed = extractedFolder.renameTo(cefRuntimeFolder); + if (!renamed) { + Log.warning("Failed to rename extracted folder to cef_runtime"); + } + } else { + Log.warning("Extracted folder does not exist, rename skipped."); + } + + // Delete the tar.gz file + if (tarGzFile.exists()) { + tarGzFile.delete(); + } + } + return true; + } catch (IOException e) { + Log.error("Failed to download or extract JCEF build: " + e.getMessage()); + e.printStackTrace(); + return false; + } + } + + /** + * Returns an info string if an MCEF update is available. + * @return an info string if a newer version exists, null otherwise. + */ + public String getUpdateString() { + if(version == null) + return null; + + Version cur = new Version(MCEF.VERSION); + Version cfg = new Version(version); + + if(cfg.isBiggerThan(cur)) + return "New MCEF version available. Current: " + cur + ", latest: " + cfg + '.'; + + return null; + } +} diff --git a/src/main/java/net/montoyo/mcef/setup/CfgParser.java b/src/main/java/net/montoyo/mcef/setup/CfgParser.java new file mode 100644 index 0000000..b8bf269 --- /dev/null +++ b/src/main/java/net/montoyo/mcef/setup/CfgParser.java @@ -0,0 +1,348 @@ +package net.montoyo.mcef.setup; + +import java.io.*; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class CfgParser { + + private static abstract class Line { + + public abstract void write(BufferedWriter bw) throws IOException; + public abstract void read(String content, Matcher m); + + } + + private static class CommentLine extends Line { + + private String data; + + public CommentLine(String d) { + data = d; + } + + @Override + public void write(BufferedWriter bw) throws IOException { + bw.write(data + "\n"); + } + + @Override + public void read(String content, Matcher m) { + data = content; + } + + } + + private static class BeginCategoryLine extends Line { + + public static final String REGEX = "^(\\s*)([a-z]+)(\\s+)\\{(\\s*)$"; + private String prefix; + private String category; + private String inBetween; + private String suffix; + + public BeginCategoryLine() { + } + + public BeginCategoryLine(String name) { + prefix = ""; + category = name; + inBetween = " "; + suffix = ""; + } + + @Override + public void write(BufferedWriter bw) throws IOException { + bw.write(prefix); + bw.write(category); + bw.write(inBetween); + bw.write("{"); + bw.write(suffix + "\n"); + } + + @Override + public void read(String content, Matcher m) { + prefix = m.group(1); + category = m.group(2); + inBetween = m.group(3); + suffix = m.group(4); + } + + public String getCategoryName() { + return category; + } + + } + + private static class EndCategoryLine extends Line { + + public static final String REGEX = "^(\\s*)\\}(\\s*)$"; + private String prefix; + private String suffix; + + public EndCategoryLine() { + prefix = ""; + suffix = ""; + } + + @Override + public void write(BufferedWriter bw) throws IOException { + bw.write(prefix); + bw.write("}"); + bw.write(suffix + "\n"); + } + + @Override + public void read(String content, Matcher m) { + prefix = m.group(1); + suffix = m.group(2); + } + + } + + private static class PropertyLine extends Line { + + public static final String REGEX = "^(\\s*)([A-Z])\\:([A-Za-z]+)=(.*)$"; + private String prefix; + private char type; + private String key; + private String value; + + public PropertyLine() { + } + + public PropertyLine(char t, String key, String val) { + prefix = " "; + type = t; + this.key = key; + value = val; + } + + @Override + public void write(BufferedWriter bw) throws IOException { + bw.write(prefix); + bw.write(type + ":" + key); + bw.write("=" + value + "\n"); + } + + @Override + public void read(String content, Matcher m) { + prefix = m.group(1); + type = m.group(2).charAt(0); + key = m.group(3); + value = m.group(4); + } + + public String getKey() { + return key; + } + + public String getValue() { + return value; + } + + public boolean getBooleanValue(boolean def) { + String data = value.trim().toLowerCase(); + + if(data.equals("false")) + return false; + else if(data.equals("true")) + return true; + else { + value = def ? "true" : "false"; + return def; + } + } + + public void setValue(String v) { + value = v; + } + + } + + private enum LineType { + + CATEGORY_BEGIN(BeginCategoryLine.REGEX, BeginCategoryLine.class), + CATEGORY_END(EndCategoryLine.REGEX, EndCategoryLine.class), + PROPERTY(PropertyLine.REGEX, PropertyLine.class); + + private final Pattern pattern; + private final Class cls; + + LineType(String regex, Class cls) { + pattern = Pattern.compile(regex); + this.cls = cls; + } + + public static Line parseLine(String l) { + for(LineType lt : values()) { + Matcher m = lt.pattern.matcher(l); + + if(m.matches()) { + try { + Line ret = lt.cls.newInstance(); + ret.read(l, m); + + return ret; + } catch(Throwable t) { + System.err.println("Could not instantiate line class \"" + lt.cls.getCanonicalName() + "\":"); + t.printStackTrace(); + return null; + } + } + } + + return null; + } + + } + + private ArrayList lines = new ArrayList(); + private HashMap> data = new HashMap>(); + private File location; + + public CfgParser(File loc) { + location = loc; + } + + public boolean load() { + try { + unsafeRead(); + return true; + } catch(Throwable t) { + System.err.println("Could not read config file \"" + location.getAbsolutePath() + "\":"); + t.printStackTrace(); + return false; + } + } + + private void unsafeRead() throws Throwable { + lines.clear(); + data.clear(); + + BufferedReader br = new BufferedReader(new FileReader(location)); + String line; + String currentCategory = null; + int lineCnt = 0; + + while((line = br.readLine()) != null) { + String trimmed = line.trim(); + Line l; + + if(trimmed.isEmpty() || trimmed.charAt(0) == '#') + l = new CommentLine(line); + else + l = LineType.parseLine(line); + + if(l == null) + throw new RuntimeException("Could not parse line #" + (lineCnt + 1) + "."); + + if(l instanceof BeginCategoryLine) { + if(currentCategory == null) { + currentCategory = ((BeginCategoryLine) l).getCategoryName(); + data.put(currentCategory, new HashMap()); + } else + throw new RuntimeException("At line #" + (lineCnt + 1) + ": Forgot to close brackets."); + } else if(l instanceof EndCategoryLine) { + if(currentCategory == null) + throw new RuntimeException("At line #" + (lineCnt + 1) + ": Closing non-opened bracket."); + else + currentCategory = null; + } else if(l instanceof PropertyLine) { + if(currentCategory == null) + throw new RuntimeException("At line #" + (lineCnt + 1) + ": Setting property outside brackets."); + else + data.get(currentCategory).put(((PropertyLine) l).getKey(), (PropertyLine) l); + } + + lines.add(l); + lineCnt++; + } + + SetupUtil.silentClose(br); + } + + public boolean save() { + try { + unsafeWrite(); + return true; + } catch(Throwable t) { + System.err.println("Could not write config file \"" + location.getAbsolutePath() + "\":"); + t.printStackTrace(); + return false; + } + } + + private void unsafeWrite() throws Throwable { + BufferedWriter bw = new BufferedWriter(new FileWriter(location)); + for(Line l : lines) + l.write(bw); + + SetupUtil.silentClose(bw); + } + + private int findCategoryBeginning(String cat) { + for(int i = 0; i < lines.size(); i++) { + Line l = lines.get(i); + + if(l instanceof BeginCategoryLine && ((BeginCategoryLine) l).getCategoryName().equals(cat)) + return i; + } + + return -1; + } + + private PropertyLine getValue(char type, String category, String key, String def) { + HashMap subdata; + + if(data.containsKey(category)) { + subdata = data.get(category); + + if(subdata.containsKey(key)) + return subdata.get(key); + else { + int pos = findCategoryBeginning(category); + if(pos < 0) + throw new RuntimeException("Could not find beginning for category \"" + category + "\"! This should NOT happen!"); + + PropertyLine pl = new PropertyLine(type, key, def); + lines.add(pos + 1, pl); + subdata.put(key, pl); + return pl; + } + } else { + BeginCategoryLine bcl = new BeginCategoryLine(category); + PropertyLine pl = new PropertyLine(type, key, def); + EndCategoryLine ecl = new EndCategoryLine(); + + lines.add(bcl); + lines.add(pl); + lines.add(ecl); + + subdata = new HashMap(); + subdata.put(key, pl); + data.put(category, subdata); + return pl; + } + } + + public String getStringValue(String category, String key, String def) { + return getValue('S', category, key, def).getValue(); + } + + public boolean getBooleanValue(String category, String key, boolean def) { + return getValue('B', category, key, def ? "true" : "false").getBooleanValue(def); + } + + public void setStringValue(String category, String key, String val) { + getValue('S', category, key, val).setValue(val); + } + + public void setBooleanValue(String category, String key, boolean val) { + String data = val ? "true" : "false"; + getValue('B', category, key, data).setValue(data); + } + +} diff --git a/src/main/java/net/montoyo/mcef/setup/ConfigForm.java b/src/main/java/net/montoyo/mcef/setup/ConfigForm.java new file mode 100644 index 0000000..7253801 --- /dev/null +++ b/src/main/java/net/montoyo/mcef/setup/ConfigForm.java @@ -0,0 +1,223 @@ +package net.montoyo.mcef.setup; + +import javax.swing.*; +import javax.swing.border.EmptyBorder; +import javax.swing.border.TitledBorder; +import java.awt.*; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.awt.event.WindowEvent; +import java.awt.event.WindowListener; +import java.io.File; + +public class ConfigForm implements ActionListener, WindowListener { + + private CfgParser config; + private JFrame parent; + private JFrame frame; + private JPanel contentPane; + + //Main + private JPanel cMainPane; + private JTextField cMainForcedMirror; + private JCheckBox cMainSkipUpdates; + private JCheckBox cMainForgeSplash; + private JCheckBox cMainWarnUpdates; + + //Browser + private JPanel cBrowserPane; + private JCheckBox cBrowserEnable; + private JTextField cBrowserHome; + + //Buttons + private JPanel btnPane; + private JButton btnOk; + private JButton btnBack; + private JButton btnApply; + + public ConfigForm(JFrame p, File cfgFile) { + GridBagConstraints c; + config = new CfgParser(cfgFile); + parent = p; + + config.load(); + + //Frame and content pane + frame = new JFrame("MCEF Setup - Configuration"); + frame.setMinimumSize(new Dimension(500, 1)); + frame.setLocationRelativeTo(null); + frame.setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE); + frame.addWindowListener(this); + + contentPane = new JPanel(); + contentPane.setBorder(new EmptyBorder(3, 3, 3, 3)); + contentPane.setLayout(new GridBagLayout()); + + //Main + cMainPane = new JPanel(); + cMainPane.setBorder(new TitledBorder("Main")); + cMainPane.setLayout(new GridBagLayout()); + + cMainForcedMirror = new JTextField(); + cMainSkipUpdates = new JCheckBox(); + cMainForgeSplash = new JCheckBox(); + cMainWarnUpdates = new JCheckBox(); + addFormComponent(cMainPane, 0, "Forced mirror", cMainForcedMirror); + addFormComponent(cMainPane, 1, "Skip updates", cMainSkipUpdates); + addFormComponent(cMainPane, 2, "Use forge splash", cMainForgeSplash); + addFormComponent(cMainPane, 3, "Warn updates", cMainWarnUpdates); + + cMainForcedMirror.setText(config.getStringValue("main", "forcedMirror", "")); + cMainSkipUpdates.setSelected(config.getBooleanValue("main", "skipUpdates", false)); + cMainForgeSplash.setSelected(config.getBooleanValue("main", "useForgeSplash", true)); + cMainWarnUpdates.setSelected(config.getBooleanValue("main", "warnUpdates", true)); + + c = new GridBagConstraints(); + c.gridy = 4; + c.fill = GridBagConstraints.VERTICAL; + c.weighty = 1.0; + cMainPane.add(Box.createVerticalGlue(), c); + + c = new GridBagConstraints(); + c.fill = GridBagConstraints.BOTH; + c.weightx = 1.0; + c.weighty = 0.5; + contentPane.add(cMainPane, c); + + //Browser + cBrowserPane = new JPanel(); + cBrowserPane.setBorder(new TitledBorder("Browser")); + cBrowserPane.setLayout(new GridBagLayout()); + + cBrowserEnable = new JCheckBox(); + cBrowserHome = new JTextField(); + addFormComponent(cBrowserPane, 0, "Enable", cBrowserEnable); + addFormComponent(cBrowserPane, 1, "Home page", cBrowserHome); + + cBrowserEnable.setSelected(config.getBooleanValue("examplebrowser", "enable", true)); + cBrowserHome.setText(config.getStringValue("examplebrowser", "home", "mod://mcef/home.html")); + + c = new GridBagConstraints(); + c.gridy = 2; + c.fill = GridBagConstraints.VERTICAL; + c.weighty = 1.0; + cBrowserPane.add(Box.createVerticalGlue(), c); + + c = new GridBagConstraints(); + c.gridy = 1; + c.fill = GridBagConstraints.BOTH; + c.weightx = 1.0; + c.weighty = 0.5; + contentPane.add(cBrowserPane, c); + + //Buttons + btnPane = new JPanel(); + btnPane.setLayout(new GridBagLayout()); + + btnOk = new JButton("Ok"); + btnBack = new JButton("Back"); + btnApply = new JButton("Apply"); + btnOk.addActionListener(this); + btnBack.addActionListener(this); + btnApply.addActionListener(this); + + c = new GridBagConstraints(); + c.fill = GridBagConstraints.HORIZONTAL; + c.weightx = 1.0; + btnPane.add(Box.createHorizontalGlue(), c); + + addFormButton(1, btnOk); + addFormButton(2, btnBack); + addFormButton(3, btnApply); + + c = new GridBagConstraints(); + c.gridy = 2; + c.fill = GridBagConstraints.HORIZONTAL; + c.weightx = 1.0; + contentPane.add(btnPane, c); + + //Display + frame.setContentPane(contentPane); + frame.pack(); + parent.setVisible(false); + frame.setVisible(true); + } + + private void addFormComponent(JPanel pane, int line, String label, Component comp) { + GridBagConstraints c = new GridBagConstraints(); + c.insets = new Insets(0, 3, 3, 3); + c.fill = GridBagConstraints.HORIZONTAL; + c.gridy = line; + pane.add(new JLabel(label), c); + + c = new GridBagConstraints(); + c.insets = new Insets(0, 0, 3, 3); + c.gridx = 1; + c.gridy = line; + c.fill = GridBagConstraints.HORIZONTAL; + c.weightx = 1.0; + pane.add(comp, c); + } + + private void addFormButton(int x, JButton btn) { + GridBagConstraints c = new GridBagConstraints(); + c.insets = new Insets(3, 0, 3, 3); + c.gridx = x; + btnPane.add(btn, c); + } + + private void saveChanges() { + config.setStringValue("main", "forcedMirror", cMainForcedMirror.getText()); + config.setBooleanValue("main", "skipUpdates", cMainSkipUpdates.isSelected()); + config.setBooleanValue("main", "useForgeSplash", cMainForgeSplash.isSelected()); + config.setBooleanValue("main", "warnUpdates", cMainWarnUpdates.isSelected()); + config.setBooleanValue("examplebrowser", "enable", cBrowserEnable.isSelected()); + config.setStringValue("examplebrowser", "home", cBrowserHome.getText()); + + if(!config.save()) + JOptionPane.showMessageDialog(frame, "Could not save configuration file.\nMake sure you have the permissions to write in the config folder.", "Error", JOptionPane.ERROR_MESSAGE); + } + + @Override + public void actionPerformed(ActionEvent e) { + if(e.getSource() == btnBack) + windowClosing(null); + else if(e.getSource() == btnApply) + saveChanges(); + else if(e.getSource() == btnOk) { + saveChanges(); + windowClosing(null); + } + } + + @Override + public void windowOpened(WindowEvent e) { + } + + @Override + public void windowClosing(WindowEvent e) { + parent.setVisible(true); + frame.dispose(); + } + + @Override + public void windowClosed(WindowEvent e) { + } + + @Override + public void windowIconified(WindowEvent e) { + } + + @Override + public void windowDeiconified(WindowEvent e) { + } + + @Override + public void windowActivated(WindowEvent e) { + } + + @Override + public void windowDeactivated(WindowEvent e) { + } + +} diff --git a/src/main/java/net/montoyo/mcef/setup/DefaultComparator.java b/src/main/java/net/montoyo/mcef/setup/DefaultComparator.java new file mode 100644 index 0000000..01243aa --- /dev/null +++ b/src/main/java/net/montoyo/mcef/setup/DefaultComparator.java @@ -0,0 +1,17 @@ +package net.montoyo.mcef.setup; + +import java.util.Comparator; + +final class DefaultComparator implements Comparator { + + @Override + public int compare(String a, String b) { + return a.compareTo(b); + } + + @Override + public boolean equals(Object o) { + return o != null && o instanceof DefaultComparator; + } + +} diff --git a/src/main/java/net/montoyo/mcef/setup/Deleter.java b/src/main/java/net/montoyo/mcef/setup/Deleter.java new file mode 100644 index 0000000..a69bf6b --- /dev/null +++ b/src/main/java/net/montoyo/mcef/setup/Deleter.java @@ -0,0 +1,47 @@ +package net.montoyo.mcef.setup; + +import java.io.File; + +/** + * This class will be extracted in another temporary JAR in order to self-destroy the MCEF jar after exit. + * It has to be standalone and must remain as light as possible for the user's convenience. + */ +public class Deleter { + + //Sorry about the copy/paste, but this class needs to be standalone! + private static boolean tryDelete(File f) { + if(!f.exists()) + return true; + + if(f.delete()) + return true; + else { + File dst = new File(f.getParentFile(), f.getName() + "_" + System.currentTimeMillis() % ((int) (Math.random() * 10000)) + ".tmp"); + + if(f.renameTo(dst)) { + if(!dst.delete()) + dst.deleteOnExit(); + + return true; + } else + return false; + } + } + + public static void main(String[] args) { + File f = new File(args[0]); + String lowerName = f.getName().toLowerCase(); + + if(lowerName.startsWith("mcef") && lowerName.endsWith(".jar")) { + for(int i = 0; i < 30; i++) { + if(tryDelete(f)) + return; + + try { + Thread.sleep(3000); + } catch(Throwable t) {} + } + } + } + +} diff --git a/src/main/java/net/montoyo/mcef/setup/FileListing.java b/src/main/java/net/montoyo/mcef/setup/FileListing.java new file mode 100644 index 0000000..ccfc683 --- /dev/null +++ b/src/main/java/net/montoyo/mcef/setup/FileListing.java @@ -0,0 +1,120 @@ +package net.montoyo.mcef.setup; + +import java.io.*; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.zip.ZipEntry; +import java.util.zip.ZipInputStream; + +public class FileListing { + + private ArrayList fileNames = new ArrayList(); + private File location; + + public FileListing(File dir) { + location = new File(dir, "mcefFiles.lst"); + + if(location.exists()) + load(); + } + + public boolean load() { + try { + unsafeLoad(); + return true; + } catch(Throwable t) { + System.err.println("Coud not read file listing:"); + t.printStackTrace(); + return false; + } + } + + private void unsafeLoad() throws Throwable { + BufferedReader br = new BufferedReader(new FileReader(location)); + String line; + + fileNames.clear(); + + while((line = br.readLine()) != null) { + line = line.trim(); + + if(line.length() > 0 && line.charAt(0) != '#' && line.charAt(0) != '.' && line.charAt(0) != '/' && line.charAt(0) != '\\') + fileNames.add(line); + } + + SetupUtil.silentClose(br); + } + + public boolean save() { + try { + unsafeSave(); + return true; + } catch(Throwable t) { + System.err.println("Coud not write file listing:"); + t.printStackTrace(); + return false; + } + } + + private void unsafeSave() throws Throwable { + if(location.exists()) + SetupUtil.tryDelete(location); + + BufferedWriter bw = new BufferedWriter(new FileWriter(location)); + bw.write("# DO NOT EDIT THIS FILE. IT HAS BEEN AUTOMATICALLY GENERATED.\n"); + bw.write("# This file contains the list of files installed by MCEF.\n"); + bw.write("# If you remove MCEF, they are no longer needed and you can safely remove them,\n"); + bw.write("# or you can let the uninstaller do it for you. Just run the MCEF mod jar using Java.\n\n"); + + for(String f : fileNames) + bw.write(f + "\n"); + + SetupUtil.silentClose(bw); + } + + public void addFile(String f) { + if(!fileNames.contains(f)) + fileNames.add(f); + } + + public boolean addZip(String fname) { + try { + addZipUnsafe(fname); + return true; + } catch(Throwable t) { + System.err.println("Coud not list file in ZIP archive \"" + fname + "\":"); + t.printStackTrace(); + return false; + } + } + + private void addZipUnsafe(String fname) throws Throwable { + ArrayList files = new ArrayList(); + ZipInputStream zis = new ZipInputStream(new FileInputStream(fname)); + ZipEntry ze; + + while((ze = zis.getNextEntry()) != null) { + String name = ze.getName(); + + if(ze.isDirectory() && (name.endsWith("/") || name.endsWith("\\"))) + files.add(name.substring(0, name.length() - 1)); + else + files.add(name); + } + + SetupUtil.silentClose(zis); + + files.sort(new SlashComparator(new DefaultComparator())); + for(String t: files) + addFile(t); //Use addFile instead of fileNames.addAll() to remove duplicates + } + + public Iterator iterator() { + return fileNames.iterator(); + } + + public boolean selfDestruct() { + return SetupUtil.tryDelete(location); + } + +} diff --git a/src/main/java/net/montoyo/mcef/setup/McLocationPrompt.java b/src/main/java/net/montoyo/mcef/setup/McLocationPrompt.java new file mode 100644 index 0000000..8f0cba7 --- /dev/null +++ b/src/main/java/net/montoyo/mcef/setup/McLocationPrompt.java @@ -0,0 +1,200 @@ +package net.montoyo.mcef.setup; + +import javax.swing.*; +import javax.swing.border.EmptyBorder; +import java.awt.*; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.awt.event.WindowEvent; +import java.awt.event.WindowListener; +import java.io.File; + +public class McLocationPrompt implements ActionListener, WindowListener { + + private JFrame parent; + private JFrame frame; + private GridLayout layout; + private JPanel mainPane; + private JTextField locationField; + private JButton btnLocate; + private JButton btnBack; + private JButton btnOk; + private String action; + + public McLocationPrompt(JFrame p, String action) { + parent = p; + this.action = action; + + //Setup + frame = new JFrame("MCEF Setup - Minecraft location"); + frame.setMinimumSize(new Dimension(500, 1)); + frame.setLocationRelativeTo(null); + frame.setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE); + frame.addWindowListener(this); + + mainPane = new JPanel(); + layout = new GridLayout(3, 1, 3, 3); + mainPane.setBorder(new EmptyBorder(3, 3, 3, 3)); + mainPane.setLayout(layout); + + //First line: label + mainPane.add(new JLabel("Please tell us where Minecraft is installed:")); + + //Second line: field location, locate button + JPanel line = new JPanel(new GridBagLayout()); + line.setMinimumSize(new Dimension(1, 250)); + + GridBagConstraints c = new GridBagConstraints(); + c.fill = GridBagConstraints.BOTH; + c.weightx = 1.0; + c.weighty = 1.0; + c.insets = new Insets(0, 0, 0, 3); + line.add(locationField = new JTextField(), c); + + c = new GridBagConstraints(); + c.fill = GridBagConstraints.VERTICAL; + c.gridx = 1; + c.weighty = 1.0; + line.add(btnLocate = new JButton("..."), c); + btnLocate.addActionListener(this); + mainPane.add(line); + + //Third line: gap, back, ok + line = new JPanel(new GridBagLayout()); + c = new GridBagConstraints(); + c.fill = GridBagConstraints.HORIZONTAL; + c.weightx = 1.0; + line.add(Box.createHorizontalGlue(), c); + + c = new GridBagConstraints(); + c.gridx = 1; + c.insets = new Insets(0, 0, 0, 3); + line.add(btnBack = new JButton("Back"), c); + btnBack.addActionListener(this); + + c = new GridBagConstraints(); + c.gridx = 2; + line.add(btnOk = new JButton("Ok"), c); + btnOk.addActionListener(this); + mainPane.add(line); + + //Fill location field + try { + locationField.setText(autoLocateMinecraft()); + } catch(Throwable t) { + System.err.println("Note: could not locate Minecraft:"); + t.printStackTrace(); + } + + //Display + frame.setContentPane(mainPane); + frame.pack(); + parent.setVisible(false); + frame.setVisible(true); + } + + private String autoLocateMinecraft() { + File cDir = (new File(".")).getAbsoluteFile(); + if(cDir.getName().equals("mods")) { + File pFile = cDir.getParentFile(); + File saves = new File(pFile, "saves"); + File rpacks = new File(pFile, "resourcepacks"); + + if(saves.exists() && saves.isDirectory() && rpacks.exists() && rpacks.isDirectory()) + return pFile.getAbsolutePath(); + } + + File root = new File(System.getProperty("user.home", ".")); + String os = System.getProperty("os.name").toLowerCase(); + + if(os.contains("win")) + root = new File(System.getenv("APPDATA")); + else if(os.contains("mac")) + root = new File(new File(root, "Library"), "Application Support"); + + root = new File(root, ".minecraft"); + return root.exists() ? root.getAbsolutePath() : ""; + } + + @Override + public void actionPerformed(ActionEvent e) { + if(e.getSource() == btnLocate) { + JFileChooser fc = new JFileChooser(); + fc.setDialogTitle("Where's Minecraft?"); + fc.setCurrentDirectory(new File(locationField.getText())); + fc.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY); + + if(fc.showOpenDialog(frame) == JFileChooser.APPROVE_OPTION) + locationField.setText(fc.getSelectedFile().getAbsolutePath()); + } else if(e.getSource() == btnBack) { + parent.setVisible(true); + frame.dispose(); + } else if(e.getSource() == btnOk) { + File loc = new File(locationField.getText()); + if(!loc.exists() || !loc.isDirectory()) { + JOptionPane.showMessageDialog(frame, "The selected directory does not exist.", "Error", JOptionPane.ERROR_MESSAGE); + return; + } + + File saves = new File(loc, "saves"); + File rpacks = new File(loc, "resourcepacks"); + + if(!saves.exists() || !saves.isDirectory() || !rpacks.exists() || !rpacks.isDirectory()) { + if(JOptionPane.showConfirmDialog(frame, "The selected directory does not look like a valid Minecraft setup...\nWould you like to continue?", "Hmmm...", JOptionPane.YES_NO_OPTION) == JOptionPane.NO_OPTION) + return; + } + + if(action.equals("configure")) { + File configDir = new File(loc, "config"); + if(!configDir.exists()) + configDir.mkdirs(); + + new ConfigForm(parent, new File(configDir, "MCEF.cfg")); + frame.dispose(); + return; + } + + try { + if(((Boolean) Processes.class.getMethod(action, JFrame.class, File.class).invoke(null, frame, loc)).booleanValue()) { + parent.setVisible(true); + frame.dispose(); + } + } catch(Throwable t) { + System.err.println("Could not execute action \"" + action + "\":"); + t.printStackTrace(); + JOptionPane.showMessageDialog(frame, "Could not execute action \"" + action + "\".\nThis shouldn't happen; please contact mod author.", "Error", JOptionPane.ERROR_MESSAGE); + } + } + } + + @Override + public void windowOpened(WindowEvent e) { + } + + @Override + public void windowClosing(WindowEvent e) { + parent.setVisible(true); + frame.dispose(); + } + + @Override + public void windowClosed(WindowEvent e) { + } + + @Override + public void windowIconified(WindowEvent e) { + } + + @Override + public void windowDeiconified(WindowEvent e) { + } + + @Override + public void windowActivated(WindowEvent e) { + } + + @Override + public void windowDeactivated(WindowEvent e) { + } + +} diff --git a/src/main/java/net/montoyo/mcef/setup/Processes.java b/src/main/java/net/montoyo/mcef/setup/Processes.java new file mode 100644 index 0000000..82d173e --- /dev/null +++ b/src/main/java/net/montoyo/mcef/setup/Processes.java @@ -0,0 +1,125 @@ +package net.montoyo.mcef.setup; + +import javax.swing.*; +import java.io.File; +import java.util.Iterator; + +public class Processes { + + public static boolean install(JFrame parent, File dst) { + File curJar = SetupUtil.getSelfJarLocation(); + if(curJar == null) { + JOptionPane.showMessageDialog(parent, "Could not locate the current JAR file.\nThis shouldn't happen, contact mod author.\nCannot continue.", "Error", JOptionPane.ERROR_MESSAGE); + return false; + } + + File mods = new File(dst, "mods"); + if(mods.exists()) { + File[] modList = mods.listFiles(); + + for(File f: modList) { + String fname = f.getName().toLowerCase(); + + if(SetupUtil.areFileEqual(f, curJar)) { + SetupUI.INSTANCE.abortSelfDestruct(); + JOptionPane.showMessageDialog(parent, "MCEF was successfully installed!\nIn fact, it was already installed here.\nAlso make sure Forge is installed!", "Well... there was nothing to do!", JOptionPane.INFORMATION_MESSAGE); + return true; + } else if(f.isFile() && fname.startsWith("mcef") && fname.endsWith(".jar")) { + while(!SetupUtil.tryDelete(f)) { + if(JOptionPane.showConfirmDialog(parent, "An older version of MCEF has been found and cannot be deleted.\nPlease close Minecraft or remove the following file manually:\n" + f.getAbsolutePath(), "WARNING", JOptionPane.OK_CANCEL_OPTION) == JOptionPane.CANCEL_OPTION) + return false; + } + } + } + } else if(!mods.mkdir()) { + JOptionPane.showMessageDialog(parent, "Could not create mods directory. Make sure you have the rights to do that.\nCannot continue.", "Error", JOptionPane.ERROR_MESSAGE); + return false; + } + + if(SetupUtil.copyFile(curJar, new File(mods, curJar.getName()))) { + JOptionPane.showMessageDialog(parent, "MCEF was successfully installed!\nDon't forget to install Forge!", "All done!", JOptionPane.INFORMATION_MESSAGE); + return true; + } else { + JOptionPane.showMessageDialog(parent, "Installation failed\nCould not copy JAR to mods folder.", "Critical error", JOptionPane.ERROR_MESSAGE); + return false; + } + } + + private static boolean recursiveDelete(File dir) { + if(!dir.exists()) + return true; + + File[] files = dir.listFiles(); + boolean allOk = true; + + for(File f: files) { + if(f.isDirectory()) { + if(!recursiveDelete(f)) + allOk = false; + } else if(!SetupUtil.tryDelete(f)) + allOk = false; + } + + return SetupUtil.tryDelete(dir) && allOk; + } + + public static boolean uninstall(JFrame parent, File dst) { + File configDir = new File(dst, "config"); + FileListing fl = new FileListing(configDir); + if(!fl.load()) { + JOptionPane.showMessageDialog(parent, "Could not locate MCEF file listing. It is either missing,\nor you selected the wrong Minecraft location.\nCannot continue.", "Error", JOptionPane.ERROR_MESSAGE); + return false; + } + + //Destroy resources and cache + boolean allDeleted = true; + Iterator files = fl.iterator(); + + while(files.hasNext()) { + if(!SetupUtil.tryDelete(new File(dst, files.next()))) + allDeleted = false; + } + + if(!recursiveDelete(new File(dst, "MCEFCache"))) + allDeleted = false; + + //Destroy file listing and configs + if(!fl.selfDestruct()) + allDeleted = false; + + if(!SetupUtil.tryDelete(new File(configDir, "MCEF.cfg"))) + allDeleted = false; + + if(!SetupUtil.tryDelete(new File(dst, "mcef.json"))) + allDeleted = false; + + //Destroy mod file + File curJar = SetupUtil.getSelfJarLocation(); + File mods = new File(dst, "mods"); + + if(mods.exists()) { + File[] modList = mods.listFiles(); + + for(File f: modList) { + String fname = f.getName().toLowerCase(); + + if(SetupUtil.areFileEqual(f, curJar)) { + //Can't self-destruct JAR; add to delete-at-exit file list + SetupUI.INSTANCE.initiateSelfDestruct(f); + } else if(f.isFile() && fname.startsWith("mcef") && fname.endsWith(".jar")) { + if(!SetupUtil.tryDelete(f)) + allDeleted = false; + } + } + } + + //Show results + if(allDeleted) + JOptionPane.showMessageDialog(parent, "MCEF was successfully uninstalled!\nThanks for using it!", "All done!", JOptionPane.INFORMATION_MESSAGE); + else + JOptionPane.showMessageDialog(parent, "MCEF was uninstalled, but some files couldn't be removed; sorry about that...", "Almost everything done!", JOptionPane.WARNING_MESSAGE); + + return true; + } + +} diff --git a/src/main/java/net/montoyo/mcef/setup/SetupUI.java b/src/main/java/net/montoyo/mcef/setup/SetupUI.java new file mode 100644 index 0000000..d00ab7c --- /dev/null +++ b/src/main/java/net/montoyo/mcef/setup/SetupUI.java @@ -0,0 +1,199 @@ +package net.montoyo.mcef.setup; + +import javax.swing.*; +import javax.swing.border.EmptyBorder; +import java.awt.*; +import java.awt.event.*; +import java.io.File; +import java.io.FileOutputStream; +import java.io.InputStream; +import java.net.URI; +import java.util.zip.ZipEntry; +import java.util.zip.ZipOutputStream; + +public class SetupUI implements ActionListener, WindowListener, MouseListener { + + public static SetupUI INSTANCE = null; + + public static void main(String[] args) { + try { + UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName()); + } catch(Throwable t) { + t.printStackTrace(); + } + + INSTANCE = new SetupUI(); + } + + private File selfDestruct = null; + private JFrame frame; + private GridLayout layout; + private JPanel mainPane; + private JButton btnInstall; + private JButton btnConfigure; + private JButton btnUninstall; + private JButton btnExit; + private JLabel aboutLabel; + + public SetupUI() { + //Setup + frame = new JFrame("MCEF Setup"); + frame.setMinimumSize(new Dimension(300, 100)); + frame.setLocationRelativeTo(null); + frame.setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE); + frame.addWindowListener(this); + + //Layout & content + btnInstall = new JButton("Install"); + btnConfigure = new JButton("Configure"); + btnUninstall = new JButton("Uninstall"); + btnExit = new JButton("Exit"); + + btnInstall.addActionListener(this); + btnConfigure.addActionListener(this); + btnUninstall.addActionListener(this); + btnExit.addActionListener(this); + + JPanel labelPane = new JPanel(); + labelPane.setLayout(new BoxLayout(labelPane, BoxLayout.PAGE_AXIS)); + labelPane.add(new JLabel("Welcome to the MCEF Setup Wizard.")); + labelPane.add(new JLabel("What do you like to do?")); + + mainPane = new JPanel(); + layout = new GridLayout(6, 1, 3, 3); + mainPane.setBorder(new EmptyBorder(3, 3, 3, 3)); + mainPane.setLayout(layout); + mainPane.add(labelPane); + mainPane.add(btnInstall); + mainPane.add(btnConfigure); + mainPane.add(btnUninstall); + mainPane.add(btnExit); + + aboutLabel = new JLabel("MCEF was written by montoyo  "); + aboutLabel.setHorizontalAlignment(JLabel.RIGHT); + aboutLabel.addMouseListener(this); + mainPane.add(aboutLabel); + + //Display + frame.setContentPane(mainPane); + frame.pack(); + frame.setVisible(true); + } + + @Override + public void actionPerformed(ActionEvent e) { + if(e.getSource() == btnExit) + windowClosing(null); + else if(e.getSource() == btnInstall) + new McLocationPrompt(frame, "install"); + else if(e.getSource() == btnConfigure) + new McLocationPrompt(frame, "configure"); + else if(e.getSource() == btnUninstall) + new McLocationPrompt(frame, "uninstall"); + } + + void initiateSelfDestruct(File f) { + selfDestruct = f; + } + + void abortSelfDestruct() { + selfDestruct = null; + } + + private void runSelfDestructionUnsafe() throws Throwable { + File tmp = File.createTempFile("mcef-deleter", ".jar"); + ZipOutputStream zos = new ZipOutputStream(new FileOutputStream(tmp)); + InputStream is = SetupUI.class.getResourceAsStream("/net/montoyo/mcef/setup/Deleter.class"); + byte[] buf = new byte[8192]; + int read; + + zos.putNextEntry(new ZipEntry("net/montoyo/mcef/setup/Deleter.class")); + while((read = is.read(buf)) > 0) + zos.write(buf, 0, read); + + try { + zos.closeEntry(); + } catch(Throwable t) {} + + SetupUtil.silentClose(zos); + SetupUtil.silentClose(is); + + String java = "\"" + System.getProperty("java.home") + File.separator + "bin" + File.separator + "java"; + if(System.getProperty("os.name").toLowerCase().contains("win")) + java += "w.exe"; + + java += "\" -classpath \""; + java += tmp.getAbsolutePath(); + java += "\" net.montoyo.mcef.setup.Deleter \""; + java += selfDestruct.getAbsolutePath(); + java += "\""; + + System.out.println("Running auto-deleter:"); + System.out.println(java); + Runtime.getRuntime().exec(java); + } + + @Override + public void windowOpened(WindowEvent e) { + } + + @Override + public void windowClosing(WindowEvent e) { + frame.dispose(); + + if(selfDestruct != null) { + try { + runSelfDestructionUnsafe(); + } catch(Throwable t) { + System.err.println("Failed to destruct myself:"); + t.printStackTrace(); + } + } + } + + @Override + public void windowClosed(WindowEvent e) { + } + + @Override + public void windowIconified(WindowEvent e) { + } + + @Override + public void windowDeiconified(WindowEvent e) { + } + + @Override + public void windowActivated(WindowEvent e) { + } + + @Override + public void windowDeactivated(WindowEvent e) { + } + + @Override + public void mouseClicked(MouseEvent e) { + try { + Desktop.getDesktop().browse(new URI("https://montoyo.net")); + } catch(Throwable t) { + t.printStackTrace(); + } + } + + @Override + public void mousePressed(MouseEvent e) { + } + + @Override + public void mouseReleased(MouseEvent e) { + } + + @Override + public void mouseEntered(MouseEvent e) { + } + + @Override + public void mouseExited(MouseEvent e) { + } + +} diff --git a/src/main/java/net/montoyo/mcef/setup/SetupUtil.java b/src/main/java/net/montoyo/mcef/setup/SetupUtil.java new file mode 100644 index 0000000..b6ed62d --- /dev/null +++ b/src/main/java/net/montoyo/mcef/setup/SetupUtil.java @@ -0,0 +1,94 @@ +package net.montoyo.mcef.setup; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; + +public class SetupUtil { + + static boolean tryDelete(File f) { + if(!f.exists()) + return true; + + if(f.delete()) + return true; + else { + File dst = new File(f.getParentFile(), f.getName() + "_" + System.currentTimeMillis() + "_" + ((int) (Math.random() * 10000)) + ".tmp"); + + if(f.renameTo(dst)) { + if(!dst.delete()) + dst.deleteOnExit(); + + return true; + } else + return false; + } + } + + static File getSelfJarLocation() { + try { + File ret = new File(SetupUtil.class.getProtectionDomain().getCodeSource().getLocation().toURI().getPath()); + if(ret.exists() && ret.isFile()) + return ret; + } catch(Throwable t) { + System.err.println("Could not locate own JAR (try #1):"); + t.printStackTrace(); + } + + try { + File ret = new File(ClassLoader.getSystemClassLoader().getResource(".").getPath()); + if(ret.exists() && ret.isFile()) + return ret; + } catch(Throwable t) { + System.err.println("Could not locate own JAR (try #2):"); + t.printStackTrace(); + } + + return null; + } + + static void silentClose(Object o) { + try { + o.getClass().getMethod("close").invoke(o); + } catch(Throwable t) {} + } + + static boolean copyFile(File src, File dst) { + byte[] buf = new byte[65536]; + int read; + + try { + FileInputStream fis = new FileInputStream(src); + FileOutputStream fos = new FileOutputStream(dst); + + while((read = fis.read(buf)) > 0) + fos.write(buf, 0, read); + + silentClose(fos); + silentClose(fis); + return true; + } catch(Throwable t) { + System.err.println("Could NOT copy \"" + src.getAbsolutePath() + "\" to \"" + dst.getAbsolutePath() + "\":"); + t.printStackTrace(); + return false; + } + } + + static boolean areFileEqual(File a, File b) { + if(a == null || b == null) + return false; + + try { + String ap = a.getCanonicalPath(); + String bp = b.getCanonicalPath(); + + return System.getProperty("os.name").toLowerCase().contains("win") ? ap.equalsIgnoreCase(bp) : ap.equals(bp); //Windows paths are case-insensitive + } catch(IOException e) { + System.err.println("Could not compare file path, returning non-equal:"); + e.printStackTrace(); + return false; + } + } + +} diff --git a/src/main/java/net/montoyo/mcef/setup/SlashComparator.java b/src/main/java/net/montoyo/mcef/setup/SlashComparator.java new file mode 100644 index 0000000..7064ef2 --- /dev/null +++ b/src/main/java/net/montoyo/mcef/setup/SlashComparator.java @@ -0,0 +1,50 @@ +package net.montoyo.mcef.setup; + +import java.util.Comparator; + +final class SlashComparator implements Comparator { + + private Comparator fallback; + + SlashComparator(Comparator fb) { + fallback = fb; + } + + @Override + public int compare(String a, String b) { + int slashA = 0; + int slashB = 0; + + for(int i = 0; i < a.length(); i++) { //WARNING: Sub-optimized code here! + if(a.charAt(i) == '/' || a.charAt(i) == '\\') + slashA++; + } + + for(int i = 0; i < b.length(); i++) { + if(b.charAt(i) == '/' || b.charAt(i) == '\\') + slashB++; + } + + int ret = slashB - slashA; + if(ret == 0 && fallback != null) + return fallback.compare(a, b); + else + return ret; + } + + @Override + public boolean equals(Object obj) { + if(obj == null) + return false; + else if(obj instanceof SlashComparator) { + SlashComparator other = (SlashComparator) obj; + + if(fallback == null) + return other.fallback == null; + else + return other.fallback != null && fallback.equals(other.fallback); + } else + return false; + } + +} diff --git a/src/main/java/net/montoyo/mcef/utilities/DummyProgressListener.java b/src/main/java/net/montoyo/mcef/utilities/DummyProgressListener.java new file mode 100644 index 0000000..12a1f97 --- /dev/null +++ b/src/main/java/net/montoyo/mcef/utilities/DummyProgressListener.java @@ -0,0 +1,23 @@ +package net.montoyo.mcef.utilities; + +/** + * Dummy progress listener. Does nothing. + * @author montoyo + * @see Util#secure(IProgressListener) + * + */ +public class DummyProgressListener implements IProgressListener { + + @Override + public void onProgressed(double d) { + } + + @Override + public void onTaskChanged(String name) { + } + + @Override + public void onProgressEnd() { + } + +} diff --git a/src/main/java/net/montoyo/mcef/utilities/ForgeProgressListener.java b/src/main/java/net/montoyo/mcef/utilities/ForgeProgressListener.java new file mode 100644 index 0000000..859eb27 --- /dev/null +++ b/src/main/java/net/montoyo/mcef/utilities/ForgeProgressListener.java @@ -0,0 +1,44 @@ +package net.montoyo.mcef.utilities; + +import net.minecraftforge.fml.common.ProgressManager; + +public class ForgeProgressListener implements IProgressListener { + + private ProgressManager.ProgressBar progressBar = null; + private int lastVal = 0; + + private void stepUntil(int val) { + // Ensures progress is properly updated before popping + while (lastVal < val && progressBar != null) { + progressBar.step(lastVal + "%"); + lastVal++; + } + } + + @Override + public void onProgressed(double d) { + stepUntil((int) Util.clamp(d, 0.d, 100.d)); + } + + @Override + public void onTaskChanged(String name) { + if (progressBar != null) { + stepUntil(100); // Ensure the last task is fully completed before popping + ProgressManager.pop(progressBar); + progressBar = null; // Reset progressBar after popping + } + + progressBar = ProgressManager.push(name, 100, false); + lastVal = 0; + } + + @Override + public void onProgressEnd() { + if (progressBar != null) { + stepUntil(100); // Ensure progress reaches 100% before popping + ProgressManager.pop(progressBar); + progressBar = null; + } + } + +} diff --git a/src/main/java/net/montoyo/mcef/utilities/ForgeVersionParser.java b/src/main/java/net/montoyo/mcef/utilities/ForgeVersionParser.java new file mode 100644 index 0000000..6c7d3da --- /dev/null +++ b/src/main/java/net/montoyo/mcef/utilities/ForgeVersionParser.java @@ -0,0 +1,42 @@ +package net.montoyo.mcef.utilities; + +import java.util.HashMap; +import java.util.Map; + +import net.montoyo.mcef.MCEF; +import net.montoyo.mcef.utilities.Log; + +public class ForgeVersionParser { + // Predefined mapping of Forge versions to Minecraft versions + private static final Map forgeVersionMapping = new HashMap<>(); + + static { + // Mapping Forge versions to Minecraft versions + forgeVersionMapping.put("forge-14.23.5.2859", "1.12.2"); + forgeVersionMapping.put("forge-14.23.5.2838", "1.12.2"); + forgeVersionMapping.put("forge-14.23.5.2768", "1.12.2"); + // Add more mappings here as needed + } + + // Method to parse the version + public String parse(String version) { + if (version == null) { + return null; + } + + // Check if the version contains "forge-" + if (version.startsWith("forge-")) { + // Look up the Forge version in the mapping table + String realVersion = forgeVersionMapping.get(version); + if (realVersion != null) { + return realVersion; + } else { + // Handle unknown Forge version, maybe log a warning or use a default + return "Unknown Minecraft Version (Forge)"; + } + } else { + // If it doesn't contain "forge-", it's likely a real Minecraft version + return version; + } + } +} diff --git a/src/main/java/net/montoyo/mcef/utilities/IProgressListener.java b/src/main/java/net/montoyo/mcef/utilities/IProgressListener.java new file mode 100644 index 0000000..579494d --- /dev/null +++ b/src/main/java/net/montoyo/mcef/utilities/IProgressListener.java @@ -0,0 +1,27 @@ +package net.montoyo.mcef.utilities; + +/** + * This interface is used by classes that wants to keep track of the progress of some tasks. + * @author montoyo + * + */ +public interface IProgressListener { + + /** + * Call this when the current task progressed. + * @param d The current task progress in percent. + */ + public void onProgressed(double d); + + /** + * Call this if the current task changed. + * @param name The name of the new task. + */ + public void onTaskChanged(String name); + + /** + * Call this when everything is finished. + */ + public void onProgressEnd(); + +} diff --git a/src/main/java/net/montoyo/mcef/utilities/Log.java b/src/main/java/net/montoyo/mcef/utilities/Log.java new file mode 100644 index 0000000..2224ddf --- /dev/null +++ b/src/main/java/net/montoyo/mcef/utilities/Log.java @@ -0,0 +1,29 @@ +package net.montoyo.mcef.utilities; + +import org.apache.logging.log4j.Level; +import org.apache.logging.log4j.LogManager; + +/** + * A set of functions to log messages into the MCEF log channel. + * @author montoyo + * + */ +public class Log { + + public static void info(String what, Object... data) { + LogManager.getLogger("MCEF").log(Level.INFO, String.format("[MCEF] " + what, data)); + } + + public static void warning(String what, Object... data) { + LogManager.getLogger("MCEF").log(Level.WARN, String.format("[MCEF] " + what, data)); + } + + public static void error(String what, Object... data) { + LogManager.getLogger("MCEF").log(Level.ERROR, String.format("[MCEF] " + what, data)); + } + + public static void errorEx(String what, Throwable t, Object... data) { + LogManager.getLogger("MCEF").log(Level.ERROR, String.format("[MCEF] " + what, data), t); + } + +} diff --git a/src/main/java/net/montoyo/mcef/utilities/Platform.java b/src/main/java/net/montoyo/mcef/utilities/Platform.java new file mode 100644 index 0000000..e397887 --- /dev/null +++ b/src/main/java/net/montoyo/mcef/utilities/Platform.java @@ -0,0 +1,97 @@ +package net.montoyo.mcef.utilities; + +import net.montoyo.mcef.MCEF; +import net.montoyo.mcef.utilities.Log; +import org.cef.OS; + +import java.util.Locale; + +public class Platform { + + public static String id; + public static String idFull; + public static String arch; + public static String archFull; + public static String PLATFORM; + public static String PLATFORMFULL; + + static { // Static block for initialization + detectPlatform(); + } + + private static void detectPlatform() { + try { + id = System.getProperty("os.name", "unknown").toLowerCase(Locale.US); + arch = System.getProperty("os.arch", "unknown").toLowerCase(Locale.US); + } catch (Exception e) { + Log.error("Couldn't get OS name or architecture."); + id = "unknown"; + arch = "unknown"; + } + + // Normalize OS name + if (OS.isWindows()) { + id = "win"; + idFull = "windows"; + } else if (OS.isMacintosh()) { + id = "mac"; + idFull = "macos"; + } else if (OS.isLinux()) { + id = "linux"; + idFull = "linux"; + } else { + Log.error("Your OS isn't supported by MCEF."); + id = "unknown"; + idFull = "unknown"; + return; + } + + // Normalize CPU architecture + if (arch.equals("x86_64") || arch.equals("amd64") || arch.equals("64")) { + arch = "64"; + archFull = "amd64"; + } else if (arch.equals("aarch64") || arch.equals("arm64")) { + if (!id.equals("mac") || !id.equals("darwin")) { // Only allow ARM64 for Macs + Log.error("ARM-based platforms other than Mac are not supported by MCEF."); + id = "unknown"; + idFull = "unknown"; + return; + } + arch = "arm64"; + archFull = "arm64"; + } else { + Log.error("Your CPU architecture isn't supported by MCEF."); + id = "unknown"; + idFull = "unknown"; + return; + } + + // Set platform strings + PLATFORM = id + arch; + PLATFORMFULL = idFull + "_" + archFull; + } + + public static String getOS() { + return id; + } + + public static String getPlatform() { + return PLATFORM; + } + + public static String getPlatformFull() { + return PLATFORMFULL; + } + + public static boolean isWindows() { + return "win".equals(id); + } + + public static boolean isMacOS() { + return "mac".equals(id) || "darwin".equals(id); + } + + public static boolean isLinux() { + return "linux".equals(id); + } +} diff --git a/src/main/java/net/montoyo/mcef/utilities/Util.java b/src/main/java/net/montoyo/mcef/utilities/Util.java new file mode 100644 index 0000000..386e2eb --- /dev/null +++ b/src/main/java/net/montoyo/mcef/utilities/Util.java @@ -0,0 +1,347 @@ +package net.montoyo.mcef.utilities; + +import java.io.*; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.net.HttpURLConnection; +import java.net.MalformedURLException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.util.zip.GZIPInputStream; +import java.util.zip.ZipEntry; +import java.util.zip.ZipInputStream; +import org.apache.commons.compress.archivers.tar.TarArchiveEntry; +import org.apache.commons.compress.archivers.tar.TarArchiveInputStream; +import org.apache.commons.compress.compressors.gzip.GzipCompressorInputStream; + +import net.montoyo.mcef.MCEF; +import net.montoyo.mcef.remote.Mirror; +import net.montoyo.mcef.remote.MirrorManager; +import org.cef.OS; + +import javax.net.ssl.HttpsURLConnection; +import java.net.URL; + +public class Util { + + private static final DummyProgressListener DPH = new DummyProgressListener(); + + /** + * Clamps d between min and max. + * + * @param d The value to clamp. + * @param min The minimum. + * @param max The maximum. + * @return The clamped value. + */ + public static double clamp(double d, double min, double max) { + if(d < min) + return min; + else if(d > max) + return max; + else + return d; + } + + /** + * Extracts a ZIP archive into a folder. + * + * @param zip The ZIP archive file to extract. + * @param out The output directory for the ZIP content. + * @return true if the extraction was successful. + */ + public static boolean extractZip(File zip, File out) { + // For macOS, the "unzip" utility seems to be reliable at setting certain flags on executables when extracting + // Otherwise, extracting a .app is a pain. It refuses to run without setting executable flags on the contents, etc + if (OS.isMacintosh()) { + try { + Process unzip = Runtime.getRuntime().exec(new String[]{"/usr/bin/unzip", zip.getAbsolutePath(), "-d", out.getAbsolutePath()}); + unzip.waitFor(); + return true; + } catch (Exception e) { + e.printStackTrace(); + return false; + } + } + + ZipInputStream zis; + + try { + zis = new ZipInputStream(new FileInputStream(zip)); + } catch(FileNotFoundException e) { + Log.error("Couldn't extract %s: File not found.", zip.getName()); + e.printStackTrace(); + return false; + } + + try { + ZipEntry ze; + while((ze = zis.getNextEntry()) != null) { + if(ze.isDirectory()) + continue; + + File dst = new File(out, ze.getName()); + delete(dst); + mkdirs(dst); + + FileOutputStream fos = new FileOutputStream(dst); + byte[] data = new byte[65536]; + int read; + + while((read = zis.read(data)) > 0) + fos.write(data, 0, read); + + close(fos); + } + + return true; + } catch(FileNotFoundException e) { + Log.error("Couldn't extract a file from %s. Maybe you're missing some permissions?", zip.getName()); + e.printStackTrace(); + return false; + } catch(IOException e) { + Log.error("IOException while extracting %s.", zip.getName()); + e.printStackTrace(); + return false; + } finally { + close(zis); + } + } + + /** + * Downloads a file from a URL. + * + * @param urlString The URL to download from. + * @param outputFile The file to save the downloaded content to. + * @param ph A progress handler. + * @throws IOException If the download fails. + */ + public static void downloadFile(String urlString, File outputFile, IProgressListener ph) throws IOException { + try { + Log.info("Downloading: " + urlString + " -> " + outputFile.getCanonicalPath()); + + ph = secure(ph); + ph.onTaskChanged("Downloading " + outputFile.getName()); + + URL url = new URL(urlString); + HttpURLConnection urlConnection; + int statusCode; + + urlConnection = (HttpURLConnection) url.openConnection(); + + // Set custom User-Agent + String userAgent = "MCEF/3.0 (Java/" + System.getProperty("java.version") + "; " + System.getProperty("os.name") + " " + System.getProperty("os.arch") + ")"; + urlConnection.setRequestProperty("User-Agent", userAgent); + + urlConnection.connect(); + + statusCode = urlConnection.getResponseCode(); + + if (statusCode != HttpURLConnection.HTTP_OK) { + Log.info("Error: " + urlString + " returned HTTP " + statusCode); + throw new IOException("Server returned HTTP response code: " + statusCode); + } + + int fileSize = urlConnection.getContentLength(); + BufferedInputStream inputStream = new BufferedInputStream(urlConnection.getInputStream()); + FileOutputStream outputStream = new FileOutputStream(outputFile); + + byte[] buffer = new byte[2048]; + int count; + int readBytes = 0; + while ((count = inputStream.read(buffer)) != -1) { + outputStream.write(buffer, 0, count); + readBytes += count; + float percentComplete = (float) readBytes / fileSize; + ph.onProgressed(percentComplete * 100); + } + + inputStream.close(); + outputStream.close(); + } catch (IOException e) { + e.printStackTrace(); + Log.info(e.getMessage()); + throw new IOException("Failed to download " + urlString, e); + } + } + + /** + * Same as {@link #downloadFile(String, File, IProgressListener)} but returns true if the download was successful. + * + * @param urlString The URL to download from. + * @param outputFile The file to save the downloaded content to. + * @param ph A progress handler. + * @throws IOException If the download fails. + */ + public static boolean downloadFileSafe(String urlString, File outputFile, IProgressListener ph) { + try { + // Check if the file path (dir) exists, if not create it + mkdirs(outputFile); + // Download the file + downloadFile(urlString, outputFile, ph); + return true; + } catch (IOException e) { + Log.error("Failed to download %s", urlString); + e.printStackTrace(); + return false; + } + } + + /** + * Extracts a tar.gz file. + * + * @param tarGzFile The tar.gz file to extract. + * @param outputDirectory The directory to extract files to. + */ + public static void extractTarGz(File tarGzFile, File outputDirectory, IProgressListener ph) { + ph = secure(ph); + ph.onTaskChanged("Extracting " + tarGzFile.getName()); + + outputDirectory.mkdirs(); + long fileSize = tarGzFile.length(); + long totalBytesRead = 0; + + try (TarArchiveInputStream tarInput = new TarArchiveInputStream(new GzipCompressorInputStream(new FileInputStream(tarGzFile)))) { + TarArchiveEntry entry; + while ((entry = tarInput.getNextTarEntry()) != null) { + if (entry.isDirectory()) { + continue; + } + + File outputFile = new File(outputDirectory, entry.getName()); + outputFile.getParentFile().mkdirs(); + + try (OutputStream outputStream = new FileOutputStream(outputFile)) { + byte[] buffer = new byte[4096]; + int bytesRead; + while ((bytesRead = tarInput.read(buffer)) != -1) { + outputStream.write(buffer, 0, bytesRead); + totalBytesRead += bytesRead; + float percentComplete = ((float) totalBytesRead / fileSize) / 2.6158204f; // Rough compression ratio + ph.onProgressed(percentComplete * 100); + } + } + } + } catch (IOException e) { + Log.error("Failed to extract gzip file to " + outputDirectory, e); + } + + ph.onProgressed(100); + } + + /** + * Convenience function. Secures a progress listener. + * If pl is null, then a dummy empty progress listener will be returned. + * + * @param pl The progress handler to secure. + * @return A progress handler that is never null. + * @see IProgressListener + */ + public static IProgressListener secure(IProgressListener pl) { + return (pl == null) ? DPH : pl; + } + + /** + * Renames a file using a string. + * + * @param src The file to rename. + * @param name The new name of the file. + * @return the new file or null if it failed. + */ + public static File rename(File src, String name) { + File ret = new File(src.getParentFile(), name); + + if(src.renameTo(ret)) + return ret; + else + return null; + } + + /** + * Makes sure that the directory in which the file is exists. + * If this one doesn't exist, i'll be created. + * + * @param f The file. + */ + public static void mkdirs(File f) { + File p = f.getParentFile(); + if(!p.exists()) + p.mkdirs(); + } + + /** + * Tries to delete a file in an advanced way. + * Does a warning in log if it couldn't delete it neither rename it. + * + * @param f The file to be deleted. + * @see #delete(File) + */ + public static void delete(String f) { + delete(new File(f)); + } + + /** + * Tries to delete a file in an advanced way. + * Does a warning in log if it couldn't delete it neither rename it. + * + * @param f The file to be deleted. + * @see #delete(String) + */ + public static void delete(File f) { + if(!f.exists() || f.delete()) + return; + + File mv = new File(f.getParentFile(), "deleteme" + ((int) (Math.random() * 100000.d))); + if(f.renameTo(mv)) { + if(!mv.delete()) + mv.deleteOnExit(); + + return; + } + + Log.warning("Couldn't delete file! If there's any problems, please try to remove it yourself. Path: %s", f.getAbsolutePath()); + } + + /** + * Calls "close" on the specified object without throwing any exceptions. + * This is usefull with input and output streams. + * + * @param o The object to call close on. + */ + public static void close(Object o) { + try { + o.getClass().getMethod("close").invoke(o); + } catch(Throwable t) {} + } + + /** + * Same as {@link Files#isSameFile(Path, Path)} but if an {@link IOException} is thrown, + * return false. + * + * @param p1 Path 1 + * @param p2 Path 2 + * @return true if the paths are the same, false if they are not or if an exception is thrown during the comparison + */ + public static boolean isSameFile(Path p1, Path p2) { + try { + return Files.isSameFile(p1, p2); + } catch(IOException e) { + return false; + } + } + + /** + * Same as {@link System#getenv(String)}, but if no such environment variable is + * defined, will return an empty string instead of null. + * + * @param name Name of the environment variable to get + * @return The value of this environment variable (may be empty but never null) + */ + public static String getenv(String name) { + String ret = System.getenv(name); + return ret == null ? "" : ret; + } + +} diff --git a/src/main/java/net/montoyo/mcef/utilities/Version.java b/src/main/java/net/montoyo/mcef/utilities/Version.java new file mode 100644 index 0000000..86bc01a --- /dev/null +++ b/src/main/java/net/montoyo/mcef/utilities/Version.java @@ -0,0 +1,107 @@ +package net.montoyo.mcef.utilities; + +/** + * A utility class used for parsing decimal dot-separated versions from strings and comparing them. + * Example: 5.4.9 + * + * @author montoyo + * + */ +public class Version { + + private int[] numbers; + + /** + * Constructs a version from a decimal dot-separated version string. + * @param v A version string, such as 5.4.9 + */ + public Version(String v) { + String[] ray = v.trim().split("\\."); + numbers = new int[ray.length]; + + for(int i = 0; i < ray.length; i++) { + try { + numbers[i] = Integer.parseInt(ray[i]); + } catch(NumberFormatException e) { + Log.error("Couldn't parse %s. Number %d will be zero.", v, i); + e.printStackTrace(); + numbers[i] = 0; + } + } + + //Look for useless ending zeroes + int end; + for(end = numbers.length - 1; end >= 0; end--) { + if(numbers[end] != 0) + break; + } + + end++; + if(end != numbers.length) { //Is there any? Remove them! + int[] na = new int[end]; + System.arraycopy(numbers, 0, na, 0, end); + numbers = na; + } + } + + /** + * Compares two versions. Return true if this instance is bigger than v. + * If both describes the same version, false is returned. + * + * @param v The version to compare with. + * @return true if this instance is bigger than v. + */ + public boolean isBiggerThan(Version v) { + int len = Math.min(numbers.length, v.numbers.length); + + for(int i = 0; i < len; i++) { + if(numbers[i] > v.numbers[i]) + return true; + } + + return numbers.length > v.numbers.length; + } + + /** + * Compares two versions. Returns true if this instance is equal to o. + * @param o The version to compare with. + * @return true if both objects describes the same version. + */ + @Override + public boolean equals(Object o) { + if(!(o instanceof Version)) + return false; + + Version v = (Version) o; + if(v == this) + return true; + + if(numbers.length != v.numbers.length) + return false; + + for(int i = 0; i < numbers.length; i++) { + if(numbers[i] != v.numbers[i]) + return false; + } + + return true; + } + + /** + * Turns this version class into a decimal dot-separated version string. + * @return a version string, such as 5.4.9 + */ + @Override + public String toString() { + String ret = ""; + for(int i = 0; i < numbers.length; i++) { + if(i > 0) + ret += '.'; + + ret += numbers[i]; + } + + return ret; + } + +} diff --git a/src/main/java/net/montoyo/mcef/virtual/VirtualBrowser.java b/src/main/java/net/montoyo/mcef/virtual/VirtualBrowser.java new file mode 100644 index 0000000..be4f1e7 --- /dev/null +++ b/src/main/java/net/montoyo/mcef/virtual/VirtualBrowser.java @@ -0,0 +1,80 @@ +package net.montoyo.mcef.virtual; + +import net.montoyo.mcef.api.IBrowser; +import net.montoyo.mcef.api.IStringVisitor; + +public class VirtualBrowser implements IBrowser { + + @Override + public void close() { + } + + @Override + public void resize(int width, int height) { + } + + @Override + public void draw(double x1, double y1, double x2, double y2) { + } + + @Override + public int getTextureID() { + return 0; + } + + @Override + public void injectMouseMove(int x, int y, int mods, boolean left) { + } + + @Override + public void injectMouseButton(int x, int y, int mods, int btn, boolean pressed, int ccnt) { + } + + @Override + public void injectKeyTyped(char c, int mods) { + } + + @Override + public void injectKeyPressedByKeyCode(int keyCode, char c, int mods) { + } + + @Override + public void injectKeyReleasedByKeyCode(int keyCode, char c, int mods) { + } + + @Override + public void injectMouseWheel(int x, int y, int mods, int amount, int rot) { + } + + @Override + public void runJS(String script, String frame) { + } + + @Override + public void loadURL(String url) { + } + + @Override + public void goBack() { + } + + @Override + public void goForward() { + } + + @Override + public String getURL() { + return "about:blank"; + } + + @Override + public void visitSource(IStringVisitor isv) { + isv.visit("https://www.youtube.com/watch?v=VX5gXHcbJAk"); + } + + @Override + public boolean isPageLoading() { + return true; + } + +} diff --git a/src/main/java/org/cef/CefApp.java b/src/main/java/org/cef/CefApp.java new file mode 100644 index 0000000..1c305b6 --- /dev/null +++ b/src/main/java/org/cef/CefApp.java @@ -0,0 +1,546 @@ +// Copyright (c) 2013 The Chromium Embedded Framework Authors. All rights +// reserved. Use of this source code is governed by a BSD-style license that +// can be found in the LICENSE file. + +package org.cef; + +import org.cef.callback.CefSchemeHandlerFactory; +import org.cef.handler.CefAppHandler; +import org.cef.handler.CefAppHandlerAdapter; + +import javax.swing.*; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.io.File; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.HashSet; + +/** + * Exposes static methods for managing the global CEF context. + */ +public class CefApp extends CefAppHandlerAdapter { + public final class CefVersion { + public final int JCEF_COMMIT_NUMBER; + + public final int CEF_VERSION_MAJOR; + public final int CEF_VERSION_MINOR; + public final int CEF_VERSION_PATCH; + public final int CEF_COMMIT_NUMBER; + + public final int CHROME_VERSION_MAJOR; + public final int CHROME_VERSION_MINOR; + public final int CHROME_VERSION_BUILD; + public final int CHROME_VERSION_PATCH; + + private CefVersion(int jcefCommitNo, int cefMajor, int cefMinor, int cefPatch, + int cefCommitNo, int chrMajor, int chrMin, int chrBuild, int chrPatch) { + JCEF_COMMIT_NUMBER = jcefCommitNo; + + CEF_VERSION_MAJOR = cefMajor; + CEF_VERSION_MINOR = cefMinor; + CEF_VERSION_PATCH = cefPatch; + CEF_COMMIT_NUMBER = cefCommitNo; + + CHROME_VERSION_MAJOR = chrMajor; + CHROME_VERSION_MINOR = chrMin; + CHROME_VERSION_BUILD = chrBuild; + CHROME_VERSION_PATCH = chrPatch; + } + + public String getJcefVersion() { + return CEF_VERSION_MAJOR + "." + CEF_VERSION_MINOR + "." + CEF_VERSION_PATCH + "." + + JCEF_COMMIT_NUMBER; + } + + public String getCefVersion() { + return CEF_VERSION_MAJOR + "." + CEF_VERSION_MINOR + "." + CEF_VERSION_PATCH; + } + + public String getChromeVersion() { + return CHROME_VERSION_MAJOR + "." + CHROME_VERSION_MINOR + "." + CHROME_VERSION_BUILD + + "." + CHROME_VERSION_PATCH; + } + + @Override + public String toString() { + return "JCEF Version = " + getJcefVersion() + "\n" + + "CEF Version = " + getCefVersion() + "\n" + + "Chromium Version = " + getChromeVersion(); + } + } + + /** + * The CefAppState gives you a hint if the CefApp is already usable or not + * usable any more. See values for details. + */ + public enum CefAppState { + /** + * No CefApp instance was created yet. Call getInstance() to create a new + * one. + */ + NONE, + + /** + * CefApp is new created but not initialized yet. No CefClient and no + * CefBrowser was created until now. + */ + NEW, + + /** + * CefApp is in its initializing process. Please wait until initializing is + * finished. + */ + INITIALIZING, + + /** + * CefApp is up and running. At least one CefClient was created and the + * message loop is running. You can use all classes and methods of JCEF now. + */ + INITIALIZED, + + /** + * CefApp is in its shutdown process. All CefClients and CefBrowser + * instances will be disposed. No new CefClient or CefBrowser is allowed to + * be created. The message loop will be performed until all CefClients and + * all CefBrowsers are disposed completely. + */ + SHUTTING_DOWN, + + /** + * CefApp is terminated and can't be used any more. You can shutdown the + * application safely now. + */ + TERMINATED + } + + /** + * According the singleton pattern, this attribute keeps + * one single object of this class. + */ + private static CefApp self = null; + private static CefAppHandler appHandler_ = null; + private static CefAppState state_ = CefAppState.NONE; + private Timer workTimer_ = null; + private HashSet clients_ = new HashSet(); + private CefSettings settings_ = null; + + /** + * To get an instance of this class, use the method + * getInstance() instead of this CTOR. + *

+ * The CTOR is called by getInstance() as needed and + * loads all required JCEF libraries. + * + * @throws UnsatisfiedLinkError + */ + private CefApp(String[] args, CefSettings settings) throws UnsatisfiedLinkError { + super(args); + if (settings != null) settings_ = settings.clone(); + + if (appHandler_ == null) { + appHandler_ = this; + } + + if (!N_PreInitialize()) + throw new IllegalStateException("Failed to pre-initialize native code"); + } + + /** + * Assign an AppHandler to CefApp. The AppHandler can be used to evaluate + * application arguments, to register your own schemes and to hook into the + * shutdown sequence. See CefAppHandler for more details. + *

+ * This method must be called before CefApp is initialized. CefApp will be + * initialized automatically if you call createClient() the first time. + * + * @param appHandler An instance of CefAppHandler. + * @throws IllegalStateException in case of CefApp is already initialized + */ + public static void addAppHandler(CefAppHandler appHandler) throws IllegalStateException { + if (getState().compareTo(CefAppState.NEW) > 0) + throw new IllegalStateException("Must be called before CefApp is initialized"); + appHandler_ = appHandler; + } + + /** + * Get an instance of this class. + * + * @return an instance of this class + * @throws UnsatisfiedLinkError + */ + public static synchronized CefApp getInstance() throws UnsatisfiedLinkError { + return getInstance(null, null); + } + + public static synchronized CefApp getInstance(String[] args) throws UnsatisfiedLinkError { + return getInstance(args, null); + } + + public static synchronized CefApp getInstance(CefSettings settings) + throws UnsatisfiedLinkError { + return getInstance(null, settings); + } + + public static synchronized CefApp getInstance(String[] args, CefSettings settings) + throws UnsatisfiedLinkError { + if (settings != null) { + if (getState() != CefAppState.NONE && getState() != CefAppState.NEW) + throw new IllegalStateException("Settings can only be passed to CEF" + + " before createClient is called the first time."); + } + if (self == null) { + if (getState() == CefAppState.TERMINATED) + throw new IllegalStateException("CefApp was terminated"); + self = new CefApp(args, settings); + setState(CefAppState.NEW); + } + return self; + } + + public final void setSettings(CefSettings settings) throws IllegalStateException { + if (getState() != CefAppState.NONE && getState() != CefAppState.NEW) + throw new IllegalStateException("Settings can only be passed to CEF" + + " before createClient is called the first time."); + settings_ = settings.clone(); + } + + public final CefVersion getVersion() { + try { + return N_GetVersion(); + } catch (UnsatisfiedLinkError ule) { + ule.printStackTrace(); + } + return null; + } + + /** + * Returns the current state of CefApp. + * + * @return current state. + */ + public final static CefAppState getState() { + synchronized (state_) { + return state_; + } + } + + private static final void setState(final CefAppState state) { + synchronized (state_) { + state_ = state; + } + if (appHandler_ != null) appHandler_.stateHasChanged(state); + } + + //montoyo: Added for MCEF + public static final void forceShutdownState() { + synchronized (state_) { + state_ = CefAppState.SHUTTING_DOWN; + } + } + + /** + * To shutdown the system, it's important to call the dispose + * method. Calling this method closes all client instances with + * and all browser instances each client owns. After that the + * message loop is terminated and CEF is shutdown. + */ + public synchronized final void dispose() { + switch (getState()) { + case NEW: + // Nothing to do inspite of invalidating the state + setState(CefAppState.TERMINATED); + break; + + case INITIALIZING: + case INITIALIZED: + // (3) Shutdown sequence. Close all clients and continue. + setState(CefAppState.SHUTTING_DOWN); + if (clients_.isEmpty()) { + shutdown(); + } else { + // shutdown() will be called from clientWasDisposed() when the last + // client is gone. + // Use a copy of the HashSet to avoid iterating during modification. + HashSet clients = new HashSet(clients_); + for (CefClient c : clients) { + c.dispose(); + } + } + break; + + case NONE: + case SHUTTING_DOWN: + case TERMINATED: + // Ignore shutdown, CefApp is already terminated, in shutdown progress + // or was never created (shouldn't be possible) + break; + } + } + + /** + * Creates a new client instance and returns it to the caller. + * One client instance is responsible for one to many browser + * instances + * + * @return a new client instance + */ + public synchronized CefClient createClient() { + switch (getState()) { + case NEW: + setState(CefAppState.INITIALIZING); + initialize(); + // FALL THRU + + case INITIALIZING: + case INITIALIZED: + CefClient client = new CefClient(); + clients_.add(client); + return client; + + default: + throw new IllegalStateException("Can't crate client in state " + state_); + } + } + + /** + * Register a scheme handler factory for the specified |scheme_name| and + * optional |domain_name|. An empty |domain_name| value for a standard scheme + * will cause the factory to match all domain names. The |domain_name| value + * will be ignored for non-standard schemes. If |scheme_name| is a built-in + * scheme and no handler is returned by |factory| then the built-in scheme + * handler factory will be called. If |scheme_name| is a custom scheme then + * also implement the CefApp::OnRegisterCustomSchemes() method in all + * processes. This function may be called multiple times to change or remove + * the factory that matches the specified |scheme_name| and optional + * |domain_name|. Returns false if an error occurs. This function may be + * called on any thread in the browser process. + */ + public boolean registerSchemeHandlerFactory( + String schemeName, String domainName, CefSchemeHandlerFactory factory) { + try { + return N_RegisterSchemeHandlerFactory(schemeName, domainName, factory); + } catch (Exception err) { + err.printStackTrace(); + } + return false; + } + + /** + * Clear all registered scheme handler factories. Returns false on error. This + * function may be called on any thread in the browser process. + */ + public boolean clearSchemeHandlerFactories() { + try { + return N_ClearSchemeHandlerFactories(); + } catch (Exception err) { + err.printStackTrace(); + } + return false; + } + + /** + * This method is called by a CefClient if it was disposed. This causes + * CefApp to clean up its list of available client instances. If all clients + * are disposed, CefApp will be shutdown. + * + * @param client the disposed client. + */ + protected final synchronized void clientWasDisposed(CefClient client) { + clients_.remove(client); + if (clients_.isEmpty() && getState().compareTo(CefAppState.SHUTTING_DOWN) >= 0) { + // Shutdown native system. + shutdown(); + } + } + + /** + * Initialize the context. + * + * @return true on success. + */ + private final void initialize() { + String jcefPath = getJcefLibPath(); + System.out.println("initialize on " + Thread.currentThread() + " with library path " + jcefPath); + + CefSettings settings = settings_ != null ? settings_ : new CefSettings(); + + if (OS.isWindows()) { + Path jcefHelperPath = Paths.get(jcefPath, "jcef_helper.exe"); + settings.browser_subprocess_path = jcefHelperPath.normalize().toAbsolutePath().toString(); + } else if (OS.isMacintosh()) { + String basePath = Paths.get(jcefPath).getParent().getParent().toString(); + settings.main_bundle_path = basePath; + settings.framework_dir_path = basePath + + "/Contents/Frameworks/Chromium Embedded Framework.framework"; + settings.locales_dir_path = basePath + + "/Contents/Frameworks/Chromium Embedded Framework.framework/Resources"; + settings.resources_dir_path = basePath + + "/Contents/Frameworks/Chromium Embedded Framework.framework/Resources"; + settings.browser_subprocess_path = basePath + + "/Contents/Frameworks/jcef Helper.app/Contents/MacOS/jcef Helper"; + } else if (OS.isLinux()) { + settings.resources_dir_path = jcefPath; + Path jcefHelperPath = Paths.get(jcefPath, "jcef_helper"); + settings.browser_subprocess_path = jcefHelperPath.normalize().toAbsolutePath().toString(); + Path localesPath = Paths.get(jcefPath, "locales"); + settings.locales_dir_path = localesPath.normalize().toAbsolutePath().toString(); + } + + if (N_Initialize(appHandler_, settings)) setState(CefAppState.INITIALIZED); + } + + /** + * This method is invoked by the native code (currently on Mac only) in case + * of a termination event (e.g. someone pressed CMD+Q). + */ + protected final void handleBeforeTerminate() { + System.out.println("Cmd+Q termination request."); + CefAppHandler handler = + (CefAppHandler) ((appHandler_ == null) ? this : appHandler_); + if (!handler.onBeforeTerminate()) dispose(); + } + + /** + * Shut down the context. + */ + private final void shutdown() { + System.out.println("shutdown on " + Thread.currentThread()); + + // Shutdown native CEF. + N_Shutdown(); + + setState(CefAppState.TERMINATED); + CefApp.self = null; + } + + /** + * Perform a single message loop iteration. Used on all platforms except + * Windows with windowed rendering. + */ + public final void doMessageLoopWork(final long delay_ms) { + // Execute on the AWT event dispatching thread. + SwingUtilities.invokeLater(new Runnable() { + @Override + public void run() { + if (getState() == CefAppState.TERMINATED) return; + + // The maximum number of milliseconds we're willing to wait between + // calls to DoMessageLoopWork(). + final long kMaxTimerDelay = 1000 / 30; // 30fps + + if (workTimer_ != null) { + workTimer_.stop(); + workTimer_ = null; + } + + if (delay_ms <= 0) { + // Execute the work immediately. + N_DoMessageLoopWork(); + + // Schedule more work later. + doMessageLoopWork(kMaxTimerDelay); + } else { + long timer_delay_ms = delay_ms; + // Never wait longer than the maximum allowed time. + if (timer_delay_ms > kMaxTimerDelay) timer_delay_ms = kMaxTimerDelay; + + workTimer_ = new Timer((int) timer_delay_ms, new ActionListener() { + @Override + public void actionPerformed(ActionEvent evt) { + // Timer has timed out. + workTimer_.stop(); + workTimer_ = null; + + N_DoMessageLoopWork(); + + // Schedule more work later. + doMessageLoopWork(kMaxTimerDelay); + } + }); + workTimer_.start(); + } + } + }); + } + + /** + * This method must be called at the beginning of the main() method to perform platform- + * specific startup initialization. On Linux this initializes Xlib multithreading and on + * macOS this dynamically loads the CEF framework. + * + * @param args Command-line arguments massed to main(). + * @return True on successful startup. + */ + public static final boolean startup(String[] args) { + String jcefPath = getJcefLibPath(); + + if (OS.isWindows()) { + System.load(jcefPath + "\\d3dcompiler_47.dll"); + System.load(jcefPath + "\\libGLESv2.dll"); + System.load(jcefPath + "\\libEGL.dll"); + System.load(jcefPath + "\\chrome_elf.dll"); + System.load(jcefPath + "\\libcef.dll"); + System.load(jcefPath + "\\jcef.dll"); + return true; + } else if (OS.isMacintosh()) { + System.load(jcefPath + "/libjcef.dylib"); + return N_Startup(getCefFrameworkPath(args)); + } else if (OS.isLinux()) { + System.load(jcefPath + "/libcef.so"); + System.load(jcefPath + "/libjcef.so"); + return N_Startup(null); + } + + return false; + } + + private static final String getJcefLibPath() { + Path runtimeDir = Paths.get(""); + final Path jcefPath; + if (OS.isWindows() || OS.isLinux()) { + jcefPath = runtimeDir.resolve("jcef/cef_runtime"); + } else if (OS.isMacintosh()) { + jcefPath = runtimeDir.resolve("jcef/jcef_app.app/Contents/Java"); + } else { + jcefPath = null; + } + return jcefPath != null ? jcefPath.toAbsolutePath().toString() : null; + } + + /** + * Get the path that contains the CEF Framework on macOS. + * + * @return The path to the CEF Framework. + */ + private static final String getCefFrameworkPath(String[] args) { + // Check for the path on the command-line. + String switchPrefix = "--framework-dir-path="; + for (String arg : args) { + if (arg.startsWith(switchPrefix)) { + return new File(arg.substring(switchPrefix.length())).getAbsolutePath(); + } + } + + // Determine the path relative to the JCEF lib location in the app bundle. + return new File(getJcefLibPath() + "/../Frameworks/Chromium Embedded Framework.framework") + .getAbsolutePath(); + } + + private final static native boolean N_Startup(String pathToCefFramework); + + private final native boolean N_PreInitialize(); + + private final native boolean N_Initialize(CefAppHandler appHandler, CefSettings settings); + + public final native void N_Shutdown(); + + public final native void N_DoMessageLoopWork(); + + private final native CefVersion N_GetVersion(); + + private final native boolean N_RegisterSchemeHandlerFactory( + String schemeName, String domainName, CefSchemeHandlerFactory factory); + + private final native boolean N_ClearSchemeHandlerFactories(); +} diff --git a/src/main/java/org/cef/CefClient.java b/src/main/java/org/cef/CefClient.java new file mode 100644 index 0000000..f72b4d1 --- /dev/null +++ b/src/main/java/org/cef/CefClient.java @@ -0,0 +1,869 @@ +// Copyright (c) 2013 The Chromium Embedded Framework Authors. All rights +// reserved. Use of this source code is governed by a BSD-style license that +// can be found in the LICENSE file. + +package org.cef; + +import org.cef.browser.CefBrowser; +import org.cef.browser.CefBrowserFactory; +import org.cef.browser.CefFrame; +import org.cef.browser.CefMessageRouter; +import org.cef.browser.CefRequestContext; +import org.cef.callback.CefAuthCallback; +import org.cef.callback.CefBeforeDownloadCallback; +import org.cef.callback.CefCallback; +import org.cef.callback.CefContextMenuParams; +import org.cef.callback.CefDownloadItem; +import org.cef.callback.CefDownloadItemCallback; +import org.cef.callback.CefDragData; +import org.cef.callback.CefFileDialogCallback; +import org.cef.callback.CefJSDialogCallback; +import org.cef.callback.CefMenuModel; +import org.cef.callback.CefPrintDialogCallback; +import org.cef.callback.CefPrintJobCallback; +import org.cef.handler.CefClientHandler; +import org.cef.handler.CefContextMenuHandler; +import org.cef.handler.CefDialogHandler; +import org.cef.handler.CefDisplayHandler; +import org.cef.handler.CefDownloadHandler; +import org.cef.handler.CefDragHandler; +import org.cef.handler.CefFocusHandler; +import org.cef.handler.CefJSDialogHandler; +import org.cef.handler.CefKeyboardHandler; +import org.cef.handler.CefLifeSpanHandler; +import org.cef.handler.CefLoadHandler; +import org.cef.handler.CefPrintHandler; +import org.cef.handler.CefRenderHandler; +import org.cef.handler.CefRequestHandler; +import org.cef.handler.CefResourceHandler; +import org.cef.handler.CefResourceRequestHandler; +import org.cef.handler.CefScreenInfo; +import org.cef.handler.CefWindowHandler; +import org.cef.misc.BoolRef; +import org.cef.misc.CefPrintSettings; +import org.cef.misc.StringRef; +import org.cef.network.CefRequest; +import org.cef.network.CefRequest.TransitionType; +import org.cef.network.CefResponse; +import org.cef.network.CefURLRequest; + +import java.awt.Component; +import java.awt.Container; +import java.awt.Dimension; +import java.awt.FocusTraversalPolicy; +import java.awt.KeyboardFocusManager; +import java.awt.Point; +import java.awt.Rectangle; +import java.beans.PropertyChangeEvent; +import java.beans.PropertyChangeListener; +import java.nio.ByteBuffer; +import java.util.Collection; +import java.util.HashMap; +import java.util.Vector; + +import javax.swing.SwingUtilities; + +/** + * Client that owns a browser and renderer. + */ +public class CefClient extends CefClientHandler + implements CefContextMenuHandler, CefDialogHandler, CefDisplayHandler, CefDownloadHandler, + CefDragHandler, CefFocusHandler, CefJSDialogHandler, CefKeyboardHandler, + CefLifeSpanHandler, CefLoadHandler, CefPrintHandler, CefRenderHandler, + CefRequestHandler, CefWindowHandler { + private HashMap browser_ = new HashMap(); + private CefContextMenuHandler contextMenuHandler_ = null; + private CefDialogHandler dialogHandler_ = null; + private CefDisplayHandler displayHandler_ = null; + private CefDownloadHandler downloadHandler_ = null; + private CefDragHandler dragHandler_ = null; + private CefFocusHandler focusHandler_ = null; + private CefJSDialogHandler jsDialogHandler_ = null; + private CefKeyboardHandler keyboardHandler_ = null; + private CefLifeSpanHandler lifeSpanHandler_ = null; + private CefLoadHandler loadHandler_ = null; + private CefPrintHandler printHandler_ = null; + private CefRequestHandler requestHandler_ = null; + private boolean isDisposed_ = false; + private volatile CefBrowser focusedBrowser_ = null; + private final PropertyChangeListener propertyChangeListener = new PropertyChangeListener() { + @Override + public void propertyChange(PropertyChangeEvent evt) { + if (focusedBrowser_ != null) { + Component browserUI = focusedBrowser_.getUIComponent(); + Object oldUI = evt.getOldValue(); + if (isPartOf(oldUI, browserUI)) { + focusedBrowser_.setFocus(false); + focusedBrowser_ = null; + } + } + } + }; + + /** + * The CTOR is only accessible within this package. + * Use CefApp.createClient() to create an instance of + * this class. + * @see org.cef.CefApp.createClient() + */ + CefClient() throws UnsatisfiedLinkError { + super(); + + KeyboardFocusManager.getCurrentKeyboardFocusManager().addPropertyChangeListener( + propertyChangeListener); + } + + private boolean isPartOf(Object obj, Component browserUI) { + if (obj == browserUI) return true; + if (obj instanceof Container) { + Component childs[] = ((Container) obj).getComponents(); + for (Component child : childs) { + return isPartOf(child, browserUI); + } + } + return false; + } + + @Override + public void dispose() { + isDisposed_ = true; + cleanupBrowser(-1); + } + + // CefClientHandler + + public CefBrowser createBrowser( + String url, boolean isOffscreenRendered, boolean isTransparent) { + return createBrowser(url, isOffscreenRendered, isTransparent, null); + } + + public CefBrowser createBrowser(String url, boolean isOffscreenRendered, boolean isTransparent, + CefRequestContext context) { + if (isDisposed_) + throw new IllegalStateException("Can't create browser. CefClient is disposed"); + return CefBrowserFactory.create(this, url, isOffscreenRendered, isTransparent, context); + } + + @Override + protected CefBrowser getBrowser(int identifier) { + synchronized (browser_) { + return browser_.get(new Integer(identifier)); + } + } + + @Override + protected Object[] getAllBrowser() { + synchronized (browser_) { + return browser_.values().toArray(); + } + } + + @Override + protected CefContextMenuHandler getContextMenuHandler() { + return this; + } + + @Override + protected CefDialogHandler getDialogHandler() { + return this; + }; + + @Override + protected CefDisplayHandler getDisplayHandler() { + return this; + } + + @Override + protected CefDownloadHandler getDownloadHandler() { + return this; + } + + @Override + protected CefDragHandler getDragHandler() { + return this; + } + + @Override + protected CefFocusHandler getFocusHandler() { + return this; + } + + @Override + protected CefJSDialogHandler getJSDialogHandler() { + return this; + } + + @Override + protected CefKeyboardHandler getKeyboardHandler() { + return this; + } + + @Override + protected CefLifeSpanHandler getLifeSpanHandler() { + return this; + } + + @Override + protected CefLoadHandler getLoadHandler() { + return this; + } + + @Override + protected CefPrintHandler getPrintHandler() { + return this; + } + + @Override + protected CefRenderHandler getRenderHandler() { + return this; + } + + @Override + protected CefRequestHandler getRequestHandler() { + return this; + } + + @Override + protected CefWindowHandler getWindowHandler() { + return this; + } + + // CefContextMenuHandler + + public CefClient addContextMenuHandler(CefContextMenuHandler handler) { + if (contextMenuHandler_ == null) contextMenuHandler_ = handler; + return this; + } + + public void removeContextMenuHandler() { + contextMenuHandler_ = null; + } + + @Override + public void onBeforeContextMenu( + CefBrowser browser, CefFrame frame, CefContextMenuParams params, CefMenuModel model) { + if (contextMenuHandler_ != null && browser != null) + contextMenuHandler_.onBeforeContextMenu(browser, frame, params, model); + } + + @Override + public boolean onContextMenuCommand(CefBrowser browser, CefFrame frame, + CefContextMenuParams params, int commandId, int eventFlags) { + if (contextMenuHandler_ != null && browser != null) + return contextMenuHandler_.onContextMenuCommand( + browser, frame, params, commandId, eventFlags); + return false; + } + + @Override + public void onContextMenuDismissed(CefBrowser browser, CefFrame frame) { + if (contextMenuHandler_ != null && browser != null) + contextMenuHandler_.onContextMenuDismissed(browser, frame); + } + + // CefDialogHandler + + public CefClient addDialogHandler(CefDialogHandler handler) { + if (dialogHandler_ == null) dialogHandler_ = handler; + return this; + } + + public void removeDialogHandler() { + dialogHandler_ = null; + } + + @Override + public boolean onFileDialog(CefBrowser browser, FileDialogMode mode, String title, + String defaultFilePath, Vector acceptFilters, int selectedAcceptFilter, + CefFileDialogCallback callback) { + if (dialogHandler_ != null && browser != null) { + return dialogHandler_.onFileDialog(browser, mode, title, defaultFilePath, acceptFilters, + selectedAcceptFilter, callback); + } + return false; + } + + // CefDisplayHandler + + public CefClient addDisplayHandler(CefDisplayHandler handler) { + if (displayHandler_ == null) displayHandler_ = handler; + return this; + } + + public void removeDisplayHandler() { + displayHandler_ = null; + } + + @Override + public void onAddressChange(CefBrowser browser, CefFrame frame, String url) { + if (displayHandler_ != null && browser != null) + displayHandler_.onAddressChange(browser, frame, url); + } + + @Override + public void onTitleChange(CefBrowser browser, String title) { + if (displayHandler_ != null && browser != null) + displayHandler_.onTitleChange(browser, title); + } + + @Override + public boolean onTooltip(CefBrowser browser, String text) { + if (displayHandler_ != null && browser != null) { + return displayHandler_.onTooltip(browser, text); + } + return false; + } + + @Override + public void onStatusMessage(CefBrowser browser, String value) { + if (displayHandler_ != null && browser != null) { + displayHandler_.onStatusMessage(browser, value); + } + } + + @Override + public boolean onConsoleMessage(CefBrowser browser, CefSettings.LogSeverity level, + String message, String source, int line) { + if (displayHandler_ != null && browser != null) { + return displayHandler_.onConsoleMessage(browser, level, message, source, line); + } + return false; + } + + @Override + public boolean onCursorChange(CefBrowser browser, int cursorType) { + if (browser == null) { + return false; + } + + if (displayHandler_ != null && displayHandler_.onCursorChange(browser, cursorType)) { + return true; + } + + CefRenderHandler realHandler = browser.getRenderHandler(); + if (realHandler != null) { + return realHandler.onCursorChange(browser, cursorType); + } + + return false; + } + + // CefDownloadHandler + + public CefClient addDownloadHandler(CefDownloadHandler handler) { + if (downloadHandler_ == null) downloadHandler_ = handler; + return this; + } + + public void removeDownloadHandler() { + downloadHandler_ = null; + } + + @Override + public void onBeforeDownload(CefBrowser browser, CefDownloadItem downloadItem, + String suggestedName, CefBeforeDownloadCallback callback) { + if (downloadHandler_ != null && browser != null) + downloadHandler_.onBeforeDownload(browser, downloadItem, suggestedName, callback); + } + + @Override + public void onDownloadUpdated( + CefBrowser browser, CefDownloadItem downloadItem, CefDownloadItemCallback callback) { + if (downloadHandler_ != null && browser != null) + downloadHandler_.onDownloadUpdated(browser, downloadItem, callback); + } + + // CefDragHandler + + public CefClient addDragHandler(CefDragHandler handler) { + if (dragHandler_ == null) dragHandler_ = handler; + return this; + } + + public void removeDragHandler() { + dragHandler_ = null; + } + + @Override + public boolean onDragEnter(CefBrowser browser, CefDragData dragData, int mask) { + if (dragHandler_ != null && browser != null) + return dragHandler_.onDragEnter(browser, dragData, mask); + return false; + } + + // CefFocusHandler + + public CefClient addFocusHandler(CefFocusHandler handler) { + if (focusHandler_ == null) focusHandler_ = handler; + return this; + } + + public void removeFocusHandler() { + focusHandler_ = null; + } + + @Override + public void onTakeFocus(CefBrowser browser, boolean next) { + if (browser == null) return; + + browser.setFocus(false); + Container parent = browser.getUIComponent().getParent(); + if (parent != null) { + FocusTraversalPolicy policy = null; + while (parent != null) { + policy = parent.getFocusTraversalPolicy(); + if (policy != null) break; + parent = parent.getParent(); + } + if (policy != null) { + Component nextComp = next + ? policy.getComponentAfter(parent, browser.getUIComponent()) + : policy.getComponentBefore(parent, browser.getUIComponent()); + if (nextComp == null) { + policy.getDefaultComponent(parent).requestFocus(); + } else { + nextComp.requestFocus(); + } + } + } + focusedBrowser_ = null; + if (focusHandler_ != null) focusHandler_.onTakeFocus(browser, next); + } + + @Override + public boolean onSetFocus(final CefBrowser browser, FocusSource source) { + if (browser == null) return false; + + boolean alreadyHandled = false; + if (focusHandler_ != null) alreadyHandled = focusHandler_.onSetFocus(browser, source); + return alreadyHandled; + } + + @Override + public void onGotFocus(CefBrowser browser) { + if (browser == null) return; + + focusedBrowser_ = browser; + browser.setFocus(true); + if (focusHandler_ != null) focusHandler_.onGotFocus(browser); + } + + // CefJSDialogHandler + + public CefClient addJSDialogHandler(CefJSDialogHandler handler) { + if (jsDialogHandler_ == null) jsDialogHandler_ = handler; + return this; + } + + public void removeJSDialogHandler() { + jsDialogHandler_ = null; + } + + @Override + public boolean onJSDialog(CefBrowser browser, String origin_url, JSDialogType dialog_type, + String message_text, String default_prompt_text, CefJSDialogCallback callback, + BoolRef suppress_message) { + if (jsDialogHandler_ != null && browser != null) + return jsDialogHandler_.onJSDialog(browser, origin_url, dialog_type, message_text, + default_prompt_text, callback, suppress_message); + return false; + } + + @Override + public boolean onBeforeUnloadDialog(CefBrowser browser, String message_text, boolean is_reload, + CefJSDialogCallback callback) { + if (jsDialogHandler_ != null && browser != null) + return jsDialogHandler_.onBeforeUnloadDialog( + browser, message_text, is_reload, callback); + return false; + } + + @Override + public void onResetDialogState(CefBrowser browser) { + if (jsDialogHandler_ != null && browser != null) + jsDialogHandler_.onResetDialogState(browser); + } + + @Override + public void onDialogClosed(CefBrowser browser) { + if (jsDialogHandler_ != null && browser != null) jsDialogHandler_.onDialogClosed(browser); + } + + // CefKeyboardHandler + + public CefClient addKeyboardHandler(CefKeyboardHandler handler) { + if (keyboardHandler_ == null) keyboardHandler_ = handler; + return this; + } + + public void removeKeyboardHandler() { + keyboardHandler_ = null; + } + + @Override + public boolean onPreKeyEvent( + CefBrowser browser, CefKeyEvent event, BoolRef is_keyboard_shortcut) { + if (keyboardHandler_ != null && browser != null) + return keyboardHandler_.onPreKeyEvent(browser, event, is_keyboard_shortcut); + return false; + } + + @Override + public boolean onKeyEvent(CefBrowser browser, CefKeyEvent event) { + if (keyboardHandler_ != null && browser != null) + return keyboardHandler_.onKeyEvent(browser, event); + return false; + } + + // CefLifeSpanHandler + + public CefClient addLifeSpanHandler(CefLifeSpanHandler handler) { + if (lifeSpanHandler_ == null) lifeSpanHandler_ = handler; + return this; + } + + public void removeLifeSpanHandler() { + lifeSpanHandler_ = null; + } + + @Override + public boolean onBeforePopup( + CefBrowser browser, CefFrame frame, String target_url, String target_frame_name) { + if (isDisposed_) return true; + if (lifeSpanHandler_ != null && browser != null) + return lifeSpanHandler_.onBeforePopup(browser, frame, target_url, target_frame_name); + return false; + } + + @Override + public void onAfterCreated(CefBrowser browser) { + if (browser == null) return; + + // keep browser reference + Integer identifier = browser.getIdentifier(); + synchronized (browser_) { + browser_.put(identifier, browser); + } + if (lifeSpanHandler_ != null) lifeSpanHandler_.onAfterCreated(browser); + } + + @Override + public void onAfterParentChanged(CefBrowser browser) { + if (browser == null) return; + if (lifeSpanHandler_ != null) lifeSpanHandler_.onAfterParentChanged(browser); + } + + @Override + public boolean doClose(CefBrowser browser) { + if (browser == null) return false; + if (lifeSpanHandler_ != null) return lifeSpanHandler_.doClose(browser); + return browser.doClose(); + } + + @Override + public void onBeforeClose(CefBrowser browser) { + if (browser == null) return; + if (lifeSpanHandler_ != null) lifeSpanHandler_.onBeforeClose(browser); + browser.onBeforeClose(); + + // remove browser reference + cleanupBrowser(browser.getIdentifier()); + } + + private void cleanupBrowser(int identifier) { + synchronized (browser_) { + if (identifier >= 0) { + // Remove the specific browser that closed. + browser_.remove(identifier); + } else if (!browser_.isEmpty()) { + // Close all browsers. + Collection browserList = browser_.values(); + for (CefBrowser browser : browserList) { + browser.close(true); + } + return; + } + + if (browser_.isEmpty() && isDisposed_) { + KeyboardFocusManager.getCurrentKeyboardFocusManager().removePropertyChangeListener( + propertyChangeListener); + removeContextMenuHandler(this); + removeDialogHandler(this); + removeDisplayHandler(this); + removeDownloadHandler(this); + removeDragHandler(this); + removeFocusHandler(this); + removeJSDialogHandler(this); + removeKeyboardHandler(this); + removeLifeSpanHandler(this); + removeLoadHandler(this); + removePrintHandler(this); + removeRenderHandler(this); + removeRequestHandler(this); + removeWindowHandler(this); + super.dispose(); + + CefApp.getInstance().clientWasDisposed(this); + } + } + } + + // CefLoadHandler + + public CefClient addLoadHandler(CefLoadHandler handler) { + if (loadHandler_ == null) loadHandler_ = handler; + return this; + } + + public void removeLoadHandler() { + loadHandler_ = null; + } + + @Override + public void onLoadingStateChange( + CefBrowser browser, boolean isLoading, boolean canGoBack, boolean canGoForward) { + if (loadHandler_ != null && browser != null) + loadHandler_.onLoadingStateChange(browser, isLoading, canGoBack, canGoForward); + } + + @Override + public void onLoadStart(CefBrowser browser, CefFrame frame, TransitionType transitionType) { + if (loadHandler_ != null && browser != null) + loadHandler_.onLoadStart(browser, frame, transitionType); + } + + @Override + public void onLoadEnd(CefBrowser browser, CefFrame frame, int httpStatusCode) { + if (loadHandler_ != null && browser != null) + loadHandler_.onLoadEnd(browser, frame, httpStatusCode); + } + + @Override + public void onLoadError(CefBrowser browser, CefFrame frame, ErrorCode errorCode, + String errorText, String failedUrl) { + if (loadHandler_ != null && browser != null) + loadHandler_.onLoadError(browser, frame, errorCode, errorText, failedUrl); + } + + // CefPrintHandler + + public CefClient addPrintHandler(CefPrintHandler handler) { + if (printHandler_ == null) printHandler_ = handler; + return this; + } + + public void removePrintHandler() { + printHandler_ = null; + } + + @Override + public void onPrintStart(CefBrowser browser) { + if (printHandler_ != null && browser != null) printHandler_.onPrintStart(browser); + } + + @Override + public void onPrintSettings( + CefBrowser browser, CefPrintSettings settings, boolean getDefaults) { + if (printHandler_ != null && browser != null) + printHandler_.onPrintSettings(browser, settings, getDefaults); + } + + @Override + public boolean onPrintDialog( + CefBrowser browser, boolean hasSelection, CefPrintDialogCallback callback) { + if (printHandler_ != null && browser != null) + return printHandler_.onPrintDialog(browser, hasSelection, callback); + return false; + } + + @Override + public boolean onPrintJob(CefBrowser browser, String documentName, String pdfFilePath, + CefPrintJobCallback callback) { + if (printHandler_ != null && browser != null) + return printHandler_.onPrintJob(browser, documentName, pdfFilePath, callback); + return false; + } + + @Override + public void onPrintReset(CefBrowser browser) { + if (printHandler_ != null && browser != null) printHandler_.onPrintReset(browser); + } + + @Override + public Dimension getPdfPaperSize(CefBrowser browser, int deviceUnitsPerInch) { + if (printHandler_ != null && browser != null) + return printHandler_.getPdfPaperSize(browser, deviceUnitsPerInch); + return null; + } + + // CefMessageRouter + + @Override + public synchronized void addMessageRouter(CefMessageRouter messageRouter) { + super.addMessageRouter(messageRouter); + } + + @Override + public synchronized void removeMessageRouter(CefMessageRouter messageRouter) { + super.removeMessageRouter(messageRouter); + } + + // CefRenderHandler + + @Override + public Rectangle getViewRect(CefBrowser browser) { + if (browser == null) return new Rectangle(0, 0, 0, 0); + + CefRenderHandler realHandler = browser.getRenderHandler(); + if (realHandler != null) return realHandler.getViewRect(browser); + return new Rectangle(0, 0, 0, 0); + } + + @Override + public Point getScreenPoint(CefBrowser browser, Point viewPoint) { + if (browser == null) return new Point(0, 0); + + CefRenderHandler realHandler = browser.getRenderHandler(); + if (realHandler != null) return realHandler.getScreenPoint(browser, viewPoint); + return new Point(0, 0); + } + + @Override + public void onPopupShow(CefBrowser browser, boolean show) { + if (browser == null) return; + + CefRenderHandler realHandler = browser.getRenderHandler(); + if (realHandler != null) realHandler.onPopupShow(browser, show); + } + + @Override + public void onPopupSize(CefBrowser browser, Rectangle size) { + if (browser == null) return; + + CefRenderHandler realHandler = browser.getRenderHandler(); + if (realHandler != null) realHandler.onPopupSize(browser, size); + } + + @Override + public void onPaint(CefBrowser browser, boolean popup, Rectangle[] dirtyRects, + ByteBuffer buffer, int width, int height) { + if (browser == null) return; + + CefRenderHandler realHandler = browser.getRenderHandler(); + if (realHandler != null) + realHandler.onPaint(browser, popup, dirtyRects, buffer, width, height); + } + + @Override + public boolean startDragging(CefBrowser browser, CefDragData dragData, int mask, int x, int y) { + if (browser == null) return false; + + CefRenderHandler realHandler = browser.getRenderHandler(); + if (realHandler != null) return realHandler.startDragging(browser, dragData, mask, x, y); + return false; + } + + @Override + public void updateDragCursor(CefBrowser browser, int operation) { + if (browser == null) return; + + CefRenderHandler realHandler = browser.getRenderHandler(); + if (realHandler != null) realHandler.updateDragCursor(browser, operation); + } + + // CefRequestHandler + + public CefClient addRequestHandler(CefRequestHandler handler) { + if (requestHandler_ == null) requestHandler_ = handler; + return this; + } + + public void removeRequestHandler() { + requestHandler_ = null; + } + + @Override + public boolean onBeforeBrowse(CefBrowser browser, CefFrame frame, CefRequest request, + boolean user_gesture, boolean is_redirect) { + if (requestHandler_ != null && browser != null) + return requestHandler_.onBeforeBrowse( + browser, frame, request, user_gesture, is_redirect); + return false; + } + + @Override + public boolean onOpenURLFromTab( + CefBrowser browser, CefFrame frame, String target_url, boolean user_gesture) { + if (isDisposed_) return true; + if (requestHandler_ != null && browser != null) + return requestHandler_.onOpenURLFromTab(browser, frame, target_url, user_gesture); + return false; + } + + @Override + public CefResourceRequestHandler getResourceRequestHandler(CefBrowser browser, CefFrame frame, + CefRequest request, boolean isNavigation, boolean isDownload, String requestInitiator, + BoolRef disableDefaultHandling) { + if (requestHandler_ != null && browser != null) { + return requestHandler_.getResourceRequestHandler(browser, frame, request, isNavigation, + isDownload, requestInitiator, disableDefaultHandling); + } + return null; + } + + @Override + public boolean getAuthCredentials(CefBrowser browser, String origin_url, boolean isProxy, + String host, int port, String realm, String scheme, CefAuthCallback callback) { + if (requestHandler_ != null && browser != null) + return requestHandler_.getAuthCredentials( + browser, origin_url, isProxy, host, port, realm, scheme, callback); + return false; + } + + @Override + public boolean onQuotaRequest( + CefBrowser browser, String origin_url, long new_size, CefCallback callback) { + if (requestHandler_ != null && browser != null) + return requestHandler_.onQuotaRequest(browser, origin_url, new_size, callback); + return false; + } + + @Override + public boolean onCertificateError( + CefBrowser browser, ErrorCode cert_error, String request_url, CefCallback callback) { + if (requestHandler_ != null) + return requestHandler_.onCertificateError(browser, cert_error, request_url, callback); + return false; + } + + @Override + public void onRenderProcessTerminated(CefBrowser browser, TerminationStatus status) { + if (requestHandler_ != null) requestHandler_.onRenderProcessTerminated(browser, status); + } + + // CefWindowHandler + + @Override + public Rectangle getRect(CefBrowser browser) { + if (browser == null) return new Rectangle(0, 0, 0, 0); + + CefWindowHandler realHandler = browser.getWindowHandler(); + if (realHandler != null) return realHandler.getRect(browser); + return new Rectangle(0, 0, 0, 0); + } + + @Override + public void onMouseEvent( + CefBrowser browser, int event, int screenX, int screenY, int modifier, int button) { + if (browser == null) return; + + CefWindowHandler realHandler = browser.getWindowHandler(); + if (realHandler != null) + realHandler.onMouseEvent(browser, event, screenX, screenY, modifier, button); + } + + @Override + public boolean getScreenInfo(CefBrowser arg0, CefScreenInfo arg1) { + return false; + } +} diff --git a/src/main/java/org/cef/CefSettings.java b/src/main/java/org/cef/CefSettings.java new file mode 100644 index 0000000..f15534a --- /dev/null +++ b/src/main/java/org/cef/CefSettings.java @@ -0,0 +1,259 @@ +// Copyright (c) 2013 The Chromium Embedded Framework Authors. All rights +// reserved. Use of this source code is governed by a BSD-style license that +// can be found in the LICENSE file. + +package org.cef; + +/** + * Initialization settings. Specify NULL or 0 to get the recommended default + * values. Many of these and other settings can also configured using command- + * line switches. + */ +public class CefSettings { + /** + * Log severity levels. + */ + public enum LogSeverity { + /** + * Default logging (currently INFO logging). + */ + LOGSEVERITY_DEFAULT, + + /** + * Verbose logging. + */ + LOGSEVERITY_VERBOSE, + + /** + * INFO logging. + */ + LOGSEVERITY_INFO, + + /** + * WARNING logging. + */ + LOGSEVERITY_WARNING, + + /** + * ERROR logging. + */ + LOGSEVERITY_ERROR, + + /** + * FATAL logging. + */ + LOGSEVERITY_FATAL, + + /** + * Completely disable logging. + */ + LOGSEVERITY_DISABLE + } + + /** + * 32-bit ARGB color value, not premultiplied. The color components are always + * in a known order. Equivalent to the SkColor type. + */ + public class ColorType { + private long color_value = 0; + + private ColorType() {} + + public ColorType(int alpha, int red, int green, int blue) { + color_value = (alpha << 24) | (red << 16) | (green << 8) | (blue << 0); + } + + public long getColor() { + return color_value; + } + + @Override + public ColorType clone() { + ColorType res = new ColorType(); + res.color_value = this.color_value; + return res; + } + } + + // MacOS specific settings + public String framework_dir_path = null; + public String main_bundle_path = null; + + /** + * The path to a separate executable that will be launched for sub-processes. + * By default the browser process executable is used. See the comments on + * CefExecuteProcess() for details. Also configurable using the + * "browser-subprocess-path" command-line switch. + */ + public String browser_subprocess_path = null; + + /** + * Set to true to enable windowless (off-screen) rendering support. Do not + * enable this value if the application does not use windowless rendering as + * it may reduce rendering performance on some systems. + */ + public boolean windowless_rendering_enabled = true; + + /** + * Set to true to disable configuration of browser process features using + * standard CEF and Chromium command-line arguments. Configuration can still + * be specified using CEF data structures or via the + * CefApp::OnBeforeCommandLineProcessing() method. + */ + public boolean command_line_args_disabled = false; + + /** + * The location where cache data will be stored on disk. If empty an in-memory + * cache will be used for some features and a temporary disk cache for others. + * HTML5 databases such as localStorage will only persist across sessions if a + * cache path is specified. + */ + public String cache_path = null; + + /** + * To persist session cookies (cookies without an expiry date or validity + * interval) by default when using the global cookie manager set this value to + * true. Session cookies are generally intended to be transient and most Web + * browsers do not persist them. A |cache_path| value must also be specified to + * enable this feature. Also configurable using the "persist-session-cookies" + * command-line switch. + */ + public boolean persist_session_cookies = false; + + /** + * Value that will be returned as the User-Agent HTTP header. If empty the + * default User-Agent string will be used. Also configurable using the + * "user-agent" command-line switch. + */ + public String user_agent = null; + + /** + * Value that will be inserted as the product portion of the default + * User-Agent string. If empty the Chromium product version will be used. If + * |userAgent| is specified this value will be ignored. Also configurable + * using the "product-version" command-line switch. + */ + public String product_version = null; + + /** + * The locale string that will be passed to Blink. If empty the default + * locale of "en-US" will be used. This value is ignored on Linux where locale + * is determined using environment variable parsing with the precedence order: + * LANGUAGE, LC_ALL, LC_MESSAGES and LANG. Also configurable using the "lang" + * command-line switch. + */ + public String locale = null; + + /** + * The directory and file name to use for the debug log. If empty, the + * default name of "debug.log" will be used and the file will be written + * to the application directory. Also configurable using the "log-file" + * command-line switch. + */ + public String log_file = null; + + /** + * The log severity. Only messages of this severity level or higher will be + * logged. Also configurable using the "log-severity" command-line switch with + * a value of "verbose", "info", "warning", "error", "error-report" or + * "disable". + */ + public LogSeverity log_severity = LogSeverity.LOGSEVERITY_DEFAULT; + + /** + * Custom flags that will be used when initializing the V8 JavaScript engine. + * The consequences of using custom flags may not be well tested. Also + * configurable using the "js-flags" command-line switch. + */ + public String javascript_flags = null; + + /** + * The fully qualified path for the resources directory. If this value is + * empty the cef.pak and/or devtools_resources.pak files must be located in + * the module directory on Windows/Linux or the app bundle Resources directory + * on Mac OS X. Also configurable using the "resources-dir-path" command-line + * switch. + */ + public String resources_dir_path = null; + + /** + * The fully qualified path for the locales directory. If this value is empty + * the locales directory must be located in the module directory. This value + * is ignored on Mac OS X where pack files are always loaded from the app + * bundle Resources directory. Also configurable using the "locales-dir-path" + * command-line switch. + */ + public String locales_dir_path = null; + + /** + * Set to true to disable loading of pack files for resources and locales. + * A resource bundle handler must be provided for the browser and render + * processes via CefApp::GetResourceBundleHandler() if loading of pack files + * is disabled. Also configurable using the "disable-pack-loading" command- + * line switch. + */ + public boolean pack_loading_disabled = false; + + /** + * Set to a value between 1024 and 65535 to enable remote debugging on the + * specified port. For example, if 8080 is specified the remote debugging URL + * will be http: *localhost:8080. CEF can be remotely debugged from any CEF or + * Chrome browser window. Also configurable using the "remote-debugging-port" + * command-line switch. + */ + public int remote_debugging_port = 0; + + /** + * The number of stack trace frames to capture for uncaught exceptions. + * Specify a positive value to enable the CefV8ContextHandler:: + * OnUncaughtException() callback. Specify 0 (default value) and + * OnUncaughtException() will not be called. Also configurable using the + * "uncaught-exception-stack-size" command-line switch. + */ + public int uncaught_exception_stack_size = 0; + + /** + * Set to true to ignore errors related to invalid SSL certificates. + * Enabling this setting can lead to potential security vulnerabilities like + * "man in the middle" attacks. Applications that load content from the + * internet should not enable this setting. Also configurable using the + * "ignore-certificate-errors" command-line switch. + */ + public boolean ignore_certificate_errors = false; + + /** + * Opaque background color used for accelerated content. By default the + * background color will be white. Only the RGB compontents of the specified + * value will be used. The alpha component must greater than 0 to enable use + * of the background color but will be otherwise ignored. + */ + public ColorType background_color = null; + + public CefSettings() {} + + @Override + public CefSettings clone() { + CefSettings tmp = new CefSettings(); + tmp.framework_dir_path = framework_dir_path; // for MacOS + tmp.main_bundle_path = main_bundle_path; // for MacOS + tmp.browser_subprocess_path = browser_subprocess_path; + tmp.windowless_rendering_enabled = windowless_rendering_enabled; + tmp.command_line_args_disabled = command_line_args_disabled; + tmp.cache_path = cache_path; + tmp.persist_session_cookies = persist_session_cookies; + tmp.user_agent = user_agent; + tmp.product_version = product_version; + tmp.locale = locale; + tmp.log_file = log_file; + tmp.log_severity = log_severity; + tmp.javascript_flags = javascript_flags; + tmp.resources_dir_path = resources_dir_path; + tmp.locales_dir_path = locales_dir_path; + tmp.pack_loading_disabled = pack_loading_disabled; + tmp.remote_debugging_port = remote_debugging_port; + tmp.uncaught_exception_stack_size = uncaught_exception_stack_size; + tmp.ignore_certificate_errors = ignore_certificate_errors; + if (background_color != null) tmp.background_color = background_color.clone(); + return tmp; + } +} \ No newline at end of file diff --git a/src/main/java/org/cef/OS.java b/src/main/java/org/cef/OS.java new file mode 100644 index 0000000..be464d2 --- /dev/null +++ b/src/main/java/org/cef/OS.java @@ -0,0 +1,44 @@ +// Copyright (c) 2014 The Chromium Embedded Framework Authors. All rights +// reserved. Use of this source code is governed by a BSD-style license that +// can be found in the LICENSE file. + +package org.cef; + +public class OS { + private static enum OSType { + OSUndefined, + OSLinux, + OSWindows, + OSMacintosh, + OSUnknown, + } + ; + private static OSType osType = OSType.OSUndefined; + + public static final boolean isWindows() { + return getOSType() == OSType.OSWindows; + } + + public static final boolean isMacintosh() { + return getOSType() == OSType.OSMacintosh; + } + + public static final boolean isLinux() { + return getOSType() == OSType.OSLinux; + } + + private static final OSType getOSType() { + if (osType == OSType.OSUndefined) { + String os = System.getProperty("os.name").toLowerCase(); + if (os.startsWith("windows")) + osType = OSType.OSWindows; + else if (os.startsWith("linux")) + osType = OSType.OSLinux; + else if (os.startsWith("mac")) + osType = OSType.OSMacintosh; + else + osType = OSType.OSUnknown; + } + return osType; + } +} diff --git a/src/main/java/org/cef/SystemBootstrap.java b/src/main/java/org/cef/SystemBootstrap.java new file mode 100644 index 0000000..0a217ac --- /dev/null +++ b/src/main/java/org/cef/SystemBootstrap.java @@ -0,0 +1,38 @@ +// Copyright (c) 2020 The Chromium Embedded Framework Authors. All rights +// reserved. Use of this source code is governed by a BSD-style license that +// can be found in the LICENSE file. +package org.cef; + +/** + * To allow customization of System.load() calls by supplying a different + * implementation. You'll want to call setLoader with your custom + * implementation before calling into any other CEF classes which then in turn + * will start triggering libraries to be loaded at runtime. + */ +public class SystemBootstrap { + /** + * Simple interface for how a library by name should be loaded. + */ + static public interface Loader { public void loadLibrary(String libname); } + + /** + * Default implementation is to call System.loadLibrary + */ + static private Loader loader_ = new Loader() { + @Override + public void loadLibrary(String libname) { + System.loadLibrary(libname); + } + }; + + static public void setLoader(Loader loader) { + if (loader == null) { + throw new NullPointerException("Loader cannot be null"); + } + loader_ = loader; + } + + static public void loadLibrary(String libname) { + loader_.loadLibrary(libname); + } +} \ No newline at end of file diff --git a/src/main/java/org/cef/browser/CefBrowser.java b/src/main/java/org/cef/browser/CefBrowser.java new file mode 100644 index 0000000..14eeab2 --- /dev/null +++ b/src/main/java/org/cef/browser/CefBrowser.java @@ -0,0 +1,383 @@ +// Copyright (c) 2013 The Chromium Embedded Framework Authors. All rights +// reserved. Use of this source code is governed by a BSD-style license that +// can be found in the LICENSE file. + +package org.cef.browser; + +import org.cef.CefClient; +import org.cef.callback.CefPdfPrintCallback; +import org.cef.callback.CefRunFileDialogCallback; +import org.cef.callback.CefStringVisitor; +import org.cef.handler.CefDialogHandler.FileDialogMode; +import org.cef.handler.CefRenderHandler; +import org.cef.handler.CefWindowHandler; +import org.cef.misc.CefPdfPrintSettings; +import org.cef.network.CefRequest; + +import java.awt.Component; +import java.awt.Point; +import java.awt.image.BufferedImage; +import java.util.Vector; +import java.util.concurrent.CompletableFuture; + +/** + * Interface representing a browser. + */ +public interface CefBrowser { + /** + * Call to immediately create the underlying browser object. By default the + * browser object will be created when the parent container is displayed for + * the first time. + */ + public void createImmediately(); + + /** + * Get the underlying UI component (e.g. java.awt.Canvas). + * @return The underlying UI component. + */ + public Component getUIComponent(); + + /** + * Get the client associated with this browser. + * @return The browser client. + */ + public CefClient getClient(); + + /** + * Get an implementation of CefRenderHandler if any. + * @return An instance of CefRenderHandler or null. + */ + public CefRenderHandler getRenderHandler(); + + /** + * Get an implementation of CefWindowHandler if any. + * @return An instance of CefWindowHandler or null. + */ + public CefWindowHandler getWindowHandler(); + + // + // The following methods are forwarded to CefBrowser. + // + + /** + * Tests if the browser can navigate backwards. + * @return true if the browser can navigate backwards. + */ + public boolean canGoBack(); + + /** + * Go back. + */ + public void goBack(); + + /** + * Tests if the browser can navigate forwards. + * @return true if the browser can navigate forwards. + */ + public boolean canGoForward(); + + /** + * Go forward. + */ + public void goForward(); + + /** + * Tests if the browser is currently loading. + * @return true if the browser is currently loading. + */ + public boolean isLoading(); + + /** + * Reload the current page. + */ + public void reload(); + + /** + * Reload the current page ignoring any cached data. + */ + public void reloadIgnoreCache(); + + /** + * Stop loading the page. + */ + public void stopLoad(); + + /** + * Returns the unique browser identifier. + * @return The browser identifier + */ + public int getIdentifier(); + + /** + * Returns the main (top-level) frame for the browser window. + * @return The main frame + */ + public CefFrame getMainFrame(); + + /** + * Returns the focused frame for the browser window. + * @return The focused frame + */ + public CefFrame getFocusedFrame(); + + /** + * Returns the frame with the specified identifier, or NULL if not found. + * @param identifier The unique frame identifier + * @return The frame or NULL if not found + */ + public CefFrame getFrame(long identifier); + + /** + * Returns the frame with the specified name, or NULL if not found. + * @param name The specified name + * @return The frame or NULL if not found + */ + public CefFrame getFrame(String name); + + /** + * Returns the identifiers of all existing frames. + * @return All identifiers of existing frames. + */ + public Vector getFrameIdentifiers(); + + /** + * Returns the names of all existing frames. + * @return The names of all existing frames. + */ + public Vector getFrameNames(); + + /** + * Returns the number of frames that currently exist. + * @return The number of frames + */ + public int getFrameCount(); + + /** + * Tests if the window is a popup window. + * @return true if the window is a popup window. + */ + public boolean isPopup(); + + /** + * Tests if a document has been loaded in the browser. + * @return true if a document has been loaded in the browser. + */ + public boolean hasDocument(); + + // + // The following methods are forwarded to the mainFrame. + // + + /** + * Save this frame's HTML source to a temporary file and open it in the + * default text viewing application. This method can only be called from the + * browser process. + */ + public void viewSource(); + + /** + * Retrieve this frame's HTML source as a string sent to the specified + * visitor. + * + * @param visitor + */ + public void getSource(CefStringVisitor visitor); + + /** + * Retrieve this frame's display text as a string sent to the specified + * visitor. + * + * @param visitor + */ + public void getText(CefStringVisitor visitor); + + /** + * Load the request represented by the request object. + * + * @param request The request object. + */ + public void loadRequest(CefRequest request); + + /** + * Load the specified URL in the main frame. + * @param url The URL to load. + */ + public void loadURL(String url); + + /** + * Execute a string of JavaScript code in this frame. The url + * parameter is the URL where the script in question can be found, if any. + * The renderer may request this URL to show the developer the source of the + * error. The line parameter is the base line number to use for error + * reporting. + * + * @param code The code to be executed. + * @param url The URL where the script in question can be found. + * @param line The base line number to use for error reporting. + */ + public void executeJavaScript(String code, String url, int line); + + /** + * Emits the URL currently loaded in this frame. + * @return the URL currently loaded in this frame. + */ + public String getURL(); + + // The following methods are forwarded to CefBrowserHost. + + /** + * Request that the browser close. + * @param force force the close. + */ + public void close(boolean force); + + /** + * Allow the browser to close. + */ + public void setCloseAllowed(); + + /** + * Called from CefClient.doClose. + */ + public boolean doClose(); + + /** + * Called from CefClient.onBeforeClose. + */ + public void onBeforeClose(); + + /** + * Set or remove keyboard focus to/from the browser window. + * @param enable set to true to give the focus to the browser + **/ + public void setFocus(boolean enable); + + /** + * Set whether the window containing the browser is visible + * (minimized/unminimized, app hidden/unhidden, etc). Only used on Mac OS X. + * @param visible + */ + public void setWindowVisibility(boolean visible); + + /** + * Get the current zoom level. The default zoom level is 0.0. + * @return The current zoom level. + */ + public double getZoomLevel(); + + /** + * Change the zoom level to the specified value. Specify 0.0 to reset the + * zoom level. + * + * @param zoomLevel The zoom level to be set. + */ + public void setZoomLevel(double zoomLevel); + + /** + * Call to run a file chooser dialog. Only a single file chooser dialog may be + * pending at any given time.The dialog will be initiated asynchronously on + * the UI thread. + * + * @param mode represents the type of dialog to display. + * @param title to be used for the dialog and may be empty to show the + * default title ("Open" or "Save" depending on the mode). + * @param defaultFilePath is the path with optional directory and/or file name + * component that should be initially selected in the dialog. + * @param acceptFilters are used to restrict the selectable file types and may + * any combination of (a) valid lower-cased MIME types (e.g. "text/*" or + * "image/*"), (b) individual file extensions (e.g. ".txt" or ".png"), or (c) + * combined description and file extension delimited using "|" and ";" (e.g. + * "Image Types|.png;.gif;.jpg"). + * @param selectedAcceptFilter is the 0-based index of the filter that should + * be selected by default. + * @param callback will be executed after the dialog is dismissed or + * immediately if another dialog is already pending. + */ + public void runFileDialog(FileDialogMode mode, String title, String defaultFilePath, + Vector acceptFilters, int selectedAcceptFilter, + CefRunFileDialogCallback callback); + + /** + * Download the file at url using CefDownloadHandler. + * + * @param url URL to download that file. + */ + public void startDownload(String url); + + /** + * Print the current browser contents. + */ + public void print(); + + /** + * Print the current browser contents to a PDF. + * + * @param path The path of the file to write to (will be overwritten if it + * already exists). Cannot be null. + * @param settings The pdf print settings to use. If null then defaults + * will be used. + * @param callback Called when the pdf print job has completed. + */ + public void printToPDF(String path, CefPdfPrintSettings settings, CefPdfPrintCallback callback); + + /** + * Search for some kind of text on the page. + * + * @param searchText to be searched for. + * @param forward indicates whether to search forward or backward within the page. + * @param matchCase indicates whether the search should be case-sensitive. + * @param findNext indicates whether this is the first request or a follow-up. + */ + public void find(String searchText, boolean forward, boolean matchCase, boolean findNext); + + /** + * Cancel all searches that are currently going on. + * @param clearSelection Set to true to reset selection. + */ + public void stopFinding(boolean clearSelection); + + /** + * Get an instance of the dev tools to be displayed in its own window or to be + * embedded within your UI. Only one instance per browser is available. + */ + public CefBrowser getDevTools(); + + /** + * Get an instance of the dev tools to be displayed in its own window or to be + * embedded within your UI. Only one instance per browser is available. + * + * @param inspectAt a position in the UI which should be inspected. + */ + public CefBrowser getDevTools(Point inspectAt); + + /** + * If a misspelled word is currently selected in an editable node calling + * this method will replace it with the specified |word|. + * + * @param word replace selected word with this word. + */ + public void replaceMisspelling(String word); + + /** + * Captures a screenshot-like image of the currently displayed content and returns it. + *

+ * If executed on the AWT Event Thread, this returns an immediately resolved {@link + * java.util.concurrent.CompletableFuture}. If executed from another thread, the {@link + * java.util.concurrent.CompletableFuture} returned is resolved as soon as the screenshot + * has been taken (which must happen on the event thread). + *

+ * The generated screenshot can either be returned as-is, containing all natively-rendered + * pixels, or it can be scaled to match the logical width and height of the window. + * This distinction is only relevant in case of differing logical and physical resolutions + * (for example with HiDPI/Retina displays, which have a scaling factor of for example 2 + * between the logical width of a window (ex. 400px) and the actual number of pixels in + * each row (ex. 800px with a scaling factor of 2)). + * + * @param nativeResolution whether to return an image at full native resolution (true) + * or a scaled-down version whose width and height are equal to the logical size + * of the screenshotted browser window + * @return the screenshot image + * @throws UnsupportedOperationException if not supported + */ + public CompletableFuture createScreenshot(boolean nativeResolution); +} diff --git a/src/main/java/org/cef/browser/CefBrowserFactory.java b/src/main/java/org/cef/browser/CefBrowserFactory.java new file mode 100644 index 0000000..708d0f2 --- /dev/null +++ b/src/main/java/org/cef/browser/CefBrowserFactory.java @@ -0,0 +1,18 @@ +// Copyright (c) 2014 The Chromium Embedded Framework Authors. All rights +// reserved. Use of this source code is governed by a BSD-style license that +// can be found in the LICENSE file. + +package org.cef.browser; + +import org.cef.CefClient; + +/** + * Creates a new instance of CefBrowser according the passed values + */ +public class CefBrowserFactory { + public static CefBrowser create(CefClient client, String url, boolean isOffscreenRendered, + boolean isTransparent, CefRequestContext context) { + if (isOffscreenRendered) return new CefBrowserOsr(client, url, isTransparent, context); + return new CefBrowserWr(client, url, context); + } +} diff --git a/src/main/java/org/cef/browser/CefBrowserOsr.java b/src/main/java/org/cef/browser/CefBrowserOsr.java new file mode 100644 index 0000000..f380897 --- /dev/null +++ b/src/main/java/org/cef/browser/CefBrowserOsr.java @@ -0,0 +1,346 @@ +// Copyright (c) 2014 The Chromium Embedded Framework Authors. All rights +// reserved. Use of this source code is governed by a BSD-style license that +// can be found in the LICENSE file. + +package org.cef.browser; + +import net.montoyo.mcef.MCEF; +import net.montoyo.mcef.api.IBrowser; +import net.montoyo.mcef.api.IStringVisitor; +import net.montoyo.mcef.client.ClientProxy; +import net.montoyo.mcef.client.StringVisitor; +import net.montoyo.mcef.utilities.Log; +import org.apache.commons.lang3.NotImplementedException; +import org.cef.CefClient; +import org.cef.callback.CefDragData; +import org.cef.handler.CefRenderHandler; +import org.cef.handler.CefScreenInfo; +import org.lwjgl.BufferUtils; +import org.lwjgl.input.Keyboard; + +import java.awt.*; +import java.awt.event.KeyEvent; +import java.awt.event.MouseEvent; +import java.awt.event.MouseWheelEvent; +import java.awt.image.BufferedImage; +import java.nio.ByteBuffer; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.CompletableFuture; + +/** + * This class represents an off-screen rendered browser. + * The visibility of this class is "package". To create a new + * CefBrowser instance, please use CefBrowserFactory. + */ +public class CefBrowserOsr extends CefBrowser_N implements CefRenderHandler, IBrowser { + private CefRenderer renderer_; + private long window_handle_ = 0; + private boolean justCreated_ = false; + private Rectangle browser_rect_ = new Rectangle(0, 0, 1, 1); // Work around CEF issue #1437. + private Point screenPoint_ = new Point(0, 0); + private double scaleFactor_ = 1.0; + private int depth = 32; + private int depth_per_component = 8; + private boolean isTransparent_; + private final Component dc_ = new Component() { + @Override + public Point getLocationOnScreen() { + return new Point(0, 0); + } + }; + private MouseEvent lastMouseEvent = new MouseEvent(dc_, MouseEvent.MOUSE_MOVED, 0, 0, 0, 0, 0, false); + public static boolean CLEANUP = true; + + CefBrowserOsr(CefClient client, String url, boolean transparent, CefRequestContext context) { + this(client, url, transparent, context, null, null); + } + + private CefBrowserOsr(CefClient client, String url, boolean transparent, + CefRequestContext context, CefBrowserOsr parent, Point inspectAt) { + super(client, url, context, parent, inspectAt); + isTransparent_ = transparent; + renderer_ = new CefRenderer(transparent); + } + + @Override + public void createImmediately() { + justCreated_ = true; + // Create the browser immediately. + createBrowserIfRequired(false); + } + + @Override + public Component getUIComponent() { + return dc_; + } + + @Override + public CefRenderHandler getRenderHandler() { + return this; + } + + @Override + protected CefBrowser_N createDevToolsBrowser(CefClient client, String url, + CefRequestContext context, CefBrowser_N parent, Point inspectAt) { + return new CefBrowserOsr( + client, url, isTransparent_, context, (CefBrowserOsr) this, inspectAt); + } + + @Override + public Rectangle getViewRect(CefBrowser browser) { + return browser_rect_; + } + + @Override + public Point getScreenPoint(CefBrowser browser, Point viewPoint) { + Point screenPoint = new Point(screenPoint_); + screenPoint.translate(viewPoint.x, viewPoint.y); + return screenPoint; + } + + @Override + public void onPopupShow(CefBrowser browser, boolean show) { + if (!show) { + renderer_.clearPopupRects(); + invalidate(); + } + } + + @Override + public void onPopupSize(CefBrowser browser, Rectangle size) { + renderer_.onPopupSize(size); + } + + @Override + public void close() { + if (CLEANUP) { + ((ClientProxy) MCEF.PROXY).removeBrowser(this); + renderer_.cleanup(); + } + + super.close(true); //true to ignore confirmation popups + } + + @Override + public void resize(int width, int height) { + browser_rect_.setBounds(0, 0, width, height); + dc_.setBounds(browser_rect_); + dc_.setVisible(true); + wasResized(width, height); + } + + @Override + public void draw(double x1, double y1, double x2, double y2) { + renderer_.render(x1, y1, x2, y2); + } + + @Override + public int getTextureID() { + return renderer_.texture_id_[0]; + } + + @Override + public void injectMouseMove(int x, int y, int mods, boolean left) { + //FIXME: 'left' is not used as it causes bugs since MCEF 1.11 + + MouseEvent ev = new MouseEvent(dc_, MouseEvent.MOUSE_MOVED, 0, mods, x, y, 0, false); + lastMouseEvent = ev; + sendMouseEvent(ev); + } + + @Override + public void injectMouseButton(int x, int y, int mods, int btn, boolean pressed, int ccnt) { + MouseEvent ev = new MouseEvent(dc_, pressed ? MouseEvent.MOUSE_PRESSED : MouseEvent.MOUSE_RELEASED, 0, mods, x, y, ccnt, false, btn); + sendMouseEvent(ev); + } + + @Override + public void injectKeyTyped(char c, int mods) { + KeyEvent ev = new KeyEvent(dc_, KeyEvent.KEY_TYPED, 0, mods, 0, c); + sendKeyEvent(ev); + } + + public static int remapKeycode(int kc, char c) { + switch (kc) { + case Keyboard.KEY_BACK: + return KeyEvent.VK_BACK_SPACE; + case Keyboard.KEY_DELETE: + return KeyEvent.VK_DELETE; + case Keyboard.KEY_DOWN: + return KeyEvent.VK_DOWN; + case Keyboard.KEY_RETURN: + return KeyEvent.VK_ENTER; + case Keyboard.KEY_ESCAPE: + return KeyEvent.VK_ESCAPE; + case Keyboard.KEY_LEFT: + return KeyEvent.VK_LEFT; + case Keyboard.KEY_RIGHT: + return KeyEvent.VK_RIGHT; + case Keyboard.KEY_TAB: + return KeyEvent.VK_TAB; + case Keyboard.KEY_UP: + return KeyEvent.VK_UP; + case Keyboard.KEY_PRIOR: + return KeyEvent.VK_PAGE_UP; + case Keyboard.KEY_NEXT: + return KeyEvent.VK_PAGE_DOWN; + case Keyboard.KEY_END: + return Keyboard.KEY_END; + case Keyboard.KEY_HOME: + return Keyboard.KEY_HOME; + default: + return c; + } + } + + @Override + public void injectKeyPressedByKeyCode(int keyCode, char c, int mods) { + if (c != '\0') { + synchronized (WORST_HACK) { + WORST_HACK.put(keyCode, c); + } + } + + KeyEvent ev = new KeyEvent(dc_, KeyEvent.KEY_PRESSED, 0, mods, remapKeycode(keyCode, c), c); + sendKeyEvent(ev); + } + + private static final Map WORST_HACK = new HashMap<>(); + + @Override + public void injectKeyReleasedByKeyCode(int keyCode, char c, int mods) { + if (c == '\0') { + synchronized (WORST_HACK) { + c = WORST_HACK.getOrDefault(keyCode, '\0'); + } + } + + KeyEvent ev = new KeyEvent(dc_, KeyEvent.KEY_RELEASED, 0, mods, remapKeycode(keyCode, c), c); + sendKeyEvent(ev); + } + + @Override + public void injectMouseWheel(int x, int y, int mods, int amount, int rot) { + MouseWheelEvent ev = new MouseWheelEvent(dc_, MouseEvent.MOUSE_WHEEL, 0, mods, x, y, 0, false, MouseWheelEvent.WHEEL_UNIT_SCROLL, amount, rot); + sendMouseWheelEvent(ev); + } + + @Override + public void runJS(String script, String frame) { + executeJavaScript(script, frame, 0); + } + + @Override + public void visitSource(IStringVisitor isv) { + getSource(new StringVisitor(isv)); + } + + @Override + public boolean isPageLoading() { + return isLoading(); + } + + private static class PaintData { + private ByteBuffer buffer; + private int width; + private int height; + private Rectangle[] dirtyRects; + private boolean hasFrame; + private boolean fullReRender; + } + + private final PaintData paintData = new PaintData(); + + @Override + public void onPaint(CefBrowser browser, boolean popup, Rectangle[] dirtyRects, + ByteBuffer buffer, int width, int height) { + if (popup) { + return; + } + + final int size = (width * height) << 2; + + synchronized (paintData) { + if (buffer.limit() > size) + Log.warning("Skipping MCEF browser frame, data is too heavy"); //TODO: Don't spam + else { + if (paintData.hasFrame) //The previous frame was not uploaded to GL texture, so we skip it and render this on instead + paintData.fullReRender = true; + + if (paintData.buffer == null || size != paintData.buffer.capacity()) //This only happens when the browser gets resized + paintData.buffer = BufferUtils.createByteBuffer(size); + + paintData.buffer.position(0); + paintData.buffer.limit(buffer.limit()); + buffer.position(0); + paintData.buffer.put(buffer); + paintData.buffer.position(0); + + paintData.width = width; + paintData.height = height; + paintData.dirtyRects = dirtyRects; + paintData.hasFrame = true; + } + } + } + + public void mcefUpdate() { + synchronized (paintData) { + if (paintData.hasFrame) { + renderer_.onPaint(false, paintData.dirtyRects, paintData.buffer, paintData.width, paintData.height, paintData.fullReRender); + paintData.hasFrame = false; + paintData.fullReRender = false; + } + } + + //So sadly this is the only way I could get around the "youtube not rendering video if the mouse doesn't move bug" + //Even the test browser from the original JCEF library doesn't fix this... + //What I hope, however, is that it doesn't redraw the entire browser... otherwise I could just call "invalidate" + sendMouseEvent(lastMouseEvent); + } + + @Override + public boolean onCursorChange(CefBrowser browser, final int cursorType) { + return true; + } + + @Override + public boolean startDragging(CefBrowser browser, CefDragData dragData, int mask, int x, int y) { + // TODO(JCEF) Prepared for DnD support using OSR mode. + return false; + } + + @Override + public void updateDragCursor(CefBrowser browser, int operation) { + // TODO(JCEF) Prepared for DnD support using OSR mode. + } + + private void createBrowserIfRequired(boolean hasParent) { + if (getNativeRef("CefBrowser") == 0) { + if (getParentBrowser() != null) { + createDevTools(getParentBrowser(), getClient(), 0, true, isTransparent_, + null, getInspectAt()); + } else { + createBrowser(getClient(), 0, getUrl(), true, isTransparent_, null, + getRequestContext()); + } + } else { + // OSR windows cannot be reparented after creation. + setFocus(true); + } + } + + @Override + public boolean getScreenInfo(CefBrowser browser, CefScreenInfo screenInfo) { + screenInfo.Set(scaleFactor_, depth, depth_per_component, false, browser_rect_.getBounds(), + browser_rect_.getBounds()); + + return true; + } + + @Override + public CompletableFuture createScreenshot(boolean nativeResolution) { + throw new NotImplementedException("createScreenshot not implemented on MCEF"); + } +} diff --git a/src/main/java/org/cef/browser/CefBrowserWindow.java b/src/main/java/org/cef/browser/CefBrowserWindow.java new file mode 100644 index 0000000..3d7f595 --- /dev/null +++ b/src/main/java/org/cef/browser/CefBrowserWindow.java @@ -0,0 +1,20 @@ +// Copyright (c) 2014 The Chromium Embedded Framework Authors. All rights +// reserved. Use of this source code is governed by a BSD-style license that +// can be found in the LICENSE file. + +package org.cef.browser; + +import java.awt.Component; + +/** + * Interface representing system dependent methods for the browser. + */ +public interface CefBrowserWindow { + /** + * Get the window handle for the given UI object. + * + * @param comp a UI component + * @return a window pointer if any + */ + public long getWindowHandle(Component comp); +} diff --git a/src/main/java/org/cef/browser/CefBrowserWr.java b/src/main/java/org/cef/browser/CefBrowserWr.java new file mode 100644 index 0000000..b142bc5 --- /dev/null +++ b/src/main/java/org/cef/browser/CefBrowserWr.java @@ -0,0 +1,424 @@ +// Copyright (c) 2014 The Chromium Embedded Framework Authors. All rights +// reserved. Use of this source code is governed by a BSD-style license that +// can be found in the LICENSE file. + +package org.cef.browser; + +import org.cef.CefClient; +import org.cef.OS; +import org.cef.handler.CefWindowHandler; +import org.cef.handler.CefWindowHandlerAdapter; + +import java.awt.BorderLayout; +import java.awt.Canvas; +import java.awt.Component; +import java.awt.Container; +import java.awt.Dimension; +import java.awt.Graphics; +import java.awt.Graphics2D; +import java.awt.MouseInfo; +import java.awt.Point; +import java.awt.Rectangle; +import java.awt.Toolkit; +import java.awt.Window; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.awt.event.FocusEvent; +import java.awt.event.FocusListener; +import java.awt.event.HierarchyBoundsListener; +import java.awt.event.HierarchyEvent; +import java.awt.event.HierarchyListener; +import java.awt.event.MouseEvent; +import java.awt.event.MouseWheelEvent; +import java.awt.image.BufferedImage; +import java.util.Date; +import java.util.concurrent.CompletableFuture; + +import javax.swing.JPanel; +import javax.swing.JPopupMenu; +import javax.swing.MenuSelectionManager; +import javax.swing.SwingUtilities; +import javax.swing.Timer; +import javax.swing.ToolTipManager; + +/** + * This class represents a windowed rendered browser. + * The visibility of this class is "package". To create a new + * CefBrowser instance, please use CefBrowserFactory. + */ +class CefBrowserWr extends CefBrowser_N { + private Canvas canvas_ = null; + private Component component_ = null; + private Rectangle content_rect_ = new Rectangle(0, 0, 0, 0); + private long window_handle_ = 0; + private boolean justCreated_ = false; + private double scaleFactor_ = 1.0; + private Timer delayedUpdate_ = new Timer(100, new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + SwingUtilities.invokeLater(new Runnable() { + @Override + public void run() { + if (isClosed()) return; + + boolean hasCreatedUI = createBrowserIfRequired(true); + + if (hasCreatedUI) { + delayedUpdate_.restart(); + } else { + // If on Mac, this is needed due to the quirk described below + // (in org.cef.browser.CefBrowserWr.CefBrowserWr(...).new JPanel() + // {...}.paint(Graphics)). If on Linux, this is needed to invoke an + // XMoveResizeWindow call shortly after the UI was created. That seems to be + // necessary to actually get a windowed renderer to display something. + if (OS.isMacintosh() || OS.isLinux()) doUpdate(); + } + } + }); + } + }); + + private CefWindowHandlerAdapter win_handler_ = new CefWindowHandlerAdapter() { + private Point lastPos = new Point(-1, -1); + private long[] nextClick = new long[MouseInfo.getNumberOfButtons()]; + private int[] clickCnt = new int[MouseInfo.getNumberOfButtons()]; + + @Override + public Rectangle getRect(CefBrowser browser) { + synchronized (content_rect_) { + return content_rect_; + } + } + + @Override + public void onMouseEvent(CefBrowser browser, int event, final int screenX, + final int screenY, final int modifier, final int button) { + final Point pt = new Point(screenX, screenY); + if (event == MouseEvent.MOUSE_MOVED) { + // Remove mouse-moved events if the position of the cursor hasn't + // changed. + if (pt.equals(lastPos)) return; + lastPos = pt; + + // Change mouse-moved event to mouse-dragged event if the left mouse + // button is pressed. + if ((modifier & MouseEvent.BUTTON1_DOWN_MASK) != 0) + event = MouseEvent.MOUSE_DRAGGED; + } + + final int finalEvent = event; + + SwingUtilities.invokeLater(new Runnable() { + @Override + public void run() { + // Send mouse event to the root UI component instead to the browser UI. + // Otherwise no mouse-entered and no mouse-exited events would be fired. + Component parent = SwingUtilities.getRoot(component_); + if (parent == null) { + return; + } + SwingUtilities.convertPointFromScreen(pt, parent); + + int clickCnt = 0; + long now = new Date().getTime(); + if (finalEvent == MouseEvent.MOUSE_WHEEL) { + int scrollType = MouseWheelEvent.WHEEL_UNIT_SCROLL; + int rotation = button > 0 ? 1 : -1; + component_.dispatchEvent(new MouseWheelEvent(parent, finalEvent, now, + modifier, pt.x, pt.y, 0, false, scrollType, 3, rotation)); + } else { + clickCnt = getClickCount(finalEvent, button); + component_.dispatchEvent(new MouseEvent(parent, finalEvent, now, modifier, + pt.x, pt.y, screenX, screenY, clickCnt, false, button)); + } + + // Always fire a mouse-clicked event after a mouse-released event. + if (finalEvent == MouseEvent.MOUSE_RELEASED) { + component_.dispatchEvent( + new MouseEvent(parent, MouseEvent.MOUSE_CLICKED, now, modifier, + pt.x, pt.y, screenX, screenY, clickCnt, false, button)); + } + } + }); + } + + public int getClickCount(int event, int button) { + // avoid exceptions by using modulo + int idx = button % nextClick.length; + + switch (event) { + case MouseEvent.MOUSE_PRESSED: + long currTime = new Date().getTime(); + if (currTime > nextClick[idx]) { + nextClick[idx] = currTime + + (Integer) Toolkit.getDefaultToolkit().getDesktopProperty( + "awt.multiClickInterval"); + clickCnt[idx] = 1; + } else { + clickCnt[idx]++; + } + // FALL THRU + case MouseEvent.MOUSE_RELEASED: + return clickCnt[idx]; + default: + return 0; + } + } + }; + + CefBrowserWr(CefClient client, String url, CefRequestContext context) { + this(client, url, context, null, null); + } + + @SuppressWarnings("serial") + private CefBrowserWr(CefClient client, String url, CefRequestContext context, + CefBrowserWr parent, Point inspectAt) { + super(client, url, context, parent, inspectAt); + delayedUpdate_.setRepeats(false); + + // Disabling lightweight of popup menu is required because + // otherwise it will be displayed behind the content of component_ + JPopupMenu.setDefaultLightWeightPopupEnabled(false); + ToolTipManager.sharedInstance().setLightWeightPopupEnabled(false); + + // We're using a JComponent instead of a Canvas now because the + // JComponent has clipping informations, which aren't accessible for Canvas. + component_ = new JPanel(new BorderLayout()) { + private boolean removed_ = true; + + @Override + public void setBounds(int x, int y, int width, int height) { + super.setBounds(x, y, width, height); + wasResized((int) (width * scaleFactor_), (int) (height * scaleFactor_)); + } + + @Override + public void setBounds(Rectangle r) { + setBounds(r.x, r.y, r.width, r.height); + } + + @Override + public void setSize(int width, int height) { + super.setSize(width, height); + wasResized((int) (width * scaleFactor_), (int) (height * scaleFactor_)); + } + + @Override + public void setSize(Dimension d) { + setSize(d.width, d.height); + } + + @Override + public void paint(Graphics g) { + // If the user resizes the UI component, the new size and clipping + // informations are forwarded to the native code. + // But on Mac the last resize information doesn't resize the native UI + // accurately (sometimes the native UI is too small). An easy way to + // solve this, is to send the last Update-Information again. Therefore + // we're setting up a delayedUpdate timer which is reset each time + // paint is called. This prevents the us of sending the UI update too + // often. + if (g instanceof Graphics2D) { + scaleFactor_ = ((Graphics2D) g).getTransform().getScaleX(); + } + doUpdate(); + delayedUpdate_.restart(); + } + + @Override + public void addNotify() { + super.addNotify(); + if (removed_) { + setParent(getWindowHandle(this), canvas_); + removed_ = false; + } + } + + @Override + public void removeNotify() { + if (!removed_) { + if (!isClosed()) { + setParent(0, null); + } + removed_ = true; + } + super.removeNotify(); + } + }; + + // On windows we have to use a Canvas because its a heavyweight component + // and we need its native HWND as parent for the browser UI. The same + // technique is used on Linux as well. + if (OS.isWindows() || OS.isLinux()) { + canvas_ = new Canvas(); + ((JPanel) component_).add(canvas_, BorderLayout.CENTER); + } + + // Initial minimal size of the component. Otherwise the UI won't work + // accordingly in panes like JSplitPane. + component_.setMinimumSize(new Dimension(0, 0)); + component_.setFocusable(true); + component_.addFocusListener(new FocusListener() { + @Override + public void focusLost(FocusEvent e) { + setFocus(false); + } + + @Override + public void focusGained(FocusEvent e) { + // Dismiss any Java menus that are currently displayed. + MenuSelectionManager.defaultManager().clearSelectedPath(); + setFocus(true); + } + }); + component_.addHierarchyBoundsListener(new HierarchyBoundsListener() { + @Override + public void ancestorResized(HierarchyEvent e) { + doUpdate(); + } + @Override + public void ancestorMoved(HierarchyEvent e) { + doUpdate(); + notifyMoveOrResizeStarted(); + } + }); + component_.addHierarchyListener(new HierarchyListener() { + @Override + public void hierarchyChanged(HierarchyEvent e) { + if ((e.getChangeFlags() & HierarchyEvent.SHOWING_CHANGED) != 0) { + setWindowVisibility(e.getChanged().isVisible()); + } + } + }); + } + + @Override + public void createImmediately() { + justCreated_ = true; + SwingUtilities.invokeLater(new Runnable() { + @Override + public void run() { + // Create the browser immediately. It will be parented to the Java + // window once it becomes available. + createBrowserIfRequired(false); + } + }); + } + + @Override + public Component getUIComponent() { + return component_; + } + + @Override + public CefWindowHandler getWindowHandler() { + return win_handler_; + } + + @Override + protected CefBrowser_N createDevToolsBrowser(CefClient client, String url, + CefRequestContext context, CefBrowser_N parent, Point inspectAt) { + return new CefBrowserWr(client, url, context, (CefBrowserWr) this, inspectAt); + } + + private synchronized long getWindowHandle() { + if (window_handle_ == 0 && OS.isMacintosh()) { + window_handle_ = getWindowHandle(component_); + } + return window_handle_; + } + + private static long getWindowHandle(Component component) { + if (OS.isMacintosh()) { + try { + Class cls = Class.forName("org.cef.browser.mac.CefBrowserWindowMac"); + CefBrowserWindow browserWindow = (CefBrowserWindow) cls.newInstance(); + if (browserWindow != null) { + return browserWindow.getWindowHandle(component); + } + } catch (ClassNotFoundException e) { + e.printStackTrace(); + } catch (InstantiationException e) { + e.printStackTrace(); + } catch (IllegalAccessException e) { + e.printStackTrace(); + } + } + return 0; + } + + private void doUpdate() { + if (isClosed()) return; + + Rectangle vr = ((JPanel) component_).getVisibleRect(); + Rectangle clipping = new Rectangle((int) (vr.getX() * scaleFactor_), + (int) (vr.getY() * scaleFactor_), (int) (vr.getWidth() * scaleFactor_), + (int) (vr.getHeight() * scaleFactor_)); + + if (OS.isMacintosh()) { + Container parent = component_.getParent(); + Point contentPos = component_.getLocation(); + while (parent != null) { + Container next = parent.getParent(); + if (next != null && next instanceof Window) break; + Point parentPos = parent.getLocation(); + contentPos.translate(parentPos.x, parentPos.y); + parent = next; + } + contentPos.translate(clipping.x, clipping.y); + + Point browserPos = clipping.getLocation(); + browserPos.x *= -1; + browserPos.y *= -1; + + synchronized (content_rect_) { + content_rect_ = new Rectangle(contentPos, clipping.getSize()); + Rectangle browserRect = new Rectangle(browserPos, component_.getSize()); + updateUI(content_rect_, browserRect); + } + } else { + synchronized (content_rect_) { + Rectangle bounds = component_.getBounds(); + content_rect_ = new Rectangle((int) (bounds.getX() * scaleFactor_), + (int) (bounds.getY() * scaleFactor_), + (int) (bounds.getWidth() * scaleFactor_), + (int) (bounds.getHeight() * scaleFactor_)); + updateUI(clipping, content_rect_); + } + } + } + + private boolean createBrowserIfRequired(boolean hasParent) { + if (isClosed()) return false; + + long windowHandle = 0; + Component canvas = null; + if (hasParent) { + windowHandle = getWindowHandle(); + canvas = (OS.isWindows() || OS.isLinux()) ? canvas_ : component_; + } + + if (getNativeRef("CefBrowser") == 0) { + if (getParentBrowser() != null) { + createDevTools(getParentBrowser(), getClient(), windowHandle, false, false, canvas, + getInspectAt()); + return true; + } else { + createBrowser(getClient(), windowHandle, getUrl(), false, false, canvas, + getRequestContext()); + return true; + } + } else if (hasParent && justCreated_) { + setParent(windowHandle, canvas); + setFocus(true); + justCreated_ = false; + } + + return false; + } + + @Override + public CompletableFuture createScreenshot(boolean nativeResolution) { + throw new UnsupportedOperationException("Unsupported for windowed rendering"); + } +} diff --git a/src/main/java/org/cef/browser/CefBrowser_N.java b/src/main/java/org/cef/browser/CefBrowser_N.java new file mode 100644 index 0000000..406d025 --- /dev/null +++ b/src/main/java/org/cef/browser/CefBrowser_N.java @@ -0,0 +1,817 @@ +// Copyright (c) 2013 The Chromium Embedded Framework Authors. All rights +// reserved. Use of this source code is governed by a BSD-style license that +// can be found in the LICENSE file. + +package org.cef.browser; + +import org.cef.CefClient; +import org.cef.browser.CefRequestContext; +import org.cef.callback.CefDragData; +import org.cef.callback.CefNativeAdapter; +import org.cef.callback.CefPdfPrintCallback; +import org.cef.callback.CefRunFileDialogCallback; +import org.cef.callback.CefStringVisitor; +import org.cef.handler.CefClientHandler; +import org.cef.handler.CefDialogHandler.FileDialogMode; +import org.cef.handler.CefRenderHandler; +import org.cef.handler.CefWindowHandler; +import org.cef.misc.CefPdfPrintSettings; +import org.cef.network.CefRequest; + +import java.awt.Canvas; +import java.awt.Component; +import java.awt.Point; +import java.awt.Rectangle; +import java.awt.Window; +import java.awt.event.KeyEvent; +import java.awt.event.MouseEvent; +import java.awt.event.MouseWheelEvent; +import java.awt.event.WindowEvent; +import java.util.Vector; + +import javax.swing.SwingUtilities; + +/** + * This class represents all methods which are connected to the + * native counterpart CEF. + * The visibility of this class is "package". To create a new + * CefBrowser instance, please use CefBrowserFactory. + */ +abstract class CefBrowser_N extends CefNativeAdapter implements CefBrowser { + private volatile boolean isPending_ = false; + private final CefClient client_; + private final String url_; + private final CefRequestContext request_context_; + private volatile CefBrowser_N parent_ = null; + private volatile Point inspectAt_ = null; + private volatile CefBrowser_N devTools_ = null; + private boolean closeAllowed_ = false; + private volatile boolean isClosed_ = false; + private volatile boolean isClosing_ = false; + + protected CefBrowser_N(CefClient client, String url, CefRequestContext context, + CefBrowser_N parent, Point inspectAt) { + client_ = client; + url_ = url; + request_context_ = context; + parent_ = parent; + inspectAt_ = inspectAt; + } + + protected String getUrl() { + return url_; + } + + protected CefRequestContext getRequestContext() { + return request_context_; + } + + protected CefBrowser_N getParentBrowser() { + return parent_; + } + + protected Point getInspectAt() { + return inspectAt_; + } + + protected boolean isClosed() { + return isClosed_; + } + + @Override + public CefClient getClient() { + return client_; + } + + @Override + public CefRenderHandler getRenderHandler() { + return null; + } + + @Override + public CefWindowHandler getWindowHandler() { + return null; + } + + @Override + public synchronized void setCloseAllowed() { + closeAllowed_ = true; + } + + @Override + public synchronized boolean doClose() { + if (closeAllowed_) { + // Allow the close to proceed. + return false; + } + + SwingUtilities.invokeLater(new Runnable() { + @Override + public void run() { + // Trigger close of the parent window. + Component parent = SwingUtilities.getRoot(getUIComponent()); + if (parent != null) { + parent.dispatchEvent( + new WindowEvent((Window) parent, WindowEvent.WINDOW_CLOSING)); + } + } + }); + + // Cancel the close. + return true; + } + + @Override + public synchronized void onBeforeClose() { + isClosed_ = true; + if (request_context_ != null) request_context_.dispose(); + if (parent_ != null) { + parent_.closeDevTools(); + parent_.devTools_ = null; + parent_ = null; + } + } + + @Override + public CefBrowser getDevTools() { + return getDevTools(null); + } + + @Override + public synchronized CefBrowser getDevTools(Point inspectAt) { + if (devTools_ == null) { + devTools_ = createDevToolsBrowser(client_, url_, request_context_, this, inspectAt); + } + return devTools_; + } + + protected abstract CefBrowser_N createDevToolsBrowser(CefClient client, String url, + CefRequestContext context, CefBrowser_N parent, Point inspectAt); + + /** + * Create a new browser. + */ + protected void createBrowser(CefClientHandler clientHandler, long windowHandle, String url, + boolean osr, boolean transparent, Component canvas, CefRequestContext context) { + if (getNativeRef("CefBrowser") == 0 && !isPending_) { + try { + N_CreateBrowser( + clientHandler, windowHandle, url, osr, transparent, canvas, context); + } catch (UnsatisfiedLinkError err) { + err.printStackTrace(); + } + } + } + + /** + * Called async from the (native) main UI thread. + */ + private void notifyBrowserCreated() { + isPending_ = true; + } + + /** + * Create a new browser as dev tools + */ + protected final void createDevTools(CefBrowser_N parent, CefClientHandler clientHandler, + long windowHandle, boolean osr, boolean transparent, Component canvas, + Point inspectAt) { + if (getNativeRef("CefBrowser") == 0 && !isPending_) { + try { + isPending_ = N_CreateDevTools( + parent, clientHandler, windowHandle, osr, transparent, canvas, inspectAt); + } catch (UnsatisfiedLinkError err) { + err.printStackTrace(); + } + } + } + + /** + * Returns the native window handle for the specified native surface handle. + */ + protected final long getWindowHandle(long surfaceHandle) { + try { + return N_GetWindowHandle(surfaceHandle); + } catch (UnsatisfiedLinkError err) { + err.printStackTrace(); + } + return 0; + } + + @Override + protected void finalize() throws Throwable { + close(true); + super.finalize(); + } + + @Override + public boolean canGoBack() { + try { + return N_CanGoBack(); + } catch (UnsatisfiedLinkError ule) { + ule.printStackTrace(); + } + return false; + } + + @Override + public void goBack() { + try { + N_GoBack(); + } catch (UnsatisfiedLinkError ule) { + ule.printStackTrace(); + } + } + + @Override + public boolean canGoForward() { + try { + return N_CanGoForward(); + } catch (UnsatisfiedLinkError ule) { + ule.printStackTrace(); + } + return false; + } + + @Override + public void goForward() { + try { + N_GoForward(); + } catch (UnsatisfiedLinkError ule) { + ule.printStackTrace(); + } + } + + @Override + public boolean isLoading() { + try { + return N_IsLoading(); + } catch (UnsatisfiedLinkError ule) { + ule.printStackTrace(); + } + return false; + } + + @Override + public void reload() { + try { + N_Reload(); + } catch (UnsatisfiedLinkError ule) { + ule.printStackTrace(); + } + } + + @Override + public void reloadIgnoreCache() { + try { + N_ReloadIgnoreCache(); + } catch (UnsatisfiedLinkError ule) { + ule.printStackTrace(); + } + } + + @Override + public void stopLoad() { + try { + N_StopLoad(); + } catch (UnsatisfiedLinkError ule) { + ule.printStackTrace(); + } + } + + @Override + public int getIdentifier() { + try { + return N_GetIdentifier(); + } catch (UnsatisfiedLinkError ule) { + ule.printStackTrace(); + return -1; + } + } + + @Override + public CefFrame getMainFrame() { + try { + return N_GetMainFrame(); + } catch (UnsatisfiedLinkError ule) { + ule.printStackTrace(); + return null; + } + } + + @Override + public CefFrame getFocusedFrame() { + try { + return N_GetFocusedFrame(); + } catch (UnsatisfiedLinkError ule) { + ule.printStackTrace(); + return null; + } + } + + @Override + public CefFrame getFrame(long identifier) { + try { + return N_GetFrame(identifier); + } catch (UnsatisfiedLinkError ule) { + ule.printStackTrace(); + return null; + } + } + + @Override + public CefFrame getFrame(String name) { + try { + return N_GetFrame2(name); + } catch (UnsatisfiedLinkError ule) { + ule.printStackTrace(); + return null; + } + } + + @Override + public Vector getFrameIdentifiers() { + try { + return N_GetFrameIdentifiers(); + } catch (UnsatisfiedLinkError ule) { + ule.printStackTrace(); + return null; + } + } + + @Override + public Vector getFrameNames() { + try { + return N_GetFrameNames(); + } catch (UnsatisfiedLinkError ule) { + ule.printStackTrace(); + return null; + } + } + + @Override + public int getFrameCount() { + try { + return N_GetFrameCount(); + } catch (UnsatisfiedLinkError ule) { + ule.printStackTrace(); + return -1; + } + } + + @Override + public boolean isPopup() { + try { + return N_IsPopup(); + } catch (UnsatisfiedLinkError ule) { + ule.printStackTrace(); + } + return false; + } + + @Override + public boolean hasDocument() { + try { + return N_HasDocument(); + } catch (UnsatisfiedLinkError ule) { + ule.printStackTrace(); + } + return false; + } + + public void viewSource() { + try { + N_ViewSource(); + } catch (UnsatisfiedLinkError ule) { + ule.printStackTrace(); + } + } + + public void getSource(CefStringVisitor visitor) { + try { + N_GetSource(visitor); + } catch (UnsatisfiedLinkError ule) { + ule.printStackTrace(); + } + } + + public void getText(CefStringVisitor visitor) { + try { + N_GetText(visitor); + } catch (UnsatisfiedLinkError ule) { + ule.printStackTrace(); + } + } + + @Override + public void loadRequest(CefRequest request) { + try { + N_LoadRequest(request); + } catch (UnsatisfiedLinkError ule) { + ule.printStackTrace(); + } + } + + @Override + public void loadURL(String url) { + try { + N_LoadURL(url); + } catch (UnsatisfiedLinkError ule) { + ule.printStackTrace(); + } + } + + @Override + public void executeJavaScript(String code, String url, int line) { + try { + N_ExecuteJavaScript(code, url, line); + } catch (UnsatisfiedLinkError ule) { + ule.printStackTrace(); + } + } + + @Override + public String getURL() { + try { + return N_GetURL(); + } catch (UnsatisfiedLinkError ule) { + ule.printStackTrace(); + } + return ""; + } + + @Override + public void close(boolean force) { + if (isClosing_ || isClosed_) return; + if (force) isClosing_ = true; + + try { + N_Close(force); + } catch (UnsatisfiedLinkError ule) { + ule.printStackTrace(); + } + } + + @Override + public void setFocus(boolean enable) { + try { + N_SetFocus(enable); + } catch (UnsatisfiedLinkError ule) { + ule.printStackTrace(); + } + } + + @Override + public void setWindowVisibility(boolean visible) { + try { + N_SetWindowVisibility(visible); + } catch (UnsatisfiedLinkError ule) { + ule.printStackTrace(); + } + } + + @Override + public double getZoomLevel() { + try { + return N_GetZoomLevel(); + } catch (UnsatisfiedLinkError ule) { + ule.printStackTrace(); + } + return 0.0; + } + + @Override + public void setZoomLevel(double zoomLevel) { + try { + N_SetZoomLevel(zoomLevel); + } catch (UnsatisfiedLinkError ule) { + ule.printStackTrace(); + } + } + + @Override + public void runFileDialog(FileDialogMode mode, String title, String defaultFilePath, + Vector acceptFilters, int selectedAcceptFilter, + CefRunFileDialogCallback callback) { + try { + N_RunFileDialog( + mode, title, defaultFilePath, acceptFilters, selectedAcceptFilter, callback); + } catch (UnsatisfiedLinkError ule) { + ule.printStackTrace(); + } + } + + @Override + public void startDownload(String url) { + try { + N_StartDownload(url); + } catch (UnsatisfiedLinkError ule) { + ule.printStackTrace(); + } + } + + @Override + public void print() { + try { + N_Print(); + } catch (UnsatisfiedLinkError ule) { + ule.printStackTrace(); + } + } + + @Override + public void printToPDF( + String path, CefPdfPrintSettings settings, CefPdfPrintCallback callback) { + if (path == null || path.isEmpty()) { + throw new IllegalArgumentException("path was null or empty"); + } + try { + N_PrintToPDF(path, settings, callback); + } catch (UnsatisfiedLinkError ule) { + ule.printStackTrace(); + } + } + + @Override + public void find(String searchText, boolean forward, boolean matchCase, boolean findNext) { + try { + N_Find(searchText, forward, matchCase, findNext); + } catch (UnsatisfiedLinkError ule) { + ule.printStackTrace(); + } + } + + @Override + public void stopFinding(boolean clearSelection) { + try { + N_StopFinding(clearSelection); + } catch (UnsatisfiedLinkError ule) { + ule.printStackTrace(); + } + } + + protected final void closeDevTools() { + try { + N_CloseDevTools(); + } catch (UnsatisfiedLinkError ule) { + ule.printStackTrace(); + } + } + + @Override + public void replaceMisspelling(String word) { + try { + N_ReplaceMisspelling(word); + } catch (UnsatisfiedLinkError ule) { + ule.printStackTrace(); + } + } + + /** + * Notify that the browser was resized. + * @param width The new width of the browser + * @param height The new height of the browser + */ + protected final void wasResized(int width, int height) { + try { + N_WasResized(width, height); + } catch (UnsatisfiedLinkError ule) { + ule.printStackTrace(); + } + } + + /** + * Invalidate the UI. + */ + protected final void invalidate() { + try { + N_Invalidate(); + } catch (UnsatisfiedLinkError ule) { + ule.printStackTrace(); + } + } + + /** + * Send a key event. + * @param e The event to send. + */ + protected final void sendKeyEvent(KeyEvent e) { + try { + N_SendKeyEvent(e); + } catch (UnsatisfiedLinkError ule) { + ule.printStackTrace(); + } + } + + /** + * Send a mouse event. + * @param e The event to send. + */ + protected final void sendMouseEvent(MouseEvent e) { + try { + N_SendMouseEvent(e); + } catch (UnsatisfiedLinkError ule) { + ule.printStackTrace(); + } + } + + /** + * Send a mouse wheel event. + * @param e The event to send. + */ + protected final void sendMouseWheelEvent(MouseWheelEvent e) { + try { + N_SendMouseWheelEvent(e); + } catch (UnsatisfiedLinkError ule) { + ule.printStackTrace(); + } + } + + /** + * Call this method when the user drags the mouse into the web view (before + * calling DragTargetDragOver/DragTargetLeave/DragTargetDrop). + * |drag_data| should not contain file contents as this type of data is not + * allowed to be dragged into the web view. File contents can be removed using + * CefDragData::ResetFileContents (for example, if |drag_data| comes from + * CefRenderHandler::StartDragging). + * This method is only used when window rendering is disabled. + */ + protected final void dragTargetDragEnter( + CefDragData dragData, Point pos, int modifiers, int allowedOps) { + try { + N_DragTargetDragEnter(dragData, pos, modifiers, allowedOps); + } catch (UnsatisfiedLinkError ule) { + ule.printStackTrace(); + } + } + + /** + * Call this method each time the mouse is moved across the web view during + * a drag operation (after calling DragTargetDragEnter and before calling + * DragTargetDragLeave/DragTargetDrop). + * This method is only used when window rendering is disabled. + */ + protected final void dragTargetDragOver(Point pos, int modifiers, int allowedOps) { + try { + N_DragTargetDragOver(pos, modifiers, allowedOps); + } catch (UnsatisfiedLinkError ule) { + ule.printStackTrace(); + } + } + + /** + * Call this method when the user drags the mouse out of the web view (after + * calling DragTargetDragEnter). + * This method is only used when window rendering is disabled. + */ + protected final void dragTargetDragLeave() { + try { + N_DragTargetDragLeave(); + } catch (UnsatisfiedLinkError ule) { + ule.printStackTrace(); + } + } + + /** + * Call this method when the user completes the drag operation by dropping + * the object onto the web view (after calling DragTargetDragEnter). + * The object being dropped is |drag_data|, given as an argument to + * the previous DragTargetDragEnter call. + * This method is only used when window rendering is disabled. + */ + protected final void dragTargetDrop(Point pos, int modifiers) { + try { + N_DragTargetDrop(pos, modifiers); + } catch (UnsatisfiedLinkError ule) { + ule.printStackTrace(); + } + } + + /** + * Call this method when the drag operation started by a + * CefRenderHandler.startDragging call has ended either in a drop or + * by being cancelled. |x| and |y| are mouse coordinates relative to the + * upper-left corner of the view. If the web view is both the drag source + * and the drag target then all DragTarget* methods should be called before + * DragSource* methods. + * This method is only used when window rendering is disabled. + */ + protected final void dragSourceEndedAt(Point pos, int operation) { + try { + N_DragSourceEndedAt(pos, operation); + } catch (UnsatisfiedLinkError ule) { + ule.printStackTrace(); + } + } + + /** + * Call this method when the drag operation started by a + * CefRenderHandler.startDragging call has completed. This method may be + * called immediately without first calling DragSourceEndedAt to cancel a + * drag operation. If the web view is both the drag source and the drag + * target then all DragTarget* methods should be called before DragSource* + * methods. + * This method is only used when window rendering is disabled. + */ + protected final void dragSourceSystemDragEnded() { + try { + N_DragSourceSystemDragEnded(); + } catch (UnsatisfiedLinkError ule) { + ule.printStackTrace(); + } + } + + protected final void updateUI(Rectangle contentRect, Rectangle browserRect) { + try { + N_UpdateUI(contentRect, browserRect); + } catch (UnsatisfiedLinkError ule) { + ule.printStackTrace(); + } + } + + protected final void setParent(long windowHandle, Component canvas) { + if (isClosing_ || isClosed_) return; + + try { + N_SetParent(windowHandle, canvas); + } catch (UnsatisfiedLinkError ule) { + ule.printStackTrace(); + } + } + + /** + * Call this method if the browser frame was moved. + * This fixes positioning of select popups and dismissal on window move/resize. + */ + protected final void notifyMoveOrResizeStarted() { + try { + N_NotifyMoveOrResizeStarted(); + } catch (UnsatisfiedLinkError ule) { + ule.printStackTrace(); + } + } + + private final native boolean N_CreateBrowser(CefClientHandler clientHandler, long windowHandle, + String url, boolean osr, boolean transparent, Component canvas, + CefRequestContext context); + private final native boolean N_CreateDevTools(CefBrowser parent, CefClientHandler clientHandler, + long windowHandle, boolean osr, boolean transparent, Component canvas, Point inspectAt); + private final native long N_GetWindowHandle(long surfaceHandle); + private final native boolean N_CanGoBack(); + private final native void N_GoBack(); + private final native boolean N_CanGoForward(); + private final native void N_GoForward(); + private final native boolean N_IsLoading(); + private final native void N_Reload(); + private final native void N_ReloadIgnoreCache(); + private final native void N_StopLoad(); + private final native int N_GetIdentifier(); + private final native CefFrame N_GetMainFrame(); + private final native CefFrame N_GetFocusedFrame(); + private final native CefFrame N_GetFrame(long identifier); + private final native CefFrame N_GetFrame2(String name); + private final native Vector N_GetFrameIdentifiers(); + private final native Vector N_GetFrameNames(); + private final native int N_GetFrameCount(); + private final native boolean N_IsPopup(); + private final native boolean N_HasDocument(); + private final native void N_ViewSource(); + private final native void N_GetSource(CefStringVisitor visitor); + private final native void N_GetText(CefStringVisitor visitor); + private final native void N_LoadRequest(CefRequest request); + private final native void N_LoadURL(String url); + private final native void N_ExecuteJavaScript(String code, String url, int line); + private final native String N_GetURL(); + private final native void N_Close(boolean force); + private final native void N_SetFocus(boolean enable); + private final native void N_SetWindowVisibility(boolean visible); + private final native double N_GetZoomLevel(); + private final native void N_SetZoomLevel(double zoomLevel); + private final native void N_RunFileDialog(FileDialogMode mode, String title, + String defaultFilePath, Vector acceptFilters, int selectedAcceptFilter, + CefRunFileDialogCallback callback); + private final native void N_StartDownload(String url); + private final native void N_Print(); + private final native void N_PrintToPDF( + String path, CefPdfPrintSettings settings, CefPdfPrintCallback callback); + private final native void N_Find( + String searchText, boolean forward, boolean matchCase, boolean findNext); + private final native void N_StopFinding(boolean clearSelection); + private final native void N_CloseDevTools(); + private final native void N_ReplaceMisspelling(String word); + private final native void N_WasResized(int width, int height); + private final native void N_Invalidate(); + private final native void N_SendKeyEvent(KeyEvent e); + private final native void N_SendMouseEvent(MouseEvent e); + private final native void N_SendMouseWheelEvent(MouseWheelEvent e); + private final native void N_DragTargetDragEnter( + CefDragData dragData, Point pos, int modifiers, int allowed_ops); + private final native void N_DragTargetDragOver(Point pos, int modifiers, int allowed_ops); + private final native void N_DragTargetDragLeave(); + private final native void N_DragTargetDrop(Point pos, int modifiers); + private final native void N_DragSourceEndedAt(Point pos, int operation); + private final native void N_DragSourceSystemDragEnded(); + private final native void N_UpdateUI(Rectangle contentRect, Rectangle browserRect); + private final native void N_SetParent(long windowHandle, Component canvas); + private final native void N_NotifyMoveOrResizeStarted(); +} diff --git a/src/main/java/org/cef/browser/CefDropTargetListener.java b/src/main/java/org/cef/browser/CefDropTargetListener.java new file mode 100644 index 0000000..e5e0be0 --- /dev/null +++ b/src/main/java/org/cef/browser/CefDropTargetListener.java @@ -0,0 +1,127 @@ +// Copyright (c) 2019 The Chromium Embedded Framework Authors. All rights +// reserved. Use of this source code is governed by a BSD-style license that +// can be found in the LICENSE file. + +package org.cef.browser; + +import org.cef.callback.CefDragData; +import org.cef.misc.EventFlags; + +import java.awt.datatransfer.DataFlavor; +import java.awt.datatransfer.Transferable; +import java.awt.dnd.DnDConstants; +import java.awt.dnd.DropTarget; +import java.awt.dnd.DropTargetDragEvent; +import java.awt.dnd.DropTargetDropEvent; +import java.awt.dnd.DropTargetEvent; +import java.awt.dnd.DropTargetListener; +import java.io.File; +import java.util.List; + +class CefDropTargetListener implements DropTargetListener { + private CefBrowser_N browser_; + private CefDragData dragData_ = null; + private int dragOperations_ = CefDragData.DragOperations.DRAG_OPERATION_COPY; + private int dragModifiers_ = EventFlags.EVENTFLAG_NONE; + private int acceptOperations_ = DnDConstants.ACTION_COPY; + + CefDropTargetListener(CefBrowser_N browser) { + browser_ = browser; + } + + @Override + public void dragEnter(DropTargetDragEvent event) { + CreateDragData(event); + browser_.dragTargetDragEnter( + dragData_, event.getLocation(), dragModifiers_, dragOperations_); + } + + @Override + public void dragExit(DropTargetEvent event) { + AssertDragData(); + browser_.dragTargetDragLeave(); + ClearDragData(); + } + + @Override + public void dragOver(DropTargetDragEvent event) { + AssertDragData(); + browser_.dragTargetDragOver(event.getLocation(), dragModifiers_, dragOperations_); + } + + @Override + public void dropActionChanged(DropTargetDragEvent event) { + AssertDragData(); + acceptOperations_ = event.getDropAction(); + switch (acceptOperations_) { + case DnDConstants.ACTION_LINK: + dragOperations_ = CefDragData.DragOperations.DRAG_OPERATION_LINK; + dragModifiers_ = + EventFlags.EVENTFLAG_CONTROL_DOWN | EventFlags.EVENTFLAG_SHIFT_DOWN; + break; + case DnDConstants.ACTION_COPY: + dragOperations_ = CefDragData.DragOperations.DRAG_OPERATION_COPY; + dragModifiers_ = EventFlags.EVENTFLAG_CONTROL_DOWN; + break; + case DnDConstants.ACTION_MOVE: + dragOperations_ = CefDragData.DragOperations.DRAG_OPERATION_MOVE; + dragModifiers_ = EventFlags.EVENTFLAG_SHIFT_DOWN; + break; + case DnDConstants.ACTION_NONE: + // The user did not select an action, so use COPY as the default. + dragOperations_ = CefDragData.DragOperations.DRAG_OPERATION_COPY; + dragModifiers_ = EventFlags.EVENTFLAG_NONE; + acceptOperations_ = DnDConstants.ACTION_COPY; + break; + } + } + + @Override + public void drop(DropTargetDropEvent event) { + AssertDragData(); + browser_.dragTargetDrop(event.getLocation(), dragModifiers_); + event.acceptDrop(acceptOperations_); + event.dropComplete(true); + ClearDragData(); + } + + private void CreateDragData(DropTargetDragEvent event) { + assert dragData_ == null; + dragData_ = createDragData(event); + dropActionChanged(event); + } + + private void AssertDragData() { + assert dragData_ != null; + } + + private void ClearDragData() { + dragData_ = null; + } + + private static CefDragData createDragData(DropTargetDragEvent event) { + CefDragData dragData = CefDragData.create(); + + Transferable transferable = event.getTransferable(); + DataFlavor[] flavors = transferable.getTransferDataFlavors(); + for (DataFlavor flavor : flavors) { + try { + if (flavor.isFlavorTextType()) { + Object ob = transferable.getTransferData(flavor); + if (!(ob instanceof String)) continue; + dragData.setFragmentText((String) ob); + } else if (flavor.isFlavorJavaFileListType()) { + List files = (List) transferable.getTransferData(flavor); + for (File file : files) { + dragData.addFile(file.getPath(), file.getName()); + } + } + } catch (Exception e) { + // Data is no longer available or of unsupported flavor. + e.printStackTrace(); + } + } + + return dragData; + } +} diff --git a/src/main/java/org/cef/browser/CefFrame.java b/src/main/java/org/cef/browser/CefFrame.java new file mode 100644 index 0000000..3a2393f --- /dev/null +++ b/src/main/java/org/cef/browser/CefFrame.java @@ -0,0 +1,101 @@ +// Copyright (c) 2017 The Chromium Embedded Framework Authors. All rights +// reserved. Use of this source code is governed by a BSD-style license that +// can be found in the LICENSE file. + +package org.cef.browser; + +/** + * Interface representing a frame. + */ +public interface CefFrame { + /** + * Removes the native reference from an unused object. + */ + void dispose(); + + /** + * Returns the globally unique identifier for this frame or < 0 if the + * underlying frame does not yet exist. + * @return The frame identifier + */ + long getIdentifier(); + + /** + * Emits the URL currently loaded in this frame. + * @return the URL currently loaded in this frame. + */ + String getURL(); + + /** + * Returns the name for this frame. If the frame has an assigned name (for + * example, set via the iframe "name" attribute) then that value will be + * returned. Otherwise a unique name will be constructed based on the frame + * parent hierarchy. The main (top-level) frame will always have an empty name + * value. + * @return The frame name + */ + String getName(); + + /** + * Returns true if this is the main (top-level) frame. + * @return True if this frame is top-level otherwise false. + */ + boolean isMain(); + + /** + * True if this object is currently attached to a valid frame. + * @return True if valid otherwise false. + */ + boolean isValid(); + + /** + * Returns true if this is the focused frame. + * @return True if valid otherwise false. + */ + boolean isFocused(); + + /** + * Returns the parent of this frame or NULL if this is the main (top-level) + * frame. + * @return The parent frame or NULL if this is the main frame + */ + CefFrame getParent(); + + /** + * Execute a string of JavaScript code in this frame. The url + * parameter is the URL where the script in question can be found, if any. + * The renderer may request this URL to show the developer the source of the + * error. The line parameter is the base line number to use for error + * reporting. + * + * @param code The code to be executed. + * @param url The URL where the script in question can be found. + * @param line The base line number to use for error reporting. + */ + public void executeJavaScript(String code, String url, int line); + + /** + * Execute undo in this frame. + */ + public void undo(); + + /** + * Execute redo in this frame. + */ + public void redo(); + + /** + * Execute cut in this frame. + */ + public void cut(); + + /** + * Execute copy in this frame. + */ + public void copy(); + + /** + * Execute paste in this frame. + */ + public void paste(); +} \ No newline at end of file diff --git a/src/main/java/org/cef/browser/CefFrame_N.java b/src/main/java/org/cef/browser/CefFrame_N.java new file mode 100644 index 0000000..8e129f1 --- /dev/null +++ b/src/main/java/org/cef/browser/CefFrame_N.java @@ -0,0 +1,165 @@ +// Copyright (c) 2017 The Chromium Embedded Framework Authors. All rights +// reserved. Use of this source code is governed by a BSD-style license that +// can be found in the LICENSE file. + +package org.cef.browser; + +import org.cef.callback.CefNativeAdapter; + +/** + * This class represents all methods which are connected to the + * native counterpart CEF. + * The visibility of this class is "package". + */ +class CefFrame_N extends CefNativeAdapter implements CefFrame { + CefFrame_N() {} + + @Override + protected void finalize() throws Throwable { + dispose(); + super.finalize(); + } + + @Override + public void dispose() { + try { + N_Dispose(getNativeRef(null)); + } catch (UnsatisfiedLinkError ule) { + ule.printStackTrace(); + } + } + + @Override + public long getIdentifier() { + try { + return N_GetIdentifier(getNativeRef(null)); + } catch (UnsatisfiedLinkError ule) { + ule.printStackTrace(); + return -1; + } + } + + @Override + public String getURL() { + try { + return N_GetURL(getNativeRef(null)); + } catch (UnsatisfiedLinkError ule) { + ule.printStackTrace(); + return null; + } + } + + @Override + public String getName() { + try { + return N_GetName(getNativeRef(null)); + } catch (UnsatisfiedLinkError ule) { + ule.printStackTrace(); + return null; + } + } + + @Override + public boolean isMain() { + try { + return N_IsMain(getNativeRef(null)); + } catch (UnsatisfiedLinkError ule) { + ule.printStackTrace(); + return false; + } + } + + @Override + public boolean isValid() { + try { + return N_IsValid(getNativeRef(null)); + } catch (UnsatisfiedLinkError ule) { + ule.printStackTrace(); + return false; + } + } + + @Override + public boolean isFocused() { + try { + return N_IsFocused(getNativeRef(null)); + } catch (UnsatisfiedLinkError ule) { + ule.printStackTrace(); + return false; + } + } + + @Override + public CefFrame getParent() { + try { + return N_GetParent(getNativeRef(null)); + } catch (UnsatisfiedLinkError ule) { + ule.printStackTrace(); + return null; + } + } + + @Override + public void executeJavaScript(String code, String url, int line) { + try { + N_ExecuteJavaScript(getNativeRef(null), code, url, line); + } catch (UnsatisfiedLinkError ule) { + ule.printStackTrace(); + } + } + + public void undo() { + try { + N_Undo(getNativeRef(null)); + } catch (UnsatisfiedLinkError ule) { + ule.printStackTrace(); + } + } + + public void redo() { + try { + N_Redo(getNativeRef(null)); + } catch (UnsatisfiedLinkError ule) { + ule.printStackTrace(); + } + } + + public void cut() { + try { + N_Cut(getNativeRef(null)); + } catch (UnsatisfiedLinkError ule) { + ule.printStackTrace(); + } + } + + public void copy() { + try { + N_Copy(getNativeRef(null)); + } catch (UnsatisfiedLinkError ule) { + ule.printStackTrace(); + } + } + + public void paste() { + try { + N_Paste(getNativeRef(null)); + } catch (UnsatisfiedLinkError ule) { + ule.printStackTrace(); + } + } + + private final native void N_Dispose(long self); + private final native long N_GetIdentifier(long self); + private final native String N_GetURL(long self); + private final native String N_GetName(long self); + private final native boolean N_IsMain(long self); + private final native boolean N_IsValid(long self); + private final native boolean N_IsFocused(long self); + private final native CefFrame N_GetParent(long self); + private final native void N_ExecuteJavaScript(long self, String code, String url, int line); + private final native void N_Undo(long self); + private final native void N_Redo(long self); + private final native void N_Cut(long self); + private final native void N_Copy(long self); + private final native void N_Paste(long self); +} diff --git a/src/main/java/org/cef/browser/CefMessageRouter.java b/src/main/java/org/cef/browser/CefMessageRouter.java new file mode 100644 index 0000000..1fdfd69 --- /dev/null +++ b/src/main/java/org/cef/browser/CefMessageRouter.java @@ -0,0 +1,261 @@ +// Copyright (c) 2014 The Chromium Embedded Framework Authors. All rights +// reserved. Use of this source code is governed by a BSD-style license that +// can be found in the LICENSE file. + +package org.cef.browser; + +import org.cef.handler.CefMessageRouterHandler; + +/** + * The below classes implement support for routing aynchronous messages between + * JavaScript running in the renderer process and C++ running in the browser + * process. An application interacts with the router by passing it data from + * standard CEF C++ callbacks (OnBeforeBrowse, OnProcessMessageRecieved, + * OnContextCreated, etc). The renderer-side router supports generic JavaScript + * callback registration and execution while the browser-side router supports + * application-specific logic via one or more application-provided Handler + * instances. + * + * The renderer-side router implementation exposes a query function and a cancel + * function via the JavaScript 'window' object: + * + * // Create and send a new query. + * var request_id = window.cefQuery({ + * request: 'my_request', + * persistent: false, + * onSuccess: function(response) {}, + * onFailure: function(error_code, error_message) {} + * }); + * + * // Optionally cancel the query. + * window.cefQueryCancel(request_id); + * + * When |window.cefQuery| is executed the request is sent asynchronously to one + * or more C++ Handler objects registered in the browser process. Each C++ + * Handler can choose to either handle or ignore the query in the + * Handler::OnQuery callback. If a Handler chooses to handle the query then it + * should execute Callback::Success when a response is available or + * Callback::Failure if an error occurs. This will result in asynchronous + * execution of the associated JavaScript callback in the renderer process. Any + * queries unhandled by C++ code in the browser process will be automatically + * canceled and the associated JavaScript onFailure callback will be executed + * with an error code of -1. + * + * Queries can be either persistent or non-persistent. If the query is + * persistent than the callbacks will remain registered until one of the + * following conditions are met: + * + * A. The query is canceled in JavaScript using the |window.cefQueryCancel| + * function. + * B. The query is canceled in C++ code using the Callback::Failure function. + * C. The context associated with the query is released due to browser + * destruction, navigation or renderer process termination. + * + * If the query is non-persistent then the registration will be removed after + * the JavaScript callback is executed a single time. If a query is canceled for + * a reason other than Callback::Failure being executed then the associated + * Handler's OnQueryCanceled method will be called. + * + * Some possible usage patterns include: + * + * One-time Request. Use a non-persistent query to send a JavaScript request. + * The Handler evaluates the request and returns the response. The query is + * then discarded. + * + * Broadcast. Use a persistent query to register as a JavaScript broadcast + * receiver. The Handler keeps track of all registered Callbacks and executes + * them sequentially to deliver the broadcast message. + * + * Subscription. Use a persistent query to register as a JavaScript subscription + * receiver. The Handler initiates the subscription feed on the first request + * and delivers responses to all registered subscribers as they become + * available. The Handler cancels the subscription feed when there are no + * longer any registered JavaScript receivers. + * + * Message routing occurs on a per-browser and per-context basis. Consequently, + * additional application logic can be applied by restricting which browser or + * context instances are passed into the router. If you choose to use this + * approach do so cautiously. In order for the router to function correctly any + * browser or context instance passed into a single router callback must then + * be passed into all router callbacks. + * + * There is generally no need to have multiple renderer-side routers unless you + * wish to have multiple bindings with different JavaScript function names. It + * can be useful to have multiple browser-side routers with different client- + * provided Handler instances when implementing different behaviors on a per- + * browser basis. + * + * This implementation places no formatting restrictions on payload content. + * An application may choose to exchange anything from simple formatted + * strings to serialized XML or JSON data. + * + * + * EXAMPLE USAGE + * + * 1. Define the router configuration. You can optionally specify settings + * like the JavaScript function names. The configuration must be the same in + * both the browser and renderer processes. If using multiple routers in the + * same application make sure to specify unique function names for each + * router configuration. + * + * // Example config object showing the default values. + * CefMessageRouterConfig config = new CefMessageRouterConfig(); + * config.jsQueryFunction = "cefQuery"; + * config.jsCancelFunction = "cefQueryCancel"; + * + * 2. Create an instance of CefMessageRouter in the browser process. + * + * messageRouter_ = CefMessageRouter.create(config); + * + * 3. Register one or more Handlers. The Handler instances must either outlive + * the router or be removed from the router before they're deleted. + * + * messageRouter_.addHandler(myHandler); + * + * 4. Add your message router to all CefClient instances you want to get your + * JavaScript code be handled. + * + * myClient.addMessageRouter(messageRouter_); + * + * 4. Execute the query function from JavaScript code. + * + * window.cefQuery({request: 'my_request', + * persistent: false, + * onSuccess: function(response) { print(response); }, + * onFailure: function(error_code, error_message) {} }); + * + * 5. Handle the query in your CefMessageRouterHandler.onQuery implementation + * and execute the appropriate callback either immediately or asynchronously. + * + * public boolean onQuery(CefBrowser browser, + * long query_id, + * String request, + * boolean persistent, + * CefQueryCallback callback) { + * if (request.indexOf("my_request") == 0) { + * callback.success("my_response"); + * return true; + * } + * return false; // Not handled. + * } + * + * 6. Notice that the success callback is executed in JavaScript. + */ +public abstract class CefMessageRouter { + private CefMessageRouterConfig routerConfig_ = null; + + /** + * Used to configure the query router. If using multiple router pairs make + * sure to choose values that do not conflict. + */ + public static class CefMessageRouterConfig { + /** + * Name of the JavaScript function that will be added to the 'window' object + * for sending a query. The default value is "cefQuery". + */ + public String jsQueryFunction; + + /** + * Name of the JavaScript function that will be added to the 'window' object + * for canceling a pending query. The default value is "cefQueryCancel". + */ + public String jsCancelFunction; + + public CefMessageRouterConfig() { + this("cefQuery", "cefQueryCancel"); + } + + public CefMessageRouterConfig(String queryFunction, String cancelFunction) { + jsQueryFunction = queryFunction; + jsCancelFunction = cancelFunction; + } + } + + // This CTOR can't be called directly. Call method create() instead. + CefMessageRouter() {} + + @Override + protected void finalize() throws Throwable { + dispose(); + super.finalize(); + } + + /** + * Create a new router with the default configuration. The addHandler() method should be called + * to add a handler. + */ + public static final CefMessageRouter create() { + return CefMessageRouter.create(null, null); + } + + /** + * Create a new router with the specified configuration. The addHandler() method should be + * called to add a handler. + */ + public static final CefMessageRouter create(CefMessageRouterConfig config) { + return CefMessageRouter.create(config, null); + } + + /** + * Create a new router with the specified handler and default configuration. + */ + public static final CefMessageRouter create(CefMessageRouterHandler handler) { + return CefMessageRouter.create(null, handler); + } + + /** + * Create a new router with the specified handler and configuration. + */ + public static final CefMessageRouter create( + CefMessageRouterConfig config, CefMessageRouterHandler handler) { + CefMessageRouter router = CefMessageRouter_N.createNative(config); + if (router != null && handler != null) router.addHandler(handler, true); + return router; + } + + /** + * Must be called if the CefMessageRouter instance isn't used any more. + */ + public abstract void dispose(); + + // Called from native code during handling of createNative(). + void setMessageRouterConfig(CefMessageRouterConfig config) { + routerConfig_ = config; + } + + // Called from native code during handling of CefClientHandler.[add|remove]MessageRouter(). + public final CefMessageRouterConfig getMessageRouterConfig() { + return routerConfig_; + } + + /** + * Add a new query handler. + * + * @param handler The handler to be added. + * @param first If true the handler will be added as the first handler, otherwise it will be + * added as the last handler. + * @return True if the handler is added successfully. + */ + public abstract boolean addHandler(CefMessageRouterHandler handler, boolean first); + + /** + * Remove an existing query handler. Any pending queries associated with the handler will be + * canceled. onQueryCanceled will be called and the associated JavaScript onFailure callback + * will be executed with an error code of -1. + * + * @param handler The handler to be removed. + * @return True if the handler is removed successfully. + */ + public abstract boolean removeHandler(CefMessageRouterHandler handler); + + /** + * Cancel all pending queries associated with either |browser| or |handler|. If both |browser| + * and |handler| are NULL all pending queries will be canceled. onQueryCanceled will be called + * and the associated JavaScript onFailure callback will be executed in all cases with an error + * code of -1. + * + * @param browser The associated browser, or null. + * @param handler The associated handler, or null. + */ + public abstract void cancelPending(CefBrowser browser, CefMessageRouterHandler handler); +} diff --git a/src/main/java/org/cef/browser/CefMessageRouter_N.java b/src/main/java/org/cef/browser/CefMessageRouter_N.java new file mode 100644 index 0000000..5f8fefd --- /dev/null +++ b/src/main/java/org/cef/browser/CefMessageRouter_N.java @@ -0,0 +1,82 @@ +// Copyright (c) 2014 The Chromium Embedded Framework Authors. All rights +// reserved. Use of this source code is governed by a BSD-style license that +// can be found in the LICENSE file. + +package org.cef.browser; + +import org.cef.callback.CefNative; +import org.cef.handler.CefMessageRouterHandler; + +class CefMessageRouter_N extends CefMessageRouter implements CefNative { + // Used internally to store a pointer to the CEF object. + private long N_CefHandle = 0; + + @Override + public void setNativeRef(String identifer, long nativeRef) { + N_CefHandle = nativeRef; + } + + @Override + public long getNativeRef(String identifer) { + return N_CefHandle; + } + + private CefMessageRouter_N() { + super(); + } + + public static final CefMessageRouter createNative(CefMessageRouterConfig config) { + try { + return CefMessageRouter_N.N_Create(config); + } catch (UnsatisfiedLinkError ule) { + ule.printStackTrace(); + return null; + } + } + + @Override + public void dispose() { + try { + N_Dispose(N_CefHandle); + } catch (UnsatisfiedLinkError ule) { + ule.printStackTrace(); + } + } + + @Override + public boolean addHandler(CefMessageRouterHandler handler, boolean first) { + try { + return N_AddHandler(N_CefHandle, handler, first); + } catch (UnsatisfiedLinkError ule) { + ule.printStackTrace(); + return false; + } + } + + @Override + public boolean removeHandler(CefMessageRouterHandler handler) { + try { + return N_RemoveHandler(N_CefHandle, handler); + } catch (UnsatisfiedLinkError ule) { + ule.printStackTrace(); + return false; + } + } + + @Override + public void cancelPending(CefBrowser browser, CefMessageRouterHandler handler) { + try { + N_CancelPending(N_CefHandle, browser, handler); + } catch (UnsatisfiedLinkError ule) { + ule.printStackTrace(); + } + } + + private final native static CefMessageRouter_N N_Create(CefMessageRouterConfig config); + private final native void N_Dispose(long self); + private final native boolean N_AddHandler( + long self, CefMessageRouterHandler handler, boolean first); + private final native boolean N_RemoveHandler(long self, CefMessageRouterHandler handler); + private final native void N_CancelPending( + long self, CefBrowser browser, CefMessageRouterHandler handler); +} diff --git a/src/main/java/org/cef/browser/CefRenderer.java b/src/main/java/org/cef/browser/CefRenderer.java new file mode 100644 index 0000000..d453eba --- /dev/null +++ b/src/main/java/org/cef/browser/CefRenderer.java @@ -0,0 +1,209 @@ +// Copyright (c) 2013 The Chromium Embedded Framework Authors. All rights +// reserved. Use of this source code is governed by a BSD-style license that +// can be found in the LICENSE file. + +package org.cef.browser; + +import net.minecraft.client.renderer.BufferBuilder; +import net.minecraft.client.renderer.GlStateManager; +import net.minecraft.client.renderer.Tessellator; +import net.minecraft.client.renderer.vertex.DefaultVertexFormats; +import net.montoyo.mcef.MCEF; +import net.montoyo.mcef.utilities.Log; +import org.lwjgl.opengl.EXTBgra; + +import java.awt.*; +import java.nio.ByteBuffer; +import java.util.ArrayList; + +import static org.lwjgl.opengl.GL11.*; + +public class CefRenderer { + + //montoyo: debug tool + //montoyo: debug tool + private static final ArrayList GL_TEXTURES = new ArrayList<>(); + + public static void dumpVRAMLeak() { + Log.info(">>>>> MCEF: Beginning VRAM leak report"); + GL_TEXTURES.forEach(tex -> Log.warning(">>>>> MCEF: This texture has not been freed: " + tex)); + Log.info(">>>>> MCEF: End of VRAM leak report"); + } + + private boolean transparent_; + public int[] texture_id_ = new int[1]; + private int view_width_ = 0; + private int view_height_ = 0; + private float spin_x_ = 0f; + private float spin_y_ = 0f; + private Rectangle popup_rect_ = new Rectangle(0, 0, 0, 0); + private Rectangle original_popup_rect_ = new Rectangle(0, 0, 0, 0); + private boolean use_draw_pixels_ = false; + + protected CefRenderer(boolean transparent) { + transparent_ = transparent; + initialize(); + } + + protected boolean isTransparent() { + return transparent_; + } + + protected int getTextureID() { + return texture_id_[0]; + } + + protected void initialize() { + GlStateManager.enableTexture2D(); + texture_id_[0] = glGenTextures(); + + if (MCEF.CHECK_VRAM_LEAK) + GL_TEXTURES.add(texture_id_[0]); + + GlStateManager.bindTexture(texture_id_[0]); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE); + GlStateManager.bindTexture(0); + } + + protected void cleanup() { + if (texture_id_[0] != 0) { + if (MCEF.CHECK_VRAM_LEAK) + GL_TEXTURES.remove((Object) texture_id_[0]); + + glDeleteTextures(texture_id_[0]); + } + } + + protected void render(double x1, double y1, double x2, double y2) { + if (view_width_ == 0 || view_height_ == 0) + return; + + Tessellator t = Tessellator.getInstance(); + BufferBuilder vb = t.getBuffer(); + + GlStateManager.bindTexture(texture_id_[0]); + vb.begin(GL_QUADS, DefaultVertexFormats.POSITION_TEX_COLOR); + vb.pos(x1, y1, 0.0).tex(0.0, 1.0).color(255, 255, 255, 255).endVertex(); + vb.pos(x2, y1, 0.0).tex(1.f, 1.f).color(255, 255, 255, 255).endVertex(); + vb.pos(x2, y2, 0.0).tex(1.f, 0.0).color(255, 255, 255, 255).endVertex(); + vb.pos(x1, y2, 0.0).tex(0.0, 0.0).color(255, 255, 255, 255).endVertex(); + t.draw(); + GlStateManager.bindTexture(0); + } + + protected void onPopupSize(Rectangle rect) { + if (rect.width <= 0 || rect.height <= 0) return; + original_popup_rect_ = rect; + popup_rect_ = getPopupRectInWebView(original_popup_rect_); + } + + protected Rectangle getPopupRect() { + return (Rectangle) popup_rect_.clone(); + } + + protected Rectangle getPopupRectInWebView(Rectangle original_rect) { + Rectangle rc = original_rect; + // if x or y are negative, move them to 0. + if (rc.x < 0) rc.x = 0; + if (rc.y < 0) rc.y = 0; + // if popup goes outside the view, try to reposition origin + if (rc.x + rc.width > view_width_) rc.x = view_width_ - rc.width; + if (rc.y + rc.height > view_height_) rc.y = view_height_ - rc.height; + // if x or y became negative, move them to 0 again. + if (rc.x < 0) rc.x = 0; + if (rc.y < 0) rc.y = 0; + return rc; + } + + protected void clearPopupRects() { + popup_rect_.setBounds(0, 0, 0, 0); + original_popup_rect_.setBounds(0, 0, 0, 0); + } + + protected void onPaint(boolean popup, Rectangle[] dirtyRects, ByteBuffer buffer, int width, int height, boolean completeReRender) { + if (transparent_) // Enable alpha blending. + GlStateManager.enableBlend(); + + final int size = (width * height) << 2; + if (size > buffer.limit()) { + Log.warning("Bad data passed to CefRenderer.onPaint() triggered safe guards... (1)"); + return; + } + + // Enable 2D textures. + GlStateManager.enableTexture2D(); + GlStateManager.bindTexture(texture_id_[0]); + + int oldAlignement = glGetInteger(GL_UNPACK_ALIGNMENT); + glPixelStorei(GL_UNPACK_ALIGNMENT, 1); + + if (!popup) { + if (completeReRender || width != view_width_ || height != view_height_) { + // Update/resize the whole texture. + view_width_ = width; + view_height_ = height; + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, view_width_, view_height_, 0, EXTBgra.GL_BGRA_EXT, GL_UNSIGNED_BYTE, buffer); + } else { + glPixelStorei(GL_UNPACK_ROW_LENGTH, view_width_); + + // Update just the dirty rectangles. + for (Rectangle rect : dirtyRects) { + if (rect.x < 0 || rect.y < 0 || rect.x + rect.width > view_width_ || rect.y + rect.height > view_height_) + Log.warning("Bad data passed to CefRenderer.onPaint() triggered safe guards... (2)"); + else { + glPixelStorei(GL_UNPACK_SKIP_PIXELS, rect.x); + glPixelStorei(GL_UNPACK_SKIP_ROWS, rect.y); + glTexSubImage2D(GL_TEXTURE_2D, 0, rect.x, rect.y, rect.width, rect.height, EXTBgra.GL_BGRA_EXT, GL_UNSIGNED_BYTE, buffer); + } + } + + glPixelStorei(GL_UNPACK_SKIP_PIXELS, 0); + glPixelStorei(GL_UNPACK_SKIP_ROWS, 0); + glPixelStorei(GL_UNPACK_ROW_LENGTH, 0); + } + } else if (popup_rect_.width > 0 && popup_rect_.height > 0) { + int skip_pixels = 0, x = popup_rect_.x; + int skip_rows = 0, y = popup_rect_.y; + int w = width; + int h = height; + + // Adjust the popup to fit inside the view. + if (x < 0) { + skip_pixels = -x; + x = 0; + } + if (y < 0) { + skip_rows = -y; + y = 0; + } + if (x + w > view_width_) + w -= x + w - view_width_; + if (y + h > view_height_) + h -= y + h - view_height_; + + // Update the popup rectangle. + glPixelStorei(GL_UNPACK_ROW_LENGTH, width); + glPixelStorei(GL_UNPACK_SKIP_PIXELS, skip_pixels); + glPixelStorei(GL_UNPACK_SKIP_ROWS, skip_rows); + glTexSubImage2D(GL_TEXTURE_2D, 0, x, y, w, h, EXTBgra.GL_BGRA_EXT, GL_UNSIGNED_BYTE, buffer); + glPixelStorei(GL_UNPACK_ROW_LENGTH, 0); + glPixelStorei(GL_UNPACK_SKIP_PIXELS, 0); + glPixelStorei(GL_UNPACK_SKIP_ROWS, 0); + } + + glPixelStorei(GL_UNPACK_ALIGNMENT, oldAlignement); + GlStateManager.bindTexture(0); + } + + protected void setSpin(float spinX, float spinY) { + spin_x_ = spinX; + spin_y_ = spinY; + } + + protected void incrementSpin(float spinDX, float spinDY) { + spin_x_ -= spinDX; + spin_y_ -= spinDY; + } +} diff --git a/src/main/java/org/cef/browser/CefRequestContext.java b/src/main/java/org/cef/browser/CefRequestContext.java new file mode 100644 index 0000000..26fa66a --- /dev/null +++ b/src/main/java/org/cef/browser/CefRequestContext.java @@ -0,0 +1,53 @@ +// Copyright (c) 2014 The Chromium Embedded Framework Authors. All rights +// reserved. Use of this source code is governed by a BSD-style license that +// can be found in the LICENSE file. + +package org.cef.browser; + +import org.cef.handler.CefRequestContextHandler; + +/** + * A request context provides request handling for a set of related browser + * objects. A request context is specified when creating a new browser object + * via the CefClient.createBrowser method. Browser objects with different + * request contexts will never be hosted in the same render process. Browser + * objects with the same request context may or may not be hosted in the same + * render process depending on the process model. Browser objects created + * indirectly via the JavaScript window.open function or targeted links will + * share the same render process and the same request context as the source + * browser. When running in single-process mode there is only a single render + * process (the main process) and so all browsers created in single-process mode + * will share the same request context. This will be the first request context + * passed into the CefClient.createBrowser method and all other request + * context objects will be ignored. + */ +public abstract class CefRequestContext { + // This CTOR can't be called directly. Call method create() instead. + CefRequestContext() {} + + /** + * Returns the global context object. + */ + public static final CefRequestContext getGlobalContext() { + return CefRequestContext_N.getGlobalContextNative(); + } + + /** + * Creates a new context object with the specified handler. + */ + public static final CefRequestContext createContext(CefRequestContextHandler handler) { + return CefRequestContext_N.createNative(handler); + } + + public abstract void dispose(); + + /** + * Returns true if this object is the global context. + */ + public abstract boolean isGlobal(); + + /** + * Returns the handler for this context if any. + */ + public abstract CefRequestContextHandler getHandler(); +} diff --git a/src/main/java/org/cef/browser/CefRequestContext_N.java b/src/main/java/org/cef/browser/CefRequestContext_N.java new file mode 100644 index 0000000..622a9ab --- /dev/null +++ b/src/main/java/org/cef/browser/CefRequestContext_N.java @@ -0,0 +1,86 @@ +// Copyright (c) 2014 The Chromium Embedded Framework Authors. All rights +// reserved. Use of this source code is governed by a BSD-style license that +// can be found in the LICENSE file. + +package org.cef.browser; + +import org.cef.callback.CefNative; +import org.cef.handler.CefRequestContextHandler; + +class CefRequestContext_N extends CefRequestContext implements CefNative { + // Used internally to store a pointer to the CEF object. + private long N_CefHandle = 0; + private static CefRequestContext_N globalInstance = null; + private CefRequestContextHandler handler = null; + + @Override + public void setNativeRef(String identifer, long nativeRef) { + N_CefHandle = nativeRef; + } + + @Override + public long getNativeRef(String identifer) { + return N_CefHandle; + } + + CefRequestContext_N() { + super(); + } + + static final CefRequestContext_N getGlobalContextNative() { + CefRequestContext_N result = null; + try { + result = CefRequestContext_N.N_GetGlobalContext(); + } catch (UnsatisfiedLinkError ule) { + ule.printStackTrace(); + } + + if (globalInstance == null) { + globalInstance = result; + } else if (globalInstance.N_CefHandle == result.N_CefHandle) { + result.N_CefRequestContext_DTOR(); + } + return globalInstance; + } + + static final CefRequestContext_N createNative(CefRequestContextHandler handler) { + CefRequestContext_N result = null; + try { + result = CefRequestContext_N.N_CreateContext(handler); + } catch (UnsatisfiedLinkError ule) { + ule.printStackTrace(); + } + if (result != null) result.handler = handler; + return result; + } + + @Override + public void dispose() { + try { + N_CefRequestContext_DTOR(); + } catch (UnsatisfiedLinkError ule) { + ule.printStackTrace(); + } + } + + @Override + public boolean isGlobal() { + try { + return N_IsGlobal(); + } catch (UnsatisfiedLinkError ule) { + ule.printStackTrace(); + } + return false; + } + + @Override + public CefRequestContextHandler getHandler() { + return handler; + } + + private final static native CefRequestContext_N N_GetGlobalContext(); + private final static native CefRequestContext_N N_CreateContext( + CefRequestContextHandler handler); + private final native boolean N_IsGlobal(); + private final native void N_CefRequestContext_DTOR(); +} diff --git a/src/main/java/org/cef/browser/mac/CefBrowserWindowMac.java b/src/main/java/org/cef/browser/mac/CefBrowserWindowMac.java new file mode 100644 index 0000000..ed2580e --- /dev/null +++ b/src/main/java/org/cef/browser/mac/CefBrowserWindowMac.java @@ -0,0 +1,16 @@ +// Copyright (c) 2014 The Chromium Embedded Framework Authors. All rights +// reserved. Use of this source code is governed by a BSD-style license that +// can be found in the LICENSE file. + +package org.cef.browser.mac; + +import org.cef.browser.CefBrowserWindow; + +import java.awt.Component; + +public class CefBrowserWindowMac implements CefBrowserWindow { + @Override + public long getWindowHandle(Component comp) { + return 0L; + } +} diff --git a/src/main/java/org/cef/callback/CefAuthCallback.java b/src/main/java/org/cef/callback/CefAuthCallback.java new file mode 100644 index 0000000..9cef71b --- /dev/null +++ b/src/main/java/org/cef/callback/CefAuthCallback.java @@ -0,0 +1,21 @@ +// Copyright (c) 2014 The Chromium Embedded Framework Authors. All rights +// reserved. Use of this source code is governed by a BSD-style license that +// can be found in the LICENSE file. + +package org.cef.callback; + +/** + * Callback interface used for asynchronous continuation of authentication + * requests. + */ +public interface CefAuthCallback { + /** + * Continue the authentication request. + */ + public void Continue(String username, String password); + + /** + * Cancel the authentication request. + */ + public void cancel(); +} diff --git a/src/main/java/org/cef/callback/CefAuthCallback_N.java b/src/main/java/org/cef/callback/CefAuthCallback_N.java new file mode 100644 index 0000000..7957557 --- /dev/null +++ b/src/main/java/org/cef/callback/CefAuthCallback_N.java @@ -0,0 +1,36 @@ +// Copyright (c) 2014 The Chromium Embedded Framework Authors. All rights +// reserved. Use of this source code is governed by a BSD-style license that +// can be found in the LICENSE file. + +package org.cef.callback; + +class CefAuthCallback_N extends CefNativeAdapter implements CefAuthCallback { + CefAuthCallback_N() {} + + @Override + protected void finalize() throws Throwable { + cancel(); + super.finalize(); + } + + @Override + public void Continue(String username, String password) { + try { + N_Continue(getNativeRef(null), username, password); + } catch (UnsatisfiedLinkError ule) { + ule.printStackTrace(); + } + } + + @Override + public void cancel() { + try { + N_Cancel(getNativeRef(null)); + } catch (UnsatisfiedLinkError ule) { + ule.printStackTrace(); + } + } + + private final native void N_Continue(long self, String username, String password); + private final native void N_Cancel(long self); +} diff --git a/src/main/java/org/cef/callback/CefBeforeDownloadCallback.java b/src/main/java/org/cef/callback/CefBeforeDownloadCallback.java new file mode 100644 index 0000000..442eb49 --- /dev/null +++ b/src/main/java/org/cef/callback/CefBeforeDownloadCallback.java @@ -0,0 +1,20 @@ +// Copyright (c) 2014 The Chromium Embedded Framework Authors. All rights +// reserved. Use of this source code is governed by a BSD-style license that +// can be found in the LICENSE file. + +package org.cef.callback; + +/** + * Callback interface used to asynchronously continue a download. + */ +public interface CefBeforeDownloadCallback { + /** + * Call to continue the download. + * @param downloadPath Set it to the full file path for the download + * including the file name or leave blank to use the suggested name and + * the default temp directory. + * @param showDialog Set it to true if you do wish to show the default + * "Save As" dialog. + */ + public void Continue(String downloadPath, boolean showDialog); +} diff --git a/src/main/java/org/cef/callback/CefBeforeDownloadCallback_N.java b/src/main/java/org/cef/callback/CefBeforeDownloadCallback_N.java new file mode 100644 index 0000000..93a14f2 --- /dev/null +++ b/src/main/java/org/cef/callback/CefBeforeDownloadCallback_N.java @@ -0,0 +1,26 @@ +// Copyright (c) 2014 The Chromium Embedded Framework Authors. All rights +// reserved. Use of this source code is governed by a BSD-style license that +// can be found in the LICENSE file. + +package org.cef.callback; + +class CefBeforeDownloadCallback_N extends CefNativeAdapter implements CefBeforeDownloadCallback { + CefBeforeDownloadCallback_N() {} + + @Override + protected void finalize() throws Throwable { + Continue("", false); + super.finalize(); + } + + @Override + public void Continue(String downloadPath, boolean showDialog) { + try { + N_Continue(getNativeRef(null), downloadPath, showDialog); + } catch (UnsatisfiedLinkError ule) { + ule.printStackTrace(); + } + } + + private final native void N_Continue(long self, String downloadPath, boolean showDialog); +} diff --git a/src/main/java/org/cef/callback/CefCallback.java b/src/main/java/org/cef/callback/CefCallback.java new file mode 100644 index 0000000..9d4f405 --- /dev/null +++ b/src/main/java/org/cef/callback/CefCallback.java @@ -0,0 +1,20 @@ +// Copyright (c) 2014 The Chromium Embedded Framework Authors. All rights +// reserved. Use of this source code is governed by a BSD-style license that +// can be found in the LICENSE file. + +package org.cef.callback; + +/** + * Generic callback interface used for asynchronous continuation. + */ +public interface CefCallback { + /** + * Continue processing. + */ + void Continue(); + + /** + * Cancel processing. + */ + void cancel(); +} diff --git a/src/main/java/org/cef/callback/CefCallback_N.java b/src/main/java/org/cef/callback/CefCallback_N.java new file mode 100644 index 0000000..fb4321a --- /dev/null +++ b/src/main/java/org/cef/callback/CefCallback_N.java @@ -0,0 +1,36 @@ +// Copyright (c) 2014 The Chromium Embedded Framework Authors. All rights +// reserved. Use of this source code is governed by a BSD-style license that +// can be found in the LICENSE file. + +package org.cef.callback; + +class CefCallback_N extends CefNativeAdapter implements CefCallback { + CefCallback_N() {} + + @Override + protected void finalize() throws Throwable { + cancel(); + super.finalize(); + } + + @Override + public void Continue() { + try { + N_Continue(getNativeRef(null)); + } catch (UnsatisfiedLinkError ule) { + ule.printStackTrace(); + } + } + + @Override + public void cancel() { + try { + N_Cancel(getNativeRef(null)); + } catch (UnsatisfiedLinkError ule) { + ule.printStackTrace(); + } + } + + private final native void N_Continue(long self); + private final native void N_Cancel(long self); +} diff --git a/src/main/java/org/cef/callback/CefCommandLine.java b/src/main/java/org/cef/callback/CefCommandLine.java new file mode 100644 index 0000000..1daeba4 --- /dev/null +++ b/src/main/java/org/cef/callback/CefCommandLine.java @@ -0,0 +1,95 @@ +// Copyright (c) 2014 The Chromium Embedded Framework Authors. All rights +// reserved. Use of this source code is governed by a BSD-style license that +// can be found in the LICENSE file. + +package org.cef.callback; + +import java.util.Map; +import java.util.Vector; + +/** + * Class used to create and/or parse command line arguments. Arguments with + * '--', '-' and, on Windows, '/' prefixes are considered switches. Switches + * will always precede any arguments without switch prefixes. Switches can + * optionally have a value specified using the '=' delimiter (e.g. + * "-switch=value"). An argument of "--" will terminate switch parsing with all + * subsequent tokens, regardless of prefix, being interpreted as non-switch + * arguments. Switch names are considered case-insensitive. + */ +public interface CefCommandLine { + /** + * Reset the command-line switches and arguments but leave the program + * component unchanged. + */ + public void reset(); + + /** + * Get the program part of the command line string (the first item). + */ + public String getProgram(); + + /** + * Set the program part of the command line string (the first item). + * @param program Name of the program. + */ + public void setProgram(String program); + + /** + * Checks if the command line has switches. + * @return true if the command line has switches. + */ + public boolean hasSwitches(); + + /** + * Checks if the command line has a specific switches. + * @param name A switch name to test for. + * @return true if the command line contains the given switch. + */ + public boolean hasSwitch(String name); + + /** + * Returns the value associated with the given switch. If the switch has no + * value or isn't present this method returns the empty string. + * @param name the name of the switch. + * @return the value of the switch. + */ + public String getSwitchValue(String name); + + /** + * Returns the map of switch names and values. If a switch has no value an + * empty string is returned. + * @return Map of switches and each value. + */ + public Map getSwitches(); + + /** + * Add a switch with an empty value to the end of the command line. + * @param name name of the switch. + */ + public void appendSwitch(String name); + + /** + * Add a switch with the specified value to the end of the command line. + * @param name name of the switch. + * @param value value for the switch. + */ + public void appendSwitchWithValue(String name, String value); + + /** + * Tests if there are remaining command line arguments. + * @return True if there are remaining command line arguments. + */ + public boolean hasArguments(); + + /** + * Get the remaining command line arguments. + * @return Vector of command line arguments. + */ + public Vector getArguments(); + + /** + * Add an argument to the end of the command line. + * @param argument name of the argument. + */ + public void appendArgument(String argument); +} diff --git a/src/main/java/org/cef/callback/CefCommandLine_N.java b/src/main/java/org/cef/callback/CefCommandLine_N.java new file mode 100644 index 0000000..27881fc --- /dev/null +++ b/src/main/java/org/cef/callback/CefCommandLine_N.java @@ -0,0 +1,154 @@ +// Copyright (c) 2014 The Chromium Embedded Framework Authors. All rights +// reserved. Use of this source code is governed by a BSD-style license that +// can be found in the LICENSE file. + +package org.cef.callback; + +import java.util.Map; +import java.util.Vector; + +class CefCommandLine_N extends CefNativeAdapter implements CefCommandLine { + CefCommandLine_N() {} + + @Override + public void reset() { + try { + N_Reset(getNativeRef(null)); + } catch (UnsatisfiedLinkError err) { + err.printStackTrace(); + } + } + + @Override + public String getProgram() { + try { + return N_GetProgram(getNativeRef(null)); + } catch (UnsatisfiedLinkError err) { + err.printStackTrace(); + } + return null; + } + + @Override + public void setProgram(String program) { + try { + N_SetProgram(getNativeRef(null), program); + } catch (UnsatisfiedLinkError err) { + err.printStackTrace(); + } + } + + @Override + public boolean hasSwitches() { + try { + return N_HasSwitches(getNativeRef(null)); + } catch (UnsatisfiedLinkError err) { + err.printStackTrace(); + } + return false; + } + + @Override + public boolean hasSwitch(String name) { + try { + return N_HasSwitch(getNativeRef(null), name); + } catch (UnsatisfiedLinkError err) { + err.printStackTrace(); + } + return false; + } + + @Override + public String getSwitchValue(String name) { + try { + return N_GetSwitchValue(getNativeRef(null), name); + } catch (UnsatisfiedLinkError err) { + err.printStackTrace(); + } + return null; + } + + @Override + public Map getSwitches() { + try { + return N_GetSwitches(getNativeRef(null)); + } catch (UnsatisfiedLinkError err) { + err.printStackTrace(); + } + return null; + } + + @Override + public void appendSwitch(String name) { + try { + N_AppendSwitch(getNativeRef(null), name); + } catch (UnsatisfiedLinkError err) { + err.printStackTrace(); + } + } + + @Override + public void appendSwitchWithValue(String name, String value) { + try { + N_AppendSwitchWithValue(getNativeRef(null), name, value); + } catch (UnsatisfiedLinkError err) { + err.printStackTrace(); + } + } + + @Override + public boolean hasArguments() { + try { + return N_HasArguments(getNativeRef(null)); + } catch (UnsatisfiedLinkError err) { + err.printStackTrace(); + } + return false; + } + + @Override + public Vector getArguments() { + try { + return N_GetArguments(getNativeRef(null)); + } catch (UnsatisfiedLinkError err) { + err.printStackTrace(); + } + return null; + } + + @Override + public void appendArgument(String argument) { + try { + N_AppendArgument(getNativeRef(null), argument); + } catch (UnsatisfiedLinkError err) { + err.printStackTrace(); + } + } + + @Override + public String toString() { + String result = "CefCommandLine [program=\'" + getProgram() + "\'"; + if (hasSwitches()) { + Map switches = getSwitches(); + result += ", switches=" + switches; + } + if (hasArguments()) { + Vector arguments = getArguments(); + result += ", arguments=" + arguments; + } + return result + "]"; + } + + private final native void N_Reset(long self); + private final native String N_GetProgram(long self); + private final native void N_SetProgram(long self, String program); + private final native boolean N_HasSwitches(long self); + private final native boolean N_HasSwitch(long self, String name); + private final native String N_GetSwitchValue(long self, String name); + private final native Map N_GetSwitches(long self); + private final native void N_AppendSwitch(long self, String name); + private final native void N_AppendSwitchWithValue(long self, String name, String value); + private final native boolean N_HasArguments(long self); + private final native Vector N_GetArguments(long self); + private final native void N_AppendArgument(long self, String argument); +} diff --git a/src/main/java/org/cef/callback/CefCompletionCallback.java b/src/main/java/org/cef/callback/CefCompletionCallback.java new file mode 100644 index 0000000..7349390 --- /dev/null +++ b/src/main/java/org/cef/callback/CefCompletionCallback.java @@ -0,0 +1,15 @@ +// Copyright (c) 2014 The Chromium Embedded Framework Authors. All rights +// reserved. Use of this source code is governed by a BSD-style license that +// can be found in the LICENSE file. + +package org.cef.callback; + +/** + * Generic callback interface used for asynchronous completion. + */ +public interface CefCompletionCallback { + /** + * Method that will be called once the task is complete. + */ + public abstract void onComplete(); +} diff --git a/src/main/java/org/cef/callback/CefContextMenuParams.java b/src/main/java/org/cef/callback/CefContextMenuParams.java new file mode 100644 index 0000000..e2c4d58 --- /dev/null +++ b/src/main/java/org/cef/callback/CefContextMenuParams.java @@ -0,0 +1,180 @@ +// Copyright (c) 2014 The Chromium Embedded Framework Authors. All rights +// reserved. Use of this source code is governed by a BSD-style license that +// can be found in the LICENSE file. + +package org.cef.callback; + +import java.util.Vector; + +/** + * Provides information about the context menu state. The methods of this class + * can only be accessed on browser process the UI thread. + */ +public interface CefContextMenuParams { + /** + * Supported context menu type flags. + */ + public static final class TypeFlags { + public final static int CM_TYPEFLAG_NONE = 0; //!< No node is selected. + public final static int CM_TYPEFLAG_PAGE = 1 << 0; //!< The top page is selected. + public final static int CM_TYPEFLAG_FRAME = 1 << 1; //!< A subframe page is selected. + public final static int CM_TYPEFLAG_LINK = 1 << 2; //!< A link is selected. + public final static int CM_TYPEFLAG_MEDIA = 1 << 3; //!< A media node is selected. + public final static int CM_TYPEFLAG_SELECTION = 1 + << 4; //!< There is a textual or mixed selection that is selected. + public final static int CM_TYPEFLAG_EDITABLE = 1 << 5; //!< An editable element is selected. + } + + /** + * Supported context menu media types. + */ + public enum MediaType { + CM_MEDIATYPE_NONE, //!< No special node is in context. + CM_MEDIATYPE_IMAGE, //!< An image node is selected. + CM_MEDIATYPE_VIDEO, //!< A video node is selected. + CM_MEDIATYPE_AUDIO, //!< An audio node is selected. + CM_MEDIATYPE_FILE, //!< A file node is selected. + CM_MEDIATYPE_PLUGIN, //!< A plugin node is selected. + } + + /** + * Supported context menu media state bit flags. + */ + public static final class MediaStateFlags { + public final static int CM_MEDIAFLAG_NONE = 0; + public final static int CM_MEDIAFLAG_ERROR = 1 << 0; + public final static int CM_MEDIAFLAG_PAUSED = 1 << 1; + public final static int CM_MEDIAFLAG_MUTED = 1 << 2; + public final static int CM_MEDIAFLAG_LOOP = 1 << 3; + public final static int CM_MEDIAFLAG_CAN_SAVE = 1 << 4; + public final static int CM_MEDIAFLAG_HAS_AUDIO = 1 << 5; + public final static int CM_MEDIAFLAG_HAS_VIDEO = 1 << 6; + public final static int CM_MEDIAFLAG_CONTROL_ROOT_ELEMENT = 1 << 7; + public final static int CM_MEDIAFLAG_CAN_PRINT = 1 << 8; + public final static int CM_MEDIAFLAG_CAN_ROTATE = 1 << 9; + } + + /** + * Supported context menu edit state bit flags. + */ + public static final class EditStateFlags { + public final static int CM_EDITFLAG_NONE = 0; + public final static int CM_EDITFLAG_CAN_UNDO = 1 << 0; + public final static int CM_EDITFLAG_CAN_REDO = 1 << 1; + public final static int CM_EDITFLAG_CAN_CUT = 1 << 2; + public final static int CM_EDITFLAG_CAN_COPY = 1 << 3; + public final static int CM_EDITFLAG_CAN_PASTE = 1 << 4; + public final static int CM_EDITFLAG_CAN_DELETE = 1 << 5; + public final static int CM_EDITFLAG_CAN_SELECT_ALL = 1 << 6; + public final static int CM_EDITFLAG_CAN_TRANSLATE = 1 << 7; + } + + /** + * Returns the X coordinate of the mouse where the context menu was invoked. + * Coords are relative to the associated RenderView's origin. + */ + int getXCoord(); + + /** + * Returns the Y coordinate of the mouse where the context menu was invoked. + * Coords are relative to the associated RenderView's origin. + */ + int getYCoord(); + + /** + * Returns flags representing the type of node that the context menu was + * invoked on. See TypeFlags for supported values + */ + int getTypeFlags(); + + /** + * Returns the URL of the link, if any, that encloses the node that the + * context menu was invoked on. + */ + String getLinkUrl(); + + /** + * Returns the link URL, if any, to be used ONLY for "copy link address". We + * don't validate this field in the frontend process. + */ + String getUnfilteredLinkUrl(); + + /** + * Returns the source URL, if any, for the element that the context menu was + * invoked on. Example of elements with source URLs are img, audio, and video. + */ + String getSourceUrl(); + + /** + * Returns true if the context menu was invoked on an image which has + * non-empty contents. + */ + boolean hasImageContents(); + + /** + * Returns the URL of the top level page that the context menu was invoked on. + */ + String getPageUrl(); + + /** + * Returns the URL of the subframe that the context menu was invoked on. + */ + String getFrameUrl(); + + /** + * Returns the character encoding of the subframe that the context menu was + * invoked on. + */ + String getFrameCharset(); + + /** + * Returns the type of context node that the context menu was invoked on. + */ + MediaType getMediaType(); + + /** + * Returns flags representing the actions supported by the media element, if + * any, that the context menu was invoked on. See MediaStateFlags for possible + * values. + */ + int getMediaStateFlags(); + + /** + * Returns the text of the selection, if any, that the context menu was + * invoked on. + */ + + String getSelectionText(); + + /** + * Returns the text of the misspelled word, if any, that the context menu was + * invoked on. + */ + String getMisspelledWord(); + + /** + * Returns true if suggestions exist, false otherwise. Fills in |suggestions| + * from the spell check service for the misspelled word if there is one. + */ + boolean getDictionarySuggestions(Vector suggestions); + + /** + * Returns true if the context menu was invoked on an editable node. + */ + + boolean isEditable(); + + /** + * Returns true if the context menu was invoked on an editable node where + * spell-check is enabled. + */ + + boolean isSpellCheckEnabled(); + + /** + * Returns flags representing the actions supported by the editable node, if + * any, that the context menu was invoked on. See EditStateFlags for possible + * values. + */ + int getEditStateFlags(); +} diff --git a/src/main/java/org/cef/callback/CefContextMenuParams_N.java b/src/main/java/org/cef/callback/CefContextMenuParams_N.java new file mode 100644 index 0000000..a7bd7ad --- /dev/null +++ b/src/main/java/org/cef/callback/CefContextMenuParams_N.java @@ -0,0 +1,210 @@ +// Copyright (c) 2014 The Chromium Embedded Framework Authors. All rights +// reserved. Use of this source code is governed by a BSD-style license that +// can be found in the LICENSE file. + +package org.cef.callback; + +import java.util.Vector; + +class CefContextMenuParams_N extends CefNativeAdapter implements CefContextMenuParams { + CefContextMenuParams_N() {} + + @Override + public int getXCoord() { + try { + return N_GetXCoord(getNativeRef(null)); + } catch (UnsatisfiedLinkError ule) { + ule.printStackTrace(); + } + return 0; + } + + @Override + public int getYCoord() { + try { + return N_GetYCoord(getNativeRef(null)); + } catch (UnsatisfiedLinkError ule) { + ule.printStackTrace(); + } + return 0; + } + + @Override + public int getTypeFlags() { + try { + return N_GetTypeFlags(getNativeRef(null)); + } catch (UnsatisfiedLinkError ule) { + ule.printStackTrace(); + } + return 0; + } + + @Override + public String getLinkUrl() { + try { + return N_GetLinkUrl(getNativeRef(null)); + } catch (UnsatisfiedLinkError ule) { + ule.printStackTrace(); + } + return null; + } + + @Override + public String getUnfilteredLinkUrl() { + try { + return N_GetUnfilteredLinkUrl(getNativeRef(null)); + } catch (UnsatisfiedLinkError ule) { + ule.printStackTrace(); + } + return null; + } + + @Override + public String getSourceUrl() { + try { + return N_GetSourceUrl(getNativeRef(null)); + } catch (UnsatisfiedLinkError ule) { + ule.printStackTrace(); + } + return null; + } + + @Override + public boolean hasImageContents() { + try { + return N_HasImageContents(getNativeRef(null)); + } catch (UnsatisfiedLinkError ule) { + ule.printStackTrace(); + } + return false; + } + + @Override + public String getPageUrl() { + try { + return N_GetPageUrl(getNativeRef(null)); + } catch (UnsatisfiedLinkError ule) { + ule.printStackTrace(); + } + return null; + } + + @Override + public String getFrameUrl() { + try { + return N_GetFrameUrl(getNativeRef(null)); + } catch (UnsatisfiedLinkError ule) { + ule.printStackTrace(); + } + return null; + } + + @Override + public String getFrameCharset() { + try { + return N_GetFrameCharset(getNativeRef(null)); + } catch (UnsatisfiedLinkError ule) { + ule.printStackTrace(); + } + return null; + } + + @Override + public MediaType getMediaType() { + try { + return N_GetMediaType(getNativeRef(null)); + } catch (UnsatisfiedLinkError ule) { + ule.printStackTrace(); + } + return null; + } + + @Override + public int getMediaStateFlags() { + try { + return N_GetMediaStateFlags(getNativeRef(null)); + } catch (UnsatisfiedLinkError ule) { + ule.printStackTrace(); + } + return 0; + } + + @Override + public String getSelectionText() { + try { + return N_GetSelectionText(getNativeRef(null)); + } catch (UnsatisfiedLinkError ule) { + ule.printStackTrace(); + } + return null; + } + + @Override + public String getMisspelledWord() { + try { + return N_GetMisspelledWord(getNativeRef(null)); + } catch (UnsatisfiedLinkError ule) { + ule.printStackTrace(); + } + return null; + } + + @Override + public boolean getDictionarySuggestions(Vector suggestions) { + try { + return N_GetDictionarySuggestions(getNativeRef(null), suggestions); + } catch (UnsatisfiedLinkError ule) { + ule.printStackTrace(); + } + return false; + } + + @Override + public boolean isEditable() { + try { + return N_IsEditable(getNativeRef(null)); + } catch (UnsatisfiedLinkError ule) { + ule.printStackTrace(); + } + return false; + } + + @Override + public boolean isSpellCheckEnabled() { + try { + return N_IsSpellCheckEnabled(getNativeRef(null)); + } catch (UnsatisfiedLinkError ule) { + ule.printStackTrace(); + } + return false; + } + + @Override + public int getEditStateFlags() { + try { + return N_GetEditStateFlags(getNativeRef(null)); + } catch (UnsatisfiedLinkError ule) { + ule.printStackTrace(); + } + return 0; + } + + private final native int N_GetXCoord(long self); + private final native int N_GetYCoord(long self); + private final native int N_GetTypeFlags(long self); + private final native String N_GetLinkUrl(long self); + private final native String N_GetUnfilteredLinkUrl(long self); + private final native String N_GetSourceUrl(long self); + private final native boolean N_HasImageContents(long self); + private final native String N_GetPageUrl(long self); + private final native String N_GetFrameUrl(long self); + private final native String N_GetFrameCharset(long self); + private final native MediaType N_GetMediaType(long self); + private final native int N_GetMediaStateFlags(long self); + private final native String N_GetSelectionText(long self); + private final native String N_GetMisspelledWord(long self); + private final native boolean N_GetDictionarySuggestions(long self, Vector suggestions); + private final native boolean N_IsEditable(long self); + private final native boolean N_IsSpellCheckEnabled(long self); + private final native int N_GetEditStateFlags(long self); +} diff --git a/src/main/java/org/cef/callback/CefCookieVisitor.java b/src/main/java/org/cef/callback/CefCookieVisitor.java new file mode 100644 index 0000000..1d55764 --- /dev/null +++ b/src/main/java/org/cef/callback/CefCookieVisitor.java @@ -0,0 +1,23 @@ +// Copyright (c) 2014 The Chromium Embedded Framework Authors. All rights +// reserved. Use of this source code is governed by a BSD-style license that +// can be found in the LICENSE file. + +package org.cef.callback; + +import org.cef.misc.BoolRef; +import org.cef.network.CefCookie; + +/** + * Interface to implement for visiting cookie values. The methods of this class + * will always be called on the IO thread. + */ +public interface CefCookieVisitor { + /** + * Method that will be called once for each cookie. |count| is the 0-based + * index for the current cookie. |total| is the total number of cookies. + * Set |deleteCookie| to true to delete the cookie currently being visited. + * Return false to stop visiting cookies. This method may never be called if + * no cookies are found. + */ + public abstract boolean visit(CefCookie cookie, int count, int total, BoolRef delete); +} diff --git a/src/main/java/org/cef/callback/CefDownloadItem.java b/src/main/java/org/cef/callback/CefDownloadItem.java new file mode 100644 index 0000000..b9302dd --- /dev/null +++ b/src/main/java/org/cef/callback/CefDownloadItem.java @@ -0,0 +1,94 @@ +// Copyright (c) 2014 The Chromium Embedded Framework Authors. All rights +// reserved. Use of this source code is governed by a BSD-style license that +// can be found in the LICENSE file. + +package org.cef.callback; + +import java.util.Date; + +/** + * Class used to represent a download item. + */ +public interface CefDownloadItem { + /** + * Returns true if this object is valid. Do not call any other methods if this + * function returns false. + */ + boolean isValid(); + + /** + * Returns true if the download is in progress. + */ + boolean isInProgress(); + + /** + * Returns true if the download is complete. + */ + boolean isComplete(); + + /** + * Returns true if the download has been canceled or interrupted. + */ + boolean isCanceled(); + + /** + * Returns a simple speed estimate in bytes/s. + */ + long getCurrentSpeed(); + + /** + * Returns the rough percent complete or -1 if the receive total size is + * unknown. + */ + int getPercentComplete(); + + /** + * Returns the total number of bytes. + */ + long getTotalBytes(); + + /** + * Returns the number of received bytes. + */ + long getReceivedBytes(); + + /** + * Returns the time that the download started. + */ + Date getStartTime(); + + /** + * Returns the time that the download ended. + */ + Date getEndTime(); + + /** + * Returns the full path to the downloaded or downloading file. + */ + String getFullPath(); + + /** + * Returns the unique identifier for this download. + */ + int getId(); + + /** + * Returns the URL. + */ + String getURL(); + + /** + * Returns the suggested file name. + */ + String getSuggestedFileName(); + + /** + * Returns the content disposition. + */ + String getContentDisposition(); + + /** + * Returns the mime type. + */ + String getMimeType(); +} diff --git a/src/main/java/org/cef/callback/CefDownloadItemCallback.java b/src/main/java/org/cef/callback/CefDownloadItemCallback.java new file mode 100644 index 0000000..1e2fc34 --- /dev/null +++ b/src/main/java/org/cef/callback/CefDownloadItemCallback.java @@ -0,0 +1,25 @@ +// Copyright (c) 2014 The Chromium Embedded Framework Authors. All rights +// reserved. Use of this source code is governed by a BSD-style license that +// can be found in the LICENSE file. + +package org.cef.callback; + +/** + * Callback interface used to asynchronously modify download status. + */ +public interface CefDownloadItemCallback { + /** + * Call to cancel the download. + */ + public void cancel(); + + /** + * Call to pause the download. + */ + public void pause(); + + /** + * Call to resume the download. + */ + public void resume(); +} diff --git a/src/main/java/org/cef/callback/CefDownloadItemCallback_N.java b/src/main/java/org/cef/callback/CefDownloadItemCallback_N.java new file mode 100644 index 0000000..46aace1 --- /dev/null +++ b/src/main/java/org/cef/callback/CefDownloadItemCallback_N.java @@ -0,0 +1,51 @@ +// Copyright (c) 2014 The Chromium Embedded Framework Authors. All rights +// reserved. Use of this source code is governed by a BSD-style license that +// can be found in the LICENSE file. + +package org.cef.callback; + +class CefDownloadItemCallback_N extends CefNativeAdapter implements CefDownloadItemCallback { + CefDownloadItemCallback_N() {} + + @Override + protected void finalize() throws Throwable { + try { + N_Dispose(getNativeRef(null)); + } catch (UnsatisfiedLinkError ule) { + ule.printStackTrace(); + } + super.finalize(); + } + + @Override + public void cancel() { + try { + N_Cancel(getNativeRef(null)); + } catch (UnsatisfiedLinkError ule) { + ule.printStackTrace(); + } + } + + @Override + public void pause() { + try { + N_Pause(getNativeRef(null)); + } catch (UnsatisfiedLinkError ule) { + ule.printStackTrace(); + } + } + + @Override + public void resume() { + try { + N_Resume(getNativeRef(null)); + } catch (UnsatisfiedLinkError ule) { + ule.printStackTrace(); + } + } + + private final native void N_Dispose(long self); + private final native void N_Cancel(long self); + private final native void N_Pause(long self); + private final native void N_Resume(long self); +} diff --git a/src/main/java/org/cef/callback/CefDownloadItem_N.java b/src/main/java/org/cef/callback/CefDownloadItem_N.java new file mode 100644 index 0000000..114c4af --- /dev/null +++ b/src/main/java/org/cef/callback/CefDownloadItem_N.java @@ -0,0 +1,188 @@ +// Copyright (c) 2014 The Chromium Embedded Framework Authors. All rights +// reserved. Use of this source code is governed by a BSD-style license that +// can be found in the LICENSE file. + +package org.cef.callback; + +import java.util.Date; + +class CefDownloadItem_N extends CefNativeAdapter implements CefDownloadItem { + CefDownloadItem_N() {} + + @Override + public boolean isValid() { + try { + return N_IsValid(getNativeRef(null)); + } catch (UnsatisfiedLinkError ule) { + ule.printStackTrace(); + } + return false; + } + + @Override + public boolean isInProgress() { + try { + return N_IsInProgress(getNativeRef(null)); + } catch (UnsatisfiedLinkError ule) { + ule.printStackTrace(); + } + return false; + } + + @Override + public boolean isComplete() { + try { + return N_IsComplete(getNativeRef(null)); + } catch (UnsatisfiedLinkError ule) { + ule.printStackTrace(); + } + return false; + } + + @Override + public boolean isCanceled() { + try { + return N_IsCanceled(getNativeRef(null)); + } catch (UnsatisfiedLinkError ule) { + ule.printStackTrace(); + } + return false; + } + + @Override + public long getCurrentSpeed() { + try { + return N_GetCurrentSpeed(getNativeRef(null)); + } catch (UnsatisfiedLinkError ule) { + ule.printStackTrace(); + } + return 0; + } + + @Override + public int getPercentComplete() { + try { + return N_GetPercentComplete(getNativeRef(null)); + } catch (UnsatisfiedLinkError ule) { + ule.printStackTrace(); + } + return 0; + } + + @Override + public long getTotalBytes() { + try { + return N_GetTotalBytes(getNativeRef(null)); + } catch (UnsatisfiedLinkError ule) { + ule.printStackTrace(); + } + return 0; + } + + @Override + public long getReceivedBytes() { + try { + return N_GetReceivedBytes(getNativeRef(null)); + } catch (UnsatisfiedLinkError ule) { + ule.printStackTrace(); + } + return 0; + } + + @Override + public Date getStartTime() { + try { + return N_GetStartTime(getNativeRef(null)); + } catch (UnsatisfiedLinkError ule) { + ule.printStackTrace(); + } + return null; + } + + @Override + public Date getEndTime() { + try { + return N_GetEndTime(getNativeRef(null)); + } catch (UnsatisfiedLinkError ule) { + ule.printStackTrace(); + } + return null; + } + + @Override + public String getFullPath() { + try { + return N_GetFullPath(getNativeRef(null)); + } catch (UnsatisfiedLinkError ule) { + ule.printStackTrace(); + } + return null; + } + + @Override + public int getId() { + try { + return N_GetId(getNativeRef(null)); + } catch (UnsatisfiedLinkError ule) { + ule.printStackTrace(); + } + return 0; + } + + @Override + public String getURL() { + try { + return N_GetURL(getNativeRef(null)); + } catch (UnsatisfiedLinkError ule) { + ule.printStackTrace(); + } + return null; + } + + @Override + public String getSuggestedFileName() { + try { + return N_GetSuggestedFileName(getNativeRef(null)); + } catch (UnsatisfiedLinkError ule) { + ule.printStackTrace(); + } + return null; + } + + @Override + public String getContentDisposition() { + try { + return N_GetContentDisposition(getNativeRef(null)); + } catch (UnsatisfiedLinkError ule) { + ule.printStackTrace(); + } + return null; + } + + @Override + public String getMimeType() { + try { + return N_GetMimeType(getNativeRef(null)); + } catch (UnsatisfiedLinkError ule) { + ule.printStackTrace(); + } + return null; + } + + private final native boolean N_IsValid(long self); + private final native boolean N_IsInProgress(long self); + private final native boolean N_IsComplete(long self); + private final native boolean N_IsCanceled(long self); + private final native long N_GetCurrentSpeed(long self); + private final native int N_GetPercentComplete(long self); + private final native long N_GetTotalBytes(long self); + private final native long N_GetReceivedBytes(long self); + private final native Date N_GetStartTime(long self); + private final native Date N_GetEndTime(long self); + private final native String N_GetFullPath(long self); + private final native int N_GetId(long self); + private final native String N_GetURL(long self); + private final native String N_GetSuggestedFileName(long self); + private final native String N_GetContentDisposition(long self); + private final native String N_GetMimeType(long self); +} diff --git a/src/main/java/org/cef/callback/CefDragData.java b/src/main/java/org/cef/callback/CefDragData.java new file mode 100644 index 0000000..0d13029 --- /dev/null +++ b/src/main/java/org/cef/callback/CefDragData.java @@ -0,0 +1,189 @@ +// Copyright (c) 2014 The Chromium Embedded Framework Authors. All rights +// reserved. Use of this source code is governed by a BSD-style license that +// can be found in the LICENSE file. + +package org.cef.callback; + +import java.io.OutputStream; +import java.util.Vector; + +/** + * Class used to represent drag data. The methods of this class may be called + * on any thread. + */ +public abstract class CefDragData { + /** + * Supported drag operation bit flags. + */ + public static final class DragOperations { + public final static int DRAG_OPERATION_NONE = 0; + public final static int DRAG_OPERATION_COPY = 1; + public final static int DRAG_OPERATION_LINK = 2; + public final static int DRAG_OPERATION_GENERIC = 4; + public final static int DRAG_OPERATION_PRIVATE = 8; + public final static int DRAG_OPERATION_MOVE = 16; + public final static int DRAG_OPERATION_DELETE = 32; + public final static int DRAG_OPERATION_EVERY = Integer.MAX_VALUE; + } + + // This CTOR can't be called directly. Call method create() instead. + CefDragData() {} + + @Override + protected void finalize() throws Throwable { + dispose(); + super.finalize(); + } + + /** + * Create a new CefDragData object. + */ + public static final CefDragData create() { + return CefDragData_N.createNative(); + } + + /** + * Returns a copy of the current object + */ + public abstract CefDragData clone(); + + /** + * Removes the native reference from an unused object. + */ + public abstract void dispose(); + + /** + * Test if the object is set to read-only. + * @return true if this object is read-only. + */ + public abstract boolean isReadOnly(); + + /** + * Returns true if the drag data is a link. + */ + public abstract boolean isLink(); + + /** + * Returns true if the drag data is a text or html fragment. + */ + public abstract boolean isFragment(); + + /** + * Returns true if the drag data is a file. + */ + public abstract boolean isFile(); + + /** + * Return the link URL that is being dragged. + */ + public abstract String getLinkURL(); + + /** + * Return the title associated with the link being dragged. + */ + public abstract String getLinkTitle(); + + /** + * Return the metadata, if any, associated with the link being dragged. + */ + public abstract String getLinkMetadata(); + + /** + * Return the plain text fragment that is being dragged. + */ + public abstract String getFragmentText(); + + /** + * Return the text/html fragment that is being dragged. + */ + public abstract String getFragmentHtml(); + + /** + * Return the base URL that the fragment came from. This value is used for + * resolving relative URLs and may be empty. + */ + public abstract String getFragmentBaseURL(); + + /** + * Write the contents of the file being dragged out of the web view into + * |writer|. Returns the number of bytes sent to |writer|. If |writer| is + * NULL this method will return the size of the file contents in bytes. + * Call getFileName() to get a suggested name for the file. + * + * @param writer Writes the contents into this object. + * @return The number of bytes sent to writer. If writer is NULL the size of + * the file contents in bytes is returned. + */ + public abstract int getFileContents(OutputStream writer); + + /** + * Return the name of the file being dragged out of the browser window. + */ + public abstract String getFileName(); + + /** + * Retrieve the list of file names that are being dragged into the browser + * window. + */ + public abstract boolean getFileNames(Vector names); + + /** + * Set the link URL that is being dragged. + * @param url The link URL to be set. + */ + public abstract void setLinkURL(String url); + + /** + * Set the title associated with the link being dragged. + * @param title The tile associated with the link. + */ + public abstract void setLinkTitle(String title); + + /** + * Set the metadata associated with the link being dragged. + * @param data The metadata associated with the link. + */ + public abstract void setLinkMetadata(String data); + + /** + * Set the plain text fragment that is being dragged. + * @param text The plain text fragment to be set. + */ + public abstract void setFragmentText(String text); + + /** + * Set the text/html fragment that is being dragged. + * @param html The html fragment to be set. + */ + public abstract void setFragmentHtml(String html); + + /** + * Set the base URL that the fragment came from. + * @param baseUrl The base URL to be set. + */ + public abstract void setFragmentBaseURL(String baseUrl); + + /** + * Reset the file contents. You should do this before calling + * CefBrowser.dragTargetDragEnter as the web view does not allow us to + * drag in this kind of data. + */ + public abstract void resetFileContents(); + + /** + * Add a file that is being dragged into the webview. + * @param path The file and path to be set. + * @param displayName The name to be displayed. + */ + public abstract void addFile(String path, String displayName); + + @Override + public String toString() { + return "CefDragData [isReadOnly()=" + isReadOnly() + ", isLink()=" + isLink() + + ", isFragment()=" + isFragment() + ", isFile()=" + isFile() + ", getLinkURL()=" + + getLinkURL() + ", getLinkTitle()=" + getLinkTitle() + ", getLinkMetadata()=" + + getLinkMetadata() + ", getFragmentText()=" + getFragmentText() + + ", getFragmentHtml()=" + getFragmentHtml() + ", getFragmentBaseURL()=" + + getFragmentBaseURL() + ", getFileName()=" + getFileName() + "]"; + } +} diff --git a/src/main/java/org/cef/callback/CefDragData_N.java b/src/main/java/org/cef/callback/CefDragData_N.java new file mode 100644 index 0000000..9f6e0fe --- /dev/null +++ b/src/main/java/org/cef/callback/CefDragData_N.java @@ -0,0 +1,287 @@ +package org.cef.callback; + +import java.io.OutputStream; +import java.util.Vector; + +class CefDragData_N extends CefDragData implements CefNative { + // Used internally to store a pointer to the CEF object. + private long N_CefHandle = 0; + + @Override + public void setNativeRef(String identifer, long nativeRef) { + N_CefHandle = nativeRef; + } + + @Override + public long getNativeRef(String identifer) { + return N_CefHandle; + } + + CefDragData_N() { + super(); + } + + public static CefDragData createNative() { + try { + return CefDragData_N.N_Create(); + } catch (UnsatisfiedLinkError ule) { + ule.printStackTrace(); + return null; + } + } + + @Override + public CefDragData clone() { + try { + return N_Clone(N_CefHandle); + } catch (UnsatisfiedLinkError ule) { + ule.printStackTrace(); + return null; + } + } + + @Override + public void dispose() { + try { + N_Dispose(N_CefHandle); + } catch (UnsatisfiedLinkError ule) { + ule.printStackTrace(); + } + } + + @Override + public boolean isReadOnly() { + try { + return N_IsReadOnly(N_CefHandle); + } catch (UnsatisfiedLinkError ule) { + ule.printStackTrace(); + return true; + } + } + + @Override + public boolean isLink() { + try { + return N_IsLink(N_CefHandle); + } catch (UnsatisfiedLinkError ule) { + ule.printStackTrace(); + } + return false; + } + + @Override + public boolean isFragment() { + try { + return N_IsFragment(N_CefHandle); + } catch (UnsatisfiedLinkError ule) { + ule.printStackTrace(); + } + return false; + } + + @Override + public boolean isFile() { + try { + return N_IsFile(N_CefHandle); + } catch (UnsatisfiedLinkError ule) { + ule.printStackTrace(); + } + return false; + } + + @Override + public String getLinkURL() { + try { + return N_GetLinkURL(N_CefHandle); + } catch (UnsatisfiedLinkError ule) { + ule.printStackTrace(); + } + return null; + } + + @Override + public String getLinkTitle() { + try { + return N_GetLinkTitle(N_CefHandle); + } catch (UnsatisfiedLinkError ule) { + ule.printStackTrace(); + } + return null; + } + + @Override + public String getLinkMetadata() { + try { + return N_GetLinkMetadata(N_CefHandle); + } catch (UnsatisfiedLinkError ule) { + ule.printStackTrace(); + } + return null; + } + + @Override + public String getFragmentText() { + try { + return N_GetFragmentText(N_CefHandle); + } catch (UnsatisfiedLinkError ule) { + ule.printStackTrace(); + } + return null; + } + + @Override + public String getFragmentHtml() { + try { + return N_GetFragmentHtml(N_CefHandle); + } catch (UnsatisfiedLinkError ule) { + ule.printStackTrace(); + } + return null; + } + + @Override + public String getFragmentBaseURL() { + try { + return N_GetFragmentBaseURL(N_CefHandle); + } catch (UnsatisfiedLinkError ule) { + ule.printStackTrace(); + } + return null; + } + + @Override + public int getFileContents(OutputStream writer) { + try { + return N_GetFileContents(N_CefHandle, writer); + } catch (UnsatisfiedLinkError ule) { + ule.printStackTrace(); + } + return 0; + } + + @Override + public String getFileName() { + try { + return N_GetFileName(N_CefHandle); + } catch (UnsatisfiedLinkError ule) { + ule.printStackTrace(); + } + return null; + } + + @Override + public boolean getFileNames(Vector names) { + try { + return N_GetFileNames(N_CefHandle, names); + } catch (UnsatisfiedLinkError ule) { + ule.printStackTrace(); + } + return false; + } + + public void setLinkURL(String url) { + try { + N_SetLinkURL(N_CefHandle, url); + } catch (UnsatisfiedLinkError ule) { + ule.printStackTrace(); + } + } + + public void setLinkTitle(String title) { + try { + N_SetLinkTitle(N_CefHandle, title); + } catch (UnsatisfiedLinkError ule) { + ule.printStackTrace(); + } + } + + public void setLinkMetadata(String data) { + try { + N_SetLinkMetadata(N_CefHandle, data); + } catch (UnsatisfiedLinkError ule) { + ule.printStackTrace(); + } + } + + public void setFragmentText(String text) { + try { + N_SetFragmentText(N_CefHandle, text); + } catch (UnsatisfiedLinkError ule) { + ule.printStackTrace(); + } + } + + public void setFragmentHtml(String html) { + try { + N_SetFragmentHtml(N_CefHandle, html); + } catch (UnsatisfiedLinkError ule) { + ule.printStackTrace(); + } + } + + public void setFragmentBaseURL(String baseUrl) { + try { + N_SetFragmentBaseURL(N_CefHandle, baseUrl); + } catch (UnsatisfiedLinkError ule) { + ule.printStackTrace(); + } + } + + public void resetFileContents() { + try { + N_ResetFileContents(N_CefHandle); + } catch (UnsatisfiedLinkError ule) { + ule.printStackTrace(); + } + } + + public void addFile(String path, String displayName) { + try { + N_AddFile(N_CefHandle, path, displayName); + } catch (UnsatisfiedLinkError ule) { + ule.printStackTrace(); + } + } + + private final native static CefDragData_N N_Create(); + private final native CefDragData_N N_Clone(long self); + private final native void N_Dispose(long self); + private final native boolean N_IsReadOnly(long self); + private final native boolean N_IsLink(long self); + private final native boolean N_IsFragment(long self); + private final native boolean N_IsFile(long self); + private final native String N_GetLinkURL(long self); + private final native String N_GetLinkTitle(long self); + private final native String N_GetLinkMetadata(long self); + private final native String N_GetFragmentText(long self); + private final native String N_GetFragmentHtml(long self); + private final native String N_GetFragmentBaseURL(long self); + private final native int N_GetFileContents(long self, OutputStream writer); + private final native String N_GetFileName(long self); + private final native boolean N_GetFileNames(long self, Vector names); + private final native void N_SetLinkURL(long self, String url); + private final native void N_SetLinkTitle(long self, String title); + private final native void N_SetLinkMetadata(long self, String data); + private final native void N_SetFragmentText(long self, String text); + private final native void N_SetFragmentHtml(long self, String html); + private final native void N_SetFragmentBaseURL(long self, String baseUrl); + private final native void N_ResetFileContents(long self); + private final native void N_AddFile(long self, String path, String displayName); + + @Override + public String toString() { + Vector names = new Vector<>(); + getFileNames(names); + String fileNamesStr = "{"; + for (String s : names) fileNamesStr += s + ","; + fileNamesStr += "}"; + + return "CefDragData_N [isLink()=" + isLink() + ", isFragment()=" + isFragment() + + ", isFile()=" + isFile() + ", getLinkURL()=" + getLinkURL() + + ", getLinkTitle()=" + getLinkTitle() + ", getLinkMetadata()=" + getLinkMetadata() + + ", getFragmentText()=" + getFragmentText() + ", getFragmentHtml()=" + + getFragmentHtml() + ", getFragmentBaseURL()=" + getFragmentBaseURL() + + ", getFileName()=" + getFileName() + ", getFileNames(vector)=" + fileNamesStr + + "]"; + } +} diff --git a/src/main/java/org/cef/callback/CefFileDialogCallback.java b/src/main/java/org/cef/callback/CefFileDialogCallback.java new file mode 100644 index 0000000..1bbb0b3 --- /dev/null +++ b/src/main/java/org/cef/callback/CefFileDialogCallback.java @@ -0,0 +1,28 @@ +// Copyright (c) 2014 The Chromium Embedded Framework Authors. All rights +// reserved. Use of this source code is governed by a BSD-style license that +// can be found in the LICENSE file. + +package org.cef.callback; + +import java.util.Vector; + +/** + * Callback interface for asynchronous continuation of file dialog requests. + */ +public interface CefFileDialogCallback { + /** + * Continue the file selection with the specified file_paths. This may be + * a single value or a list of values depending on the dialog mode. An empty + * value is treated the same as calling Cancel(). + * + * @param selectedAcceptFilter 0-based index of the value selected from the + * accept filters array passed to CefDialogHandler::OnFileDialog. + * @param filePaths list of selected file paths or an empty list. + */ + public void Continue(int selectedAcceptFilter, Vector filePaths); + + /** + * Cancel the file selection. + */ + public void Cancel(); +} diff --git a/src/main/java/org/cef/callback/CefFileDialogCallback_N.java b/src/main/java/org/cef/callback/CefFileDialogCallback_N.java new file mode 100644 index 0000000..de3d541 --- /dev/null +++ b/src/main/java/org/cef/callback/CefFileDialogCallback_N.java @@ -0,0 +1,39 @@ +// Copyright (c) 2014 The Chromium Embedded Framework Authors. All rights +// reserved. Use of this source code is governed by a BSD-style license that +// can be found in the LICENSE file. + +package org.cef.callback; + +import java.util.Vector; + +class CefFileDialogCallback_N extends CefNativeAdapter implements CefFileDialogCallback { + CefFileDialogCallback_N() {} + + @Override + protected void finalize() throws Throwable { + Cancel(); + super.finalize(); + } + + @Override + public void Continue(int selectedAcceptFilter, Vector filePaths) { + try { + N_Continue(getNativeRef(null), selectedAcceptFilter, filePaths); + } catch (UnsatisfiedLinkError ule) { + ule.printStackTrace(); + } + } + + @Override + public void Cancel() { + try { + N_Cancel(getNativeRef(null)); + } catch (UnsatisfiedLinkError ule) { + ule.printStackTrace(); + } + } + + private final native void N_Continue( + long self, int selectedAcceptFilter, Vector filePaths); + private final native void N_Cancel(long self); +} diff --git a/src/main/java/org/cef/callback/CefJSDialogCallback.java b/src/main/java/org/cef/callback/CefJSDialogCallback.java new file mode 100644 index 0000000..bb30a6a --- /dev/null +++ b/src/main/java/org/cef/callback/CefJSDialogCallback.java @@ -0,0 +1,19 @@ +// Copyright (c) 2014 The Chromium Embedded Framework Authors. All rights +// reserved. Use of this source code is governed by a BSD-style license that +// can be found in the LICENSE file. + +package org.cef.callback; + +/** + * Callback interface used for asynchronous continuation of JavaScript dialog + * requests. + */ +public interface CefJSDialogCallback { + /** + * Continue the JS dialog request. + * + * @param success Set to true if the OK button was pressed. + * @param user_input The value should be specified for prompt dialogs. + */ + public void Continue(boolean success, String user_input); +} diff --git a/src/main/java/org/cef/callback/CefJSDialogCallback_N.java b/src/main/java/org/cef/callback/CefJSDialogCallback_N.java new file mode 100644 index 0000000..6256d3d --- /dev/null +++ b/src/main/java/org/cef/callback/CefJSDialogCallback_N.java @@ -0,0 +1,26 @@ +// Copyright (c) 2014 The Chromium Embedded Framework Authors. All rights +// reserved. Use of this source code is governed by a BSD-style license that +// can be found in the LICENSE file. + +package org.cef.callback; + +class CefJSDialogCallback_N extends CefNativeAdapter implements CefJSDialogCallback { + CefJSDialogCallback_N() {} + + @Override + protected void finalize() throws Throwable { + Continue(false, ""); + super.finalize(); + } + + @Override + public void Continue(boolean success, String user_input) { + try { + N_Continue(getNativeRef(null), success, user_input); + } catch (UnsatisfiedLinkError ule) { + ule.printStackTrace(); + } + } + + private final native void N_Continue(long self, boolean success, String user_input); +} diff --git a/src/main/java/org/cef/callback/CefMenuModel.java b/src/main/java/org/cef/callback/CefMenuModel.java new file mode 100644 index 0000000..e783709 --- /dev/null +++ b/src/main/java/org/cef/callback/CefMenuModel.java @@ -0,0 +1,338 @@ +// Copyright (c) 2014 The Chromium Embedded Framework Authors. All rights +// reserved. Use of this source code is governed by a BSD-style license that +// can be found in the LICENSE file. + +package org.cef.callback; + +import org.cef.misc.BoolRef; +import org.cef.misc.IntRef; + +/** + * Supports creation and modification of menus. See cef_menu_id_t for the + * command ids that have default implementations. All user-defined command ids + * should be between MENU_ID_USER_FIRST and MENU_ID_USER_LAST. The methods of + * this class can only be accessed on the browser process the UI thread. + */ +public interface CefMenuModel { + public static final class MenuId { + public static final int MENU_ID_BACK = 100; + public static final int MENU_ID_FORWARD = 101; + public static final int MENU_ID_RELOAD = 102; + public static final int MENU_ID_RELOAD_NOCACHE = 103; + public static final int MENU_ID_STOPLOAD = 104; + + // Editing. + public static final int MENU_ID_UNDO = 110; + public static final int MENU_ID_REDO = 111; + public static final int MENU_ID_CUT = 112; + public static final int MENU_ID_COPY = 113; + public static final int MENU_ID_PASTE = 114; + public static final int MENU_ID_DELETE = 115; + public static final int MENU_ID_SELECT_ALL = 116; + + // Miscellaneous. + public static final int MENU_ID_FIND = 130; + public static final int MENU_ID_PRINT = 131; + public static final int MENU_ID_VIEW_SOURCE = 132; + + // Spell checking word correction suggestions. + public static final int MENU_ID_SPELLCHECK_SUGGESTION_0 = 200; + public static final int MENU_ID_SPELLCHECK_SUGGESTION_1 = 201; + public static final int MENU_ID_SPELLCHECK_SUGGESTION_2 = 202; + public static final int MENU_ID_SPELLCHECK_SUGGESTION_3 = 203; + public static final int MENU_ID_SPELLCHECK_SUGGESTION_4 = 204; + public static final int MENU_ID_SPELLCHECK_SUGGESTION_LAST = 204; + public static final int MENU_ID_NO_SPELLING_SUGGESTIONS = 205; + + // All user-defined menu IDs should come between MENU_ID_USER_FIRST and + // MENU_ID_USER_LAST to avoid overlapping the Chromium and CEF ID ranges + // defined in the tools/gritsettings/resource_ids file. + public static final int MENU_ID_USER_FIRST = 26500; + public static final int MENU_ID_USER_LAST = 28500; + } + + /** + * Supported menu item types. + */ + public enum MenuItemType { + MENUITEMTYPE_NONE, + MENUITEMTYPE_COMMAND, + MENUITEMTYPE_CHECK, + MENUITEMTYPE_RADIO, + MENUITEMTYPE_SEPARATOR, + MENUITEMTYPE_SUBMENU, + } + + /** + * Clears the menu. Returns true on success. + */ + boolean clear(); + + /** + * Returns the number of items in this menu. + */ + int getCount(); + + /** + * Add a separator to the menu. Returns true on success. + */ + boolean addSeparator(); + + /** + * Add an item to the menu. Returns true on success. + */ + boolean addItem(int command_id, String label); + + /** + * Add a check item to the menu. Returns true on success. + */ + boolean addCheckItem(int command_id, String label); + + /** + * Add a radio item to the menu. Only a single item with the specified + * |group_id| can be checked at a time. Returns true on success. + */ + boolean addRadioItem(int command_id, String label, int group_id); + + /** + * Add a sub-menu to the menu. The new sub-menu is returned. + */ + CefMenuModel addSubMenu(int command_id, String label); + + /** + * Insert a separator in the menu at the specified |index|. Returns true on + * success. + */ + boolean insertSeparatorAt(int index); + + /** + * Insert an item in the menu at the specified |index|. Returns true on + * success. + */ + boolean insertItemAt(int index, int command_id, String label); + + /** + * Insert a check item in the menu at the specified |index|. Returns true on + * success. + */ + boolean insertCheckItemAt(int index, int command_id, String label); + + /** + * Insert a radio item in the menu at the specified |index|. Only a single + * item with the specified |group_id| can be checked at a time. Returns true + * on success. + */ + boolean insertRadioItemAt(int index, int command_id, String label, int group_id); + + /** + * Insert a sub-menu in the menu at the specified |index|. The new sub-menu + * is returned. + */ + CefMenuModel insertSubMenuAt(int index, int command_id, String label); + + /** + * Removes the item with the specified |command_id|. Returns true on success. + */ + boolean remove(int command_id); + + /** + * Removes the item at the specified |index|. Returns true on success. + */ + boolean removeAt(int index); + + /** + * Returns the index associated with the specified |command_id| or -1 if not + * found due to the command id not existing in the menu. + */ + int getIndexOf(int command_id); + + /** + * Returns the command id at the specified |index| or -1 if not found due to + * invalid range or the index being a separator. + */ + int getCommandIdAt(int index); + + /** + * Sets the command id at the specified |index|. Returns true on success. + */ + boolean setCommandIdAt(int index, int command_id); + + /** + * Returns the label for the specified |command_id| or empty if not found. + */ + String getLabel(int command_id); + + /** + * Returns the label at the specified |index| or empty if not found due to + * invalid range or the index being a separator. + */ + String getLabelAt(int index); + + /** + * Sets the label for the specified |command_id|. Returns true on success. + */ + boolean setLabel(int command_id, String label); + + /** + * Set the label at the specified |index|. Returns true on success. + */ + boolean setLabelAt(int index, String label); + + /** + * Returns the item type for the specified |command_id|. + */ + MenuItemType getType(int command_id); + + /** + * Returns the item type at the specified |index|. + */ + MenuItemType getTypeAt(int index); + + /** + * Returns the group id for the specified |command_id| or -1 if invalid. + */ + int getGroupId(int command_id); + + /** + * Returns the group id at the specified |index| or -1 if invalid. + */ + int getGroupIdAt(int index); + + /** + * Sets the group id for the specified |command_id|. Returns true on success. + */ + boolean setGroupId(int command_id, int group_id); + + /** + * Sets the group id at the specified |index|. Returns true on success. + */ + boolean setGroupIdAt(int index, int group_id); + + /** + * Returns the submenu for the specified |command_id| or empty if invalid. + */ + CefMenuModel getSubMenu(int command_id); + + /** + * Returns the submenu at the specified |index| or empty if invalid. + */ + CefMenuModel getSubMenuAt(int index); + + /** + * Returns true if the specified |command_id| is visible. + */ + boolean isVisible(int command_id); + + /** + * Returns true if the specified |index| is visible. + */ + boolean isVisibleAt(int index); + + /** + * Change the visibility of the specified |command_id|. Returns true on + * success. + */ + boolean setVisible(int command_id, boolean visible); + + /** + * Change the visibility at the specified |index|. Returns true on success. + */ + boolean setVisibleAt(int index, boolean visible); + + /** + * Returns true if the specified |command_id| is enabled. + */ + boolean isEnabled(int command_id); + + /** + * Returns true if the specified |index| is enabled. + */ + boolean isEnabledAt(int index); + + /** + * Change the enabled status of the specified |command_id|. Returns true on + * success. + */ + boolean setEnabled(int command_id, boolean enabled); + + /** + * Change the enabled status at the specified |index|. Returns true on + * success. + */ + boolean setEnabledAt(int index, boolean enabled); + + /** + * Returns true if the specified |command_id| is checked. Only applies to + * check and radio items. + */ + boolean isChecked(int command_id); + + /** + * Returns true if the specified |index| is checked. Only applies to check + * and radio items. + */ + boolean isCheckedAt(int index); + + /** + * Check the specified |command_id|. Only applies to check and radio items. + * Returns true on success. + */ + boolean setChecked(int command_id, boolean checked); + + /** + * Check the specified |index|. Only applies to check and radio items. Returns + * true on success. + */ + boolean setCheckedAt(int index, boolean checked); + + /** + * Returns true if the specified |command_id| has a keyboard accelerator + * assigned. + */ + boolean hasAccelerator(int command_id); + + /** + * Returns true if the specified |index| has a keyboard accelerator assigned. + */ + boolean hasAcceleratorAt(int index); + + /** + * Set the keyboard accelerator for the specified |command_id|. |key_code| can + * be any key or character value. Returns true on success. + */ + boolean setAccelerator(int command_id, int key_code, boolean shift_pressed, + boolean ctrl_pressed, boolean alt_pressed); + + /** + * Set the keyboard accelerator at the specified |index|. |key_code| can be + * any key or character value. Returns true on success. + */ + boolean setAcceleratorAt(int index, int key_code, boolean shift_pressed, boolean ctrl_pressed, + boolean alt_pressed); + + /** + * Remove the keyboard accelerator for the specified |command_id|. Returns + * true on success. + */ + boolean removeAccelerator(int command_id); + + /** + * Remove the keyboard accelerator at the specified |index|. Returns true on + * success. + */ + boolean removeAcceleratorAt(int index); + + /** + * Retrieves the keyboard accelerator for the specified |command_id|. Returns + * true on success. + */ + boolean getAccelerator(int command_id, IntRef key_code, BoolRef shift_pressed, + BoolRef ctrl_pressed, BoolRef alt_pressed); + + /** + * Retrieves the keyboard accelerator for the specified |index|. Returns true + * on success. + */ + boolean getAcceleratorAt(int index, IntRef key_code, BoolRef shift_pressed, + BoolRef ctrl_pressed, BoolRef alt_pressed); +} diff --git a/src/main/java/org/cef/callback/CefMenuModel_N.java b/src/main/java/org/cef/callback/CefMenuModel_N.java new file mode 100644 index 0000000..89db5c3 --- /dev/null +++ b/src/main/java/org/cef/callback/CefMenuModel_N.java @@ -0,0 +1,568 @@ +// Copyright (c) 2014 The Chromium Embedded Framework Authors. All rights +// reserved. Use of this source code is governed by a BSD-style license that +// can be found in the LICENSE file. + +package org.cef.callback; + +import org.cef.misc.BoolRef; +import org.cef.misc.IntRef; + +class CefMenuModel_N extends CefNativeAdapter implements CefMenuModel { + public CefMenuModel_N() {} + + @Override + public boolean clear() { + try { + return N_Clear(getNativeRef(null)); + } catch (UnsatisfiedLinkError ule) { + ule.printStackTrace(); + } + return false; + } + + @Override + public int getCount() { + try { + return N_GetCount(getNativeRef(null)); + } catch (UnsatisfiedLinkError ule) { + ule.printStackTrace(); + } + return 0; + } + + @Override + public boolean addSeparator() { + try { + return N_AddSeparator(getNativeRef(null)); + } catch (UnsatisfiedLinkError ule) { + ule.printStackTrace(); + } + return false; + } + + @Override + public boolean addItem(int command_id, String label) { + try { + return N_AddItem(getNativeRef(null), command_id, label); + } catch (UnsatisfiedLinkError ule) { + ule.printStackTrace(); + } + return false; + } + + @Override + public boolean addCheckItem(int command_id, String label) { + try { + return N_AddCheckItem(getNativeRef(null), command_id, label); + } catch (UnsatisfiedLinkError ule) { + ule.printStackTrace(); + } + return false; + } + + @Override + public boolean addRadioItem(int command_id, String label, int group_id) { + try { + return N_AddRadioItem(getNativeRef(null), command_id, label, group_id); + } catch (UnsatisfiedLinkError ule) { + ule.printStackTrace(); + } + return false; + } + + @Override + public CefMenuModel addSubMenu(int command_id, String label) { + try { + return N_AddSubMenu(getNativeRef(null), command_id, label); + } catch (UnsatisfiedLinkError ule) { + ule.printStackTrace(); + } + return null; + } + + @Override + public boolean insertSeparatorAt(int index) { + try { + return N_InsertSeparatorAt(getNativeRef(null), index); + } catch (UnsatisfiedLinkError ule) { + ule.printStackTrace(); + } + return false; + } + + @Override + public boolean insertItemAt(int index, int command_id, String label) { + try { + return N_InsertItemAt(getNativeRef(null), index, command_id, label); + } catch (UnsatisfiedLinkError ule) { + ule.printStackTrace(); + } + return false; + } + + @Override + public boolean insertCheckItemAt(int index, int command_id, String label) { + try { + return N_InsertCheckItemAt(getNativeRef(null), index, command_id, label); + } catch (UnsatisfiedLinkError ule) { + ule.printStackTrace(); + } + return false; + } + + @Override + public boolean insertRadioItemAt(int index, int command_id, String label, int group_id) { + try { + return N_InsertRadioItemAt(getNativeRef(null), index, command_id, label, group_id); + } catch (UnsatisfiedLinkError ule) { + ule.printStackTrace(); + } + return false; + } + + @Override + public CefMenuModel insertSubMenuAt(int index, int command_id, String label) { + try { + return N_InsertSubMenuAt(getNativeRef(null), index, command_id, label); + } catch (UnsatisfiedLinkError ule) { + ule.printStackTrace(); + } + return null; + } + + @Override + public boolean remove(int command_id) { + try { + return N_Remove(getNativeRef(null), command_id); + } catch (UnsatisfiedLinkError ule) { + ule.printStackTrace(); + } + return false; + } + + @Override + public boolean removeAt(int index) { + try { + return N_RemoveAt(getNativeRef(null), index); + } catch (UnsatisfiedLinkError ule) { + ule.printStackTrace(); + } + return false; + } + + @Override + public int getIndexOf(int command_id) { + try { + return N_GetIndexOf(getNativeRef(null), command_id); + } catch (UnsatisfiedLinkError ule) { + ule.printStackTrace(); + } + return 0; + } + + @Override + public int getCommandIdAt(int index) { + try { + return N_GetCommandIdAt(getNativeRef(null), index); + } catch (UnsatisfiedLinkError ule) { + ule.printStackTrace(); + } + return 0; + } + + @Override + public boolean setCommandIdAt(int index, int command_id) { + try { + return N_SetCommandIdAt(getNativeRef(null), index, command_id); + } catch (UnsatisfiedLinkError ule) { + ule.printStackTrace(); + } + return false; + } + + @Override + public String getLabel(int command_id) { + try { + return N_GetLabel(getNativeRef(null), command_id); + } catch (UnsatisfiedLinkError ule) { + ule.printStackTrace(); + } + return null; + } + + @Override + public String getLabelAt(int index) { + try { + return N_GetLabelAt(getNativeRef(null), index); + } catch (UnsatisfiedLinkError ule) { + ule.printStackTrace(); + } + return null; + } + + @Override + public boolean setLabel(int command_id, String label) { + try { + return N_SetLabel(getNativeRef(null), command_id, label); + } catch (UnsatisfiedLinkError ule) { + ule.printStackTrace(); + } + return false; + } + + @Override + public boolean setLabelAt(int index, String label) { + try { + return N_SetLabelAt(getNativeRef(null), index, label); + } catch (UnsatisfiedLinkError ule) { + ule.printStackTrace(); + } + return false; + } + + @Override + public MenuItemType getType(int command_id) { + try { + return N_GetType(getNativeRef(null), command_id); + } catch (UnsatisfiedLinkError ule) { + ule.printStackTrace(); + } + return null; + } + + @Override + public MenuItemType getTypeAt(int index) { + try { + return N_GetTypeAt(getNativeRef(null), index); + } catch (UnsatisfiedLinkError ule) { + ule.printStackTrace(); + } + return null; + } + + @Override + public int getGroupId(int command_id) { + try { + return N_GetGroupId(getNativeRef(null), command_id); + } catch (UnsatisfiedLinkError ule) { + ule.printStackTrace(); + } + return 0; + } + + @Override + public int getGroupIdAt(int index) { + try { + return N_GetGroupIdAt(getNativeRef(null), index); + } catch (UnsatisfiedLinkError ule) { + ule.printStackTrace(); + } + return 0; + } + + @Override + public boolean setGroupId(int command_id, int group_id) { + try { + return N_SetGroupId(getNativeRef(null), command_id, group_id); + } catch (UnsatisfiedLinkError ule) { + ule.printStackTrace(); + } + return false; + } + + @Override + public boolean setGroupIdAt(int index, int group_id) { + try { + return N_SetGroupIdAt(getNativeRef(null), index, group_id); + } catch (UnsatisfiedLinkError ule) { + ule.printStackTrace(); + } + return false; + } + + @Override + public CefMenuModel getSubMenu(int command_id) { + try { + return N_GetSubMenu(getNativeRef(null), command_id); + } catch (UnsatisfiedLinkError ule) { + ule.printStackTrace(); + } + return null; + } + + @Override + public CefMenuModel getSubMenuAt(int index) { + try { + return N_GetSubMenuAt(getNativeRef(null), index); + } catch (UnsatisfiedLinkError ule) { + ule.printStackTrace(); + } + return null; + } + + @Override + public boolean isVisible(int command_id) { + try { + return N_IsVisible(getNativeRef(null), command_id); + } catch (UnsatisfiedLinkError ule) { + ule.printStackTrace(); + } + return false; + } + + @Override + public boolean isVisibleAt(int index) { + try { + return N_IsVisibleAt(getNativeRef(null), index); + } catch (UnsatisfiedLinkError ule) { + ule.printStackTrace(); + } + return false; + } + + @Override + public boolean setVisible(int command_id, boolean visible) { + try { + return N_SetVisible(getNativeRef(null), command_id, visible); + } catch (UnsatisfiedLinkError ule) { + ule.printStackTrace(); + } + return false; + } + + @Override + public boolean setVisibleAt(int index, boolean visible) { + try { + return N_SetVisibleAt(getNativeRef(null), index, visible); + } catch (UnsatisfiedLinkError ule) { + ule.printStackTrace(); + } + return false; + } + + @Override + public boolean isEnabled(int command_id) { + try { + return N_IsEnabled(getNativeRef(null), command_id); + } catch (UnsatisfiedLinkError ule) { + ule.printStackTrace(); + } + return false; + } + + @Override + public boolean isEnabledAt(int index) { + try { + return N_IsEnabledAt(getNativeRef(null), index); + } catch (UnsatisfiedLinkError ule) { + ule.printStackTrace(); + } + return false; + } + + @Override + public boolean setEnabled(int command_id, boolean enabled) { + try { + return N_SetEnabled(getNativeRef(null), command_id, enabled); + } catch (UnsatisfiedLinkError ule) { + ule.printStackTrace(); + } + return false; + } + + @Override + public boolean setEnabledAt(int index, boolean enabled) { + try { + return N_SetEnabledAt(getNativeRef(null), index, enabled); + } catch (UnsatisfiedLinkError ule) { + ule.printStackTrace(); + } + return false; + } + + @Override + public boolean isChecked(int command_id) { + try { + return N_IsChecked(getNativeRef(null), command_id); + } catch (UnsatisfiedLinkError ule) { + ule.printStackTrace(); + } + return false; + } + + @Override + public boolean isCheckedAt(int index) { + try { + return N_IsCheckedAt(getNativeRef(null), index); + } catch (UnsatisfiedLinkError ule) { + ule.printStackTrace(); + } + return false; + } + + @Override + public boolean setChecked(int command_id, boolean checked) { + try { + return N_SetChecked(getNativeRef(null), command_id, checked); + } catch (UnsatisfiedLinkError ule) { + ule.printStackTrace(); + } + return false; + } + + @Override + public boolean setCheckedAt(int index, boolean checked) { + try { + return N_SetCheckedAt(getNativeRef(null), index, checked); + } catch (UnsatisfiedLinkError ule) { + ule.printStackTrace(); + } + return false; + } + + @Override + public boolean hasAccelerator(int command_id) { + try { + return N_HasAccelerator(getNativeRef(null), command_id); + } catch (UnsatisfiedLinkError ule) { + ule.printStackTrace(); + } + return false; + } + + @Override + public boolean hasAcceleratorAt(int index) { + try { + return N_HasAcceleratorAt(getNativeRef(null), index); + } catch (UnsatisfiedLinkError ule) { + ule.printStackTrace(); + } + return false; + } + + @Override + public boolean setAccelerator(int command_id, int key_code, boolean shift_pressed, + boolean ctrl_pressed, boolean alt_pressed) { + try { + return N_SetAccelerator(getNativeRef(null), command_id, key_code, shift_pressed, + ctrl_pressed, alt_pressed); + } catch (UnsatisfiedLinkError ule) { + ule.printStackTrace(); + } + return false; + } + + @Override + public boolean setAcceleratorAt(int index, int key_code, boolean shift_pressed, + boolean ctrl_pressed, boolean alt_pressed) { + try { + return N_SetAcceleratorAt( + getNativeRef(null), index, key_code, shift_pressed, ctrl_pressed, alt_pressed); + } catch (UnsatisfiedLinkError ule) { + ule.printStackTrace(); + } + return false; + } + + @Override + public boolean removeAccelerator(int command_id) { + try { + return N_RemoveAccelerator(getNativeRef(null), command_id); + } catch (UnsatisfiedLinkError ule) { + ule.printStackTrace(); + } + return false; + } + + @Override + public boolean removeAcceleratorAt(int index) { + try { + return N_RemoveAcceleratorAt(getNativeRef(null), index); + } catch (UnsatisfiedLinkError ule) { + ule.printStackTrace(); + } + return false; + } + + @Override + public boolean getAccelerator(int command_id, IntRef key_code, BoolRef shift_pressed, + BoolRef ctrl_pressed, BoolRef alt_pressed) { + try { + return N_GetAccelerator(getNativeRef(null), command_id, key_code, shift_pressed, + ctrl_pressed, alt_pressed); + } catch (UnsatisfiedLinkError ule) { + ule.printStackTrace(); + } + return false; + } + + @Override + public boolean getAcceleratorAt(int index, IntRef key_code, BoolRef shift_pressed, + BoolRef ctrl_pressed, BoolRef alt_pressed) { + try { + return N_GetAcceleratorAt( + getNativeRef(null), index, key_code, shift_pressed, ctrl_pressed, alt_pressed); + } catch (UnsatisfiedLinkError ule) { + ule.printStackTrace(); + } + return false; + } + + private final native boolean N_Clear(long self); + private final native int N_GetCount(long self); + private final native boolean N_AddSeparator(long self); + private final native boolean N_AddItem(long self, int command_id, String label); + private final native boolean N_AddCheckItem(long self, int command_id, String label); + private final native boolean N_AddRadioItem( + long self, int command_id, String label, int group_id); + private final native CefMenuModel N_AddSubMenu(long self, int command_id, String label); + private final native boolean N_InsertSeparatorAt(long self, int index); + private final native boolean N_InsertItemAt(long self, int index, int command_id, String label); + private final native boolean N_InsertCheckItemAt( + long self, int index, int command_id, String label); + private final native boolean N_InsertRadioItemAt( + long self, int index, int command_id, String label, int group_id); + private final native CefMenuModel N_InsertSubMenuAt( + long self, int index, int command_id, String label); + private final native boolean N_Remove(long self, int command_id); + private final native boolean N_RemoveAt(long self, int index); + private final native int N_GetIndexOf(long self, int command_id); + private final native int N_GetCommandIdAt(long self, int index); + private final native boolean N_SetCommandIdAt(long self, int index, int command_id); + private final native String N_GetLabel(long self, int command_id); + private final native String N_GetLabelAt(long self, int index); + private final native boolean N_SetLabel(long self, int command_id, String label); + private final native boolean N_SetLabelAt(long self, int index, String label); + private final native MenuItemType N_GetType(long self, int command_id); + private final native MenuItemType N_GetTypeAt(long self, int index); + private final native int N_GetGroupId(long self, int command_id); + private final native int N_GetGroupIdAt(long self, int index); + private final native boolean N_SetGroupId(long self, int command_id, int group_id); + private final native boolean N_SetGroupIdAt(long self, int index, int group_id); + private final native CefMenuModel N_GetSubMenu(long self, int command_id); + private final native CefMenuModel N_GetSubMenuAt(long self, int index); + private final native boolean N_IsVisible(long self, int command_id); + private final native boolean N_IsVisibleAt(long self, int index); + private final native boolean N_SetVisible(long self, int command_id, boolean visible); + private final native boolean N_SetVisibleAt(long self, int index, boolean visible); + private final native boolean N_IsEnabled(long self, int command_id); + private final native boolean N_IsEnabledAt(long self, int index); + private final native boolean N_SetEnabled(long self, int command_id, boolean enabled); + private final native boolean N_SetEnabledAt(long self, int index, boolean enabled); + private final native boolean N_IsChecked(long self, int command_id); + private final native boolean N_IsCheckedAt(long self, int index); + private final native boolean N_SetChecked(long self, int command_id, boolean checked); + private final native boolean N_SetCheckedAt(long self, int index, boolean checked); + private final native boolean N_HasAccelerator(long self, int command_id); + private final native boolean N_HasAcceleratorAt(long self, int index); + private final native boolean N_SetAccelerator(long self, int command_id, int key_code, + boolean shift_pressed, boolean ctrl_pressed, boolean alt_pressed); + private final native boolean N_SetAcceleratorAt(long self, int index, int key_code, + boolean shift_pressed, boolean ctrl_pressed, boolean alt_pressed); + private final native boolean N_RemoveAccelerator(long self, int command_id); + private final native boolean N_RemoveAcceleratorAt(long self, int index); + private final native boolean N_GetAccelerator(long self, int command_id, IntRef key_code, + BoolRef shift_pressed, BoolRef ctrl_pressed, BoolRef alt_pressed); + private final native boolean N_GetAcceleratorAt(long self, int index, IntRef key_code, + BoolRef shift_pressed, BoolRef ctrl_pressed, BoolRef alt_pressed); +} diff --git a/src/main/java/org/cef/callback/CefNative.java b/src/main/java/org/cef/callback/CefNative.java new file mode 100644 index 0000000..d72d88b --- /dev/null +++ b/src/main/java/org/cef/callback/CefNative.java @@ -0,0 +1,29 @@ +// Copyright (c) 2014 The Chromium Embedded Framework Authors. All rights +// reserved. Use of this source code is governed by a BSD-style license that +// can be found in the LICENSE file. + +package org.cef.callback; + +/** + * The methods of this interface are triggered by the native code + * to store and get the JNI counterparts of the JCEF implementation. + */ +public interface CefNative { + /** + * Method is called by the native code to store a reference + * to an implemented native JNI counterpart. + * + * @param identifer The name of the interface class (e.g. CefFocusHandler). + * @param nativeRef The reference to the native code. + */ + public void setNativeRef(String identifer, long nativeRef); + + /** + * Method is called by the native code to get the reference + * to an previous stored identifier. + * + * @param identifer The name of the interface class (e.g. CefFocusHandler). + * @return The stored reference value of the native code. + */ + public long getNativeRef(String identifer); +} diff --git a/src/main/java/org/cef/callback/CefNativeAdapter.java b/src/main/java/org/cef/callback/CefNativeAdapter.java new file mode 100644 index 0000000..aa227e2 --- /dev/null +++ b/src/main/java/org/cef/callback/CefNativeAdapter.java @@ -0,0 +1,16 @@ +package org.cef.callback; + +public class CefNativeAdapter implements CefNative { + // Used internally to store a pointer to the CEF object. + private long N_CefHandle = 0; + + @Override + public void setNativeRef(String identifer, long nativeRef) { + N_CefHandle = nativeRef; + } + + @Override + public long getNativeRef(String identifer) { + return N_CefHandle; + } +} diff --git a/src/main/java/org/cef/callback/CefPdfPrintCallback.java b/src/main/java/org/cef/callback/CefPdfPrintCallback.java new file mode 100644 index 0000000..7bedd5c --- /dev/null +++ b/src/main/java/org/cef/callback/CefPdfPrintCallback.java @@ -0,0 +1,22 @@ +// Copyright (c) 2017 The Chromium Embedded Framework Authors. All rights +// reserved. Use of this source code is governed by a BSD-style license that +// can be found in the LICENSE file. + +package org.cef.callback; + +/** + * Callback interface for CefBrowser.PrintToPDF(). The methods of this class + * will be called on the browser process UI thread. + */ +public interface CefPdfPrintCallback { + + /** + * Method that will be executed when the PDF printing has completed. |path| + * is the output path. |ok| will be true if the printing completed + * successfully or false otherwise. + * @param path The path of the PDF file that was written. + * @param ok True if printing completed or false otherwise. + */ + public abstract void onPdfPrintFinished(String path, boolean ok); + +} diff --git a/src/main/java/org/cef/callback/CefPrintDialogCallback.java b/src/main/java/org/cef/callback/CefPrintDialogCallback.java new file mode 100644 index 0000000..88e5c6a --- /dev/null +++ b/src/main/java/org/cef/callback/CefPrintDialogCallback.java @@ -0,0 +1,22 @@ +// Copyright (c) 2014 The Chromium Embedded Framework Authors. All rights +// reserved. Use of this source code is governed by a BSD-style license that +// can be found in the LICENSE file. + +package org.cef.callback; + +import org.cef.misc.CefPrintSettings; + +/** + * Callback interface for asynchronous continuation of print dialog requests. + */ +public interface CefPrintDialogCallback { + /** + * Continue printing with the specified |settings|. + */ + void Continue(CefPrintSettings settings); + + /** + * Cancel the printing. + */ + void cancel(); +} diff --git a/src/main/java/org/cef/callback/CefPrintDialogCallback_N.java b/src/main/java/org/cef/callback/CefPrintDialogCallback_N.java new file mode 100644 index 0000000..8883906 --- /dev/null +++ b/src/main/java/org/cef/callback/CefPrintDialogCallback_N.java @@ -0,0 +1,38 @@ +// Copyright (c) 2014 The Chromium Embedded Framework Authors. All rights +// reserved. Use of this source code is governed by a BSD-style license that +// can be found in the LICENSE file. + +package org.cef.callback; + +import org.cef.misc.CefPrintSettings; + +class CefPrintDialogCallback_N extends CefNativeAdapter implements CefPrintDialogCallback { + CefPrintDialogCallback_N() {} + + @Override + protected void finalize() throws Throwable { + cancel(); + super.finalize(); + } + + @Override + public void Continue(CefPrintSettings settings) { + try { + N_Continue(getNativeRef(null), settings); + } catch (UnsatisfiedLinkError ule) { + ule.printStackTrace(); + } + } + + @Override + public void cancel() { + try { + N_Cancel(getNativeRef(null)); + } catch (UnsatisfiedLinkError ule) { + ule.printStackTrace(); + } + } + + private final native void N_Continue(long self, CefPrintSettings settings); + private final native void N_Cancel(long self); +} diff --git a/src/main/java/org/cef/callback/CefPrintJobCallback.java b/src/main/java/org/cef/callback/CefPrintJobCallback.java new file mode 100644 index 0000000..f042537 --- /dev/null +++ b/src/main/java/org/cef/callback/CefPrintJobCallback.java @@ -0,0 +1,15 @@ +// Copyright (c) 2014 The Chromium Embedded Framework Authors. All rights +// reserved. Use of this source code is governed by a BSD-style license that +// can be found in the LICENSE file. + +package org.cef.callback; + +/** + * Callback interface for asynchronous continuation of print job requests. + */ +public interface CefPrintJobCallback { + /** + * Indicate completion of the print job. + */ + void Continue(); +} diff --git a/src/main/java/org/cef/callback/CefPrintJobCallback_N.java b/src/main/java/org/cef/callback/CefPrintJobCallback_N.java new file mode 100644 index 0000000..18e319c --- /dev/null +++ b/src/main/java/org/cef/callback/CefPrintJobCallback_N.java @@ -0,0 +1,26 @@ +// Copyright (c) 2014 The Chromium Embedded Framework Authors. All rights +// reserved. Use of this source code is governed by a BSD-style license that +// can be found in the LICENSE file. + +package org.cef.callback; + +class CefPrintJobCallback_N extends CefNativeAdapter implements CefPrintJobCallback { + CefPrintJobCallback_N() {} + + @Override + protected void finalize() throws Throwable { + Continue(); + super.finalize(); + } + + @Override + public void Continue() { + try { + N_Continue(getNativeRef(null)); + } catch (UnsatisfiedLinkError ule) { + ule.printStackTrace(); + } + } + + private final native void N_Continue(long self); +} diff --git a/src/main/java/org/cef/callback/CefQueryCallback.java b/src/main/java/org/cef/callback/CefQueryCallback.java new file mode 100644 index 0000000..0972bf2 --- /dev/null +++ b/src/main/java/org/cef/callback/CefQueryCallback.java @@ -0,0 +1,25 @@ +// Copyright (c) 2014 The Chromium Embedded Framework Authors. All rights +// reserved. Use of this source code is governed by a BSD-style license that +// can be found in the LICENSE file. + +package org.cef.callback; + +/** + * Interface representing a query callback. + */ +public interface CefQueryCallback { + /** + * Notify the associated JavaScript onSuccess callback that the query has + * completed successfully. + * @param response Response passed to JavaScript. + */ + public void success(String response); + + /** + * Notify the associated JavaScript onFailure callback that the query has + * failed. + * @param error_code Error code passed to JavaScript. + * @param error_message Error message passed to JavaScript. + */ + public void failure(int error_code, String error_message); +} diff --git a/src/main/java/org/cef/callback/CefQueryCallback_N.java b/src/main/java/org/cef/callback/CefQueryCallback_N.java new file mode 100644 index 0000000..14b32e3 --- /dev/null +++ b/src/main/java/org/cef/callback/CefQueryCallback_N.java @@ -0,0 +1,36 @@ +// Copyright (c) 2014 The Chromium Embedded Framework Authors. All rights +// reserved. Use of this source code is governed by a BSD-style license that +// can be found in the LICENSE file. + +package org.cef.callback; + +class CefQueryCallback_N extends CefNativeAdapter implements CefQueryCallback { + CefQueryCallback_N() {} + + @Override + protected void finalize() throws Throwable { + failure(-1, "Unexpected call to CefQueryCallback_N::finalize()"); + super.finalize(); + } + + @Override + public void success(String response) { + try { + N_Success(getNativeRef(null), response); + } catch (UnsatisfiedLinkError ule) { + ule.printStackTrace(); + } + } + + @Override + public void failure(int error_code, String error_message) { + try { + N_Failure(getNativeRef(null), error_code, error_message); + } catch (UnsatisfiedLinkError ule) { + ule.printStackTrace(); + } + } + + private final native void N_Success(long self, String response); + private final native void N_Failure(long self, int error_code, String error_message); +} diff --git a/src/main/java/org/cef/callback/CefRunFileDialogCallback.java b/src/main/java/org/cef/callback/CefRunFileDialogCallback.java new file mode 100644 index 0000000..21c96da --- /dev/null +++ b/src/main/java/org/cef/callback/CefRunFileDialogCallback.java @@ -0,0 +1,26 @@ +// Copyright (c) 2014 The Chromium Embedded Framework Authors. All rights +// reserved. Use of this source code is governed by a BSD-style license that +// can be found in the LICENSE file. + +package org.cef.callback; + +import java.util.Vector; +import org.cef.browser.CefBrowser; + +/** + * Callback interface for CefBrowserHost::RunFileDialog. The methods of this + * class will be called on the browser process UI thread. + */ +public interface CefRunFileDialogCallback { + /** + * Called asynchronously after the file dialog is dismissed. If the selection + * was successful filePaths will be a single value or a list of values + * depending on the dialog mode. If the selection was cancelled filePaths + * will be empty. + * + * @param selectedAcceptFilter 0-based index of the value selected from + * the accept filters array passed to CefBrowserHost::RunFileDialog. + * @param filePaths list of file paths or empty list. + */ + void onFileDialogDismissed(int selectedAcceptFilter, Vector filePaths); +} diff --git a/src/main/java/org/cef/callback/CefSchemeHandlerFactory.java b/src/main/java/org/cef/callback/CefSchemeHandlerFactory.java new file mode 100644 index 0000000..bb2546a --- /dev/null +++ b/src/main/java/org/cef/callback/CefSchemeHandlerFactory.java @@ -0,0 +1,32 @@ +// Copyright (c) 2014 The Chromium Embedded Framework Authors. All rights +// reserved. Use of this source code is governed by a BSD-style license that +// can be found in the LICENSE file. + +package org.cef.callback; + +import org.cef.browser.CefBrowser; +import org.cef.browser.CefFrame; +import org.cef.handler.CefResourceHandler; +import org.cef.network.CefRequest; + +/** + * Class that creates CefResourceHandler instances for handling scheme requests. + * The methods of this class will always be called on the IO thread. + */ +public interface CefSchemeHandlerFactory { + /** + * Return a new resource handler instance to handle the request or NULL to allow default + * handling of the request. + * + * @param browser The corresponding browser, or NULL if the request did not originate from a + * browser window (for example, if the request came from CefURLRequest). + * @param frame The frame generating the event, or NULL if the request did not originate from a + * browser window (for example, if the request came from CefURLRequest). Instance only + * valid within the scope of this method. + * @param schemeName Name of the scheme being created. + * @param request The request itself. Cannot be modified in this callback. Instance only valid + * within the scope of this method. + */ + public CefResourceHandler create( + CefBrowser browser, CefFrame frame, String schemeName, CefRequest request); +} diff --git a/src/main/java/org/cef/callback/CefSchemeRegistrar.java b/src/main/java/org/cef/callback/CefSchemeRegistrar.java new file mode 100644 index 0000000..d4fcb4f --- /dev/null +++ b/src/main/java/org/cef/callback/CefSchemeRegistrar.java @@ -0,0 +1,75 @@ +// Copyright (c) 2014 The Chromium Embedded Framework Authors. All rights +// reserved. Use of this source code is governed by a BSD-style license that +// can be found in the LICENSE file. + +package org.cef.callback; + +/** + * Class that manages custom scheme registrations. + */ +public interface CefSchemeRegistrar { + /** + * Register a custom scheme. This method should not be called for the built-in + * HTTP, HTTPS, FILE, FTP, ABOUT and DATA schemes. + * + * If |isStandard| is true the scheme will be treated as a standard scheme. + * Standard schemes are subject to URL canonicalization and parsing rules as + * defined in the Common Internet Scheme Syntax RFC 1738 Section 3.1 available + * at http://www.ietf.org/rfc/rfc1738.txt + * + * In particular, the syntax for standard scheme URLs must be of the form: + *

+     *  [scheme]://[username]:[password]@[host]:[port]/[url-path]
+     * 
+ * Standard scheme URLs must have a host component that is a fully qualified + * domain name as defined in Section 3.5 of RFC 1034 [13] and Section 2.1 of + * RFC 1123. These URLs will be canonicalized to "scheme://host/path" in the + * simplest case and "scheme://username:password@host:port/path" in the most + * explicit case. For example, "scheme:host/path" and "scheme:///host/path" + * will both be canonicalized to "scheme://host/path". The origin of a + * standard scheme URL is the combination of scheme, host and port (i.e., + * "scheme://host:port" in the most explicit case). + * + * For non-standard scheme URLs only the "scheme:" component is parsed and + * canonicalized. The remainder of the URL will be passed to the handler + * as-is. For example, "scheme:///some%20text" will remain the same. + * Non-standard scheme URLs cannot be used as a target for form submission. + * + * If |isLocal| is true the scheme will be treated with the same security + * rules as those applied to "file" URLs. Normal pages cannot link to or + * access local URLs. Also, by default, local URLs can only perform + * XMLHttpRequest calls to the same URL (origin + path) that originated the + * request. To allow XMLHttpRequest calls from a local URL to other URLs with + * the same origin set the CefSettings.file_access_from_file_urls_allowed + * value to true. To allow XMLHttpRequest calls from a local URL to all + * origins set the CefSettings.universal_access_from_file_urls_allowed value + * to true. + * + * If |isDisplayIsolated| is true the scheme can only be displayed from + * other content hosted with the same scheme. For example, pages in other + * origins cannot create iframes or hyperlinks to URLs with the scheme. For + * schemes that must be accessible from other schemes set this value to false, + * set |is_cors_enabled| to true, and use CORS "Access-Control-Allow-Origin" + * headers to further restrict access. + * + * If |isSecure| is true the scheme will be treated with the same security + * rules as those applied to "https" URLs. For example, loading this scheme + * from other secure schemes will not trigger mixed content warnings. + * + * If |isCorsEnabled| is true the scheme that can be sent CORS requests. + * This value should be true in most cases where |isStandard| is true. + * + * If |isCspBypassing| is true the scheme can bypass Content-Security-Policy + * (CSP) checks. This value should be false in most cases where |isStandard| + * is true. + * + * If |is_fetch_enabled| is true the scheme can perform Fetch API requests. + * + * This function may be called on any thread. It should only be called once + * per unique |schemeName| value. If |schemeName| is already registered or + * if an error occurs this method will return false. + */ + public boolean addCustomScheme(String schemeName, boolean isStandard, boolean isLocal, + boolean isDisplayIsolated, boolean isSecure, boolean isCorsEnabled, + boolean isCspBypassing, boolean isFetchEnabled); +} diff --git a/src/main/java/org/cef/callback/CefSchemeRegistrar_N.java b/src/main/java/org/cef/callback/CefSchemeRegistrar_N.java new file mode 100644 index 0000000..a6f47b7 --- /dev/null +++ b/src/main/java/org/cef/callback/CefSchemeRegistrar_N.java @@ -0,0 +1,24 @@ +// Copyright (c) 2014 The Chromium Embedded Framework Authors. All rights +// reserved. Use of this source code is governed by a BSD-style license that +// can be found in the LICENSE file. + +package org.cef.callback; + +class CefSchemeRegistrar_N extends CefNativeAdapter implements CefSchemeRegistrar { + @Override + public boolean addCustomScheme(String schemeName, boolean isStandard, boolean isLocal, + boolean isDisplayIsolated, boolean isSecure, boolean isCorsEnabled, + boolean isCspBypassing, boolean isFetchEnabled) { + try { + return N_AddCustomScheme(schemeName, isStandard, isLocal, isDisplayIsolated, isSecure, + isCorsEnabled, isCspBypassing, isFetchEnabled); + } catch (UnsatisfiedLinkError err) { + err.printStackTrace(); + } + return false; + } + + private final native boolean N_AddCustomScheme(String schemeName, boolean isStandard, + boolean isLocal, boolean isDisplayIsolated, boolean isSecure, boolean isCorsEnabled, + boolean isCspBypassing, boolean isFetchEnabled); +} diff --git a/src/main/java/org/cef/callback/CefStringVisitor.java b/src/main/java/org/cef/callback/CefStringVisitor.java new file mode 100644 index 0000000..19ee8d7 --- /dev/null +++ b/src/main/java/org/cef/callback/CefStringVisitor.java @@ -0,0 +1,16 @@ +// Copyright (c) 2014 The Chromium Embedded Framework Authors. All rights +// reserved. Use of this source code is governed by a BSD-style license that +// can be found in the LICENSE file. + +package org.cef.callback; + +/** + * Public interface to receive string values asynchronously. + */ +public interface CefStringVisitor { + /** + * Called when the string is available. + * @param string Requested string. + */ + void visit(String string); +} diff --git a/src/main/java/org/cef/callback/CefURLRequestClient.java b/src/main/java/org/cef/callback/CefURLRequestClient.java new file mode 100644 index 0000000..5417e8b --- /dev/null +++ b/src/main/java/org/cef/callback/CefURLRequestClient.java @@ -0,0 +1,54 @@ +// Copyright (c) 2014 The Chromium Embedded Framework Authors. All rights +// reserved. Use of this source code is governed by a BSD-style license that +// can be found in the LICENSE file. + +package org.cef.callback; + +import org.cef.network.CefURLRequest; + +/** + * Interface that should be implemented by the CefURLRequest client. The + * methods of this class will be called on the same thread that created the + * request unless otherwise documented. + */ +public interface CefURLRequestClient extends CefNative { + /** + * Notifies the client that the request has completed. Use the + * CefURLRequest::GetRequestStatus method to determine if the request was + * successful or not. + */ + void onRequestComplete(CefURLRequest request); + + /** + * Notifies the client of upload progress. |current| denotes the number of + * bytes sent so far and |total| is the total size of uploading data (or -1 if + * chunked upload is enabled). This method will only be called if the + * UR_FLAG_REPORT_UPLOAD_PROGRESS flag is set on the request. + */ + void onUploadProgress(CefURLRequest request, int current, int total); + + /** + * Notifies the client of download progress. |current| denotes the number of + * bytes received up to the call and |total| is the expected total size of the + * response (or -1 if not determined). + */ + void onDownloadProgress(CefURLRequest request, int current, int total); + + /** + * Called when some part of the response is read. |data| contains the current + * bytes received since the last call. This method will not be called if the + * UR_FLAG_NO_DOWNLOAD_DATA flag is set on the request. + */ + void onDownloadData(CefURLRequest request, byte[] data, int data_length); + + /** + * Called on the IO thread when the browser needs credentials from the user. + * |isProxy| indicates whether the host is a proxy server. |host| contains the + * hostname and |port| contains the port number. Return true to continue the + * request and call CefAuthCallback::Continue() when the authentication + * information is available. Return false to cancel the request. This method + * will only be called for requests initiated from the browser process. + */ + boolean getAuthCredentials(boolean isProxy, String host, int port, String realm, String scheme, + CefAuthCallback callback); +} diff --git a/src/main/java/org/cef/handler/CefAppHandler.java b/src/main/java/org/cef/handler/CefAppHandler.java new file mode 100644 index 0000000..095e474 --- /dev/null +++ b/src/main/java/org/cef/handler/CefAppHandler.java @@ -0,0 +1,75 @@ +// Copyright (c) 2014 The Chromium Embedded Framework Authors. All rights +// reserved. Use of this source code is governed by a BSD-style license that +// can be found in the LICENSE file. + +package org.cef.handler; + +import org.cef.CefApp.CefAppState; +import org.cef.callback.CefCommandLine; +import org.cef.callback.CefSchemeRegistrar; + +/** + * Implement this interface to provide handler implementations. Methods will be + * called by the process and/or thread indicated. + */ +public interface CefAppHandler { + /** + * Provides an opportunity to view and/or modify command-line arguments before + * processing by CEF and Chromium. The |process_type| value will be empty for + * the browser process. Be cautious when using this method to modify + * command-line arguments for non-browser processes as this may result in + * undefined behavior including crashes. + * @param process_type type of process (empty for browser process). + * @param command_line values of the command line. + */ + public void onBeforeCommandLineProcessing(String process_type, CefCommandLine command_line); + + /** + * Provides an opportunity to hook into the native shutdown process. This + * method is invoked if the user tries to terminate the app by sending the + * corresponding key code (e.g. on Mac: CMD+Q) or something similar. If you + * want to proceed with the default behavior of the native system, return + * false. If you want to abort the terminate or if you want to implement your + * own shutdown sequence return true and do the cleanup on your own. + * @return false to proceed with the default behavior, true to abort + * terminate. + */ + public boolean onBeforeTerminate(); + + /** + * Implement this method to get state changes of the CefApp. + * See {@link CefAppState} for a complete list of possible states. + * + * For example, this method can be used e.g. to get informed if CefApp has + * completed its initialization or its shutdown process. + * + * @param state The current state of CefApp. + */ + public void stateHasChanged(CefAppState state); + + /** + * Provides an opportunity to register custom schemes. Do not keep a reference + * to the |registrar| object. This method is called on the main thread for + * each process and the registered schemes should be the same across all + * processes. + */ + public void onRegisterCustomSchemes(CefSchemeRegistrar registrar); + + // Inherited of CefBrowserProcessHandler + /** + * Called on the browser process UI thread immediately after the CEF context + * has been initialized. + */ + public void onContextInitialized(); + + /** + * Called from any thread when work has been scheduled for the browser process + * main (UI) thread. This callback should schedule a + * CefApp.DoMessageLoopWork() call to happen on the main (UI) thread. + * |delay_ms| is the requested delay in milliseconds. If |delay_ms| is <= 0 + * then the call should happen reasonably soon. If |delay_ms| is > 0 then the + * call should be scheduled to happen after the specified delay and any + * currently pending scheduled call should be cancelled. + */ + public void onScheduleMessagePumpWork(long delay_ms); +} diff --git a/src/main/java/org/cef/handler/CefAppHandlerAdapter.java b/src/main/java/org/cef/handler/CefAppHandlerAdapter.java new file mode 100644 index 0000000..3714609 --- /dev/null +++ b/src/main/java/org/cef/handler/CefAppHandlerAdapter.java @@ -0,0 +1,96 @@ +// Copyright (c) 2014 The Chromium Embedded Framework Authors. All rights +// reserved. Use of this source code is governed by a BSD-style license that +// can be found in the LICENSE file. + +package org.cef.handler; + +import org.cef.CefApp; +import org.cef.CefApp.CefAppState; +import org.cef.callback.CefCommandLine; +import org.cef.callback.CefSchemeRegistrar; + +/** + * An abstract adapter class for managing app handler events. + * The methods in this class are using a default implementation. + * This class exists as convenience for creating handler objects. + */ +public abstract class CefAppHandlerAdapter implements CefAppHandler { + private String[] args_; + + public CefAppHandlerAdapter(String[] args) { + args_ = args; + } + + @Override + public void onBeforeCommandLineProcessing(String process_type, CefCommandLine command_line) { + if (process_type.isEmpty() && args_ != null) { + // Forward switches and arguments from Java to Cef + boolean parseSwitchesDone = false; + for (String arg : args_) { + if (parseSwitchesDone || arg.length() < 2) { + command_line.appendArgument(arg); + continue; + } + // Arguments with '--', '-' and, on Windows, '/' prefixes are considered switches. + int switchCnt = arg.startsWith("--") + ? 2 + : arg.startsWith("/") ? 1 : arg.startsWith("-") ? 1 : 0; + switch (switchCnt) { + case 2: + // An argument of "--" will terminate switch parsing with all subsequent + // tokens + if (arg.length() == 2) { + parseSwitchesDone = true; + continue; + } + // FALL THRU + case 1: { + // Switches can optionally have a value specified using the '=' delimiter + // (e.g. "-switch=value"). + String[] switchVals = arg.substring(switchCnt).split("="); + if (switchVals.length == 2) { + command_line.appendSwitchWithValue(switchVals[0], switchVals[1]); + } else { + command_line.appendSwitch(switchVals[0]); + } + break; + } + case 0: + command_line.appendArgument(arg); + break; + } + } + } + } + + @Override + public boolean onBeforeTerminate() { + // The default implementation does nothing + return false; + } + + @Override + public void stateHasChanged(CefAppState state) { + // The default implementation does nothing + } + + @Override + public void onRegisterCustomSchemes(CefSchemeRegistrar registrar) { + // The default implementation does nothing + } + + @Override + public void onContextInitialized() { + // The default implementation does nothing + } + + @Override + public void onScheduleMessagePumpWork(long delay_ms) { + CefApp.getInstance().doMessageLoopWork(delay_ms); + } + + //Modified by montoyo for MCEF + public void setArgs(String[] args) { + this.args_ = args; + } +} diff --git a/src/main/java/org/cef/handler/CefClientHandler.java b/src/main/java/org/cef/handler/CefClientHandler.java new file mode 100644 index 0000000..6a263a8 --- /dev/null +++ b/src/main/java/org/cef/handler/CefClientHandler.java @@ -0,0 +1,317 @@ +// Copyright (c) 2014 The Chromium Embedded Framework Authors. All rights +// reserved. Use of this source code is governed by a BSD-style license that +// can be found in the LICENSE file. + +package org.cef.handler; + +import org.cef.browser.CefBrowser; +import org.cef.browser.CefMessageRouter; +import org.cef.callback.CefNative; + +import java.util.HashMap; +import java.util.Vector; + +/** + * Implement this interface to provide handler implementations. + */ +public abstract class CefClientHandler implements CefNative { + // Used internally to store a pointer to the CEF object. + private HashMap N_CefHandle = new HashMap(); + private Vector msgRouters = new Vector<>(); + + @Override + public void setNativeRef(String identifer, long nativeRef) { + synchronized (N_CefHandle) { + N_CefHandle.put(identifer, nativeRef); + } + } + + @Override + public long getNativeRef(String identifer) { + synchronized (N_CefHandle) { + if (N_CefHandle.containsKey(identifer)) return N_CefHandle.get(identifer); + } + return 0; + } + + public CefClientHandler() { + try { + N_CefClientHandler_CTOR(); + } catch (UnsatisfiedLinkError err) { + err.printStackTrace(); + } + } + + protected void dispose() { + try { + // Call native DTOR if handler will be destroyed + for (int i = 0; i < msgRouters.size(); i++) { + msgRouters.get(i).dispose(); + } + msgRouters.clear(); + + N_CefClientHandler_DTOR(); + } catch (UnsatisfiedLinkError err) { + err.printStackTrace(); + } + } + + /** + * Returns the java part of the browser implementation. + * @param identifer the unique identifier of the browser. + * @return The found browser or null if none is found. + */ + abstract protected CefBrowser getBrowser(int identifier); + + /** + * Returns a list of all browser instances. + * @return an array of browser Instances. + */ + abstract protected Object[] getAllBrowser(); + + /** + * Return the handler for context menus. If no handler is provided the + * default implementation will be used. + */ + abstract protected CefContextMenuHandler getContextMenuHandler(); + + /** + * Return the handler for dialogs. If no handler is provided the + * default implementation will be used. + */ + abstract protected CefDialogHandler getDialogHandler(); + + /** + * Return the handler for browser display state events. + * This method is a callback method and is called by + * the native code. + */ + abstract protected CefDisplayHandler getDisplayHandler(); + + /** + * Return the handler for download events. + * This method is a callback method and is called by + * the native code. + */ + abstract protected CefDownloadHandler getDownloadHandler(); + + /** + * Return the handler for drag events. + * This method is a callback method and is called by + * the native code. + */ + abstract protected CefDragHandler getDragHandler(); + + /** + * Return the handler for focus events. + * This method is a callback method and is called by + * the native code. + */ + abstract protected CefFocusHandler getFocusHandler(); + + /** + * Return the handler for javascript dialog requests. + * This method is a callback method and is called by + * the native code. + */ + abstract protected CefJSDialogHandler getJSDialogHandler(); + + /** + * Return the handler for keyboard events. + * This method is a callback method and is called by + * the native code. + */ + abstract protected CefKeyboardHandler getKeyboardHandler(); + + /** + * Return the handler for browser life span events. + * This method is a callback method and is called by + * the native code. + */ + abstract protected CefLifeSpanHandler getLifeSpanHandler(); + + /** + * Return the handler for browser load status events. + * This method is a callback method and is called by + * the native code. + */ + abstract protected CefLoadHandler getLoadHandler(); + + /** + * Return the handler for printing on Linux. If a print handler is not + * provided then printing will not be supported on the Linux platform. + * This method is a callback method and is called by + * the native code. + */ + abstract protected CefPrintHandler getPrintHandler(); + + /** + * Return the handler for off-screen rendering events. + * This method is a callback method and is called by + * the native code. + */ + abstract protected CefRenderHandler getRenderHandler(); + + /** + * Return the handler for browser request events. + * This method is a callback method and is called by + * the native code. + */ + abstract protected CefRequestHandler getRequestHandler(); + + /** + * Return the handler for windowed rendering events. + * This method is a callback method and is called by + * the native code. + */ + abstract protected CefWindowHandler getWindowHandler(); + + protected synchronized void addMessageRouter(CefMessageRouter h) { + try { + msgRouters.add(h); + N_addMessageRouter(h); + } catch (UnsatisfiedLinkError err) { + err.printStackTrace(); + } + } + + protected void removeContextMenuHandler(CefContextMenuHandler h) { + try { + N_removeContextMenuHandler(h); + } catch (UnsatisfiedLinkError err) { + err.printStackTrace(); + } + } + + protected void removeDialogHandler(CefDialogHandler h) { + try { + N_removeDialogHandler(h); + } catch (UnsatisfiedLinkError err) { + err.printStackTrace(); + } + } + + protected void removeDisplayHandler(CefDisplayHandler h) { + try { + N_removeDisplayHandler(h); + } catch (UnsatisfiedLinkError err) { + err.printStackTrace(); + } + } + + protected void removeDownloadHandler(CefDisplayHandler h) { + try { + N_removeDownloadHandler(h); + } catch (UnsatisfiedLinkError err) { + err.printStackTrace(); + } + } + + protected void removeDragHandler(CefDragHandler h) { + try { + N_removeDragHandler(h); + } catch (UnsatisfiedLinkError err) { + err.printStackTrace(); + } + } + + protected void removeFocusHandler(CefFocusHandler h) { + try { + N_removeFocusHandler(h); + } catch (UnsatisfiedLinkError err) { + err.printStackTrace(); + } + } + + protected void removeJSDialogHandler(CefJSDialogHandler h) { + try { + N_removeJSDialogHandler(h); + } catch (UnsatisfiedLinkError err) { + err.printStackTrace(); + } + } + + protected void removeKeyboardHandler(CefKeyboardHandler h) { + try { + N_removeKeyboardHandler(h); + } catch (UnsatisfiedLinkError err) { + err.printStackTrace(); + } + } + + protected void removeLifeSpanHandler(CefLifeSpanHandler h) { + try { + N_removeLifeSpanHandler(h); + } catch (UnsatisfiedLinkError err) { + err.printStackTrace(); + } + } + + protected void removeLoadHandler(CefLoadHandler h) { + try { + N_removeLoadHandler(h); + } catch (UnsatisfiedLinkError err) { + err.printStackTrace(); + } + } + + protected void removePrintHandler(CefPrintHandler h) { + try { + N_removePrintHandler(h); + } catch (UnsatisfiedLinkError err) { + err.printStackTrace(); + } + } + + protected synchronized void removeMessageRouter(CefMessageRouter h) { + try { + msgRouters.remove(h); + N_removeMessageRouter(h); + } catch (UnsatisfiedLinkError err) { + err.printStackTrace(); + } + } + + protected void removeRenderHandler(CefRenderHandler h) { + try { + N_removeRenderHandler(h); + } catch (UnsatisfiedLinkError err) { + err.printStackTrace(); + } + } + + protected void removeRequestHandler(CefRequestHandler h) { + try { + N_removeRequestHandler(h); + } catch (UnsatisfiedLinkError err) { + err.printStackTrace(); + } + } + + protected void removeWindowHandler(CefWindowHandler h) { + try { + N_removeWindowHandler(h); + } catch (UnsatisfiedLinkError err) { + err.printStackTrace(); + } + } + + private final native void N_CefClientHandler_CTOR(); + private final native void N_addMessageRouter(CefMessageRouter h); + private final native void N_removeContextMenuHandler(CefContextMenuHandler h); + private final native void N_removeDialogHandler(CefDialogHandler h); + private final native void N_removeDisplayHandler(CefDisplayHandler h); + private final native void N_removeDownloadHandler(CefDisplayHandler h); + private final native void N_removeDragHandler(CefDragHandler h); + private final native void N_removeFocusHandler(CefFocusHandler h); + private final native void N_removeJSDialogHandler(CefJSDialogHandler h); + private final native void N_removeKeyboardHandler(CefKeyboardHandler h); + private final native void N_removeLifeSpanHandler(CefLifeSpanHandler h); + private final native void N_removeLoadHandler(CefLoadHandler h); + private final native void N_removePrintHandler(CefPrintHandler h); + private final native void N_removeMessageRouter(CefMessageRouter h); + private final native void N_removeRenderHandler(CefRenderHandler h); + private final native void N_removeRequestHandler(CefRequestHandler h); + private final native void N_removeWindowHandler(CefWindowHandler h); + private final native void N_CefClientHandler_DTOR(); +} diff --git a/src/main/java/org/cef/handler/CefContextMenuHandler.java b/src/main/java/org/cef/handler/CefContextMenuHandler.java new file mode 100644 index 0000000..4860568 --- /dev/null +++ b/src/main/java/org/cef/handler/CefContextMenuHandler.java @@ -0,0 +1,59 @@ +// Copyright (c) 2014 The Chromium Embedded Framework Authors. All rights +// reserved. Use of this source code is governed by a BSD-style license that +// can be found in the LICENSE file. + +package org.cef.handler; + +import org.cef.browser.CefBrowser; +import org.cef.browser.CefFrame; +import org.cef.callback.CefContextMenuParams; +import org.cef.callback.CefMenuModel; + +/** + * Implement this interface to handle context menu events. The methods of this + * class will be called on the UI thread. + */ +public interface CefContextMenuHandler { + /** + * Called before a context menu is displayed. + * + * @param browser The browser generating the event. + * @param frame The frame generating the event. Instance only valid within the scope + * of this method. + * @param params Provides information about the context menu state. Instance + * only valid within the scope of this method. + * @param model Can be cleared to show no context menu or modified + * to show a custom menu. Instance only valid within the scope of this + * method. + */ + public void onBeforeContextMenu( + CefBrowser browser, CefFrame frame, CefContextMenuParams params, CefMenuModel model); + + /** + * Called to execute a command selected from the context menu. Return true if + * the command was handled or false for the default implementation. See + * cef_menu_id_t for the command ids that have default implementations. All + * user-defined command ids should be between MENU_ID_USER_FIRST and + * + * @param browser The browser generating the event. + * @param frame The frame generating the event. Instance only valid within the scope + * of this method. + * @param params Will have the same values that were passed to onBeforeContextMenu(). + * Instance only valid within the scope of this method. + * @param commandId The id of the command. + * @param eventFlags A combination of event flags defined in EventFlags + * @return true if the command was handled or false for the default implementation. + */ + public boolean onContextMenuCommand(CefBrowser browser, CefFrame frame, + CefContextMenuParams params, int commandId, int eventFlags); + + /** + * Called when the context menu is dismissed irregardless of whether the menu + * was empty or a command was selected. + * + * @param browser The browser generating the event. + * @param frame The corresponding frame. Instance only valid within the scope + * of this method. + */ + public void onContextMenuDismissed(CefBrowser browser, CefFrame frame); +} diff --git a/src/main/java/org/cef/handler/CefContextMenuHandlerAdapter.java b/src/main/java/org/cef/handler/CefContextMenuHandlerAdapter.java new file mode 100644 index 0000000..1b4557c --- /dev/null +++ b/src/main/java/org/cef/handler/CefContextMenuHandlerAdapter.java @@ -0,0 +1,30 @@ +// Copyright (c) 2014 The Chromium Embedded Framework Authors. All rights +// reserved. Use of this source code is governed by a BSD-style license that +// can be found in the LICENSE file. + +package org.cef.handler; + +import org.cef.browser.CefBrowser; +import org.cef.browser.CefFrame; +import org.cef.callback.CefContextMenuParams; +import org.cef.callback.CefMenuModel; + +/** + * An abstract adapter class for receiving context menu events. + * The methods in this class are empty. + * This class exists as convenience for creating handler objects. + */ +public abstract class CefContextMenuHandlerAdapter implements CefContextMenuHandler { + @Override + public void onBeforeContextMenu( + CefBrowser browser, CefFrame frame, CefContextMenuParams params, CefMenuModel model) {} + + @Override + public boolean onContextMenuCommand(CefBrowser browser, CefFrame frame, + CefContextMenuParams params, int commandId, int eventFlags) { + return false; + } + + @Override + public void onContextMenuDismissed(CefBrowser browser, CefFrame frame) {} +} diff --git a/src/main/java/org/cef/handler/CefCookieAccessFilter.java b/src/main/java/org/cef/handler/CefCookieAccessFilter.java new file mode 100644 index 0000000..ecdba42 --- /dev/null +++ b/src/main/java/org/cef/handler/CefCookieAccessFilter.java @@ -0,0 +1,52 @@ +// Copyright (c) 2019 The Chromium Embedded Framework Authors. All rights +// reserved. Use of this source code is governed by a BSD-style license that +// can be found in the LICENSE file. + +package org.cef.handler; + +import org.cef.browser.CefBrowser; +import org.cef.browser.CefFrame; +import org.cef.network.CefCookie; +import org.cef.network.CefRequest; +import org.cef.network.CefResponse; + +/** + * Implement this interface to filter cookies that may be sent or received from resource requests. + * The methods of this class will be called on the IO thread. + */ +public interface CefCookieAccessFilter { + /** + * Called on the IO thread before a resource request is sent. The |browser| and |frame| values + * represent the source of the request, and may be null for requests originating from service + * workers or CefURLRequest. + * + * @param browser The corresponding browser. + * @param frame The frame generating the event. Instance only valid within the scope of this + * method. + * @param request The request itself. Cannot be modified in this callback. Instance only valid + * within the scope of this method. + * @param cookie The cookie that will be sent with the request. Cannot be modified in this + * callback. Instance only valid within the scope of this method. + * @return True if the cookie can be sent or false otherwise. + */ + boolean canSendCookie(CefBrowser browser, CefFrame frame, CefRequest request, CefCookie cookie); + + /** + * Called on the IO thread after a resource response is received. The |browser| and |frame| + * values represent the source of the request, and may be null for requests originating from + * service workers or CefURLRequest. + * + * @param browser The corresponding browser. + * @param frame The frame generating the event. Instance only valid within the scope of this + * method. + * @param request The request itself. Cannot be modified in this callback. Instance only valid + * within the scope of this method. + * @param response The request response. Cannot be modified in this callback. Instance only + * valid within the scope of this method. + * @param cookie The cookie that will be sent with the request. Cannot be modified in this + * callback. Instance only valid within the scope of this method. + * @return True if the cookie can be saved or false otherwise. + */ + boolean canSaveCookie(CefBrowser browser, CefFrame frame, CefRequest request, + CefResponse response, CefCookie cookie); +} diff --git a/src/main/java/org/cef/handler/CefCookieAccessFilterAdapter.java b/src/main/java/org/cef/handler/CefCookieAccessFilterAdapter.java new file mode 100644 index 0000000..94f788e --- /dev/null +++ b/src/main/java/org/cef/handler/CefCookieAccessFilterAdapter.java @@ -0,0 +1,30 @@ +// Copyright (c) 2019 The Chromium Embedded Framework Authors. All rights +// reserved. Use of this source code is governed by a BSD-style license that +// can be found in the LICENSE file. + +package org.cef.handler; + +import org.cef.browser.CefBrowser; +import org.cef.browser.CefFrame; +import org.cef.network.CefCookie; +import org.cef.network.CefRequest; +import org.cef.network.CefResponse; + +/** + * An abstract adapter class for cookie access filter events. + * The methods in this class are empty. + * This class exists as convenience for creating handler objects. + */ +public abstract class CefCookieAccessFilterAdapter implements CefCookieAccessFilter { + @Override + public boolean canSendCookie( + CefBrowser browser, CefFrame frame, CefRequest request, CefCookie cookie) { + return true; + } + + @Override + public boolean canSaveCookie(CefBrowser browser, CefFrame frame, CefRequest request, + CefResponse response, CefCookie cookie) { + return true; + } +} diff --git a/src/main/java/org/cef/handler/CefDialogHandler.java b/src/main/java/org/cef/handler/CefDialogHandler.java new file mode 100644 index 0000000..4e9a9d6 --- /dev/null +++ b/src/main/java/org/cef/handler/CefDialogHandler.java @@ -0,0 +1,51 @@ +// Copyright (c) 2014 The Chromium Embedded Framework Authors. All rights +// reserved. Use of this source code is governed by a BSD-style license that +// can be found in the LICENSE file. + +package org.cef.handler; + +import org.cef.browser.CefBrowser; +import org.cef.callback.CefFileDialogCallback; + +import java.util.Vector; + +/** + * Implement this interface to handle dialog events. The methods of this class + * will be called on the browser process UI thread. + */ +public interface CefDialogHandler { + /** + * Supported file dialog modes. + */ + enum FileDialogMode { + FILE_DIALOG_OPEN, //!< Requires that the file exists before allowing the user to pick it. + FILE_DIALOG_OPEN_MULTIPLE, //!< Like Open, but allows picking multiple files to open. + FILE_DIALOG_SAVE //!< Allows picking a nonexistent file, and prompts to overwrite if the + //! file already exists. + } + + /** + * Called to run a file chooser dialog. + * + * @param browser + * @param mode represents the type of dialog to display. + * @param title to be used for the dialog and may be empty to show the default + * title ("Open" or "Save" depending on the mode). + * @param defaultFilePath is the path with optional directory and/or file name + * component that should be initially selected in the dialog. + * @param acceptFilters are used to restrict the selectable file types and may + * any combination of (a) valid lower-cased MIME types (e.g. "text/*" or + * "image/*"), (b) individual file extensions (e.g. ".txt" or ".png"), or (c) + * combined description and file extension delimited using "|" and ";" (e.g. + * "Image Types|.png;.gif;.jpg"). + * @param selectedAcceptFilter is the 0-based index of the filter that should + * be selected by default. + * @param callback is a callback handler for handling own file dialogs. + * + * @return To display a custom dialog return true and execute callback. + * To display the default dialog return false. + */ + public boolean onFileDialog(CefBrowser browser, FileDialogMode mode, String title, + String defaultFilePath, Vector acceptFilters, int selectedAcceptFilter, + CefFileDialogCallback callback); +} diff --git a/src/main/java/org/cef/handler/CefDisplayHandler.java b/src/main/java/org/cef/handler/CefDisplayHandler.java new file mode 100644 index 0000000..0affefc --- /dev/null +++ b/src/main/java/org/cef/handler/CefDisplayHandler.java @@ -0,0 +1,65 @@ +// Copyright (c) 2014 The Chromium Embedded Framework Authors. All rights +// reserved. Use of this source code is governed by a BSD-style license that +// can be found in the LICENSE file. + +package org.cef.handler; + +import org.cef.CefSettings; +import org.cef.browser.CefBrowser; +import org.cef.browser.CefFrame; + +/** + * Implement this interface to handle events related to browser display state. + * The methods of this class will be called on the UI thread. + */ +public interface CefDisplayHandler { + /** + * Browser address changed. + * @param browser The browser generating the event. + * @param frame The frame generating the event. + * @param url The new address. + */ + public void onAddressChange(CefBrowser browser, CefFrame frame, String url); + + /** + * Browser title changed. + * @param browser The browser generating the event. + * @param title The new title. + */ + public void onTitleChange(CefBrowser browser, String title); + + /** + * About to display a tooltip. + * @param browser The browser generating the event. + * @param text Contains the text that will be displayed in the tooltip. + * @return true to handle the tooltip display yourself. + */ + public boolean onTooltip(CefBrowser browser, String text); + + /** + * Received a status message. + * @param browser The browser generating the event. + * @param value Contains the text that will be displayed in the status message. + */ + public void onStatusMessage(CefBrowser browser, String value); + + /** + * Display a console message. + * @param browser The browser generating the event. + * @param level + * @param message + * @param source + * @param line + * @return true to stop the message from being output to the console. + */ + public boolean onConsoleMessage(CefBrowser browser, CefSettings.LogSeverity level, + String message, String source, int line); + + /** + * Handle cursor changes. + * @param browser The browser generating the event. + * @param cursorType The new cursor type. + * @return true if the cursor change was handled. + */ + public boolean onCursorChange(CefBrowser browser, int cursorType); +} diff --git a/src/main/java/org/cef/handler/CefDisplayHandlerAdapter.java b/src/main/java/org/cef/handler/CefDisplayHandlerAdapter.java new file mode 100644 index 0000000..80ab96b --- /dev/null +++ b/src/main/java/org/cef/handler/CefDisplayHandlerAdapter.java @@ -0,0 +1,47 @@ +// Copyright (c) 2014 The Chromium Embedded Framework Authors. All rights +// reserved. Use of this source code is governed by a BSD-style license that +// can be found in the LICENSE file. + +package org.cef.handler; + +import org.cef.CefSettings; +import org.cef.browser.CefBrowser; +import org.cef.browser.CefFrame; + +/** + * An abstract adapter class for receiving display events. + * The methods in this class are empty. + * This class exists as convenience for creating handler objects. + */ +public abstract class CefDisplayHandlerAdapter implements CefDisplayHandler { + @Override + public void onAddressChange(CefBrowser browser, CefFrame frame, String url) { + return; + } + + @Override + public void onTitleChange(CefBrowser browser, String title) { + return; + } + + @Override + public boolean onTooltip(CefBrowser browser, String text) { + return false; + } + + @Override + public void onStatusMessage(CefBrowser browser, String value) { + return; + } + + @Override + public boolean onConsoleMessage(CefBrowser browser, CefSettings.LogSeverity level, + String message, String source, int line) { + return false; + } + + @Override + public boolean onCursorChange(CefBrowser browser, int cursorType) { + return false; + } +} diff --git a/src/main/java/org/cef/handler/CefDownloadHandler.java b/src/main/java/org/cef/handler/CefDownloadHandler.java new file mode 100644 index 0000000..66ea49a --- /dev/null +++ b/src/main/java/org/cef/handler/CefDownloadHandler.java @@ -0,0 +1,39 @@ +// Copyright (c) 2014 The Chromium Embedded Framework Authors. All rights +// reserved. Use of this source code is governed by a BSD-style license that +// can be found in the LICENSE file. + +package org.cef.handler; + +import org.cef.browser.CefBrowser; +import org.cef.callback.CefBeforeDownloadCallback; +import org.cef.callback.CefDownloadItem; +import org.cef.callback.CefDownloadItemCallback; + +/** + * Implement this interface to handle file downloads. The methods of this class + * will called on the browser process UI thread. + */ +public interface CefDownloadHandler { + /** + * Called before a download begins. By default the download will be canceled. + * Execute callback either asynchronously or in this method to continue the download + * if desired. + * + * @param browser The desired browser. + * @param downloadItem The item to be downloaded. Do not keep a reference to it outside this + * method. + * @param suggestedName is the suggested name for the download file. + * @param callback start the download by calling the Continue method + */ + public void onBeforeDownload(CefBrowser browser, CefDownloadItem downloadItem, + String suggestedName, CefBeforeDownloadCallback callback); + + /** + * Called when a download's status or progress information has been updated. + * @param browser The desired browser. + * @param downloadItem The downloading item. + * @param callback Execute callback to cancel the download + */ + public void onDownloadUpdated( + CefBrowser browser, CefDownloadItem downloadItem, CefDownloadItemCallback callback); +} diff --git a/src/main/java/org/cef/handler/CefDownloadHandlerAdapter.java b/src/main/java/org/cef/handler/CefDownloadHandlerAdapter.java new file mode 100644 index 0000000..8b1f46a --- /dev/null +++ b/src/main/java/org/cef/handler/CefDownloadHandlerAdapter.java @@ -0,0 +1,25 @@ +// Copyright (c) 2014 The Chromium Embedded Framework Authors. All rights +// reserved. Use of this source code is governed by a BSD-style license that +// can be found in the LICENSE file. + +package org.cef.handler; + +import org.cef.browser.CefBrowser; +import org.cef.callback.CefBeforeDownloadCallback; +import org.cef.callback.CefDownloadItem; +import org.cef.callback.CefDownloadItemCallback; + +/** + * An abstract adapter class for receiving download events. + * The methods in this class are empty. + * This class exists as convenience for creating handler objects. + */ +public abstract class CefDownloadHandlerAdapter implements CefDownloadHandler { + @Override + public void onBeforeDownload(CefBrowser browser, CefDownloadItem downloadItem, + String suggestedName, CefBeforeDownloadCallback callback) {} + + @Override + public void onDownloadUpdated( + CefBrowser browser, CefDownloadItem downloadItem, CefDownloadItemCallback callback) {} +} diff --git a/src/main/java/org/cef/handler/CefDragHandler.java b/src/main/java/org/cef/handler/CefDragHandler.java new file mode 100644 index 0000000..d783af1 --- /dev/null +++ b/src/main/java/org/cef/handler/CefDragHandler.java @@ -0,0 +1,39 @@ +// Copyright (c) 2014 The Chromium Embedded Framework Authors. All rights +// reserved. Use of this source code is governed by a BSD-style license that +// can be found in the LICENSE file. + +package org.cef.handler; + +import org.cef.browser.CefBrowser; +import org.cef.callback.CefDragData; + +/** + * Implement this interface to handle events related to dragging. The methods of + * this class will be called on the UI thread. + */ +public interface CefDragHandler { + /** + * + */ + public static final class DragOperationMask { + public static final int DRAG_OPERATION_NONE = 0; + public static final int DRAG_OPERATION_COPY = 1; + public static final int DRAG_OPERATION_LINK = 2; + public static final int DRAG_OPERATION_GENERIC = 4; + public static final int DRAG_OPERATION_PRIVATE = 8; + public static final int DRAG_OPERATION_MOVE = 16; + public static final int DRAG_OPERATION_DELETE = 32; + public static final int DRAG_OPERATION_EVERY = Integer.MAX_VALUE; + } + + /** + * Called when an external drag event enters the browser window. + * + * @param browser The browser generating the event. + * @param dragData Contains the drag event data. Instance only valid within the scope + * of this method. + * @param mask Represents the type of drag operation. See DragOperationMask for possible values. + * @return False for default drag handling behavior or true to cancel the drag event. + */ + public boolean onDragEnter(CefBrowser browser, CefDragData dragData, int mask); +} diff --git a/src/main/java/org/cef/handler/CefFocusHandler.java b/src/main/java/org/cef/handler/CefFocusHandler.java new file mode 100644 index 0000000..57a9327 --- /dev/null +++ b/src/main/java/org/cef/handler/CefFocusHandler.java @@ -0,0 +1,48 @@ +// Copyright (c) 2014 The Chromium Embedded Framework Authors. All rights +// reserved. Use of this source code is governed by a BSD-style license that +// can be found in the LICENSE file. + +package org.cef.handler; + +import org.cef.browser.CefBrowser; + +/** + * Implement this interface to handle events related to focus. The methods of + * this class will be called on the UI thread. + */ +public interface CefFocusHandler { + /** + * Focus sources. + */ + enum FocusSource { + FOCUS_SOURCE_NAVIGATION, //!< The source is explicit navigation via the API (LoadURL(), + //! etc). + FOCUS_SOURCE_SYSTEM //!< The source is a system-generated focus event. + } + + /** + * Called when the browser component is about to loose focus. + * For instance, if focus was on the last HTML element and the + * user pressed the TAB key. + * @param browser The browser generating the event. + * @param next will be true if the browser is giving focus to the + * next component and false if the browser is giving focus + * to the previous component. + */ + public void onTakeFocus(CefBrowser browser, boolean next); + + /** + * Called when the browser component is requesting focus. + * @param browser The browser generating the event. + * @param source indicates/ where the focus request is originating from. + * + * @return false to allow the focus to be set or true to cancel setting the focus. + */ + public boolean onSetFocus(CefBrowser browser, FocusSource source); + + /** + * Called when the browser component has received focus. + * @param browser The browser generating the event. + */ + public void onGotFocus(CefBrowser browser); +} diff --git a/src/main/java/org/cef/handler/CefFocusHandlerAdapter.java b/src/main/java/org/cef/handler/CefFocusHandlerAdapter.java new file mode 100644 index 0000000..9936b19 --- /dev/null +++ b/src/main/java/org/cef/handler/CefFocusHandlerAdapter.java @@ -0,0 +1,27 @@ +// Copyright (c) 2014 The Chromium Embedded Framework Authors. All rights +// reserved. Use of this source code is governed by a BSD-style license that +// can be found in the LICENSE file. + +package org.cef.handler; + +import org.cef.browser.CefBrowser; + +/** + * An abstract adapter class for receiving focus events. + * The methods in this class are empty. + * This class exists as convenience for creating handler objects. + */ +public abstract class CefFocusHandlerAdapter implements CefFocusHandler { + @Override + public void onTakeFocus(CefBrowser browser, boolean next) { + return; + } + + @Override + public boolean onSetFocus(CefBrowser browser, FocusSource source) { + return false; + } + + @Override + public void onGotFocus(CefBrowser browser) {} +} diff --git a/src/main/java/org/cef/handler/CefJSDialogHandler.java b/src/main/java/org/cef/handler/CefJSDialogHandler.java new file mode 100644 index 0000000..d522c9b --- /dev/null +++ b/src/main/java/org/cef/handler/CefJSDialogHandler.java @@ -0,0 +1,81 @@ +// Copyright (c) 2014 The Chromium Embedded Framework Authors. All rights +// reserved. Use of this source code is governed by a BSD-style license that +// can be found in the LICENSE file. + +package org.cef.handler; + +import org.cef.browser.CefBrowser; +import org.cef.callback.CefJSDialogCallback; +import org.cef.misc.BoolRef; + +/** + * Implement this interface to handle events related to JavaScript dialogs. The + * methods of this class will be called on the UI thread. + */ +public interface CefJSDialogHandler { + /** + * Supported JavaScript dialog types. + */ + public static enum JSDialogType { + JSDIALOGTYPE_ALERT, + JSDIALOGTYPE_CONFIRM, + JSDIALOGTYPE_PROMPT, + } + + /** + * Called to run a JavaScript dialog. Set suppress_message to true and + * return false to suppress the message (suppressing messages is preferable + * to immediately executing the callback as this is used to detect presumably + * malicious behavior like spamming alert messages in onbeforeunload). Set + * suppress_message to false and return false to use the default + * implementation (the default implementation will show one modal dialog at a + * time and suppress any additional dialog requests until the displayed dialog + * is dismissed). Return true if the application will use a custom dialog or + * if the callback has been executed immediately. Custom dialogs may be either + * modal or modeless. If a custom dialog is used the application must execute + * callback once the custom dialog is dismissed. + * + * @param browser The corresponding browser. + * @param origin_url The originating url. + * @param dialog_type the dialog type. + * @param message_text the text to be displayed. + * @param default_prompt_text value will be specified for prompt dialogs only. + * @param callback execute callback once the custom dialog is dismissed. + * @param suppress_message set to true to suppress displaying the message. + * @return false to use the default dialog implementation. Return true if the + * application will use a custom dialog. + */ + public boolean onJSDialog(CefBrowser browser, String origin_url, JSDialogType dialog_type, + String message_text, String default_prompt_text, CefJSDialogCallback callback, + BoolRef suppress_message); + + /** + * Called to run a dialog asking the user if they want to leave a page. Return + * false to use the default dialog implementation. Return true if the + * application will use a custom dialog or if the callback has been executed + * immediately. Custom dialogs may be either modal or modeless. If a custom + * dialog is used the application must execute callback once the custom + * dialog is dismissed. + * + * @param browser The corresponding browser. + * @param message_text The text to be displayed. + * @param is_reload true if the page is reloaded. + * @param callback execute callback once the custom dialog is dismissed. + * @return false to use the default dialog implementation. Return true if the + * application will use a custom dialog. + */ + public boolean onBeforeUnloadDialog(CefBrowser browser, String message_text, boolean is_reload, + CefJSDialogCallback callback); + + /** + * Called to cancel any pending dialogs and reset any saved dialog state. Will + * be called due to events like page navigation irregardless of whether any + * dialogs are currently pending. + */ + public void onResetDialogState(CefBrowser browser); + + /** + * Called when the default implementation dialog is closed. + */ + public void onDialogClosed(CefBrowser browser); +} diff --git a/src/main/java/org/cef/handler/CefJSDialogHandlerAdapter.java b/src/main/java/org/cef/handler/CefJSDialogHandlerAdapter.java new file mode 100644 index 0000000..4bbad36 --- /dev/null +++ b/src/main/java/org/cef/handler/CefJSDialogHandlerAdapter.java @@ -0,0 +1,35 @@ +// Copyright (c) 2014 The Chromium Embedded Framework Authors. All rights +// reserved. Use of this source code is governed by a BSD-style license that +// can be found in the LICENSE file. + +package org.cef.handler; + +import org.cef.browser.CefBrowser; +import org.cef.callback.CefJSDialogCallback; +import org.cef.misc.BoolRef; + +/** + * An abstract adapter class for receiving javascript dialog requests. + * The methods in this class are empty. + * This class exists as convenience for creating handler objects. + */ +public abstract class CefJSDialogHandlerAdapter implements CefJSDialogHandler { + @Override + public boolean onJSDialog(CefBrowser browser, String origin_url, JSDialogType dialog_type, + String message_text, String default_prompt_text, CefJSDialogCallback callback, + BoolRef suppress_message) { + return false; + } + + @Override + public boolean onBeforeUnloadDialog(CefBrowser browser, String message_text, boolean is_reload, + CefJSDialogCallback callback) { + return false; + } + + @Override + public void onResetDialogState(CefBrowser browser) {} + + @Override + public void onDialogClosed(CefBrowser browser) {} +} diff --git a/src/main/java/org/cef/handler/CefKeyboardHandler.java b/src/main/java/org/cef/handler/CefKeyboardHandler.java new file mode 100644 index 0000000..72027b8 --- /dev/null +++ b/src/main/java/org/cef/handler/CefKeyboardHandler.java @@ -0,0 +1,140 @@ +// Copyright (c) 2014 The Chromium Embedded Framework Authors. All rights +// reserved. Use of this source code is governed by a BSD-style license that +// can be found in the LICENSE file. + +package org.cef.handler; + +import org.cef.browser.CefBrowser; +import org.cef.misc.BoolRef; + +/** + * Implement this interface to handle events related to keyboard input. The + * methods of this class will be called on the UI thread. + */ +public interface CefKeyboardHandler { + /** + * Structure representing keyboard event information. + */ + public static final class CefKeyEvent { + /** + * Key event types. + */ + public static enum EventType { + /** + * Notification that a key transitioned from "up" to "down" + */ + KEYEVENT_RAWKEYDOWN, + + /** + * Notification that a key was pressed. This does not necessarily + * correspond to a character depending on the key and language. Use + * KEYEVENT_CHAR for character input. + */ + KEYEVENT_KEYDOWN, + + /** + * Notification that a key was released + */ + KEYEVENT_KEYUP, + + /** + * Notification that a character was typed. Use this for text input. Key + * down events may generate 0, 1, or more than one character event + * depending on the key, locale, and operating system. + */ + KEYEVENT_CHAR + } + + CefKeyEvent(EventType typeAttr, int modifiersAttr, int windows_key_codeAttr, + int native_key_codeAttr, boolean is_system_keyAttr, char characterAttr, + char unmodified_characterAttr, boolean focus_on_editable_fieldAttr) { + type = typeAttr; + modifiers = modifiersAttr; + windows_key_code = windows_key_codeAttr; + native_key_code = native_key_codeAttr; + is_system_key = is_system_keyAttr; + character = characterAttr; + unmodified_character = unmodified_characterAttr; + focus_on_editable_field = focus_on_editable_fieldAttr; + } + + /** + * The type of keyboard event. + */ + public final EventType type; + + /** + * Bit flags describing any pressed modifier keys. + * @see org.cef.handler.CefContextMenuHandler.EventFlags for values. + */ + public final int modifiers; + + /** + * The Windows key code for the key event. This value is used by the DOM + * specification. Sometimes it comes directly from the event (i.e. on + * Windows) and sometimes it's determined using a mapping function. See + * WebCore/platform/chromium/KeyboardCodes.h for the list of values. + */ + public final int windows_key_code; + + /** + * The actual key code genenerated by the platform. + */ + public final int native_key_code; + + /** + * Indicates whether the event is considered a "system key" event (see + * http://msdn.microsoft.com/en-us/library/ms646286(VS.85).aspx for details). + * This value will always be false on non-Windows platforms. + */ + public final boolean is_system_key; + + /** + * The character generated by the keystroke. + */ + public final char character; + + /** + * Same as character but unmodified by any concurrently-held modifiers + * (except shift). This is useful for working out shortcut keys. + **/ + public final char unmodified_character; + + /** + * True if the focus is currently on an editable field on the page. This is + * useful for determining if standard key events should be intercepted. + */ + public final boolean focus_on_editable_field; + + @Override + public String toString() { + return "CefKeyEvent [type=" + type + ", modifiers=" + modifiers + + ", windows_key_code=" + windows_key_code + + ", native_key_code=" + native_key_code + ", is_system_key=" + is_system_key + + ", character=" + character + ", unmodified_character=" + unmodified_character + + ", focus_on_editable_field=" + focus_on_editable_field + "]"; + } + } + + /** + * Called before a keyboard event is sent to the renderer. + * + * @param browser the corresponding browser. + * @param event contains information about the keyboard event. + * @param is_keyboard_shortcut set to true and return false, if + * the event will be handled in OnKeyEvent() as a keyboard shortcut. + * @return true if the event was handled or false otherwise. + */ + public boolean onPreKeyEvent( + CefBrowser browser, CefKeyEvent event, BoolRef is_keyboard_shortcut); + + /** + * Called after the renderer and JavaScript in the page has had a chance to + * handle the event. + * + * @param browser the corresponding browser. + * @param event contains information about the keyboard event. + * @return true if the keyboard event was handled or false otherwise. + */ + public boolean onKeyEvent(CefBrowser browser, CefKeyEvent event); +} diff --git a/src/main/java/org/cef/handler/CefKeyboardHandlerAdapter.java b/src/main/java/org/cef/handler/CefKeyboardHandlerAdapter.java new file mode 100644 index 0000000..af37023 --- /dev/null +++ b/src/main/java/org/cef/handler/CefKeyboardHandlerAdapter.java @@ -0,0 +1,26 @@ +// Copyright (c) 2014 The Chromium Embedded Framework Authors. All rights +// reserved. Use of this source code is governed by a BSD-style license that +// can be found in the LICENSE file. + +package org.cef.handler; + +import org.cef.browser.CefBrowser; +import org.cef.misc.BoolRef; + +/** + * An abstract adapter class for receiving keyboard events. + * The methods in this class are empty. + * This class exists as convenience for creating handler objects. + */ +public abstract class CefKeyboardHandlerAdapter implements CefKeyboardHandler { + @Override + public boolean onPreKeyEvent( + CefBrowser browser, CefKeyEvent event, BoolRef is_keyboard_shortcut) { + return false; + } + + @Override + public boolean onKeyEvent(CefBrowser browser, CefKeyEvent event) { + return false; + } +} diff --git a/src/main/java/org/cef/handler/CefLifeSpanHandler.java b/src/main/java/org/cef/handler/CefLifeSpanHandler.java new file mode 100644 index 0000000..cc132f3 --- /dev/null +++ b/src/main/java/org/cef/handler/CefLifeSpanHandler.java @@ -0,0 +1,65 @@ +// Copyright (c) 2014 The Chromium Embedded Framework Authors. All rights +// reserved. Use of this source code is governed by a BSD-style license that +// can be found in the LICENSE file. + +package org.cef.handler; + +import org.cef.browser.CefBrowser; +import org.cef.browser.CefFrame; + +/** + * Implement this interface to handle events related to browser life span. The methods of this class + * will be called on the UI thread unless otherwise indicated. + */ +public interface CefLifeSpanHandler { + /** + * Called on the IO thread before a new popup window is created. + * @param browser The source of the popup request. + * @param frame The source of the popup request. Instance only valid within the scope of this + * method. + * @param target_url May be empty if none is specified with the request. + * @param target_frame_name May be empty if none is specified with the request. + * @return True to cancel creation of the popup window or false to proceed. + */ + boolean onBeforePopup( + CefBrowser browser, CefFrame frame, String target_url, String target_frame_name); + + /** + * Handle creation of a new browser window. + * @param browser The browser generating the event. + */ + void onAfterCreated(CefBrowser browser); + + /** + * Called after a browser's native parent window has changed. + * @param browser The browser generating the event. + */ + void onAfterParentChanged(CefBrowser browser); + + /** + * Called when a browser has received a request to close. + * + * If CEF created an OS window for the browser returning false will send an OS close + * notification to the browser window's top-level owner (e.g. WM_CLOSE on Windows, performClose: + * on OS-X and "delete_event" on Linux). If no OS window exists (window rendering disabled) + * returning false will cause the browser object to be destroyed immediately. Return true if the + * browser is parented to another window and that other window needs to receive close + * notification via some non-standard technique. + * + * @param browser The browser generating the event. + * @return False to send an OS close notification to the browser window's top-level owner. + */ + boolean doClose(CefBrowser browser); + + /** + * Called just before a browser is destroyed. + * + * Release all references to the browser object and do not attempt to execute any methods on the + * browser object after this callback returns. If this is a modal window and a custom modal loop + * implementation was provided in runModal() this callback should be used to exit the custom + * modal loop. See doClose() documentation for additional usage information. + * + * @param browser The browser generating the event. + */ + void onBeforeClose(CefBrowser browser); +} diff --git a/src/main/java/org/cef/handler/CefLifeSpanHandlerAdapter.java b/src/main/java/org/cef/handler/CefLifeSpanHandlerAdapter.java new file mode 100644 index 0000000..4cf96ef --- /dev/null +++ b/src/main/java/org/cef/handler/CefLifeSpanHandlerAdapter.java @@ -0,0 +1,35 @@ +// Copyright (c) 2014 The Chromium Embedded Framework Authors. All rights +// reserved. Use of this source code is governed by a BSD-style license that +// can be found in the LICENSE file. + +package org.cef.handler; + +import org.cef.browser.CefBrowser; +import org.cef.browser.CefFrame; + +/** + * An abstract adapter class for receiving life span events. + * The methods in this class are empty. + * This class exists as convenience for creating handler objects. + */ +public abstract class CefLifeSpanHandlerAdapter implements CefLifeSpanHandler { + @Override + public boolean onBeforePopup( + CefBrowser browser, CefFrame frame, String target_url, String target_frame_name) { + return false; + } + + @Override + public void onAfterCreated(CefBrowser browser) {} + + @Override + public void onAfterParentChanged(CefBrowser browser) {} + + @Override + public boolean doClose(CefBrowser browser) { + return false; + } + + @Override + public void onBeforeClose(CefBrowser browser) {} +} diff --git a/src/main/java/org/cef/handler/CefLoadHandler.java b/src/main/java/org/cef/handler/CefLoadHandler.java new file mode 100644 index 0000000..ba7e503 --- /dev/null +++ b/src/main/java/org/cef/handler/CefLoadHandler.java @@ -0,0 +1,341 @@ +// Copyright (c) 2014 The Chromium Embedded Framework Authors. All rights +// reserved. Use of this source code is governed by a BSD-style license that +// can be found in the LICENSE file. + +package org.cef.handler; + +import org.cef.browser.CefBrowser; +import org.cef.browser.CefFrame; +import org.cef.network.CefRequest.TransitionType; + +import java.util.HashMap; +import java.util.Map; + +/** + * Implement this interface to handle events related to browser load status. + */ +public interface CefLoadHandler { + enum ErrorCode { + ERR_NONE(0), + ERR_IO_PENDING(-1), + ERR_FAILED(-2), + ERR_ABORTED(-3), + ERR_INVALID_ARGUMENT(-4), + ERR_INVALID_HANDLE(-5), + ERR_FILE_NOT_FOUND(-6), + ERR_TIMED_OUT(-7), + ERR_FILE_TOO_BIG(-8), + ERR_UNEXPECTED(-9), + ERR_ACCESS_DENIED(-10), + ERR_NOT_IMPLEMENTED(-11), + ERR_INSUFFICIENT_RESOURCES(-12), + ERR_OUT_OF_MEMORY(-13), + ERR_UPLOAD_FILE_CHANGED(-14), + ERR_SOCKET_NOT_CONNECTED(-15), + ERR_FILE_EXISTS(-16), + ERR_FILE_PATH_TOO_LONG(-17), + ERR_FILE_NO_SPACE(-18), + ERR_FILE_VIRUS_INFECTED(-19), + ERR_BLOCKED_BY_CLIENT(-20), + ERR_NETWORK_CHANGED(-21), + ERR_BLOCKED_BY_ADMINISTRATOR(-22), + ERR_SOCKET_IS_CONNECTED(-23), + ERR_BLOCKED_ENROLLMENT_CHECK_PENDING(-24), + ERR_UPLOAD_STREAM_REWIND_NOT_SUPPORTED(-25), + ERR_CONTEXT_SHUT_DOWN(-26), + ERR_BLOCKED_BY_RESPONSE(-27), + ERR_CLEARTEXT_NOT_PERMITTED(-29), + ERR_CONNECTION_CLOSED(-100), + ERR_CONNECTION_RESET(-101), + ERR_CONNECTION_REFUSED(-102), + ERR_CONNECTION_ABORTED(-103), + ERR_CONNECTION_FAILED(-104), + ERR_NAME_NOT_RESOLVED(-105), + ERR_INTERNET_DISCONNECTED(-106), + ERR_SSL_PROTOCOL_ERROR(-107), + ERR_ADDRESS_INVALID(-108), + ERR_ADDRESS_UNREACHABLE(-109), + ERR_SSL_CLIENT_AUTH_CERT_NEEDED(-110), + ERR_TUNNEL_CONNECTION_FAILED(-111), + ERR_NO_SSL_VERSIONS_ENABLED(-112), + ERR_SSL_VERSION_OR_CIPHER_MISMATCH(-113), + ERR_SSL_RENEGOTIATION_REQUESTED(-114), + ERR_PROXY_AUTH_UNSUPPORTED(-115), + ERR_BAD_SSL_CLIENT_AUTH_CERT(-117), + ERR_CONNECTION_TIMED_OUT(-118), + ERR_HOST_RESOLVER_QUEUE_TOO_LARGE(-119), + ERR_SOCKS_CONNECTION_FAILED(-120), + ERR_SOCKS_CONNECTION_HOST_UNREACHABLE(-121), + ERR_ALPN_NEGOTIATION_FAILED(-122), + ERR_SSL_NO_RENEGOTIATION(-123), + ERR_WINSOCK_UNEXPECTED_WRITTEN_BYTES(-124), + ERR_SSL_DECOMPRESSION_FAILURE_ALERT(-125), + ERR_SSL_BAD_RECORD_MAC_ALERT(-126), + ERR_PROXY_AUTH_REQUESTED(-127), + ERR_PROXY_CONNECTION_FAILED(-130), + ERR_MANDATORY_PROXY_CONFIGURATION_FAILED(-131), + ERR_PRECONNECT_MAX_SOCKET_LIMIT(-133), + ERR_SSL_CLIENT_AUTH_PRIVATE_KEY_ACCESS_DENIED(-134), + ERR_SSL_CLIENT_AUTH_CERT_NO_PRIVATE_KEY(-135), + ERR_PROXY_CERTIFICATE_INVALID(-136), + ERR_NAME_RESOLUTION_FAILED(-137), + ERR_NETWORK_ACCESS_DENIED(-138), + ERR_TEMPORARILY_THROTTLED(-139), + ERR_HTTPS_PROXY_TUNNEL_RESPONSE_REDIRECT(-140), + ERR_SSL_CLIENT_AUTH_SIGNATURE_FAILED(-141), + ERR_MSG_TOO_BIG(-142), + ERR_WS_PROTOCOL_ERROR(-145), + ERR_ADDRESS_IN_USE(-147), + ERR_SSL_HANDSHAKE_NOT_COMPLETED(-148), + ERR_SSL_BAD_PEER_PUBLIC_KEY(-149), + ERR_SSL_PINNED_KEY_NOT_IN_CERT_CHAIN(-150), + ERR_CLIENT_AUTH_CERT_TYPE_UNSUPPORTED(-151), + ERR_SSL_DECRYPT_ERROR_ALERT(-153), + ERR_WS_THROTTLE_QUEUE_TOO_LARGE(-154), + ERR_SSL_SERVER_CERT_CHANGED(-156), + ERR_SSL_UNRECOGNIZED_NAME_ALERT(-159), + ERR_SOCKET_SET_RECEIVE_BUFFER_SIZE_ERROR(-160), + ERR_SOCKET_SET_SEND_BUFFER_SIZE_ERROR(-161), + ERR_SOCKET_RECEIVE_BUFFER_SIZE_UNCHANGEABLE(-162), + ERR_SOCKET_SEND_BUFFER_SIZE_UNCHANGEABLE(-163), + ERR_SSL_CLIENT_AUTH_CERT_BAD_FORMAT(-164), + ERR_ICANN_NAME_COLLISION(-166), + ERR_SSL_SERVER_CERT_BAD_FORMAT(-167), + ERR_CT_STH_PARSING_FAILED(-168), + ERR_CT_STH_INCOMPLETE(-169), + ERR_UNABLE_TO_REUSE_CONNECTION_FOR_PROXY_AUTH(-170), + ERR_CT_CONSISTENCY_PROOF_PARSING_FAILED(-171), + ERR_SSL_OBSOLETE_CIPHER(-172), + ERR_WS_UPGRADE(-173), + ERR_READ_IF_READY_NOT_IMPLEMENTED(-174), + ERR_NO_BUFFER_SPACE(-176), + ERR_SSL_CLIENT_AUTH_NO_COMMON_ALGORITHMS(-177), + ERR_EARLY_DATA_REJECTED(-178), + ERR_WRONG_VERSION_ON_EARLY_DATA(-179), + ERR_TLS13_DOWNGRADE_DETECTED(-180), + ERR_SSL_KEY_USAGE_INCOMPATIBLE(-181), + ERR_INVALID_ECH_CONFIG_LIST(-182), + ERR_ECH_NOT_NEGOTIATED(-183), + ERR_ECH_FALLBACK_CERTIFICATE_INVALID(-184), + ERR_CERT_COMMON_NAME_INVALID(-200), + ERR_CERT_DATE_INVALID(-201), + ERR_CERT_AUTHORITY_INVALID(-202), + ERR_CERT_CONTAINS_ERRORS(-203), + ERR_CERT_NO_REVOCATION_MECHANISM(-204), + ERR_CERT_UNABLE_TO_CHECK_REVOCATION(-205), + ERR_CERT_REVOKED(-206), + ERR_CERT_INVALID(-207), + ERR_CERT_WEAK_SIGNATURE_ALGORITHM(-208), + ERR_CERT_NON_UNIQUE_NAME(-210), + ERR_CERT_WEAK_KEY(-211), + ERR_CERT_NAME_CONSTRAINT_VIOLATION(-212), + ERR_CERT_VALIDITY_TOO_LONG(-213), + ERR_CERTIFICATE_TRANSPARENCY_REQUIRED(-214), + ERR_CERT_SYMANTEC_LEGACY(-215), + ERR_CERT_KNOWN_INTERCEPTION_BLOCKED(-217), + ERR_CERT_END(-219), + ERR_INVALID_URL(-300), + ERR_DISALLOWED_URL_SCHEME(-301), + ERR_UNKNOWN_URL_SCHEME(-302), + ERR_INVALID_REDIRECT(-303), + ERR_TOO_MANY_REDIRECTS(-310), + ERR_UNSAFE_REDIRECT(-311), + ERR_UNSAFE_PORT(-312), + ERR_INVALID_RESPONSE(-320), + ERR_INVALID_CHUNKED_ENCODING(-321), + ERR_METHOD_NOT_SUPPORTED(-322), + ERR_UNEXPECTED_PROXY_AUTH(-323), + ERR_EMPTY_RESPONSE(-324), + ERR_RESPONSE_HEADERS_TOO_BIG(-325), + ERR_PAC_SCRIPT_FAILED(-327), + ERR_REQUEST_RANGE_NOT_SATISFIABLE(-328), + ERR_MALFORMED_IDENTITY(-329), + ERR_CONTENT_DECODING_FAILED(-330), + ERR_NETWORK_IO_SUSPENDED(-331), + ERR_SYN_REPLY_NOT_RECEIVED(-332), + ERR_ENCODING_CONVERSION_FAILED(-333), + ERR_UNRECOGNIZED_FTP_DIRECTORY_LISTING_FORMAT(-334), + ERR_NO_SUPPORTED_PROXIES(-336), + ERR_HTTP2_PROTOCOL_ERROR(-337), + ERR_INVALID_AUTH_CREDENTIALS(-338), + ERR_UNSUPPORTED_AUTH_SCHEME(-339), + ERR_ENCODING_DETECTION_FAILED(-340), + ERR_MISSING_AUTH_CREDENTIALS(-341), + ERR_UNEXPECTED_SECURITY_LIBRARY_STATUS(-342), + ERR_MISCONFIGURED_AUTH_ENVIRONMENT(-343), + ERR_UNDOCUMENTED_SECURITY_LIBRARY_STATUS(-344), + ERR_RESPONSE_BODY_TOO_BIG_TO_DRAIN(-345), + ERR_RESPONSE_HEADERS_MULTIPLE_CONTENT_LENGTH(-346), + ERR_INCOMPLETE_HTTP2_HEADERS(-347), + ERR_PAC_NOT_IN_DHCP(-348), + ERR_RESPONSE_HEADERS_MULTIPLE_CONTENT_DISPOSITION(-349), + ERR_RESPONSE_HEADERS_MULTIPLE_LOCATION(-350), + ERR_HTTP2_SERVER_REFUSED_STREAM(-351), + ERR_HTTP2_PING_FAILED(-352), + ERR_CONTENT_LENGTH_MISMATCH(-354), + ERR_INCOMPLETE_CHUNKED_ENCODING(-355), + ERR_QUIC_PROTOCOL_ERROR(-356), + ERR_RESPONSE_HEADERS_TRUNCATED(-357), + ERR_QUIC_HANDSHAKE_FAILED(-358), + ERR_HTTP2_INADEQUATE_TRANSPORT_SECURITY(-360), + ERR_HTTP2_FLOW_CONTROL_ERROR(-361), + ERR_HTTP2_FRAME_SIZE_ERROR(-362), + ERR_HTTP2_COMPRESSION_ERROR(-363), + ERR_PROXY_AUTH_REQUESTED_WITH_NO_CONNECTION(-364), + ERR_HTTP_1_1_REQUIRED(-365), + ERR_PROXY_HTTP_1_1_REQUIRED(-366), + ERR_PAC_SCRIPT_TERMINATED(-367), + ERR_INVALID_HTTP_RESPONSE(-370), + ERR_CONTENT_DECODING_INIT_FAILED(-371), + ERR_HTTP2_RST_STREAM_NO_ERROR_RECEIVED(-372), + ERR_HTTP2_PUSHED_STREAM_NOT_AVAILABLE(-373), + ERR_HTTP2_CLAIMED_PUSHED_STREAM_RESET_BY_SERVER(-374), + ERR_TOO_MANY_RETRIES(-375), + ERR_HTTP2_STREAM_CLOSED(-376), + ERR_HTTP2_CLIENT_REFUSED_STREAM(-377), + ERR_HTTP2_PUSHED_RESPONSE_DOES_NOT_MATCH(-378), + ERR_HTTP_RESPONSE_CODE_FAILURE(-379), + ERR_QUIC_CERT_ROOT_NOT_KNOWN(-380), + ERR_QUIC_GOAWAY_REQUEST_CAN_BE_RETRIED(-381), + ERR_CACHE_MISS(-400), + ERR_CACHE_READ_FAILURE(-401), + ERR_CACHE_WRITE_FAILURE(-402), + ERR_CACHE_OPERATION_NOT_SUPPORTED(-403), + ERR_CACHE_OPEN_FAILURE(-404), + ERR_CACHE_CREATE_FAILURE(-405), + ERR_CACHE_RACE(-406), + ERR_CACHE_CHECKSUM_READ_FAILURE(-407), + ERR_CACHE_CHECKSUM_MISMATCH(-408), + ERR_CACHE_LOCK_TIMEOUT(-409), + ERR_CACHE_AUTH_FAILURE_AFTER_READ(-410), + ERR_CACHE_ENTRY_NOT_SUITABLE(-411), + ERR_CACHE_DOOM_FAILURE(-412), + ERR_CACHE_OPEN_OR_CREATE_FAILURE(-413), + ERR_INSECURE_RESPONSE(-501), + ERR_NO_PRIVATE_KEY_FOR_CERT(-502), + ERR_ADD_USER_CERT_FAILED(-503), + ERR_INVALID_SIGNED_EXCHANGE(-504), + ERR_INVALID_WEB_BUNDLE(-505), + ERR_TRUST_TOKEN_OPERATION_FAILED(-506), + ERR_TRUST_TOKEN_OPERATION_SUCCESS_WITHOUT_SENDING_REQUEST(-507), + ERR_FTP_FAILED(-601), + ERR_FTP_SERVICE_UNAVAILABLE(-602), + ERR_FTP_TRANSFER_ABORTED(-603), + ERR_FTP_FILE_BUSY(-604), + ERR_FTP_SYNTAX_ERROR(-605), + ERR_FTP_COMMAND_NOT_SUPPORTED(-606), + ERR_FTP_BAD_COMMAND_SEQUENCE(-607), + ERR_PKCS12_IMPORT_BAD_PASSWORD(-701), + ERR_PKCS12_IMPORT_FAILED(-702), + ERR_IMPORT_CA_CERT_NOT_CA(-703), + ERR_IMPORT_CERT_ALREADY_EXISTS(-704), + ERR_IMPORT_CA_CERT_FAILED(-705), + ERR_IMPORT_SERVER_CERT_FAILED(-706), + ERR_PKCS12_IMPORT_INVALID_MAC(-707), + ERR_PKCS12_IMPORT_INVALID_FILE(-708), + ERR_PKCS12_IMPORT_UNSUPPORTED(-709), + ERR_KEY_GENERATION_FAILED(-710), + ERR_PRIVATE_KEY_EXPORT_FAILED(-712), + ERR_SELF_SIGNED_CERT_GENERATION_FAILED(-713), + ERR_CERT_DATABASE_CHANGED(-714), + ERR_DNS_MALFORMED_RESPONSE(-800), + ERR_DNS_SERVER_REQUIRES_TCP(-801), + ERR_DNS_SERVER_FAILED(-802), + ERR_DNS_TIMED_OUT(-803), + ERR_DNS_CACHE_MISS(-804), + ERR_DNS_SEARCH_EMPTY(-805), + ERR_DNS_SORT_ERROR(-806), + ERR_DNS_SECURE_RESOLVER_HOSTNAME_RESOLUTION_FAILED(-808), + ERR_DNS_NAME_HTTPS_ONLY(-809), + ERR_DNS_REQUEST_CANCELLED(-810); + + static private final Map CODES = new HashMap<>(); + static { + for (ErrorCode ec : ErrorCode.values()) { + // only put first value into map (so enums listed first have + // priority over others for duplicate error code values) + if (!CODES.containsKey(ec.code)) { + CODES.put(ec.code, ec); + } + } + } + + private final int code; + + ErrorCode(int code) { + this.code = code; + } + + /** + * Gets the underlying native chrome embedded framework error code value + * as an integer. + * @return The error code as an integer. + */ + public int getCode() { + return code; + } + + /** + * Finds the ErrorCode by the native chrome embedded framework integer-based + * error code value. + * @param code The integer-based raw error code. + * @return The Java enum mapped to that error code or null if none was found. + */ + static public ErrorCode findByCode(int code) { + return CODES.get(code); + } + } + + /** + * Called when the loading state has changed. This callback will be executed twice -- once when + * loading is initiated either programmatically or by user action, and once when loading is + * terminated due to completion, cancellation of failure. + * + * @param browser The corresponding browser. + * @param isLoading true if it is loading. + * @param canGoBack true if you can navigate back. + * @param canGoForward true if you can navigate forward. + */ + public void onLoadingStateChange( + CefBrowser browser, boolean isLoading, boolean canGoBack, boolean canGoForward); + + /** + * Called when the browser begins loading a frame. The frameIdentifer value will never be empty. + * Multiple frames may be loading at the same time. Sub-frames may start or continue loading + * after the main frame load has ended. This method may not be called for a particular frame if + * the load request for that frame fails. For notification of overall browser load status use + * onLoadingStateChange instead. + * + * @param browser The corresponding browser. + * @param frame The frame generating the event. Instance only valid within the scope of this + * method. + * @param transitionType The transition type. + */ + public void onLoadStart(CefBrowser browser, CefFrame frame, TransitionType transitionType); + + /** + * Called when the browser is done loading a frame. The frameIdentifer value will never be + * empty. Multiple frames may be loading at the same time. Sub-frames may start or continue + * loading after the main frame load has ended. This method will always be called for all frames + * irrespective of whether the request completes successfully. + * + * @param browser The corresponding browser. + * @param frame The frame generating the event. Instance only valid within the scope of this + * method. + * @param httpStatusCode The status code of the load. + */ + public void onLoadEnd(CefBrowser browser, CefFrame frame, int httpStatusCode); + + /** + * Called when the resource load for a navigation fails or is canceled. + * + * @param browser The corresponding browser. + * @param frame The frame generating the event. Instance only valid within the scope of this + * method. + * @param errorCode The error code number. + * @param errorText The error text. + * @param failedUrl The URL that failed to load. + */ + public void onLoadError(CefBrowser browser, CefFrame frame, ErrorCode errorCode, + String errorText, String failedUrl); +} diff --git a/src/main/java/org/cef/handler/CefLoadHandlerAdapter.java b/src/main/java/org/cef/handler/CefLoadHandlerAdapter.java new file mode 100644 index 0000000..edbbae1 --- /dev/null +++ b/src/main/java/org/cef/handler/CefLoadHandlerAdapter.java @@ -0,0 +1,30 @@ +// Copyright (c) 2014 The Chromium Embedded Framework Authors. All rights +// reserved. Use of this source code is governed by a BSD-style license that +// can be found in the LICENSE file. + +package org.cef.handler; + +import org.cef.browser.CefBrowser; +import org.cef.browser.CefFrame; +import org.cef.network.CefRequest.TransitionType; + +/** + * An abstract adapter class for receiving load events. + * The methods in this class are empty. + * This class exists as convenience for creating handler objects. + */ +public abstract class CefLoadHandlerAdapter implements CefLoadHandler { + @Override + public void onLoadingStateChange( + CefBrowser browser, boolean isLoading, boolean canGoBack, boolean canGoForward) {} + + @Override + public void onLoadStart(CefBrowser browser, CefFrame frame, TransitionType transitionType) {} + + @Override + public void onLoadEnd(CefBrowser browser, CefFrame frame, int httpStatusCode) {} + + @Override + public void onLoadError(CefBrowser browser, CefFrame frame, ErrorCode errorCode, + String errorText, String failedUrl) {} +} diff --git a/src/main/java/org/cef/handler/CefMessageRouterHandler.java b/src/main/java/org/cef/handler/CefMessageRouterHandler.java new file mode 100644 index 0000000..4827a47 --- /dev/null +++ b/src/main/java/org/cef/handler/CefMessageRouterHandler.java @@ -0,0 +1,43 @@ +// Copyright (c) 2014 The Chromium Embedded Framework Authors. All rights +// reserved. Use of this source code is governed by a BSD-style license that +// can be found in the LICENSE file. + +package org.cef.handler; + +import org.cef.browser.CefBrowser; +import org.cef.browser.CefFrame; +import org.cef.callback.CefNative; +import org.cef.callback.CefQueryCallback; + +/** + * Implement this interface to handle queries. All methods will be executed on the browser process + * UI thread. + */ +public interface CefMessageRouterHandler extends CefNative { + /** + * Called when the browser receives a JavaScript query. + * + * @param browser The corresponding browser. + * @param frame The frame generating the event. Instance only valid within the scope of this + * method. + * @param queryId The unique ID for the query. + * @param persistent True if the query is persistent. + * @param callback Object used to continue or cancel the query asynchronously. + * @return True to handle the query or false to propagate the query to other registered + * handlers, if any. If no handlers return true from this method then the query will be + * automatically canceled with an error code of -1 delivered to the JavaScript onFailure + * callback. + */ + public boolean onQuery(CefBrowser browser, CefFrame frame, long queryId, String request, + boolean persistent, CefQueryCallback callback); + + /** + * Called when a pending JavaScript query is canceled. + * + * @param browser The corresponding browser. + * @param frame The frame generating the event. Instance only valid within the scope of this + * method. + * @param queryId The unique ID for the query. + */ + public void onQueryCanceled(CefBrowser browser, CefFrame frame, long queryId); +} diff --git a/src/main/java/org/cef/handler/CefMessageRouterHandlerAdapter.java b/src/main/java/org/cef/handler/CefMessageRouterHandlerAdapter.java new file mode 100644 index 0000000..c924dbf --- /dev/null +++ b/src/main/java/org/cef/handler/CefMessageRouterHandlerAdapter.java @@ -0,0 +1,29 @@ +// Copyright (c) 2014 The Chromium Embedded Framework Authors. All rights +// reserved. Use of this source code is governed by a BSD-style license that +// can be found in the LICENSE file. + +package org.cef.handler; + +import org.cef.browser.CefBrowser; +import org.cef.browser.CefFrame; +import org.cef.callback.CefNativeAdapter; +import org.cef.callback.CefQueryCallback; + +/** + * An abstract adapter class for receiving message router events. + * The methods in this class are empty. + * This class exists as convenience for creating handler objects. + */ +public abstract class CefMessageRouterHandlerAdapter + extends CefNativeAdapter implements CefMessageRouterHandler { + @Override + public boolean onQuery(CefBrowser browser, CefFrame frame, long queryId, String request, + boolean persistent, CefQueryCallback callback) { + return false; + } + + @Override + public void onQueryCanceled(CefBrowser browser, CefFrame frame, long queryId) { + return; + } +} diff --git a/src/main/java/org/cef/handler/CefPrintHandler.java b/src/main/java/org/cef/handler/CefPrintHandler.java new file mode 100644 index 0000000..ec9cec3 --- /dev/null +++ b/src/main/java/org/cef/handler/CefPrintHandler.java @@ -0,0 +1,77 @@ +// Copyright (c) 2014 The Chromium Embedded Framework Authors. All rights +// reserved. Use of this source code is governed by a BSD-style license that +// can be found in the LICENSE file. + +package org.cef.handler; + +import org.cef.browser.CefBrowser; +import org.cef.callback.CefNative; +import org.cef.callback.CefPrintDialogCallback; +import org.cef.callback.CefPrintJobCallback; +import org.cef.misc.CefPrintSettings; + +import java.awt.Dimension; + +/** + * Implement this interface to handle printing on Linux. The methods of this class will be called on + * the browser process UI thread. + */ +public interface CefPrintHandler extends CefNative { + /** + * Called when printing has started. This method will be called before the other onPrint*() + * methods and irrespective of how printing was initiated (e.g. CefBrowser::print(), JavaScript + * window.print() or PDF extension print button). + * + * @param browser The corresponding browser. + */ + void onPrintStart(CefBrowser browser); + + /** + * Called to get print settings. + * + * @param browser The corresponding browser. + * @param settings Populate with the desired print settings. Do not keep a reference to this + * object outside of this callback. + * @param getDefaults If true |settings| should be populated with the default print settings. + */ + void onPrintSettings(CefBrowser browser, CefPrintSettings settings, boolean getDefaults); + + /** + * Called to show the print dialog. + * + * @param browser The corresponding browser. + * @param hasSelection True if the user has selected a region of the page to print. + * @param callback Callback to execute after the dialog is dismissed. + * @return True if the dialog will be displayed or false to cancel the printing immediately. + */ + boolean onPrintDialog( + CefBrowser browser, boolean hasSelection, CefPrintDialogCallback callback); + + /** + * Called to send the print job to the printer. + * + * @param browser The corresponding browser. + * @param documentName Name of the document that is printing. + * @param pdfFilePath Path to the PDF file that contains the document contents. + * @param callback Callback to execute after the print job has completed. + * @return True if the job will proceed or false to cancel the printing immediately. + */ + boolean onPrintJob(CefBrowser browser, String documentName, String pdfFilePath, + CefPrintJobCallback callback); + + /** + * Called to reset client state related to printing. + * + * @param browser The corresponding browser. + */ + void onPrintReset(CefBrowser browser); + + /** + * Called to retrieve the page size when printToPDF is requested for a browser. + * + * @param browser The corresponding browser. + * @param deviceUnitsPerInch The DPI of the print. Use this to calculate the page size to use. + * @return The page size in microns. + */ + Dimension getPdfPaperSize(CefBrowser browser, int deviceUnitsPerInch); +} diff --git a/src/main/java/org/cef/handler/CefPrintHandlerAdapter.java b/src/main/java/org/cef/handler/CefPrintHandlerAdapter.java new file mode 100644 index 0000000..65a63f5 --- /dev/null +++ b/src/main/java/org/cef/handler/CefPrintHandlerAdapter.java @@ -0,0 +1,60 @@ +// Copyright (c) 2014 The Chromium Embedded Framework Authors. All rights +// reserved. Use of this source code is governed by a BSD-style license that +// can be found in the LICENSE file. + +package org.cef.handler; + +import org.cef.browser.CefBrowser; +import org.cef.callback.CefNativeAdapter; +import org.cef.callback.CefPrintDialogCallback; +import org.cef.callback.CefPrintJobCallback; +import org.cef.misc.CefPrintSettings; + +import java.awt.Dimension; + +/** + * An abstract adapter class for receiving print events on Linux. + * The methods in this class are empty. + * This class exists as convenience for creating handler objects. + */ +public abstract class CefPrintHandlerAdapter extends CefNativeAdapter implements CefPrintHandler { + @Override + public void onPrintStart(CefBrowser browser) { + // The default implementation does nothing + } + + @Override + public void onPrintSettings( + CefBrowser browser, CefPrintSettings settings, boolean getDefaults) { + // The default implementation does nothing + } + + @Override + public boolean onPrintDialog( + CefBrowser browser, boolean hasSelection, CefPrintDialogCallback callback) { + // The default implementation does nothing + return false; + } + + @Override + public boolean onPrintJob(CefBrowser browser, String documentName, String pdfFilePath, + CefPrintJobCallback callback) { + // The default implementation does nothing + return false; + } + + @Override + public void onPrintReset(CefBrowser browser) { + // The default implementation does nothing + } + + @Override + public Dimension getPdfPaperSize(CefBrowser browser, int deviceUnitsPerInch) { + // default implementation is A4 letter size + // @ 300 DPI, A4 is 2480 x 3508 + // @ 150 DPI, A4 is 1240 x 1754 + int adjustedWidth = (int) (((double) deviceUnitsPerInch / 300d) * 2480d); + int adjustedHeight = (int) (((double) deviceUnitsPerInch / 300d) * 3508d); + return new Dimension(adjustedWidth, adjustedHeight); + } +} diff --git a/src/main/java/org/cef/handler/CefRenderHandler.java b/src/main/java/org/cef/handler/CefRenderHandler.java new file mode 100644 index 0000000..0a94bb2 --- /dev/null +++ b/src/main/java/org/cef/handler/CefRenderHandler.java @@ -0,0 +1,106 @@ +// Copyright (c) 2014 The Chromium Embedded Framework Authors. All rights +// reserved. Use of this source code is governed by a BSD-style license that +// can be found in the LICENSE file. + +package org.cef.handler; + +import org.cef.browser.CefBrowser; +import org.cef.callback.CefDragData; + +import java.awt.Point; +import java.awt.Rectangle; +import java.nio.ByteBuffer; + +/** + * Implement this interface to handle events when window rendering is disabled. + * The methods of this class will be called on the UI thread. + */ +public interface CefRenderHandler { + /** + * Retrieve the view rectangle. + * @param browser The browser generating the event. + * @return The view rectangle. + */ + public Rectangle getViewRect(CefBrowser browser); + + /** + * Retrieve the screen info. + * @param browser The browser generating the event. + * @param screenInfo The screenInfo + * @return True if this callback was handled. False to fallback to defaults. + */ + public boolean getScreenInfo(CefBrowser browser, CefScreenInfo screenInfo); + + /** + * Retrieve the screen point for the specified view point. + * @param browser The browser generating the event. + * @param viewPoint The point in the view. + * @return The screen point. + */ + public Point getScreenPoint(CefBrowser browser, Point viewPoint); + + /** + * Show or hide the popup window. + * @param browser The browser generating the event. + * @param show True if the popup window is being shown. + */ + public void onPopupShow(CefBrowser browser, boolean show); + + /** + * Size the popup window. + * @param browser The browser generating the event. + * @param size Size of the popup window. + */ + public void onPopupSize(CefBrowser browser, Rectangle size); + + /** + * Handle painting. + * @param browser The browser generating the event. + * @param popup True if painting a popup window. + * @param dirtyRects Array of dirty regions. + * @param buffer Pixel buffer for the whole window. + * @param width Width of the buffer. + * @param height Height of the buffer. + */ + public void onPaint(CefBrowser browser, boolean popup, Rectangle[] dirtyRects, + ByteBuffer buffer, int width, int height); + + /** + * Handle cursor changes. + * @param browser The browser generating the event. + * @param cursorType The new cursor type. + * @return true if the cursor change was handled. + */ + public boolean onCursorChange(CefBrowser browser, int cursorType); + + /** + * Called when the user starts dragging content in the web view. Contextual + * information about the dragged content is supplied by dragData. + * OS APIs that run a system message loop may be used within the + * StartDragging call. + * + * Return false to abort the drag operation. Don't call any of + * CefBrowser-dragSource*Ended* methods after returning false. + * + * Return true to handle the drag operation. Call + * CefBrowser.dragSourceEndedAt and CefBrowser.ragSourceSystemDragEnded either + * synchronously or asynchronously to inform the web view that the drag + * operation has ended. + * @param browser The browser generating the event. + * @param dragData Contextual information about the dragged content + * @param mask Describes the allowed operation (none, move, copy, link). + * @param x Coordinate within CefBrowser + * @param y Coordinate within CefBrowser + * @return false to abort the drag operation or true to handle the drag operation. + */ + public boolean startDragging(CefBrowser browser, CefDragData dragData, int mask, int x, int y); + + /** + * Called when the web view wants to update the mouse cursor during a + * drag & drop operation. + * + * @param browser The browser generating the event. + * @param operation Describes the allowed operation (none, move, copy, link). + */ + public void updateDragCursor(CefBrowser browser, int operation); +} diff --git a/src/main/java/org/cef/handler/CefRenderHandlerAdapter.java b/src/main/java/org/cef/handler/CefRenderHandlerAdapter.java new file mode 100644 index 0000000..ea78283 --- /dev/null +++ b/src/main/java/org/cef/handler/CefRenderHandlerAdapter.java @@ -0,0 +1,57 @@ +// Copyright (c) 2014 The Chromium Embedded Framework Authors. All rights +// reserved. Use of this source code is governed by a BSD-style license that +// can be found in the LICENSE file. + +package org.cef.handler; + +import org.cef.browser.CefBrowser; +import org.cef.callback.CefDragData; + +import java.awt.Point; +import java.awt.Rectangle; +import java.nio.ByteBuffer; + +/** + * An abstract adapter class for receiving render events. + * The methods in this class are empty. + * This class exists as convenience for creating handler objects. + */ +public abstract class CefRenderHandlerAdapter implements CefRenderHandler { + @Override + public Rectangle getViewRect(CefBrowser browser) { + return new Rectangle(0, 0, 0, 0); + } + + @Override + public boolean getScreenInfo(CefBrowser browser, CefScreenInfo screenInfo) { + return false; + } + + @Override + public Point getScreenPoint(CefBrowser browser, Point viewPoint) { + return new Point(0, 0); + } + + @Override + public void onPopupShow(CefBrowser browser, boolean show) {} + + @Override + public void onPopupSize(CefBrowser browser, Rectangle size) {} + + @Override + public void onPaint(CefBrowser browser, boolean popup, Rectangle[] dirtyRects, + ByteBuffer buffer, int width, int height) {} + + @Override + public boolean onCursorChange(CefBrowser browser, int cursorType) { + return false; + } + + @Override + public boolean startDragging(CefBrowser browser, CefDragData dragData, int mask, int x, int y) { + return false; + } + + @Override + public void updateDragCursor(CefBrowser browser, int operation) {} +} diff --git a/src/main/java/org/cef/handler/CefRequestContextHandler.java b/src/main/java/org/cef/handler/CefRequestContextHandler.java new file mode 100644 index 0000000..627f77e --- /dev/null +++ b/src/main/java/org/cef/handler/CefRequestContextHandler.java @@ -0,0 +1,40 @@ +// Copyright (c) 2014 The Chromium Embedded Framework Authors. All rights +// reserved. Use of this source code is governed by a BSD-style license that +// can be found in the LICENSE file. + +package org.cef.handler; + +import org.cef.browser.CefBrowser; +import org.cef.browser.CefFrame; +import org.cef.handler.CefResourceRequestHandler; +import org.cef.misc.BoolRef; +import org.cef.network.CefRequest; + +/** + * Implement this interface to provide handler implementations. + */ +public interface CefRequestContextHandler { + /** + * Called on the IO thread before a resource request is initiated. The |browser| and |frame| + * values represent the source of the request. This method will not be called if the client + * associated with |browser| returns a non-null value from + * CefRequestHandler.getResourceRequestHandler for the same request (identified by + * CefRequest.getIdentifier). + * + * @param browser The corresponding browser. + * @param frame The frame generating the event. Instance only valid within the scope of this + * method. + * @param request The request itself. Cannot be modified in this callback. Instance only valid + * within the scope of this method. + * @param isNavigation True if the resource request is a navigation. + * @param isDownload True if the resource request is a download. + * @param requestInitiator The origin (scheme + domain) of the page that initiated the request. + * @param disableDefaultHandling Set to true to disable default handling of the request, in + * which case it will need to be handled via + * CefResourceRequestHandler.getResourceHandler or it will be canceled. + * @return A CefResourceRequestHandler instance or null. + */ + CefResourceRequestHandler getResourceRequestHandler(CefBrowser browser, CefFrame frame, + CefRequest request, boolean isNavigation, boolean isDownload, String requestInitiator, + BoolRef disableDefaultHandling); +} diff --git a/src/main/java/org/cef/handler/CefRequestContextHandlerAdapter.java b/src/main/java/org/cef/handler/CefRequestContextHandlerAdapter.java new file mode 100644 index 0000000..7fab771 --- /dev/null +++ b/src/main/java/org/cef/handler/CefRequestContextHandlerAdapter.java @@ -0,0 +1,24 @@ +// Copyright (c) 2015 The Chromium Embedded Framework Authors. All rights +// reserved. Use of this source code is governed by a BSD-style license that +// can be found in the LICENSE file. + +package org.cef.handler; + +import org.cef.browser.CefBrowser; +import org.cef.browser.CefFrame; +import org.cef.misc.BoolRef; +import org.cef.network.CefRequest; + +/** + * An abstract adapter class for receiving browser request context events. + * The methods in this class are empty. + * This class exists as convenience for creating handler objects. + */ +public abstract class CefRequestContextHandlerAdapter implements CefRequestContextHandler { + @Override + public CefResourceRequestHandler getResourceRequestHandler(CefBrowser browser, CefFrame frame, + CefRequest request, boolean isNavigation, boolean isDownload, String requestInitiator, + BoolRef disableDefaultHandling) { + return null; + } +} diff --git a/src/main/java/org/cef/handler/CefRequestHandler.java b/src/main/java/org/cef/handler/CefRequestHandler.java new file mode 100644 index 0000000..8f5f26f --- /dev/null +++ b/src/main/java/org/cef/handler/CefRequestHandler.java @@ -0,0 +1,142 @@ +// Copyright (c) 2014 The Chromium Embedded Framework Authors. All rights +// reserved. Use of this source code is governed by a BSD-style license that +// can be found in the LICENSE file. + +package org.cef.handler; + +import org.cef.browser.CefBrowser; +import org.cef.browser.CefFrame; +import org.cef.callback.CefAuthCallback; +import org.cef.callback.CefCallback; +import org.cef.misc.BoolRef; +import org.cef.network.CefRequest; +import org.cef.network.CefURLRequest; + +/** + * Implement this interface to handle events related to browser requests. The methods of this class + * will be called on the thread indicated. + */ +public interface CefRequestHandler { + /** + * Process termination status values. + */ + enum TerminationStatus { + TS_ABNORMAL_TERMINATION, //!< Non-zero exit status. + TS_PROCESS_WAS_KILLED, //!< SIGKILL or task manager kill. + TS_PROCESS_CRASHED, //!< Segmentation fault. + TS_PROCESS_OOM //!< Out of memory. + } + + /** + * Called on the UI thread before browser navigation. + * + * CefLoadHandler.onLoadingStateChange() will be called twice in all cases. If the navigation is + * allowed CefLoadHandler.onLoadStart() and CefLoadHandler.onLoadEnd() will be called. If the + * navigation is canceled CefLoadHandler.onLoadError() will be called with an errorCode value of + * ERR_ABORTED. + * + * @param browser The corresponding browser. + * @param frame The frame generating the event. Instance only valid within the scope of this + * method. + * @param request The request itself. Cannot be modified in this callback. Instance only valid + * within the scope of this method. + * @param user_gesture True if the request was initiated by a user gesture. + * @param is_redirect True if the request was redirected. + * @return True to cancel the navigation or false to continue. + */ + boolean onBeforeBrowse(CefBrowser browser, CefFrame frame, CefRequest request, + boolean user_gesture, boolean is_redirect); + + /** + * Called on the UI thread before OnBeforeBrowse in certain limited cases + * where navigating a new or different browser might be desirable. This + * includes user-initiated navigation that might open in a special way (e.g. + * links clicked via middle-click or ctrl + left-click) and certain types of + * cross-origin navigation initiated from the renderer process (e.g. + * navigating the top-level frame to/from a file URL). + * + * @param browser The corresponding browser. + * @param frame The frame generating the event. Instance only valid within the scope of this + * method + * @param user_gesture True if the request was initiated by a user gesture. + * @return True to cancel navigation or false to continue + */ + boolean onOpenURLFromTab( + CefBrowser browser, CefFrame frame, String target_url, boolean user_gesture); + + /** + * Called on the IO thread before a resource request is initiated. The |browser| and |frame| + * values represent the source of the request. If this callback returns null the same method + * will be called on the associated CefRequestContextHandler, if any. + * + * @param browser The corresponding browser. + * @param frame The frame generating the event. Instance only valid within the scope of this + * method. + * @param request The request itself. Cannot be modified in this callback. Instance only valid + * within the scope of this method. + * @param isNavigation True if the resource request is a navigation. + * @param isDownload True if the resource request is a download. + * @param requestInitiator The origin (scheme + domain) of the page that initiated the request. + * @param disableDefaultHandling Set to true to disable default handling of the request, in + * which case it will need to be handled via + * CefResourceRequestHandler.getResourceHandler or it will be canceled. + * @return A CefResourceRequestHandler instance or null. + */ + CefResourceRequestHandler getResourceRequestHandler(CefBrowser browser, CefFrame frame, + CefRequest request, boolean isNavigation, boolean isDownload, String requestInitiator, + BoolRef disableDefaultHandling); + + /** + * Called on the IO thread when the browser needs credentials from the user. + * + * @param browser The corresponding browser. + * @param origin_url The origin making this authentication request. + * @param isProxy True if the host is a proxy server. + * @param host Hostname. + * @param port Port number. + * @param realm Realm of the request. + * @param scheme Scheme of the request. + * @param callback Call CefAuthCallback.Continue() when the authentication information is + * available. + * @return True to continue the request (callback must be executed) or false to cancel. + */ + boolean getAuthCredentials(CefBrowser browser, String origin_url, boolean isProxy, String host, + int port, String realm, String scheme, CefAuthCallback callback); + + /** + * Called on the IO thread when JavaScript requests a specific storage quota size via the + * webkitStorageInfo.requestQuota function. + * + * @param browser The corresponding browser. + * @param origin_url Origin of the page making the request. + * @param new_size Requested quota size in bytes. + * @param callback Call CefCallback.Continue() either in this method or at a later time + * to grant or deny the request. + * @return True to handle the request (callback must be executed) or false to cancel. + */ + boolean onQuotaRequest( + CefBrowser browser, String origin_url, long new_size, CefCallback callback); + + /** + * Called on the UI thread to handle requests for URLs with an invalid SSL certificate. If + * "ignore-certificate-errors" command-line switch is set all invalid certificates will be + * accepted without calling this method. + * + * @param browser The corresponding browser. + * @param cert_error Error code describing the error. + * @param request_url The requesting URL. + * @param callback Call CefCallback.Continue() either in this method or at a later time + * to continue or cancel the request. If null the error cannot be recovered from and the + * request will be canceled automatically. + * @return True to handle the request (callback must be executed) or false to reject it. + */ + boolean onCertificateError(CefBrowser browser, CefLoadHandler.ErrorCode cert_error, + String request_url, CefCallback callback); + + /** + * Called on the browser process UI thread when the render process terminates unexpectedly. + * @param browser The corresponding browser. + * @param status Indicates how the process was terminated. + */ + void onRenderProcessTerminated(CefBrowser browser, TerminationStatus status); +} diff --git a/src/main/java/org/cef/handler/CefRequestHandlerAdapter.java b/src/main/java/org/cef/handler/CefRequestHandlerAdapter.java new file mode 100644 index 0000000..0273674 --- /dev/null +++ b/src/main/java/org/cef/handler/CefRequestHandlerAdapter.java @@ -0,0 +1,61 @@ +// Copyright (c) 2014 The Chromium Embedded Framework Authors. All rights +// reserved. Use of this source code is governed by a BSD-style license that +// can be found in the LICENSE file. + +package org.cef.handler; + +import org.cef.browser.CefBrowser; +import org.cef.browser.CefFrame; +import org.cef.callback.CefAuthCallback; +import org.cef.callback.CefCallback; +import org.cef.handler.CefLoadHandler.ErrorCode; +import org.cef.misc.BoolRef; +import org.cef.network.CefRequest; +import org.cef.network.CefURLRequest; + +/** + * An abstract adapter class for receiving browser request events. + * The methods in this class are empty. + * This class exists as convenience for creating handler objects. + */ +public abstract class CefRequestHandlerAdapter implements CefRequestHandler { + @Override + public boolean onBeforeBrowse(CefBrowser browser, CefFrame frame, CefRequest request, + boolean user_gesture, boolean is_redirect) { + return false; + } + + @Override + public boolean onOpenURLFromTab( + CefBrowser browser, CefFrame frame, String target_url, boolean user_gesture) { + return false; + } + + @Override + public CefResourceRequestHandler getResourceRequestHandler(CefBrowser browser, CefFrame frame, + CefRequest request, boolean isNavigation, boolean isDownload, String requestInitiator, + BoolRef disableDefaultHandling) { + return null; + } + + @Override + public boolean getAuthCredentials(CefBrowser browser, String origin_url, boolean isProxy, + String host, int port, String realm, String scheme, CefAuthCallback callback) { + return false; + } + + @Override + public boolean onQuotaRequest( + CefBrowser browser, String origin_url, long new_size, CefCallback callback) { + return false; + } + + @Override + public boolean onCertificateError( + CefBrowser browser, ErrorCode cert_error, String request_url, CefCallback callback) { + return false; + } + + @Override + public void onRenderProcessTerminated(CefBrowser browser, TerminationStatus status) {} +} diff --git a/src/main/java/org/cef/handler/CefResourceHandler.java b/src/main/java/org/cef/handler/CefResourceHandler.java new file mode 100644 index 0000000..72f9f30 --- /dev/null +++ b/src/main/java/org/cef/handler/CefResourceHandler.java @@ -0,0 +1,59 @@ +// Copyright (c) 2014 The Chromium Embedded Framework Authors. All rights +// reserved. Use of this source code is governed by a BSD-style license that +// can be found in the LICENSE file. + +package org.cef.handler; + +import org.cef.callback.CefCallback; +import org.cef.misc.IntRef; +import org.cef.misc.StringRef; +import org.cef.network.CefCookie; +import org.cef.network.CefRequest; +import org.cef.network.CefResponse; + +/** + * Implement this interface to handle custom resource requests. The methods of + * this class will always be called on the IO thread. + */ +public interface CefResourceHandler { + /** + * Begin processing the request. + * @param request The request itself. Cannot be modified in this callback. Instance only valid + * within the scope of this method. + * @param callback Callback to continue or cancel the request. + * @return True to handle the request and call CefCallback.Continue() once the response header + * information is available. + */ + boolean processRequest(CefRequest request, CefCallback callback); + + /** + * Retrieve response header information. If the response length is not known set + * |responseLength| to -1 and readResponse() will be called until it returns false. If the + * response length is known set |responseLength| to a positive value and readResponse() will be + * called until it returns false or the specified number of bytes have been read. Use the + * |response| object to set the mime type, http status code and other optional header values. + * @param response The request response that should be returned. Instance only valid within the + * scope of this method. + * @param responseLength Optionally set the response length if known. + * @param redirectUrl Optionally redirect the request to a new URL. + */ + void getResponseHeaders(CefResponse response, IntRef responseLength, StringRef redirectUrl); + + /** + * Read response data. If data is available immediately copy up to |bytesToRead| bytes into + * |dataOut|, set |bytesRead| to the number of bytes copied, and return true. To read the data + * at a later time set |bytesRead| to 0, return true and call CefCallback.Continue() when the + * data is available. To indicate response completion return false. + * @param dataOut Write data to this buffer. + * @param bytesToRead Size of the buffer. + * @param bytesRead Number of bytes written to the buffer. + * @param callback Callback to execute if data will be available asynchronously. + * @return True if more data is or will be available. + */ + boolean readResponse(byte[] dataOut, int bytesToRead, IntRef bytesRead, CefCallback callback); + + /** + * Request processing has been canceled. + */ + void cancel(); +} diff --git a/src/main/java/org/cef/handler/CefResourceHandlerAdapter.java b/src/main/java/org/cef/handler/CefResourceHandlerAdapter.java new file mode 100644 index 0000000..9458bb2 --- /dev/null +++ b/src/main/java/org/cef/handler/CefResourceHandlerAdapter.java @@ -0,0 +1,37 @@ +// Copyright (c) 2014 The Chromium Embedded Framework Authors. All rights +// reserved. Use of this source code is governed by a BSD-style license that +// can be found in the LICENSE file. + +package org.cef.handler; + +import org.cef.callback.CefCallback; +import org.cef.misc.IntRef; +import org.cef.misc.StringRef; +import org.cef.network.CefCookie; +import org.cef.network.CefRequest; +import org.cef.network.CefResponse; + +/** + * An abstract adapter class for receiving resource requests. + * The methods in this class are empty. + * This class exists as convenience for creating handler objects. + */ +public abstract class CefResourceHandlerAdapter implements CefResourceHandler { + @Override + public boolean processRequest(CefRequest request, CefCallback callback) { + return false; + } + + @Override + public void getResponseHeaders( + CefResponse response, IntRef responseLength, StringRef redirectUrl) {} + + @Override + public boolean readResponse( + byte[] dataOut, int bytesToRead, IntRef bytesRead, CefCallback callback) { + return false; + } + + @Override + public void cancel() {} +} diff --git a/src/main/java/org/cef/handler/CefResourceRequestHandler.java b/src/main/java/org/cef/handler/CefResourceRequestHandler.java new file mode 100644 index 0000000..7556681 --- /dev/null +++ b/src/main/java/org/cef/handler/CefResourceRequestHandler.java @@ -0,0 +1,144 @@ +// Copyright (c) 2019 The Chromium Embedded Framework Authors. All rights +// reserved. Use of this source code is governed by a BSD-style license that +// can be found in the LICENSE file. + +package org.cef.handler; + +import org.cef.browser.CefBrowser; +import org.cef.browser.CefFrame; +import org.cef.callback.CefCallback; +import org.cef.misc.BoolRef; +import org.cef.misc.StringRef; +import org.cef.network.CefRequest; +import org.cef.network.CefResponse; +import org.cef.network.CefURLRequest; + +/** + * Implement this interface to handle events related to browser requests. The methods of this class + * will be called on the IO thread unless otherwise indicated. + */ +public interface CefResourceRequestHandler { + /** + * Called on the IO thread before a resource is loaded. The |browser| and |frame| values + * represent the source of the request, and may be null for requests originating from service + * workers or CefURLRequest. + * + * @param browser The corresponding browser. + * @param frame The frame generating the event. Instance only valid within the scope of this + * method. + * @param request The request itself. Cannot be modified in this callback. Instance only valid + * within the scope of this method. + * @return A CefCookieAccessFilter instance or null. + */ + CefCookieAccessFilter getCookieAccessFilter( + CefBrowser browser, CefFrame frame, CefRequest request); + + /** + * Called on the IO thread before a resource request is loaded. The |browser| and |frame| values + * represent the source of the request, and may be null for requests originating from service + * workers or CefURLRequest. To redirect or change the resource load optionally modify + * |request|. Modification of the request URL will be treated as a redirect. + * + * @param browser The corresponding browser. + * @param frame The frame generating the event. Instance only valid within the scope of this + * method. + * @param request The request itself. May be modified in this callback. Instance only valid + * within the scope of this method. + * @return To cancel the request return true otherwise return false. + */ + boolean onBeforeResourceLoad(CefBrowser browser, CefFrame frame, CefRequest request); + + /** + * Called on the IO thread before a resource is loaded. The |browser| and |frame| values + * represent the source of the request, and may be null for requests originating from service + * workers or CefURLRequest. + * + * @param browser The corresponding browser. + * @param frame The frame generating the event. Instance only valid within the scope of this + * method. + * @param request The request itself. Cannot be modified in this callback. Instance only valid + * within the scope of this method. + * @return A CefResourceHandler instance or null. + */ + CefResourceHandler getResourceHandler(CefBrowser browser, CefFrame frame, CefRequest request); + + /** + * Called on the IO thread when a resource load is redirected. The |browser| and |frame| values + * represent the source of the request, and may be null for requests originating from service + * workers or CefURLRequest. The |request| parameter will contain the old URL and other + * request-related information. + * + * @param browser The corresponding browser. + * @param frame The frame generating the event. Instance only valid within the scope of this + * method. + * @param request The request itself. Cannot be modified in this callback. Instance only valid + * within the scope of this method. + * @param response The response that resulted in the redirect. Cannot be modified in this + * callback. Instance only valid within the scope of this method. + * @param new_url Contains the new URL and can be changed if desired. + */ + void onResourceRedirect(CefBrowser browser, CefFrame frame, CefRequest request, + CefResponse response, StringRef new_url); + + /** + * Called on the IO thread when a resource response is received. The |browser| and |frame| + * values represent the source of the request, and may be null for requests originating from + * service workers or CefURLRequest. To allow the resource load to proceed without modification + * return false. To redirect or retry the resource load optionally modify |request| and return + * true. Modification of the request URL will be treated as a redirect. Requests handled using + * the default network loader cannot be redirected in this callback. + * + * @param browser The corresponding browser. + * @param frame The frame generating the event. Instance only valid within the scope of this + * method. + * @param request The request itself. May be modified in this callback. Instance only valid + * within the scope of this method. + * @param response The request response. Cannot be modified in this callback. Instance only + * valid within the scope of this method. + * @return True if |request| was modified or false otherwise + */ + boolean onResourceResponse( + CefBrowser browser, CefFrame frame, CefRequest request, CefResponse response); + + /** + * Called on the IO thread when a resource load has completed. The |browser| and |frame| values + * represent the source of the request, and may be null for requests originating from service + * workers or CefURLRequest. This method will be called for all requests, including requests + * that are aborted due to CEF shutdown or destruction of the associated browser. In cases where + * the associated browser is destroyed this callback may arrive after the + * CefLifeSpanHandler.onBeforeClose callback for that browser. The CefFrame.isValid method can + * be used to test for this situation, and care should be taken not to call |browser| or |frame| + * methods that modify state (like loadURL, sendProcessMessage, etc.) if the frame is invalid. + * + * @param browser The corresponding browser. + * @param frame The frame generating the event. Instance only valid within the scope of this + * method. + * @param request The request itself. Cannot be modified in this callback. Instance only valid + * within the scope of this method. + * @param response The request response. Cannot be modified in this callback. Instance only + * valid within the scope of this method. + * @param status The load completion status. + * @param receivedContentLength The number of bytes read. + */ + void onResourceLoadComplete(CefBrowser browser, CefFrame frame, CefRequest request, + CefResponse response, CefURLRequest.Status status, long receivedContentLength); + + /** + * Called on the IO thread to handle requests for URLs with an unknown protocol component. The + * |browser| and |frame| values represent the source of the request, and may be null for + * requests originating from service workers or CefURLRequest. + * + * SECURITY WARNING: YOU SHOULD USE THIS METHOD TO ENFORCE RESTRICTIONS BASED ON SCHEME, HOST + * OR OTHER URL ANALYSIS BEFORE ALLOWING OS EXECUTION. + * + * @param browser The corresponding browser. + * @param frame The frame generating the event. Instance only valid within the scope of this + * method. + * @param request The request itself. Cannot be modified in this callback. Instance only valid + * within the scope of this method. + * @param allowOsExecution Set to true to attempt execution via the registered + * OS protocol handler, if any. + */ + void onProtocolExecution( + CefBrowser browser, CefFrame frame, CefRequest request, BoolRef allowOsExecution); +} diff --git a/src/main/java/org/cef/handler/CefResourceRequestHandlerAdapter.java b/src/main/java/org/cef/handler/CefResourceRequestHandlerAdapter.java new file mode 100644 index 0000000..51e4e4b --- /dev/null +++ b/src/main/java/org/cef/handler/CefResourceRequestHandlerAdapter.java @@ -0,0 +1,56 @@ +// Copyright (c) 2019 The Chromium Embedded Framework Authors. All rights +// reserved. Use of this source code is governed by a BSD-style license that +// can be found in the LICENSE file. + +package org.cef.handler; + +import org.cef.browser.CefBrowser; +import org.cef.browser.CefFrame; +import org.cef.callback.CefCallback; +import org.cef.misc.BoolRef; +import org.cef.misc.StringRef; +import org.cef.network.CefRequest; +import org.cef.network.CefResponse; +import org.cef.network.CefURLRequest; + +/** + * An abstract adapter class for receiving browser request events. + * The methods in this class are empty. + * This class exists as convenience for creating handler objects. + */ +public abstract class CefResourceRequestHandlerAdapter implements CefResourceRequestHandler { + @Override + public CefCookieAccessFilter getCookieAccessFilter( + CefBrowser browser, CefFrame frame, CefRequest request) { + return null; + } + + @Override + public boolean onBeforeResourceLoad(CefBrowser browser, CefFrame frame, CefRequest request) { + return false; + } + + @Override + public CefResourceHandler getResourceHandler( + CefBrowser browser, CefFrame frame, CefRequest request) { + return null; + } + + @Override + public void onResourceRedirect(CefBrowser browser, CefFrame frame, CefRequest request, + CefResponse response, StringRef new_url) {} + + @Override + public boolean onResourceResponse( + CefBrowser browser, CefFrame frame, CefRequest request, CefResponse response) { + return false; + } + + @Override + public void onResourceLoadComplete(CefBrowser browser, CefFrame frame, CefRequest request, + CefResponse response, CefURLRequest.Status status, long receivedContentLength) {} + + @Override + public void onProtocolExecution( + CefBrowser browser, CefFrame frame, CefRequest request, BoolRef allowOsExecution) {} +} diff --git a/src/main/java/org/cef/handler/CefScreenInfo.java b/src/main/java/org/cef/handler/CefScreenInfo.java new file mode 100644 index 0000000..8a8cd4a --- /dev/null +++ b/src/main/java/org/cef/handler/CefScreenInfo.java @@ -0,0 +1,36 @@ +// Copyright (c) 2014 The Chromium Embedded Framework Authors. All rights +// reserved. Use of this source code is governed by a BSD-style license that +// can be found in the LICENSE file. + +package org.cef.handler; + +import java.awt.Rectangle; + +/** + * + * @author shannah + */ +public class CefScreenInfo { + public double device_scale_factor; + public int depth; + public int depth_per_component; + public boolean is_monochrome; + public int x, y, width, height; + public int available_x, available_y, available_width, available_height; + + public void Set(double device_scale_factor, int depth, int depth_per_component, + boolean is_monochrome, Rectangle rect, Rectangle availableRect) { + this.device_scale_factor = device_scale_factor; + this.depth = depth; + this.depth_per_component = depth_per_component; + this.is_monochrome = is_monochrome; + this.x = rect.x; + this.y = rect.y; + this.width = rect.width; + this.height = rect.height; + this.available_x = availableRect.x; + this.available_y = availableRect.y; + this.available_width = availableRect.width; + this.available_height = availableRect.height; + } +} diff --git a/src/main/java/org/cef/handler/CefWindowHandler.java b/src/main/java/org/cef/handler/CefWindowHandler.java new file mode 100644 index 0000000..5ecc960 --- /dev/null +++ b/src/main/java/org/cef/handler/CefWindowHandler.java @@ -0,0 +1,42 @@ +// Copyright (c) 2015 The Chromium Embedded Framework Authors. All rights +// reserved. Use of this source code is governed by a BSD-style license that +// can be found in the LICENSE file. + +package org.cef.handler; + +import java.awt.Rectangle; + +import org.cef.browser.CefBrowser; + +/** + * Implement this interface to handle events if window rendering is enabled. + * + */ +public interface CefWindowHandler { + /** + * Retrieve the view rectangle. + * @param browser The browser generating the event. + * @return The view rectangle. + */ + public Rectangle getRect(CefBrowser browser); + + /** + * Implement this method to handle mouse events on Windows. + * + * The method is called in case of the following events: + * MOUSE_MOVED, MOUSE_PRESSED, MOUSE_RELEASED and MOUSE_WHEEL for a + * horizontal wheel movement. + * + * @param browser The browser generating the event. + * @param event A mouse event like MouseEvent.MOUSE_MOVED. + * @param screenX The absolute X position on the screen. + * @param screenY The absolute Y position on the screen. + * @param modifier The modifier keys down during event (e.g. Shift, Ctrl). + * @param button An integer indicating which mouse button state changed. + * If parameter event is set to MouseEvent.MOUSE_WHEEL, the value of this + * parameter specifies the rotation indicator (negative value for left scroll + * and positive value for right scroll). + */ + public void onMouseEvent( + CefBrowser browser, int event, int screenX, int screenY, int modifier, int button); +} diff --git a/src/main/java/org/cef/handler/CefWindowHandlerAdapter.java b/src/main/java/org/cef/handler/CefWindowHandlerAdapter.java new file mode 100644 index 0000000..327cd42 --- /dev/null +++ b/src/main/java/org/cef/handler/CefWindowHandlerAdapter.java @@ -0,0 +1,25 @@ +// Copyright (c) 2015 The Chromium Embedded Framework Authors. All rights +// reserved. Use of this source code is governed by a BSD-style license that +// can be found in the LICENSE file. + +package org.cef.handler; + +import java.awt.Rectangle; + +import org.cef.browser.CefBrowser; + +/** + * An abstract adapter class for receiving windowed render events. + * The methods in this class are empty. + * This class exists as convenience for creating handler objects. + */ +public abstract class CefWindowHandlerAdapter implements CefWindowHandler { + @Override + public Rectangle getRect(CefBrowser browser) { + return new Rectangle(0, 0, 0, 0); + } + + @Override + public void onMouseEvent( + CefBrowser browser, int event, int screenX, int screenY, int modifier, int button) {} +} diff --git a/src/main/java/org/cef/misc/BoolRef.java b/src/main/java/org/cef/misc/BoolRef.java new file mode 100644 index 0000000..69a37e9 --- /dev/null +++ b/src/main/java/org/cef/misc/BoolRef.java @@ -0,0 +1,26 @@ +// Copyright (c) 2014 The Chromium Embedded Framework Authors. All rights +// reserved. Use of this source code is governed by a BSD-style license that +// can be found in the LICENSE file. + +package org.cef.misc; + +/** + * Helper class for passing boolean values by reference. + */ +public class BoolRef { + private boolean value_; + + public BoolRef() {} + + public BoolRef(boolean value) { + value_ = value; + } + + public void set(boolean value) { + value_ = value; + } + + public boolean get() { + return value_; + } +} diff --git a/src/main/java/org/cef/misc/CefPageRange.java b/src/main/java/org/cef/misc/CefPageRange.java new file mode 100644 index 0000000..dee4fdf --- /dev/null +++ b/src/main/java/org/cef/misc/CefPageRange.java @@ -0,0 +1,20 @@ +// Copyright (c) 2014 The Chromium Embedded Framework Authors. All rights +// reserved. Use of this source code is governed by a BSD-style license that +// can be found in the LICENSE file. + +package org.cef.misc; + +/** + * Class representing a print job page range. + */ +public class CefPageRange { + public int from; + public int to; + + public CefPageRange() {} + + public CefPageRange(int from, int to) { + this.from = from; + this.to = to; + } +} \ No newline at end of file diff --git a/src/main/java/org/cef/misc/CefPdfPrintSettings.java b/src/main/java/org/cef/misc/CefPdfPrintSettings.java new file mode 100644 index 0000000..554c292 --- /dev/null +++ b/src/main/java/org/cef/misc/CefPdfPrintSettings.java @@ -0,0 +1,111 @@ +// Copyright (c) 2017 The Chromium Embedded Framework Authors. All rights +// reserved. Use of this source code is governed by a BSD-style license that +// can be found in the LICENSE file. + +package org.cef.misc; + +/** + * PDF print settings for browser.printToPDF() + */ +public class CefPdfPrintSettings { + public enum MarginType { + // Default margins. + DEFAULT, + + // No margins + NONE, + + // Minimum margins. + MINIMUM, + + // Custom margins using the values from CefPdfPrintSettings + CUSTOM + } + + /** + * Set to true to print headers and footers or false to not print + * headers and footers. + */ + public boolean header_footer_enabled; + + /** + * Page title to display in the header. Only used if header_footer_enabled + * is set to true. + */ + public String header_footer_title; + + /** + * URL to display in the footer. Only used if header_footer_enabled is set + * to true. + */ + public String header_footer_url; + + /** + * Set to true for landscape mode or false for portrait mode. + */ + public boolean landscape; + + /** + * Set to true to print background graphics or false to not print + * background graphics. + */ + public boolean backgrounds_enabled; + + /** + * Output page size in microns (1 millimeter = 1000 microns). If either of these + * values is less than or equal to zero then the default paper size will be + * used as returned by the print_handler. A4 is 210 x 297 mm which would + * be 210000 x 297000 microns. US Letter is 215.9 x 279.4 mm which would + * be 215900 x 279400 microns. + */ + public int page_width; + public int page_height; + + /** + * Set to true to print the selection only or false to print all. + */ + public boolean selection_only; + + /** + * The percentage to scale the PDF by before printing (e.g. 50 is 50%). + * If this value is less than or equal to zero the default value of 100 + * will be used. + */ + public int scale_factor; + + /** + * Margins in points. Only used if |margin_type| is set to + * PDF_PRINT_MARGIN_CUSTOM. + */ + public int margin_top; + public int margin_right; + public int margin_bottom; + public int margin_left; + + /** + * Margin type. + */ + public MarginType margin_type; + + public CefPdfPrintSettings() {} + + @Override + public CefPdfPrintSettings clone() { + CefPdfPrintSettings tmp = new CefPdfPrintSettings(); + tmp.header_footer_enabled = this.header_footer_enabled; + tmp.header_footer_title = this.header_footer_title; + tmp.header_footer_url = this.header_footer_url; + tmp.landscape = this.landscape; + tmp.backgrounds_enabled = this.backgrounds_enabled; + tmp.page_width = this.page_width; + tmp.page_height = this.page_height; + tmp.selection_only = this.selection_only; + tmp.scale_factor = this.scale_factor; + tmp.margin_top = this.margin_top; + tmp.margin_right = this.margin_right; + tmp.margin_bottom = this.margin_bottom; + tmp.margin_left = this.margin_left; + tmp.margin_type = this.margin_type; + return tmp; + } +} diff --git a/src/main/java/org/cef/misc/CefPrintSettings.java b/src/main/java/org/cef/misc/CefPrintSettings.java new file mode 100644 index 0000000..6dc56fe --- /dev/null +++ b/src/main/java/org/cef/misc/CefPrintSettings.java @@ -0,0 +1,188 @@ +// Copyright (c) 2014 The Chromium Embedded Framework Authors. All rights +// reserved. Use of this source code is governed by a BSD-style license that +// can be found in the LICENSE file. + +package org.cef.misc; + +import java.awt.Dimension; +import java.awt.Rectangle; +import java.util.Vector; + +/** + * Class representing print settings. + */ +public abstract class CefPrintSettings { + /** + * Print job color mode values. + */ + public enum ColorModel { + COLOR_MODEL_UNKNOWN, + COLOR_MODEL_GRAY, + COLOR_MODEL_COLOR, + COLOR_MODEL_CMYK, + COLOR_MODEL_CMY, + COLOR_MODEL_KCMY, + COLOR_MODEL_CMY_K, //!< CMY_K represents CMY+K. + COLOR_MODEL_BLACK, + COLOR_MODEL_GRAYSCALE, + COLOR_MODEL_RGB, + COLOR_MODEL_RGB16, + COLOR_MODEL_RGBA, + COLOR_MODEL_COLORMODE_COLOR, //!< Used in samsung printer ppds. + COLOR_MODEL_COLORMODE_MONOCHROME, //!< Used in samsung printer ppds. + COLOR_MODEL_HP_COLOR_COLOR, //!< Used in HP color printer ppds. + COLOR_MODEL_HP_COLOR_BLACK, //!< Used in HP color printer ppds. + COLOR_MODEL_PRINTOUTMODE_NORMAL, //!< Used in foomatic ppds. + COLOR_MODEL_PRINTOUTMODE_NORMAL_GRAY, //!< Used in foomatic ppds. + COLOR_MODEL_PROCESSCOLORMODEL_CMYK, //!< Used in canon printer ppds. + COLOR_MODEL_PROCESSCOLORMODEL_GREYSCALE, //!< Used in canon printer ppds. + COLOR_MODEL_PROCESSCOLORMODEL_RGB, //!< Used in canon printer ppds + } + + /** + * Print job duplex mode values. + */ + public enum DuplexMode { + DUPLEX_MODE_UNKNOWN, + DUPLEX_MODE_SIMPLEX, + DUPLEX_MODE_LONG_EDGE, + DUPLEX_MODE_SHORT_EDGE, + } + + // This CTOR can't be called directly. Call method create() instead. + CefPrintSettings() {} + + @Override + protected void finalize() throws Throwable { + dispose(); + super.finalize(); + } + + /** + * Create a new CefPrintSettings object. + */ + public static final CefPrintSettings create() { + return CefPrintSettings_N.createNative(); + } + + /** + * Removes the native reference from an unused object. + */ + public abstract void dispose(); + + /** + * Returns true if this object is valid. Do not call any other methods if this + * function returns false. + */ + public abstract boolean isValid(); + + /** + * Returns true if the values of this object are read-only. Some APIs may + * expose read-only objects. + */ + public abstract boolean isReadOnly(); + + /** + * Set the page orientation. + */ + public abstract void setOrientation(boolean landscape); + + /** + * Returns true if the orientation is landscape. + * + */ + public abstract boolean isLandscape(); + + /** + * Set the printer printable area in device units. + * Some platforms already provide flipped area. Set |landscape_needs_flip| + * to false on those platforms to avoid double flipping. + */ + public abstract void setPrinterPrintableArea(Dimension physical_size_device_units, + Rectangle printable_area_device_units, boolean landscape_needs_flip); + + /** + * Set the device name. + */ + public abstract void setDeviceName(String name); + + /** + * Get the device name. + */ + public abstract String getDeviceName(); + + /** + * Set the DPI (dots per inch). + */ + public abstract void setDPI(int dpi); + + /** + * Get the DPI (dots per inch). + */ + public abstract int getDPI(); + + /** + * Set the page ranges. + */ + public abstract void setPageRanges(Vector ranges); + + /** + * Returns the number of page ranges that currently exist. + */ + public abstract int getPageRangesCount(); + + /** + * Retrieve the page ranges. + */ + public abstract void getPageRanges(Vector ranges); + + /** + * Set whether only the selection will be printed. + */ + public abstract void setSelectionOnly(boolean selection_only); + + /** + * Returns true if only the selection will be printed. + */ + public abstract boolean isSelectionOnly(); + + /** + * Set whether pages will be collated. + */ + public abstract void setCollate(boolean collate); + + /** + * Returns true if pages will be collated. + */ + public abstract boolean willCollate(); + + /** + * Set the color model. + */ + public abstract void setColorModel(ColorModel model); + + /** + * Get the color model. + */ + public abstract ColorModel getColorModel(); + + /** + * Set the number of copies. + */ + public abstract void setCopies(int copies); + + /** + * Get the number of copies. + */ + public abstract int getCopies(); + + /** + * Set the duplex mode. + */ + public abstract void setDuplexMode(DuplexMode mode); + + /** + * Get the duplex mode. + */ + public abstract DuplexMode getDuplexMode(); +} diff --git a/src/main/java/org/cef/misc/CefPrintSettings_N.java b/src/main/java/org/cef/misc/CefPrintSettings_N.java new file mode 100644 index 0000000..bc0f83a --- /dev/null +++ b/src/main/java/org/cef/misc/CefPrintSettings_N.java @@ -0,0 +1,286 @@ +// Copyright (c) 2014 The Chromium Embedded Framework Authors. All rights +// reserved. Use of this source code is governed by a BSD-style license that +// can be found in the LICENSE file. + +package org.cef.misc; + +import org.cef.callback.CefNative; + +import java.awt.Dimension; +import java.awt.Rectangle; +import java.util.Vector; + +class CefPrintSettings_N extends CefPrintSettings implements CefNative { + // Used internally to store a pointer to the CEF object. + private long N_CefHandle = 0; + + @Override + public void setNativeRef(String identifer, long nativeRef) { + N_CefHandle = nativeRef; + } + + @Override + public long getNativeRef(String identifer) { + return N_CefHandle; + } + + CefPrintSettings_N() { + super(); + } + + public static CefPrintSettings createNative() { + try { + return CefPrintSettings_N.N_Create(); + } catch (UnsatisfiedLinkError ule) { + ule.printStackTrace(); + return null; + } + } + + @Override + public void dispose() { + try { + N_Dispose(N_CefHandle); + } catch (UnsatisfiedLinkError ule) { + ule.printStackTrace(); + } + } + + @Override + public boolean isValid() { + try { + return N_IsValid(N_CefHandle); + } catch (UnsatisfiedLinkError ule) { + ule.printStackTrace(); + } + return false; + } + + @Override + public boolean isReadOnly() { + try { + return N_IsReadOnly(N_CefHandle); + } catch (UnsatisfiedLinkError ule) { + ule.printStackTrace(); + } + return false; + } + + @Override + public void setOrientation(boolean landscape) { + try { + N_SetOrientation(N_CefHandle, landscape); + } catch (UnsatisfiedLinkError ule) { + ule.printStackTrace(); + } + } + + @Override + public boolean isLandscape() { + try { + return N_IsLandscape(N_CefHandle); + } catch (UnsatisfiedLinkError ule) { + ule.printStackTrace(); + } + return false; + } + + @Override + public void setPrinterPrintableArea(Dimension physical_size_device_units, + Rectangle printable_area_device_units, boolean landscape_needs_flip) { + try { + N_SetPrinterPrintableArea(N_CefHandle, physical_size_device_units, + printable_area_device_units, landscape_needs_flip); + } catch (UnsatisfiedLinkError ule) { + ule.printStackTrace(); + } + } + + @Override + public void setDeviceName(String name) { + try { + N_SetDeviceName(N_CefHandle, name); + } catch (UnsatisfiedLinkError ule) { + ule.printStackTrace(); + } + } + + @Override + public String getDeviceName() { + try { + return N_GetDeviceName(N_CefHandle); + } catch (UnsatisfiedLinkError ule) { + ule.printStackTrace(); + } + return null; + } + + @Override + public void setDPI(int dpi) { + try { + N_SetDPI(N_CefHandle, dpi); + } catch (UnsatisfiedLinkError ule) { + ule.printStackTrace(); + } + } + + @Override + public int getDPI() { + try { + return N_GetDPI(N_CefHandle); + } catch (UnsatisfiedLinkError ule) { + ule.printStackTrace(); + } + return 0; + } + + @Override + public void setPageRanges(Vector ranges) { + try { + N_SetPageRanges(N_CefHandle, ranges); + } catch (UnsatisfiedLinkError ule) { + ule.printStackTrace(); + } + } + + @Override + public int getPageRangesCount() { + try { + return N_GetPageRangesCount(N_CefHandle); + } catch (UnsatisfiedLinkError ule) { + ule.printStackTrace(); + } + return 0; + } + + @Override + public void getPageRanges(Vector ranges) { + try { + N_GetPageRanges(N_CefHandle, ranges); + } catch (UnsatisfiedLinkError ule) { + ule.printStackTrace(); + } + } + + @Override + public void setSelectionOnly(boolean selection_only) { + try { + N_SetSelectionOnly(N_CefHandle, selection_only); + } catch (UnsatisfiedLinkError ule) { + ule.printStackTrace(); + } + } + + @Override + public boolean isSelectionOnly() { + try { + return N_IsSelectionOnly(N_CefHandle); + } catch (UnsatisfiedLinkError ule) { + ule.printStackTrace(); + } + return false; + } + + @Override + public void setCollate(boolean collate) { + try { + N_SetCollate(N_CefHandle, collate); + } catch (UnsatisfiedLinkError ule) { + ule.printStackTrace(); + } + } + + @Override + public boolean willCollate() { + try { + return N_WillCollate(N_CefHandle); + } catch (UnsatisfiedLinkError ule) { + ule.printStackTrace(); + } + return false; + } + + @Override + public void setColorModel(ColorModel model) { + try { + N_SetColorModel(N_CefHandle, model); + } catch (UnsatisfiedLinkError ule) { + ule.printStackTrace(); + } + } + + @Override + public ColorModel getColorModel() { + try { + return N_GetColorModel(N_CefHandle); + } catch (UnsatisfiedLinkError ule) { + ule.printStackTrace(); + } + return null; + } + + @Override + public void setCopies(int copies) { + try { + N_SetCopies(N_CefHandle, copies); + } catch (UnsatisfiedLinkError ule) { + ule.printStackTrace(); + } + } + + @Override + public int getCopies() { + try { + return N_GetCopies(N_CefHandle); + } catch (UnsatisfiedLinkError ule) { + ule.printStackTrace(); + } + return 0; + } + + @Override + public void setDuplexMode(DuplexMode mode) { + try { + N_SetDuplexMode(N_CefHandle, mode); + } catch (UnsatisfiedLinkError ule) { + ule.printStackTrace(); + } + } + + @Override + public DuplexMode getDuplexMode() { + try { + return N_GetDuplexMode(N_CefHandle); + } catch (UnsatisfiedLinkError ule) { + ule.printStackTrace(); + } + return null; + } + + private final native static CefPrintSettings_N N_Create(); + private final native void N_Dispose(long self); + private final native boolean N_IsValid(long self); + private final native boolean N_IsReadOnly(long self); + private final native void N_SetOrientation(long self, boolean landscape); + private final native boolean N_IsLandscape(long self); + private final native void N_SetPrinterPrintableArea(long self, + Dimension physical_size_device_units, Rectangle printable_area_device_units, + boolean landscape_needs_flip); + private final native void N_SetDeviceName(long self, String name); + private final native String N_GetDeviceName(long self); + private final native void N_SetDPI(long self, int dpi); + private final native int N_GetDPI(long self); + private final native void N_SetPageRanges(long self, Vector ranges); + private final native int N_GetPageRangesCount(long self); + private final native void N_GetPageRanges(long self, Vector ranges); + private final native void N_SetSelectionOnly(long self, boolean selection_only); + private final native boolean N_IsSelectionOnly(long self); + private final native void N_SetCollate(long self, boolean collate); + private final native boolean N_WillCollate(long self); + private final native void N_SetColorModel(long self, ColorModel model); + private final native ColorModel N_GetColorModel(long self); + private final native void N_SetCopies(long self, int copies); + private final native int N_GetCopies(long self); + private final native void N_SetDuplexMode(long self, DuplexMode mode); + private final native DuplexMode N_GetDuplexMode(long self); +} diff --git a/src/main/java/org/cef/misc/EventFlags.java b/src/main/java/org/cef/misc/EventFlags.java new file mode 100644 index 0000000..efcc370 --- /dev/null +++ b/src/main/java/org/cef/misc/EventFlags.java @@ -0,0 +1,21 @@ +package org.cef.misc; + +/** + * Supported event bit flags. + */ +public final class EventFlags { + public final static int EVENTFLAG_NONE = 0; + public final static int EVENTFLAG_CAPS_LOCK_ON = 1 << 0; + public final static int EVENTFLAG_SHIFT_DOWN = 1 << 1; + public final static int EVENTFLAG_CONTROL_DOWN = 1 << 2; + public final static int EVENTFLAG_ALT_DOWN = 1 << 3; + public final static int EVENTFLAG_LEFT_MOUSE_BUTTON = 1 << 4; + public final static int EVENTFLAG_MIDDLE_MOUSE_BUTTON = 1 << 5; + public final static int EVENTFLAG_RIGHT_MOUSE_BUTTON = 1 << 6; + // Mac OS-X command key. + public final static int EVENTFLAG_COMMAND_DOWN = 1 << 7; + public final static int EVENTFLAG_NUM_LOCK_ON = 1 << 8; + public final static int EVENTFLAG_IS_KEY_PAD = 1 << 9; + public final static int EVENTFLAG_IS_LEFT = 1 << 10; + public final static int EVENTFLAG_IS_RIGHT = 1 << 11; +} diff --git a/src/main/java/org/cef/misc/IntRef.java b/src/main/java/org/cef/misc/IntRef.java new file mode 100644 index 0000000..30be742 --- /dev/null +++ b/src/main/java/org/cef/misc/IntRef.java @@ -0,0 +1,26 @@ +// Copyright (c) 2014 The Chromium Embedded Framework Authors. All rights +// reserved. Use of this source code is governed by a BSD-style license that +// can be found in the LICENSE file. + +package org.cef.misc; + +/** + * Helper class for passing int values by reference. + */ +public class IntRef { + private int value_; + + public IntRef() {} + + public IntRef(int value) { + value_ = value; + } + + public void set(int value) { + value_ = value; + } + + public int get() { + return value_; + } +} diff --git a/src/main/java/org/cef/misc/StringRef.java b/src/main/java/org/cef/misc/StringRef.java new file mode 100644 index 0000000..d84ac10 --- /dev/null +++ b/src/main/java/org/cef/misc/StringRef.java @@ -0,0 +1,26 @@ +// Copyright (c) 2014 The Chromium Embedded Framework Authors. All rights +// reserved. Use of this source code is governed by a BSD-style license that +// can be found in the LICENSE file. + +package org.cef.misc; + +/** + * Helper class for passing String values by reference. + */ +public class StringRef { + private String value_; + + public StringRef() {} + + public StringRef(String value) { + value_ = value; + } + + public void set(String value) { + value_ = value; + } + + public String get() { + return value_; + } +} diff --git a/src/main/java/org/cef/network/CefCookie.java b/src/main/java/org/cef/network/CefCookie.java new file mode 100644 index 0000000..5020ba1 --- /dev/null +++ b/src/main/java/org/cef/network/CefCookie.java @@ -0,0 +1,77 @@ +// Copyright (c) 2014 The Chromium Embedded Framework Authors. All rights +// reserved. Use of this source code is governed by a BSD-style license that +// can be found in the LICENSE file. + +package org.cef.network; + +import java.util.Date; + +/** + * Cookie information. + */ +public final class CefCookie { + public CefCookie(String name, String value, String domain, String path, boolean secure, + boolean httponly, Date creation, Date lastAccess, boolean hasExpires, Date expires) { + this.name = name; + this.value = value; + this.domain = domain; + this.path = path; + this.secure = secure; + this.httponly = httponly; + this.creation = creation; + this.lastAccess = lastAccess; + this.hasExpires = hasExpires; + this.expires = expires; + } + + /** + * The cookie name. + */ + public final String name; + + /** + * The cookie value. + */ + public final String value; + + /** + * If domain is empty a host cookie will be created instead of a domain + * cookie. Domain cookies are stored with a leading "." and are visible to + * sub-domains whereas host cookies are not. + */ + public final String domain; + + /** + * If path is non-empty only URLs at or below the path will get the cookie + * value. + */ + public final String path; + + /** + * If secure is true the cookie will only be sent for HTTPS requests. + */ + public final boolean secure; + + /** + * If httponly is true the cookie will only be sent for HTTP requests. + */ + public final boolean httponly; + + /** + * The cookie creation date. This is automatically populated by the system on + * cookie creation. + */ + public final Date creation; + + /** + * The cookie last access date. This is automatically populated by the system + * on access. + */ + public final Date lastAccess; + + /** + * The cookie expiration date is only valid if |has_expires| is true. + */ + public final boolean hasExpires; + public final Date expires; +} diff --git a/src/main/java/org/cef/network/CefCookieManager.java b/src/main/java/org/cef/network/CefCookieManager.java new file mode 100644 index 0000000..83ee66d --- /dev/null +++ b/src/main/java/org/cef/network/CefCookieManager.java @@ -0,0 +1,88 @@ +// Copyright (c) 2014 The Chromium Embedded Framework Authors. All rights +// reserved. Use of this source code is governed by a BSD-style license that +// can be found in the LICENSE file. + +package org.cef.network; + +import org.cef.callback.CefCompletionCallback; +import org.cef.callback.CefCookieVisitor; + +import java.util.Vector; + +/** + * Class used for managing cookies. The methods of this class may be called on any thread unless + * otherwise indicated. + */ +public abstract class CefCookieManager { + // This CTOR can't be called directly. Call method create() instead. + CefCookieManager() {} + + @Override + protected void finalize() throws Throwable { + dispose(); + super.finalize(); + } + + /** + * Returns the global cookie manager. By default data will be stored at CefSettings.cache_path + * if specified or in memory otherwise. + * @return The global cookie manager. + */ + public static final CefCookieManager getGlobalManager() { + return CefCookieManager_N.getGlobalManagerNative(); + } + + /** + * Removes the native reference from an unused object. + */ + public abstract void dispose(); + + /** + * Visit all cookies. The returned cookies are ordered by longest path, then by earliest + * creation date. + * @param visitor Callback that will receive cookies on the UI thread. + * @return False if cookies cannot be accessed. + */ + public abstract boolean visitAllCookies(CefCookieVisitor visitor); + + /** + * Visit a subset of cookies. The returned cookies are ordered by longest path, then by earliest + * creation date. + * @param url Results are filtered by the given url scheme, host, domain and path. + * @param includeHttpOnly If true HTTP-only cookies will also be included in the results. + * @param visitor Callback that will receive cookies on the UI thread. + * @return False if cookies cannot be accessed. + */ + public abstract boolean visitUrlCookies( + String url, boolean includeHttpOnly, CefCookieVisitor visitor); + + /** + * Sets a cookie given a valid URL and explicit user-provided cookie attributes. This function + * expects each attribute to be well-formed. It will check for disallowed characters (e.g. the + * ';' character is disallowed within the cookie value attribute) and fail without setting the + * cookie if such characters are found. + * @param url The cookie URL. + * @param cookie The cookie attributes. + * @return False if an invalid URL is specified or if cookies cannot be accessed. + */ + public abstract boolean setCookie(String url, CefCookie cookie); + + /** + * Delete all cookies that match the specified parameters. If both |url| and |cookieName| values + * are specified all host and domain cookies matching both will be deleted. If only |url| is + * specified all host cookies (but not domain cookies) irrespective of path will be deleted. If + * |url| is empty all cookies for all hosts and domains will be deleted. Cookies can alternately + * be deleted using the visit*Cookies() methods. + * @param url The cookie URL to delete or null. + * @param cookieName The cookie name to delete or null. + * @return False if a non-empty invalid URL is secified or if cookies cannot be accessed. + */ + public abstract boolean deleteCookies(String url, String cookieName); + + /** + * Flush the backing store (if any) to disk. + * @param handler Callback that will be executed on the UI thread upon completion. + * @return False if cookies cannot be accessed. + */ + public abstract boolean flushStore(CefCompletionCallback handler); +} diff --git a/src/main/java/org/cef/network/CefCookieManager_N.java b/src/main/java/org/cef/network/CefCookieManager_N.java new file mode 100644 index 0000000..846240b --- /dev/null +++ b/src/main/java/org/cef/network/CefCookieManager_N.java @@ -0,0 +1,116 @@ +// Copyright (c) 2014 The Chromium Embedded Framework Authors. All rights +// reserved. Use of this source code is governed by a BSD-style license that +// can be found in the LICENSE file. + +package org.cef.network; + +import org.cef.callback.CefCompletionCallback; +import org.cef.callback.CefCookieVisitor; +import org.cef.callback.CefNative; + +import java.util.Vector; + +class CefCookieManager_N extends CefCookieManager implements CefNative { + // Used internally to store a pointer to the CEF object. + private long N_CefHandle = 0; + private static CefCookieManager_N globalInstance = null; + + @Override + public void setNativeRef(String identifer, long nativeRef) { + N_CefHandle = nativeRef; + } + + @Override + public long getNativeRef(String identifer) { + return N_CefHandle; + } + + CefCookieManager_N() { + super(); + } + + static synchronized final CefCookieManager_N getGlobalManagerNative() { + if (globalInstance != null && globalInstance.N_CefHandle != 0) { + // The global instance is still valid. + return globalInstance; + } + + CefCookieManager_N result = null; + try { + result = CefCookieManager_N.N_GetGlobalManager(); + } catch (UnsatisfiedLinkError ule) { + ule.printStackTrace(); + } + + globalInstance = result; + return globalInstance; + } + + @Override + public void dispose() { + try { + N_Dispose(N_CefHandle); + } catch (UnsatisfiedLinkError ule) { + ule.printStackTrace(); + } + } + + @Override + public boolean visitAllCookies(CefCookieVisitor visitor) { + try { + return N_VisitAllCookies(N_CefHandle, visitor); + } catch (UnsatisfiedLinkError ule) { + ule.printStackTrace(); + } + return false; + } + + @Override + public boolean visitUrlCookies(String url, boolean includeHttpOnly, CefCookieVisitor visitor) { + try { + return N_VisitUrlCookies(N_CefHandle, url, includeHttpOnly, visitor); + } catch (UnsatisfiedLinkError ule) { + ule.printStackTrace(); + } + return false; + } + + @Override + public boolean setCookie(String url, CefCookie cookie) { + try { + return N_SetCookie(N_CefHandle, url, cookie); + } catch (UnsatisfiedLinkError ule) { + ule.printStackTrace(); + } + return false; + } + + @Override + public boolean deleteCookies(String url, String cookieName) { + try { + return N_DeleteCookies(N_CefHandle, url, cookieName); + } catch (UnsatisfiedLinkError ule) { + ule.printStackTrace(); + } + return false; + } + + @Override + public boolean flushStore(CefCompletionCallback handler) { + try { + return N_FlushStore(N_CefHandle, handler); + } catch (UnsatisfiedLinkError ule) { + ule.printStackTrace(); + } + return false; + } + + private final static native CefCookieManager_N N_GetGlobalManager(); + private final native void N_Dispose(long self); + private final native boolean N_VisitAllCookies(long self, CefCookieVisitor visitor); + private final native boolean N_VisitUrlCookies( + long self, String url, boolean includeHttpOnly, CefCookieVisitor visitor); + private final native boolean N_SetCookie(long self, String url, CefCookie cookie); + private final native boolean N_DeleteCookies(long self, String url, String cookieName); + private final native boolean N_FlushStore(long self, CefCompletionCallback handler); +} diff --git a/src/main/java/org/cef/network/CefPostData.java b/src/main/java/org/cef/network/CefPostData.java new file mode 100644 index 0000000..85ba6f9 --- /dev/null +++ b/src/main/java/org/cef/network/CefPostData.java @@ -0,0 +1,81 @@ +// Copyright (c) 2014 The Chromium Embedded Framework Authors. All rights +// reserved. Use of this source code is governed by a BSD-style license that +// can be found in the LICENSE file. + +package org.cef.network; + +import java.util.Vector; + +/** + * Class used to represent post data for a web request. The methods of this + * class may be called on any thread. + */ +public abstract class CefPostData { + // This CTOR can't be called directly. Call method create() instead. + CefPostData() {} + + @Override + protected void finalize() throws Throwable { + dispose(); + super.finalize(); + } + + /** + * Create a new CefPostData object. + */ + public static final CefPostData create() { + return CefPostData_N.createNative(); + } + + /** + * Removes the native reference from an unused object. + */ + public abstract void dispose(); + + /** + * Returns true if this object is read-only. + */ + public abstract boolean isReadOnly(); + + /** + * Returns the number of existing post data elements. + */ + public abstract int getElementCount(); + + /** + * Retrieve the post data elements. + */ + public abstract void getElements(Vector elements); + + /** + * Remove the specified post data element. Returns true if the removal + * succeeds. + */ + public abstract boolean removeElement(CefPostDataElement element); + + /** + * Add the specified post data element. Returns true if the add succeeds. + */ + public abstract boolean addElement(CefPostDataElement element); + + /** + * Remove all existing post data elements. + */ + public abstract void removeElements(); + + @Override + public String toString() { + return toString(null); + } + + public String toString(String mimeType) { + Vector elements = new Vector(); + getElements(elements); + + String returnValue = ""; + for (CefPostDataElement el : elements) { + returnValue += el.toString(mimeType) + "\n"; + } + return returnValue; + } +} diff --git a/src/main/java/org/cef/network/CefPostDataElement.java b/src/main/java/org/cef/network/CefPostDataElement.java new file mode 100644 index 0000000..f8a209b --- /dev/null +++ b/src/main/java/org/cef/network/CefPostDataElement.java @@ -0,0 +1,127 @@ +// Copyright (c) 2014 The Chromium Embedded Framework Authors. All rights +// reserved. Use of this source code is governed by a BSD-style license that +// can be found in the LICENSE file. + +package org.cef.network; + +/** + * Class used to represent a single element in the request post data. The + * methods of this class may be called on any thread. + */ +public abstract class CefPostDataElement { + /** + * Post data elements may represent either bytes or files. + */ + public static enum Type { + PDE_TYPE_EMPTY, + PDE_TYPE_BYTES, + PDE_TYPE_FILE, + } + + // This CTOR can't be called directly. Call method create() instead. + CefPostDataElement() {} + + @Override + protected void finalize() throws Throwable { + dispose(); + super.finalize(); + } + + /** + * Create a new CefPostDataElement object. + */ + public static final CefPostDataElement create() { + return CefPostDataElement_N.createNative(); + } + + /** + * Removes the native reference from an unused object. + */ + public abstract void dispose(); + + /** + * Returns true if this object is read-only. + */ + public abstract boolean isReadOnly(); + + /** + * Remove all contents from the post data element. + */ + public abstract void setToEmpty(); + + /** + * The post data element will represent a file. + */ + public abstract void setToFile(String fileName); + + /** + * The post data element will represent bytes. The bytes passed + * in will be copied. + */ + public abstract void setToBytes(int size, byte[] bytes); + + /** + * Return the type of this post data element. + */ + public abstract Type getType(); + + /** + * Return the file name. + */ + public abstract String getFile(); + + /** + * Return the number of bytes. + */ + public abstract int getBytesCount(); + + /** + * Read up to size bytes into bytes and return the number of bytes + * actually read. + */ + public abstract int getBytes(int size, byte[] bytes); + + @Override + public String toString() { + return toString(null); + } + + public String toString(String mimeType) { + int bytesCnt = getBytesCount(); + byte[] bytes = null; + if (bytesCnt > 0) { + bytes = new byte[bytesCnt]; + } + + boolean asText = false; + if (mimeType != null) { + if (mimeType.startsWith("text/")) + asText = true; + else if (mimeType.startsWith("application/xml")) + asText = true; + else if (mimeType.startsWith("application/xhtml")) + asText = true; + else if (mimeType.startsWith("application/x-www-form-urlencoded")) + asText = true; + } + + String returnValue = ""; + + if (getType() == Type.PDE_TYPE_BYTES) { + int setBytes = getBytes(bytes.length, bytes); + returnValue += " Content-Length: " + bytesCnt + "\n"; + if (asText) { + returnValue += "\n " + new String(bytes); + } else { + for (int i = 0; i < setBytes; i++) { + if (i % 40 == 0) returnValue += "\n "; + returnValue += String.format("%02X", bytes[i]) + " "; + } + } + returnValue += "\n"; + } else if (getType() == Type.PDE_TYPE_FILE) { + returnValue += "\n Bytes of file: " + getFile() + "\n"; + } + return returnValue; + } +} diff --git a/src/main/java/org/cef/network/CefPostDataElement_N.java b/src/main/java/org/cef/network/CefPostDataElement_N.java new file mode 100644 index 0000000..6d3fddd --- /dev/null +++ b/src/main/java/org/cef/network/CefPostDataElement_N.java @@ -0,0 +1,132 @@ +// Copyright (c) 2014 The Chromium Embedded Framework Authors. All rights +// reserved. Use of this source code is governed by a BSD-style license that +// can be found in the LICENSE file. + +package org.cef.network; + +import org.cef.callback.CefNative; + +class CefPostDataElement_N extends CefPostDataElement implements CefNative { + // Used internally to store a pointer to the CEF object. + private long N_CefHandle = 0; + + @Override + public void setNativeRef(String identifer, long nativeRef) { + N_CefHandle = nativeRef; + } + + @Override + public long getNativeRef(String identifer) { + return N_CefHandle; + } + + CefPostDataElement_N() { + super(); + } + + public static CefPostDataElement createNative() { + try { + return CefPostDataElement_N.N_Create(); + } catch (UnsatisfiedLinkError ule) { + ule.printStackTrace(); + return null; + } + } + + @Override + public void dispose() { + try { + N_Dispose(N_CefHandle); + } catch (UnsatisfiedLinkError ule) { + ule.printStackTrace(); + } + } + + @Override + public boolean isReadOnly() { + try { + return N_IsReadOnly(N_CefHandle); + } catch (UnsatisfiedLinkError ule) { + ule.printStackTrace(); + } + return false; + } + + @Override + public void setToEmpty() { + try { + N_SetToEmpty(N_CefHandle); + } catch (UnsatisfiedLinkError ule) { + ule.printStackTrace(); + } + } + + @Override + public void setToFile(String fileName) { + try { + N_SetToFile(N_CefHandle, fileName); + } catch (UnsatisfiedLinkError ule) { + ule.printStackTrace(); + } + } + + @Override + public void setToBytes(int size, byte[] bytes) { + try { + N_SetToBytes(N_CefHandle, size, bytes); + } catch (UnsatisfiedLinkError ule) { + ule.printStackTrace(); + } + } + + @Override + public Type getType() { + try { + return N_GetType(N_CefHandle); + } catch (UnsatisfiedLinkError ule) { + ule.printStackTrace(); + } + return null; + } + + @Override + public String getFile() { + try { + return N_GetFile(N_CefHandle); + } catch (UnsatisfiedLinkError ule) { + ule.printStackTrace(); + } + return null; + } + + @Override + public int getBytesCount() { + try { + return N_GetBytesCount(N_CefHandle); + } catch (UnsatisfiedLinkError ule) { + ule.printStackTrace(); + } + return 0; + } + + @Override + public int getBytes(int size, byte[] bytes) { + try { + return N_GetBytes(N_CefHandle, size, bytes); + } catch (UnsatisfiedLinkError ule) { + ule.printStackTrace(); + } + return 0; + } + + private final native static CefPostDataElement_N N_Create(); + private final native void N_Dispose(long self); + private final native boolean N_IsReadOnly(long self); + private final native void N_SetToEmpty(long self); + private final native void N_SetToFile(long self, String fileName); + private final native void N_SetToBytes(long self, int size, byte[] bytes); + private final native Type N_GetType(long self); + private final native String N_GetFile(long self); + private final native int N_GetBytesCount(long self); + private final native int N_GetBytes(long self, int size, byte[] bytes); +} diff --git a/src/main/java/org/cef/network/CefPostData_N.java b/src/main/java/org/cef/network/CefPostData_N.java new file mode 100644 index 0000000..3b79cf5 --- /dev/null +++ b/src/main/java/org/cef/network/CefPostData_N.java @@ -0,0 +1,116 @@ +// Copyright (c) 2014 The Chromium Embedded Framework Authors. All rights +// reserved. Use of this source code is governed by a BSD-style license that +// can be found in the LICENSE file. + +package org.cef.network; + +import org.cef.callback.CefNative; + +import java.util.Vector; + +/** + * + */ +class CefPostData_N extends CefPostData implements CefNative { + // Used internally to store a pointer to the CEF object. + private long N_CefHandle = 0; + + @Override + public void setNativeRef(String identifer, long nativeRef) { + N_CefHandle = nativeRef; + } + + @Override + public long getNativeRef(String identifer) { + return N_CefHandle; + } + + CefPostData_N() { + super(); + } + + public static CefPostData createNative() { + try { + return CefPostData_N.N_Create(); + } catch (UnsatisfiedLinkError ule) { + ule.printStackTrace(); + return null; + } + } + + @Override + public void dispose() { + try { + N_Dispose(N_CefHandle); + } catch (UnsatisfiedLinkError ule) { + ule.printStackTrace(); + } + } + + @Override + public boolean isReadOnly() { + try { + return N_IsReadOnly(N_CefHandle); + } catch (UnsatisfiedLinkError ule) { + ule.printStackTrace(); + } + return false; + } + + @Override + public int getElementCount() { + try { + return N_GetElementCount(N_CefHandle); + } catch (UnsatisfiedLinkError ule) { + ule.printStackTrace(); + } + return 0; + } + + @Override + public void getElements(Vector elements) { + try { + N_GetElements(N_CefHandle, elements); + } catch (UnsatisfiedLinkError ule) { + ule.printStackTrace(); + } + } + + @Override + public boolean removeElement(CefPostDataElement element) { + try { + return N_RemoveElement(N_CefHandle, element); + } catch (UnsatisfiedLinkError ule) { + ule.printStackTrace(); + } + return false; + } + + @Override + public boolean addElement(CefPostDataElement element) { + try { + return N_AddElement(N_CefHandle, element); + } catch (UnsatisfiedLinkError ule) { + ule.printStackTrace(); + } + return false; + } + + @Override + public void removeElements() { + try { + N_RemoveElements(N_CefHandle); + } catch (UnsatisfiedLinkError ule) { + ule.printStackTrace(); + } + } + + private final native static CefPostData_N N_Create(); + private final native void N_Dispose(long self); + private final native boolean N_IsReadOnly(long self); + private final native int N_GetElementCount(long self); + private final native void N_GetElements(long self, Vector elements); + private final native boolean N_RemoveElement(long self, CefPostDataElement element); + private final native boolean N_AddElement(long self, CefPostDataElement element); + private final native void N_RemoveElements(long self); +} diff --git a/src/main/java/org/cef/network/CefRequest.java b/src/main/java/org/cef/network/CefRequest.java new file mode 100644 index 0000000..47e38e9 --- /dev/null +++ b/src/main/java/org/cef/network/CefRequest.java @@ -0,0 +1,505 @@ +// Copyright (c) 2014 The Chromium Embedded Framework Authors. All rights +// reserved. Use of this source code is governed by a BSD-style license that +// can be found in the LICENSE file. + +package org.cef.network; + +import java.util.HashMap; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; + +/** + * Class used to represent a web request. The methods of this class may be + * called on any thread. + */ +public abstract class CefRequest { + /** + * Resource type for a request. + */ + public enum ResourceType { + RT_MAIN_FRAME, //!< Top level page. + RT_SUB_FRAME, //!< Frame or iframe. + RT_STYLESHEET, //!< CSS stylesheet. + RT_SCRIPT, //!< External script. + RT_IMAGE, //!< Image (jpg/gif/png/etc). + RT_FONT_RESOURCE, //!< Font. + RT_SUB_RESOURCE, //!< Some other subresource. This is the default type if + // the actual type is unknown. + RT_OBJECT, //!< Object (or embed) tag for a plugin, or a resource + // that a plugin requested. + RT_MEDIA, //!< Media resource. + RT_WORKER, //!< Main resource of a dedicated worker. + RT_SHARED_WORKER, //!< Main resource of a shared worker. + RT_PREFETCH, //!< Explicitly requested prefetch. + RT_FAVICON, //!< Favicon. + RT_XHR, //!< XMLHttpRequest. + RT_PING, //!< A request for a + RT_SERVICE_WORKER, //!< Main resource of a service worker. + RT_CSP_REPORT, //!< A report of Content Security Policy violations. + RT_PLUGIN_RESOURCE, //!< A resource that a plugin requested. + RT_NAVIGATION_PRELOAD_MAIN_FRAME, //!< A main-frame service worker navigation preload + //!< request. + RT_NAVIGATION_PRELOAD_SUB_FRAME, //!< A sub-frame service worker navigation preload request. + } + + /** + * Transition type for a request. Made up of one source value and 0 or more + * qualifiers. + */ + public enum TransitionType { + /** + * Source is a link click or the JavaScript window.open function. This is + * also the default value for requests like sub-resource loads that are not + * navigations. + */ + TT_LINK(0), + + /** + * Source is some other "explicit" navigation action such as creating a new + * browser or using the LoadURL function. This is also the default value + * for navigations where the actual type is unknown. + */ + TT_EXPLICIT(1), + + /** + * Source is a subframe navigation. This is any content that is automatically + * loaded in a non-toplevel frame. For example, if a page consists of several + * frames containing ads, those ad URLs will have this transition type. + * The user may not even realize the content in these pages is a separate + * frame, so may not care about the URL. + */ + TT_AUTO_SUBFRAME(3), + + /** + * Source is a subframe navigation explicitly requested by the user that will + * generate new navigation entries in the back/forward list. These are + * probably more important than frames that were automatically loaded in + * the background because the user probably cares about the fact that this + * link was loaded. + */ + TT_MANUAL_SUBFRAME(4), + + /** + * Source is a form submission by the user. NOTE: In some situations + * submitting a form does not result in this transition type. This can happen + * if the form uses a script to submit the contents. + */ + TT_FORM_SUBMIT(7), + + /** + * Source is a "reload" of the page via the Reload function or by re-visiting + * the same URL. NOTE: This is distinct from the concept of whether a + * particular load uses "reload semantics" (i.e. bypasses cached data). + */ + TT_RELOAD(8); + + private int value; + private TransitionType(int source) { + value = source; + } + + /** + * Returns the integer representation of this enum, containing the source + * and the qualifier as one value. + * @return The integer value of the enum with all its qualifiers. + */ + public int getValue() { + return value; + } + + /** + * Returns the source part of the enum as integer. + * @return Integer representation of the set source. + */ + public int getSource() { + return (value & 0xFF); + } + + /** + * Any of the core values above can be augmented by one or more qualifiers + * defined as TransitionFlags. + * These qualifiers further define the transition. + */ + public void addQualifier(TransitionFlags flag) { + value |= flag.getValue(); + } + + /** + * Add qualifiers as integer value + */ + public void addQualifiers(int flags) { + value |= (flags & 0xFFFFFF00); + } + + /** + * Returns the qualifier part of the enum as integer. + * @return Integer representation of the set qualifiers. + */ + public int getQualifiers() { + return (value & 0xFFFFFF00); + } + + /** + * Removes a qualifier from the enum. + * @param The qualifier to be removed. + */ + public void removeQualifier(TransitionFlags flag) { + value &= ~flag.getValue(); + } + + /** + * Tests if a qualifier is set. + */ + public boolean isSet(TransitionFlags flag) { + return (value & flag.getValue()) != 0; + } + + /** + * Tests if one of the redirect qualifiers is set. + */ + public boolean isRedirect() { + return (value & 0xC0000000) != 0; + } + } + + public static final class CefUrlRequestFlags { + /** + * Default behavior. + */ + public static final int UR_FLAG_NONE = 0; + + /** + * If set the cache will be skipped when handling the request. + */ + public static final int UR_FLAG_SKIP_CACHE = 1 << 0; + + /** + * If set user name, password, and cookies may be sent with the request, and + * cookies may be saved from the response. + */ + public static final int UR_FLAG_ALLOW_CACHED_CREDENTIALS = 1 << 1; + + /** + * If set upload progress events will be generated when a request has a body. + */ + public static final int UR_FLAG_REPORT_UPLOAD_PROGRESS = 1 << 3; + + /** + * If set the headers sent and received for the request will be recorded. + */ + public static final int UR_FLAG_REPORT_RAW_HEADERS = 1 << 5; + + /** + * If set the CefURLRequestClient.onDownloadData method will not be called. + */ + public static final int UR_FLAG_NO_DOWNLOAD_DATA = 1 << 6; + + /** + * If set 5XX redirect errors will be propagated to the observer instead of + * automatically re-tried. This currently only applies for requests + * originated in the browser process. + */ + public static final int UR_FLAG_NO_RETRY_ON_5XX = 1 << 7; + } + + /** + * Transition qualifiers. + * Any of the core values above can be augmented by one or more qualifiers. + * These qualifiers further define the transition. + */ + public enum TransitionFlags { + /** + * Attempted to visit a URL but was blocked. + */ + TT_BLOCKED_FLAG(0x00800000), + + /** + * Used the Forward or Back function to navigate among browsing history. + */ + TT_FORWARD_BACK_FLAG(0x01000000), + + /** + * The beginning of a navigation chain. + */ + TT_CHAIN_START_FLAG(0x10000000), + + /** + * The last transition in a redirect chain. + */ + TT_CHAIN_END_FLAG(0x20000000), + + /** + * Redirects caused by JavaScript or a meta refresh tag on the page. + */ + TT_CLIENT_REDIRECT_FLAG(0x40000000), + + /** + * Redirects sent from the server by HTTP headers. + */ + TT_SERVER_REDIRECT_FLAG(0x80000000); + + private final int flag; + private TransitionFlags(int flag) { + this.flag = flag; + } + + /** + * Returns the integer representation of the enum. + * @return Integer representation of the enum. + */ + public int getValue() { + return flag; + } + } + + /** + * Policy for how the Referrer HTTP header value will be sent during navigation. + * If the `--no-referrers` command-line flag is specified then the policy value + * will be ignored and the Referrer value will never be sent. + */ + public enum ReferrerPolicy { + + /** + * This is the same as REFERRER_POLICY_CLEAR_REFERRER_ON_TRANSITION_FROM_SECURE_TO_INSECURE + * but here to match CEF native. + */ + REFERRER_POLICY_DEFAULT, + + /** + * Clear the referrer header if the header value is HTTPS but the request + * destination is HTTP. This is the default behavior. + */ + REFERRER_POLICY_CLEAR_REFERRER_ON_TRANSITION_FROM_SECURE_TO_INSECURE, + + /** + * A slight variant on CLEAR_REFERRER_ON_TRANSITION_FROM_SECURE_TO_INSECURE: + * If the request destination is HTTP, an HTTPS referrer will be cleared. If + * the request's destination is cross-origin with the referrer (but does not + * downgrade), the referrer's granularity will be stripped down to an origin + * rather than a full URL. Same-origin requests will send the full referrer. + */ + REFERRER_POLICY_REDUCE_REFERRER_GRANULARITY_ON_TRANSITION_CROSS_ORIGIN, + + /** + * Strip the referrer down to an origin when the origin of the referrer is + * different from the destination's origin. + */ + REFERRER_POLICY_ORIGIN_ONLY_ON_TRANSITION_CROSS_ORIGIN, + + /** + * Never change the referrer. + */ + REFERRER_POLICY_NEVER_CLEAR_REFERRER, + + /** + * Strip the referrer down to the origin regardless of the redirect location. + */ + REFERRER_POLICY_ORIGIN, + + /** + * Clear the referrer when the request's referrer is cross-origin with the + * request destination. + */ + REFERRER_POLICY_CLEAR_REFERRER_ON_TRANSITION_CROSS_ORIGIN, + + /** + * Strip the referrer down to the origin, but clear it entirely if the + * referrer value is HTTPS and the destination is HTTP. + */ + REFERRER_POLICY_ORIGIN_CLEAR_ON_TRANSITION_FROM_SECURE_TO_INSECURE, + + /** + * Always clear the referrer regardless of the request destination. + */ + REFERRER_POLICY_NO_REFERRER, + + /** + * Always the last value in this enumeration. + */ + REFERRER_POLICY_LAST_VALUE + } + + // This CTOR can't be called directly. Call method create() instead. + CefRequest() {} + + @Override + protected void finalize() throws Throwable { + dispose(); + super.finalize(); + } + + /** + * Create a new CefRequest object. + */ + public static final CefRequest create() { + return CefRequest_N.createNative(); + } + + /** + * Removes the native reference from an unused object. + */ + public abstract void dispose(); + + /** + * Returns the globally unique identifier for this request or 0 if not + * specified. Can be used by CefRequestHandler implementations in the browser + * process to track a single request across multiple callbacks. + */ + public abstract long getIdentifier(); + + /** + * Returns true if this object is read-only. + */ + public abstract boolean isReadOnly(); + + /** + * Get the fully qualified URL. + */ + public abstract String getURL(); + + /** + * Set the fully qualified URL. + */ + public abstract void setURL(String url); + + /** + * Get the request method type. The value will default to POST if post data + * is provided and GET otherwise. + */ + public abstract String getMethod(); + + /** + * Set the request method type. + */ + public abstract void setMethod(String method); + + /** + * Set the referrer URL and policy. If non-empty the referrer URL must be + * fully qualified with an HTTP or HTTPS scheme component. Any username, + * password or ref component will be removed. + */ + public abstract void setReferrer(String url, ReferrerPolicy policy); + + /** + * Get the referrer URL. + */ + public abstract String getReferrerURL(); + + /** + * Get the referrer policy. + */ + public abstract ReferrerPolicy getReferrerPolicy(); + + /** + * Get the post data. + */ + public abstract CefPostData getPostData(); + + /** + * Set the post data. + */ + public abstract void setPostData(CefPostData postData); + + /** + * Get the value for the specified response header field. The Referer value cannot be retrieved + * using this method. Use getHeaderMap instead if there might be multiple values. + * @param name The header name. + * @return The header value. + */ + public abstract String getHeaderByName(String name); + + /** + * Set the value for the specified response header field. The Referer value cannot be set using + * this method. + * @param name The header name. + * @param value The header value. + * @param overwrite If true any existing values will be replaced with the new value. If false + * any existing values will not be overwritten. + */ + public abstract void setHeaderByName(String name, String value, boolean overwrite); + + /** + * Get the header values. + */ + public abstract void getHeaderMap(Map headerMap); + + /** + * Set the header values. + */ + public abstract void setHeaderMap(Map headerMap); + + /** + * Set all values at one time. + */ + public abstract void set( + String url, String method, CefPostData postData, Map headerMap); + + /** + * Get the flags used in combination with CefURLRequest. See + * CefUrlRequestFlags for supported values. + */ + public abstract int getFlags(); + + /** + * Set the flags used in combination with CefURLRequest. See + * CefUrlRequestFlags for supported values. + */ + public abstract void setFlags(int flags); + + /** + * Get the URL to the first party for cookies used in combination with + * CefURLRequest. + */ + public abstract String getFirstPartyForCookies(); + + /** + * Set the URL to the first party for cookies used in combination with + * CefURLRequest. + */ + public abstract void setFirstPartyForCookies(String url); + + /** + * Get the resource type for this request. Accurate resource type information + * may only be available in the browser process. + */ + public abstract ResourceType getResourceType(); + + /** + * Get the transition type for this request. Only available in the browser + * process and only applies to requests that represent a main frame or + * sub-frame navigation. + */ + public abstract TransitionType getTransitionType(); + + @Override + public String toString() { + String returnValue = "\nHTTP-Request"; + returnValue += "\n flags: " + getFlags(); + returnValue += "\n resourceType: " + getResourceType(); + returnValue += "\n transitionType: " + getTransitionType(); + returnValue += "\n firstPartyForCookies: " + getFirstPartyForCookies(); + returnValue += "\n referrerURL: " + getReferrerURL(); + returnValue += "\n referrerPolicy: " + getReferrerPolicy(); + returnValue += "\n " + getMethod() + " " + getURL() + " HTTP/1.1\n"; + + Map headerMap = new HashMap<>(); + getHeaderMap(headerMap); + Set> entrySet = headerMap.entrySet(); + String mimeType = null; + for (Entry entry : entrySet) { + String key = entry.getKey(); + returnValue += " " + key + "=" + entry.getValue() + "\n"; + if (key.equals("Content-Type")) { + mimeType = entry.getValue(); + } + } + + CefPostData pd = getPostData(); + if (pd != null) { + returnValue += pd.toString(mimeType); + } + + return returnValue; + } +} diff --git a/src/main/java/org/cef/network/CefRequest_N.java b/src/main/java/org/cef/network/CefRequest_N.java new file mode 100644 index 0000000..71bdf00 --- /dev/null +++ b/src/main/java/org/cef/network/CefRequest_N.java @@ -0,0 +1,284 @@ +// Copyright (c) 2014 The Chromium Embedded Framework Authors. All rights +// reserved. Use of this source code is governed by a BSD-style license that +// can be found in the LICENSE file. + +package org.cef.network; + +import org.cef.callback.CefNative; + +import java.util.Map; + +class CefRequest_N extends CefRequest implements CefNative { + // Used internally to store a pointer to the CEF object. + private long N_CefHandle = 0; + + @Override + public void setNativeRef(String identifer, long nativeRef) { + N_CefHandle = nativeRef; + } + + @Override + public long getNativeRef(String identifer) { + return N_CefHandle; + } + + CefRequest_N() { + super(); + } + + public static CefRequest createNative() { + try { + return CefRequest_N.N_Create(); + } catch (UnsatisfiedLinkError ule) { + ule.printStackTrace(); + return null; + } + } + + @Override + public void dispose() { + try { + N_Dispose(N_CefHandle); + } catch (UnsatisfiedLinkError ule) { + ule.printStackTrace(); + } + } + + @Override + public long getIdentifier() { + try { + return N_GetIdentifier(N_CefHandle); + } catch (UnsatisfiedLinkError ule) { + ule.printStackTrace(); + } + return 0; + } + + @Override + public boolean isReadOnly() { + try { + return N_IsReadOnly(N_CefHandle); + } catch (UnsatisfiedLinkError ule) { + ule.printStackTrace(); + } + return false; + } + + @Override + public String getURL() { + try { + return N_GetURL(N_CefHandle); + } catch (UnsatisfiedLinkError ule) { + ule.printStackTrace(); + } + return null; + } + + @Override + public void setURL(String url) { + try { + N_SetURL(N_CefHandle, url); + } catch (UnsatisfiedLinkError ule) { + ule.printStackTrace(); + } + } + + @Override + public String getMethod() { + try { + return N_GetMethod(N_CefHandle); + } catch (UnsatisfiedLinkError ule) { + ule.printStackTrace(); + } + return null; + } + + @Override + public void setMethod(String string) { + try { + N_SetMethod(N_CefHandle, string); + } catch (UnsatisfiedLinkError ule) { + ule.printStackTrace(); + } + } + + @Override + public void setReferrer(String url, ReferrerPolicy policy) { + try { + N_SetReferrer(N_CefHandle, url, policy); + } catch (UnsatisfiedLinkError ule) { + ule.printStackTrace(); + } + } + + @Override + public String getReferrerURL() { + try { + return N_GetReferrerURL(N_CefHandle); + } catch (UnsatisfiedLinkError ule) { + ule.printStackTrace(); + } + return null; + } + + @Override + public ReferrerPolicy getReferrerPolicy() { + try { + return N_GetReferrerPolicy(N_CefHandle); + } catch (UnsatisfiedLinkError ule) { + ule.printStackTrace(); + } + return null; + } + + @Override + public CefPostData getPostData() { + try { + return N_GetPostData(N_CefHandle); + } catch (UnsatisfiedLinkError ule) { + ule.printStackTrace(); + } + return null; + } + + @Override + public void setPostData(CefPostData postData) { + try { + N_SetPostData(N_CefHandle, postData); + } catch (UnsatisfiedLinkError ule) { + ule.printStackTrace(); + } + } + + @Override + public String getHeaderByName(String name) { + try { + return N_GetHeaderByName(N_CefHandle, name); + } catch (UnsatisfiedLinkError ule) { + ule.printStackTrace(); + } + return null; + } + + @Override + public void setHeaderByName(String name, String value, boolean overwrite) { + try { + N_SetHeaderByName(N_CefHandle, name, value, overwrite); + } catch (UnsatisfiedLinkError ule) { + ule.printStackTrace(); + } + } + + @Override + public void getHeaderMap(Map headerMap) { + try { + N_GetHeaderMap(N_CefHandle, headerMap); + } catch (UnsatisfiedLinkError ule) { + ule.printStackTrace(); + } + } + + @Override + public void setHeaderMap(Map headerMap) { + try { + N_SetHeaderMap(N_CefHandle, headerMap); + } catch (UnsatisfiedLinkError ule) { + ule.printStackTrace(); + } + } + + @Override + public void set( + String url, String method, CefPostData postData, Map headerMap) { + try { + N_Set(N_CefHandle, url, method, postData, headerMap); + } catch (UnsatisfiedLinkError ule) { + ule.printStackTrace(); + } + } + + @Override + public int getFlags() { + try { + return N_GetFlags(N_CefHandle); + } catch (UnsatisfiedLinkError ule) { + ule.printStackTrace(); + } + return 0; + } + + @Override + public void setFlags(int flags) { + try { + N_SetFlags(N_CefHandle, flags); + } catch (UnsatisfiedLinkError ule) { + ule.printStackTrace(); + } + } + + @Override + public String getFirstPartyForCookies() { + try { + return N_GetFirstPartyForCookies(N_CefHandle); + } catch (UnsatisfiedLinkError ule) { + ule.printStackTrace(); + } + return null; + } + + @Override + public void setFirstPartyForCookies(String url) { + try { + N_SetFirstPartyForCookies(N_CefHandle, url); + } catch (UnsatisfiedLinkError ule) { + ule.printStackTrace(); + } + } + + @Override + public ResourceType getResourceType() { + try { + return N_GetResourceType(N_CefHandle); + } catch (UnsatisfiedLinkError ule) { + ule.printStackTrace(); + } + return ResourceType.RT_MAIN_FRAME; + } + + @Override + public TransitionType getTransitionType() { + try { + return N_GetTransitionType(N_CefHandle); + } catch (UnsatisfiedLinkError ule) { + ule.printStackTrace(); + } + return TransitionType.TT_AUTO_SUBFRAME; + } + + private final native static CefRequest_N N_Create(); + private final native void N_Dispose(long self); + private final native long N_GetIdentifier(long self); + private final native boolean N_IsReadOnly(long self); + private final native String N_GetURL(long self); + private final native void N_SetURL(long self, String url); + private final native String N_GetMethod(long self); + private final native void N_SetMethod(long self, String method); + private final native void N_SetReferrer(long self, String url, ReferrerPolicy policy); + private final native String N_GetReferrerURL(long self); + private final native ReferrerPolicy N_GetReferrerPolicy(long self); + private final native CefPostData N_GetPostData(long self); + private final native void N_SetPostData(long self, CefPostData postData); + private final native String N_GetHeaderByName(long self, String name); + private final native void N_SetHeaderByName( + long self, String name, String value, boolean overwrite); + private final native void N_GetHeaderMap(long self, Map headerMap); + private final native void N_SetHeaderMap(long self, Map headerMap); + private final native void N_Set(long self, String url, String method, CefPostData postData, + Map headerMap); + private final native int N_GetFlags(long self); + private final native void N_SetFlags(long self, int flags); + private final native String N_GetFirstPartyForCookies(long self); + private final native void N_SetFirstPartyForCookies(long self, String url); + private final native ResourceType N_GetResourceType(long self); + private final native TransitionType N_GetTransitionType(long self); +} diff --git a/src/main/java/org/cef/network/CefResponse.java b/src/main/java/org/cef/network/CefResponse.java new file mode 100644 index 0000000..fe705ba --- /dev/null +++ b/src/main/java/org/cef/network/CefResponse.java @@ -0,0 +1,130 @@ +// Copyright (c) 2014 The Chromium Embedded Framework Authors. All rights +// reserved. Use of this source code is governed by a BSD-style license that +// can be found in the LICENSE file. + +package org.cef.network; + +import org.cef.handler.CefLoadHandler.ErrorCode; + +import java.util.HashMap; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; + +/** + * Class used to represent a web response. The methods of this class may be + * called on any thread. + */ +public abstract class CefResponse { + // This CTOR can't be called directly. Call method create() instead. + CefResponse() {} + + @Override + protected void finalize() throws Throwable { + dispose(); + super.finalize(); + } + + /** + * Create a new CefRequest object. + */ + public static final CefResponse create() { + return CefResponse_N.createNative(); + } + + /** + * Removes the native reference from an unused object. + */ + public abstract void dispose(); + + /** + * Returns true if this object is read-only. + */ + public abstract boolean isReadOnly(); + + /** + * Get the response error code. Returns ERR_NONE if there was no error. + */ + public abstract ErrorCode getError(); + + /** + * Get the response error code. Returns ERR_NONE if there was no error. + */ + public abstract void setError(ErrorCode errorCode); + + /** + * Get the response status code. + */ + public abstract int getStatus(); + + /** + * Set the response status code. + */ + public abstract void setStatus(int status); + + /** + * Get the response status text. + */ + public abstract String getStatusText(); + + /** + * Set the response status text. + */ + public abstract void setStatusText(String statusText); + + /** + * Get the response mime type. + */ + public abstract String getMimeType(); + + /** + * Set the response mime type. + */ + public abstract void setMimeType(String mimeType); + + /** + * Get the value for the specified response header field. Use getHeaderMap instead if there + * might be multiple values. + * @param name The header name. + * @return The header value. + */ + public abstract String getHeaderByName(String name); + + /** + * Set the value for the specified response header field. + * @param name The header name. + * @param value The header value. + * @param overwrite If true any existing values will be replaced with the new value. If false + * any existing values will not be overwritten. + */ + public abstract void setHeaderByName(String name, String value, boolean overwrite); + + /** + * Get all response header fields. + */ + public abstract void getHeaderMap(Map headerMap); + + /** + * Set all response header fields. + */ + public abstract void setHeaderMap(Map headerMap); + + @Override + public String toString() { + String returnValue = "\nHTTP-Response:"; + + returnValue += "\n error: " + getError(); + returnValue += "\n readOnly: " + isReadOnly(); + returnValue += "\n HTTP/1.1 " + getStatus() + " " + getStatusText(); + returnValue += "\n Content-Type: " + getMimeType(); + + Map headerMap = new HashMap<>(); + getHeaderMap(headerMap); + Set> entrySet = headerMap.entrySet(); + for (Entry entry : entrySet) { + returnValue += " " + entry.getKey() + "=" + entry.getValue() + "\n"; + } + + return returnValue; + } +} diff --git a/src/main/java/org/cef/network/CefResponse_N.java b/src/main/java/org/cef/network/CefResponse_N.java new file mode 100644 index 0000000..60e2525 --- /dev/null +++ b/src/main/java/org/cef/network/CefResponse_N.java @@ -0,0 +1,187 @@ +// Copyright (c) 2014 The Chromium Embedded Framework Authors. All rights +// reserved. Use of this source code is governed by a BSD-style license that +// can be found in the LICENSE file. + +package org.cef.network; + +import org.cef.callback.CefNative; +import org.cef.handler.CefLoadHandler.ErrorCode; + +import java.util.Map; + +class CefResponse_N extends CefResponse implements CefNative { + // Used internally to store a pointer to the CEF object. + private long N_CefHandle = 0; + + @Override + public void setNativeRef(String identifer, long nativeRef) { + N_CefHandle = nativeRef; + } + + @Override + public long getNativeRef(String identifer) { + return N_CefHandle; + } + + CefResponse_N() { + super(); + } + + public static CefResponse createNative() { + try { + return CefResponse_N.N_Create(); + } catch (UnsatisfiedLinkError ule) { + ule.printStackTrace(); + return null; + } + } + + @Override + public void dispose() { + try { + N_Dispose(N_CefHandle); + } catch (UnsatisfiedLinkError ule) { + ule.printStackTrace(); + } + } + + @Override + public boolean isReadOnly() { + try { + return N_IsReadOnly(N_CefHandle); + } catch (UnsatisfiedLinkError ule) { + ule.printStackTrace(); + } + return false; + } + + @Override + public ErrorCode getError() { + try { + return N_GetError(N_CefHandle); + } catch (UnsatisfiedLinkError ule) { + ule.printStackTrace(); + } + return null; + } + + @Override + public void setError(ErrorCode errorCode) { + try { + N_SetError(N_CefHandle, errorCode); + } catch (UnsatisfiedLinkError ule) { + ule.printStackTrace(); + } + } + + @Override + public int getStatus() { + try { + return N_GetStatus(N_CefHandle); + } catch (UnsatisfiedLinkError ule) { + ule.printStackTrace(); + } + return 0; + } + + @Override + public void setStatus(int status) { + try { + N_SetStatus(N_CefHandle, status); + } catch (UnsatisfiedLinkError ule) { + ule.printStackTrace(); + } + } + + @Override + public String getStatusText() { + try { + return N_GetStatusText(N_CefHandle); + } catch (UnsatisfiedLinkError ule) { + ule.printStackTrace(); + } + return null; + } + + @Override + public void setStatusText(String statusText) { + try { + N_SetStatusText(N_CefHandle, statusText); + } catch (UnsatisfiedLinkError ule) { + ule.printStackTrace(); + } + } + + @Override + public String getMimeType() { + try { + return N_GetMimeType(N_CefHandle); + } catch (UnsatisfiedLinkError ule) { + ule.printStackTrace(); + } + return null; + } + + @Override + public void setMimeType(String mimeType) { + try { + N_SetMimeType(N_CefHandle, mimeType); + } catch (UnsatisfiedLinkError ule) { + ule.printStackTrace(); + } + } + + @Override + public String getHeaderByName(String name) { + try { + return N_GetHeaderByName(N_CefHandle, name); + } catch (UnsatisfiedLinkError ule) { + ule.printStackTrace(); + } + return null; + } + + @Override + public void setHeaderByName(String name, String value, boolean overwrite) { + try { + N_SetHeaderByName(N_CefHandle, name, value, overwrite); + } catch (UnsatisfiedLinkError ule) { + ule.printStackTrace(); + } + } + + @Override + public void getHeaderMap(Map headerMap) { + try { + N_GetHeaderMap(N_CefHandle, headerMap); + } catch (UnsatisfiedLinkError ule) { + ule.printStackTrace(); + } + } + + @Override + public void setHeaderMap(Map headerMap) { + try { + N_SetHeaderMap(N_CefHandle, headerMap); + } catch (UnsatisfiedLinkError ule) { + ule.printStackTrace(); + } + } + + private final native static CefResponse_N N_Create(); + private final native void N_Dispose(long self); + private final native boolean N_IsReadOnly(long self); + private final native ErrorCode N_GetError(long self); + private final native void N_SetError(long self, ErrorCode errorCode); + private final native int N_GetStatus(long self); + private final native void N_SetStatus(long self, int status); + private final native String N_GetStatusText(long self); + private final native void N_SetStatusText(long self, String statusText); + private final native String N_GetMimeType(long self); + private final native void N_SetMimeType(long self, String mimeType); + private final native String N_GetHeaderByName(long self, String name); + private final native void N_SetHeaderByName( + long self, String name, String value, boolean overwrite); + private final native void N_GetHeaderMap(long self, Map headerMap); + private final native void N_SetHeaderMap(long self, Map headerMap); +} diff --git a/src/main/java/org/cef/network/CefURLRequest.java b/src/main/java/org/cef/network/CefURLRequest.java new file mode 100644 index 0000000..2327cd4 --- /dev/null +++ b/src/main/java/org/cef/network/CefURLRequest.java @@ -0,0 +1,89 @@ +// Copyright (c) 2014 The Chromium Embedded Framework Authors. All rights +// reserved. Use of this source code is governed by a BSD-style license that +// can be found in the LICENSE file. + +package org.cef.network; + +import org.cef.callback.CefURLRequestClient; +import org.cef.handler.CefLoadHandler.ErrorCode; + +/** + * Class used to make a URL request. URL requests are not associated with a + * browser instance so no CefClient callbacks will be executed. URL requests + * can be created on any valid CEF thread in either the browser or render + * process. Once created the methods of the URL request object must be accessed + * on the same thread that created it. + */ +public abstract class CefURLRequest { + public static enum Status { + UR_UNKNOWN, + UR_SUCCESS, + UR_IO_PENDING, + UR_CANCELED, + UR_FAILED, + } + + // This CTOR can't be called directly. Call method create() instead. + CefURLRequest() {} + + @Override + protected void finalize() throws Throwable { + dispose(); + super.finalize(); + } + + /** + * Create a new URL request. Only GET, POST, HEAD, DELETE and PUT request + * methods are supported. Multiple post data elements are not supported and + * elements of type PDE_TYPE_FILE are only supported for requests originating + * from the browser process. Requests originating from the render process will + * receive the same handling as requests originating from Web content -- if + * the response contains Content-Disposition or Mime-Type header values that + * would not normally be rendered then the response may receive special + * handling inside the browser (for example, via the file download code path + * instead of the URL request code path). The |request| object will be marked + * as read-only after calling this method. + */ + public static final CefURLRequest create(CefRequest request, CefURLRequestClient client) { + return CefURLRequest_N.createNative(request, client); + } + + /** + * Removes the native reference from an unused object. + */ + public abstract void dispose(); + + /** + * Returns the request object used to create this URL request. The returned + * object is read-only and should not be modified. + */ + public abstract CefRequest getRequest(); + + /** + * Returns the client. + */ + public abstract CefURLRequestClient getClient(); + + /** + * Returns the request status. + */ + public abstract Status getRequestStatus(); + + /** + * Returns the request error if status is UR_CANCELED or UR_FAILED, or 0 + * otherwise. + */ + public abstract ErrorCode getRequestError(); + + /** + * Returns the response, or NULL if no response information is available. + * Response information will only be available after the upload has completed. + * The returned object is read-only and should not be modified. + */ + public abstract CefResponse getResponse(); + + /** + * Cancel the request. + */ + public abstract void cancel(); +} diff --git a/src/main/java/org/cef/network/CefURLRequest_N.java b/src/main/java/org/cef/network/CefURLRequest_N.java new file mode 100644 index 0000000..e5a73ab --- /dev/null +++ b/src/main/java/org/cef/network/CefURLRequest_N.java @@ -0,0 +1,109 @@ +// Copyright (c) 2014 The Chromium Embedded Framework Authors. All rights +// reserved. Use of this source code is governed by a BSD-style license that +// can be found in the LICENSE file. + +package org.cef.network; + +import org.cef.callback.CefNative; +import org.cef.callback.CefURLRequestClient; +import org.cef.handler.CefLoadHandler.ErrorCode; + +class CefURLRequest_N extends CefURLRequest implements CefNative { + // Used internally to store a pointer to the CEF object. + private long N_CefHandle = 0; + private final CefRequest request_; + private final CefURLRequestClient client_; + + @Override + public void setNativeRef(String identifer, long nativeRef) { + N_CefHandle = nativeRef; + } + + @Override + public long getNativeRef(String identifer) { + return N_CefHandle; + } + + CefURLRequest_N(CefRequest request, CefURLRequestClient client) { + super(); + request_ = request; + client_ = client; + } + + public static final CefURLRequest createNative(CefRequest request, CefURLRequestClient client) { + // keep a reference to the request and client objects. + CefURLRequest_N result = new CefURLRequest_N(request, client); + try { + result.N_Create(request, client); + } catch (UnsatisfiedLinkError ule) { + ule.printStackTrace(); + } + if (result.N_CefHandle == 0) return null; + return result; + } + + @Override + public void dispose() { + try { + N_Dispose(N_CefHandle); + } catch (UnsatisfiedLinkError ule) { + ule.printStackTrace(); + } + } + + @Override + public CefRequest getRequest() { + return request_; + } + + @Override + public CefURLRequestClient getClient() { + return client_; + } + + @Override + public Status getRequestStatus() { + try { + return N_GetRequestStatus(N_CefHandle); + } catch (UnsatisfiedLinkError ule) { + ule.printStackTrace(); + } + return null; + } + + @Override + public ErrorCode getRequestError() { + try { + return N_GetRequestError(N_CefHandle); + } catch (UnsatisfiedLinkError ule) { + ule.printStackTrace(); + } + return null; + } + + @Override + public CefResponse getResponse() { + try { + return N_GetResponse(N_CefHandle); + } catch (UnsatisfiedLinkError ule) { + ule.printStackTrace(); + } + return null; + } + + @Override + public void cancel() { + try { + N_Cancel(N_CefHandle); + } catch (UnsatisfiedLinkError ule) { + ule.printStackTrace(); + } + } + + private final native void N_Create(CefRequest request, CefURLRequestClient client); + private final native void N_Dispose(long self); + private final native Status N_GetRequestStatus(long self); + private final native ErrorCode N_GetRequestError(long self); + private final native CefResponse N_GetResponse(long self); + private final native void N_Cancel(long self); +} diff --git a/src/main/resources/assets/letsencrypt/isrgrootx1.der b/src/main/resources/assets/letsencrypt/isrgrootx1.der new file mode 100644 index 0000000000000000000000000000000000000000..9d2132e7f1e352fabac7eafb231488b5da91ffb2 GIT binary patch literal 1391 zcmXqLV$C*aVh&!w%*4pVB*@StaDKxjhsTjF$q#lXH+3@@@Un4gwRyCC=VfH%W@Rw& zH{>?pWMd9xVH0Kw4K~y?PzQ0igcUsVN>YpRQcDzqQMi96N{2F6x@sQ zOA8D|4TM2TnT2^ggM-`^g7WiA6e0`_)UeSUJ_S;M+V-u>HW)=goaf7yL{%}fvF;1?F_{JHX*^)7mb z_cWAjyQP1@qPLp4KvBB%lYz~z{&jb6C9i%h=6|S9(7WzD_ly5q%k{o&s`h%|Bc#ex z(95j3;9;=J8{wPpB=-w!_Uf_kT$~tqZ%sS8l;RAn=gy-c5l%vESRjulRoaDHHpQelw1#&mWmj<25Ut_nWV1qwMTG%s)L@ zZ#3Rz-J*5P@#PxEvZ-ABH|}5EDDklY(M=kbokat@+bL(=ez`Qo=d9_8$g;*;h-`WLMh;lRc_g>Iv-DFqo zCF5PpD)i^rs|NwXHO`YuHlHea-Y3t;=GdnK4#`;nE(6$dNYTB&bR(NQ2+$oz?wqHJLsjX!HYm3h*_fBZ@a%uek ze*2NA(-ox)>ah}I#svAgPldH?sMd^L9VXJTe#U|j5E;9$T9Os}&1 zjEw(TSb({M&43@o7Y6ZJ4VZzHfhFz=l^iUlGsD^9O_ z?o;@C)1`#9mMgeli7SS+ehlD?e0}ag-X~KPhVT7{&D4o6YKug*3J5*#Pa(8&H7gpwUsuC^Ywq~GKr43@rUtb$j%*V zXSzC!JAHIpY?|)Bn-;WsJ~s2)HigcR z-KW{sqcnToqipMNtEK|qJDkTmPjj*R=DdjQJNf?H>f^h&YWulf^SYpR=4sI>j;y6q zAB!&hzU1vmo%p4{|F6+t(%W~vdiUeP>Iq_(+2h=TYs}f5dM+QCHs|Whty&MJN;P<_ z^RZ+cWl3o${YKsGG(t*4WP8`@Gk9!gJ;MzXRq} z=D1zmBD#56Ufpb-X;wRebnUN2Km5&csO6u^ip8C`)?_`D(Av1dIWhXO{2lAwvQN4% zdQ0z%8|T;t|E@mm82|syq6>)@52x)|6WeWmz4WT_ftiBq<~klMDs9=v + + + + + + + + +Hello, ?




+
Google!  YouTube!
+ + \ No newline at end of file diff --git a/src/main/resources/assets/mcef/html/jquery.js b/src/main/resources/assets/mcef/html/jquery.js new file mode 100644 index 0000000..da41706 --- /dev/null +++ b/src/main/resources/assets/mcef/html/jquery.js @@ -0,0 +1,6 @@ +/*! jQuery v1.10.2 | (c) 2005, 2013 jQuery Foundation, Inc. | jquery.org/license +//@ sourceMappingURL=jquery-1.10.2.min.map +*/ +(function(e,t){var n,r,i=typeof t,o=e.location,a=e.document,s=a.documentElement,l=e.jQuery,u=e.$,c={},p=[],f="1.10.2",d=p.concat,h=p.push,g=p.slice,m=p.indexOf,y=c.toString,v=c.hasOwnProperty,b=f.trim,x=function(e,t){return new x.fn.init(e,t,r)},w=/[+-]?(?:\d*\.|)\d+(?:[eE][+-]?\d+|)/.source,T=/\S+/g,C=/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g,N=/^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]*))$/,k=/^<(\w+)\s*\/?>(?:<\/\1>|)$/,E=/^[\],:{}\s]*$/,S=/(?:^|:|,)(?:\s*\[)+/g,A=/\\(?:["\\\/bfnrt]|u[\da-fA-F]{4})/g,j=/"[^"\\\r\n]*"|true|false|null|-?(?:\d+\.|)\d+(?:[eE][+-]?\d+|)/g,D=/^-ms-/,L=/-([\da-z])/gi,H=function(e,t){return t.toUpperCase()},q=function(e){(a.addEventListener||"load"===e.type||"complete"===a.readyState)&&(_(),x.ready())},_=function(){a.addEventListener?(a.removeEventListener("DOMContentLoaded",q,!1),e.removeEventListener("load",q,!1)):(a.detachEvent("onreadystatechange",q),e.detachEvent("onload",q))};x.fn=x.prototype={jquery:f,constructor:x,init:function(e,n,r){var i,o;if(!e)return this;if("string"==typeof e){if(i="<"===e.charAt(0)&&">"===e.charAt(e.length-1)&&e.length>=3?[null,e,null]:N.exec(e),!i||!i[1]&&n)return!n||n.jquery?(n||r).find(e):this.constructor(n).find(e);if(i[1]){if(n=n instanceof x?n[0]:n,x.merge(this,x.parseHTML(i[1],n&&n.nodeType?n.ownerDocument||n:a,!0)),k.test(i[1])&&x.isPlainObject(n))for(i in n)x.isFunction(this[i])?this[i](n[i]):this.attr(i,n[i]);return this}if(o=a.getElementById(i[2]),o&&o.parentNode){if(o.id!==i[2])return r.find(e);this.length=1,this[0]=o}return this.context=a,this.selector=e,this}return e.nodeType?(this.context=this[0]=e,this.length=1,this):x.isFunction(e)?r.ready(e):(e.selector!==t&&(this.selector=e.selector,this.context=e.context),x.makeArray(e,this))},selector:"",length:0,toArray:function(){return g.call(this)},get:function(e){return null==e?this.toArray():0>e?this[this.length+e]:this[e]},pushStack:function(e){var t=x.merge(this.constructor(),e);return t.prevObject=this,t.context=this.context,t},each:function(e,t){return x.each(this,e,t)},ready:function(e){return x.ready.promise().done(e),this},slice:function(){return this.pushStack(g.apply(this,arguments))},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},eq:function(e){var t=this.length,n=+e+(0>e?t:0);return this.pushStack(n>=0&&t>n?[this[n]]:[])},map:function(e){return this.pushStack(x.map(this,function(t,n){return e.call(t,n,t)}))},end:function(){return this.prevObject||this.constructor(null)},push:h,sort:[].sort,splice:[].splice},x.fn.init.prototype=x.fn,x.extend=x.fn.extend=function(){var e,n,r,i,o,a,s=arguments[0]||{},l=1,u=arguments.length,c=!1;for("boolean"==typeof s&&(c=s,s=arguments[1]||{},l=2),"object"==typeof s||x.isFunction(s)||(s={}),u===l&&(s=this,--l);u>l;l++)if(null!=(o=arguments[l]))for(i in o)e=s[i],r=o[i],s!==r&&(c&&r&&(x.isPlainObject(r)||(n=x.isArray(r)))?(n?(n=!1,a=e&&x.isArray(e)?e:[]):a=e&&x.isPlainObject(e)?e:{},s[i]=x.extend(c,a,r)):r!==t&&(s[i]=r));return s},x.extend({expando:"jQuery"+(f+Math.random()).replace(/\D/g,""),noConflict:function(t){return e.$===x&&(e.$=u),t&&e.jQuery===x&&(e.jQuery=l),x},isReady:!1,readyWait:1,holdReady:function(e){e?x.readyWait++:x.ready(!0)},ready:function(e){if(e===!0?!--x.readyWait:!x.isReady){if(!a.body)return setTimeout(x.ready);x.isReady=!0,e!==!0&&--x.readyWait>0||(n.resolveWith(a,[x]),x.fn.trigger&&x(a).trigger("ready").off("ready"))}},isFunction:function(e){return"function"===x.type(e)},isArray:Array.isArray||function(e){return"array"===x.type(e)},isWindow:function(e){return null!=e&&e==e.window},isNumeric:function(e){return!isNaN(parseFloat(e))&&isFinite(e)},type:function(e){return null==e?e+"":"object"==typeof e||"function"==typeof e?c[y.call(e)]||"object":typeof e},isPlainObject:function(e){var n;if(!e||"object"!==x.type(e)||e.nodeType||x.isWindow(e))return!1;try{if(e.constructor&&!v.call(e,"constructor")&&!v.call(e.constructor.prototype,"isPrototypeOf"))return!1}catch(r){return!1}if(x.support.ownLast)for(n in e)return v.call(e,n);for(n in e);return n===t||v.call(e,n)},isEmptyObject:function(e){var t;for(t in e)return!1;return!0},error:function(e){throw Error(e)},parseHTML:function(e,t,n){if(!e||"string"!=typeof e)return null;"boolean"==typeof t&&(n=t,t=!1),t=t||a;var r=k.exec(e),i=!n&&[];return r?[t.createElement(r[1])]:(r=x.buildFragment([e],t,i),i&&x(i).remove(),x.merge([],r.childNodes))},parseJSON:function(n){return e.JSON&&e.JSON.parse?e.JSON.parse(n):null===n?n:"string"==typeof n&&(n=x.trim(n),n&&E.test(n.replace(A,"@").replace(j,"]").replace(S,"")))?Function("return "+n)():(x.error("Invalid JSON: "+n),t)},parseXML:function(n){var r,i;if(!n||"string"!=typeof n)return null;try{e.DOMParser?(i=new DOMParser,r=i.parseFromString(n,"text/xml")):(r=new ActiveXObject("Microsoft.XMLDOM"),r.async="false",r.loadXML(n))}catch(o){r=t}return r&&r.documentElement&&!r.getElementsByTagName("parsererror").length||x.error("Invalid XML: "+n),r},noop:function(){},globalEval:function(t){t&&x.trim(t)&&(e.execScript||function(t){e.eval.call(e,t)})(t)},camelCase:function(e){return e.replace(D,"ms-").replace(L,H)},nodeName:function(e,t){return e.nodeName&&e.nodeName.toLowerCase()===t.toLowerCase()},each:function(e,t,n){var r,i=0,o=e.length,a=M(e);if(n){if(a){for(;o>i;i++)if(r=t.apply(e[i],n),r===!1)break}else for(i in e)if(r=t.apply(e[i],n),r===!1)break}else if(a){for(;o>i;i++)if(r=t.call(e[i],i,e[i]),r===!1)break}else for(i in e)if(r=t.call(e[i],i,e[i]),r===!1)break;return e},trim:b&&!b.call("\ufeff\u00a0")?function(e){return null==e?"":b.call(e)}:function(e){return null==e?"":(e+"").replace(C,"")},makeArray:function(e,t){var n=t||[];return null!=e&&(M(Object(e))?x.merge(n,"string"==typeof e?[e]:e):h.call(n,e)),n},inArray:function(e,t,n){var r;if(t){if(m)return m.call(t,e,n);for(r=t.length,n=n?0>n?Math.max(0,r+n):n:0;r>n;n++)if(n in t&&t[n]===e)return n}return-1},merge:function(e,n){var r=n.length,i=e.length,o=0;if("number"==typeof r)for(;r>o;o++)e[i++]=n[o];else while(n[o]!==t)e[i++]=n[o++];return e.length=i,e},grep:function(e,t,n){var r,i=[],o=0,a=e.length;for(n=!!n;a>o;o++)r=!!t(e[o],o),n!==r&&i.push(e[o]);return i},map:function(e,t,n){var r,i=0,o=e.length,a=M(e),s=[];if(a)for(;o>i;i++)r=t(e[i],i,n),null!=r&&(s[s.length]=r);else for(i in e)r=t(e[i],i,n),null!=r&&(s[s.length]=r);return d.apply([],s)},guid:1,proxy:function(e,n){var r,i,o;return"string"==typeof n&&(o=e[n],n=e,e=o),x.isFunction(e)?(r=g.call(arguments,2),i=function(){return e.apply(n||this,r.concat(g.call(arguments)))},i.guid=e.guid=e.guid||x.guid++,i):t},access:function(e,n,r,i,o,a,s){var l=0,u=e.length,c=null==r;if("object"===x.type(r)){o=!0;for(l in r)x.access(e,n,l,r[l],!0,a,s)}else if(i!==t&&(o=!0,x.isFunction(i)||(s=!0),c&&(s?(n.call(e,i),n=null):(c=n,n=function(e,t,n){return c.call(x(e),n)})),n))for(;u>l;l++)n(e[l],r,s?i:i.call(e[l],l,n(e[l],r)));return o?e:c?n.call(e):u?n(e[0],r):a},now:function(){return(new Date).getTime()},swap:function(e,t,n,r){var i,o,a={};for(o in t)a[o]=e.style[o],e.style[o]=t[o];i=n.apply(e,r||[]);for(o in t)e.style[o]=a[o];return i}}),x.ready.promise=function(t){if(!n)if(n=x.Deferred(),"complete"===a.readyState)setTimeout(x.ready);else if(a.addEventListener)a.addEventListener("DOMContentLoaded",q,!1),e.addEventListener("load",q,!1);else{a.attachEvent("onreadystatechange",q),e.attachEvent("onload",q);var r=!1;try{r=null==e.frameElement&&a.documentElement}catch(i){}r&&r.doScroll&&function o(){if(!x.isReady){try{r.doScroll("left")}catch(e){return setTimeout(o,50)}_(),x.ready()}}()}return n.promise(t)},x.each("Boolean Number String Function Array Date RegExp Object Error".split(" "),function(e,t){c["[object "+t+"]"]=t.toLowerCase()});function M(e){var t=e.length,n=x.type(e);return x.isWindow(e)?!1:1===e.nodeType&&t?!0:"array"===n||"function"!==n&&(0===t||"number"==typeof t&&t>0&&t-1 in e)}r=x(a),function(e,t){var n,r,i,o,a,s,l,u,c,p,f,d,h,g,m,y,v,b="sizzle"+-new Date,w=e.document,T=0,C=0,N=st(),k=st(),E=st(),S=!1,A=function(e,t){return e===t?(S=!0,0):0},j=typeof t,D=1<<31,L={}.hasOwnProperty,H=[],q=H.pop,_=H.push,M=H.push,O=H.slice,F=H.indexOf||function(e){var t=0,n=this.length;for(;n>t;t++)if(this[t]===e)return t;return-1},B="checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|ismap|loop|multiple|open|readonly|required|scoped",P="[\\x20\\t\\r\\n\\f]",R="(?:\\\\.|[\\w-]|[^\\x00-\\xa0])+",W=R.replace("w","w#"),$="\\["+P+"*("+R+")"+P+"*(?:([*^$|!~]?=)"+P+"*(?:(['\"])((?:\\\\.|[^\\\\])*?)\\3|("+W+")|)|)"+P+"*\\]",I=":("+R+")(?:\\(((['\"])((?:\\\\.|[^\\\\])*?)\\3|((?:\\\\.|[^\\\\()[\\]]|"+$.replace(3,8)+")*)|.*)\\)|)",z=RegExp("^"+P+"+|((?:^|[^\\\\])(?:\\\\.)*)"+P+"+$","g"),X=RegExp("^"+P+"*,"+P+"*"),U=RegExp("^"+P+"*([>+~]|"+P+")"+P+"*"),V=RegExp(P+"*[+~]"),Y=RegExp("="+P+"*([^\\]'\"]*)"+P+"*\\]","g"),J=RegExp(I),G=RegExp("^"+W+"$"),Q={ID:RegExp("^#("+R+")"),CLASS:RegExp("^\\.("+R+")"),TAG:RegExp("^("+R.replace("w","w*")+")"),ATTR:RegExp("^"+$),PSEUDO:RegExp("^"+I),CHILD:RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+P+"*(even|odd|(([+-]|)(\\d*)n|)"+P+"*(?:([+-]|)"+P+"*(\\d+)|))"+P+"*\\)|)","i"),bool:RegExp("^(?:"+B+")$","i"),needsContext:RegExp("^"+P+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+P+"*((?:-\\d)?\\d*)"+P+"*\\)|)(?=[^-]|$)","i")},K=/^[^{]+\{\s*\[native \w/,Z=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,et=/^(?:input|select|textarea|button)$/i,tt=/^h\d$/i,nt=/'|\\/g,rt=RegExp("\\\\([\\da-f]{1,6}"+P+"?|("+P+")|.)","ig"),it=function(e,t,n){var r="0x"+t-65536;return r!==r||n?t:0>r?String.fromCharCode(r+65536):String.fromCharCode(55296|r>>10,56320|1023&r)};try{M.apply(H=O.call(w.childNodes),w.childNodes),H[w.childNodes.length].nodeType}catch(ot){M={apply:H.length?function(e,t){_.apply(e,O.call(t))}:function(e,t){var n=e.length,r=0;while(e[n++]=t[r++]);e.length=n-1}}}function at(e,t,n,i){var o,a,s,l,u,c,d,m,y,x;if((t?t.ownerDocument||t:w)!==f&&p(t),t=t||f,n=n||[],!e||"string"!=typeof e)return n;if(1!==(l=t.nodeType)&&9!==l)return[];if(h&&!i){if(o=Z.exec(e))if(s=o[1]){if(9===l){if(a=t.getElementById(s),!a||!a.parentNode)return n;if(a.id===s)return n.push(a),n}else if(t.ownerDocument&&(a=t.ownerDocument.getElementById(s))&&v(t,a)&&a.id===s)return n.push(a),n}else{if(o[2])return M.apply(n,t.getElementsByTagName(e)),n;if((s=o[3])&&r.getElementsByClassName&&t.getElementsByClassName)return M.apply(n,t.getElementsByClassName(s)),n}if(r.qsa&&(!g||!g.test(e))){if(m=d=b,y=t,x=9===l&&e,1===l&&"object"!==t.nodeName.toLowerCase()){c=mt(e),(d=t.getAttribute("id"))?m=d.replace(nt,"\\$&"):t.setAttribute("id",m),m="[id='"+m+"'] ",u=c.length;while(u--)c[u]=m+yt(c[u]);y=V.test(e)&&t.parentNode||t,x=c.join(",")}if(x)try{return M.apply(n,y.querySelectorAll(x)),n}catch(T){}finally{d||t.removeAttribute("id")}}}return kt(e.replace(z,"$1"),t,n,i)}function st(){var e=[];function t(n,r){return e.push(n+=" ")>o.cacheLength&&delete t[e.shift()],t[n]=r}return t}function lt(e){return e[b]=!0,e}function ut(e){var t=f.createElement("div");try{return!!e(t)}catch(n){return!1}finally{t.parentNode&&t.parentNode.removeChild(t),t=null}}function ct(e,t){var n=e.split("|"),r=e.length;while(r--)o.attrHandle[n[r]]=t}function pt(e,t){var n=t&&e,r=n&&1===e.nodeType&&1===t.nodeType&&(~t.sourceIndex||D)-(~e.sourceIndex||D);if(r)return r;if(n)while(n=n.nextSibling)if(n===t)return-1;return e?1:-1}function ft(e){return function(t){var n=t.nodeName.toLowerCase();return"input"===n&&t.type===e}}function dt(e){return function(t){var n=t.nodeName.toLowerCase();return("input"===n||"button"===n)&&t.type===e}}function ht(e){return lt(function(t){return t=+t,lt(function(n,r){var i,o=e([],n.length,t),a=o.length;while(a--)n[i=o[a]]&&(n[i]=!(r[i]=n[i]))})})}s=at.isXML=function(e){var t=e&&(e.ownerDocument||e).documentElement;return t?"HTML"!==t.nodeName:!1},r=at.support={},p=at.setDocument=function(e){var n=e?e.ownerDocument||e:w,i=n.defaultView;return n!==f&&9===n.nodeType&&n.documentElement?(f=n,d=n.documentElement,h=!s(n),i&&i.attachEvent&&i!==i.top&&i.attachEvent("onbeforeunload",function(){p()}),r.attributes=ut(function(e){return e.className="i",!e.getAttribute("className")}),r.getElementsByTagName=ut(function(e){return e.appendChild(n.createComment("")),!e.getElementsByTagName("*").length}),r.getElementsByClassName=ut(function(e){return e.innerHTML="
",e.firstChild.className="i",2===e.getElementsByClassName("i").length}),r.getById=ut(function(e){return d.appendChild(e).id=b,!n.getElementsByName||!n.getElementsByName(b).length}),r.getById?(o.find.ID=function(e,t){if(typeof t.getElementById!==j&&h){var n=t.getElementById(e);return n&&n.parentNode?[n]:[]}},o.filter.ID=function(e){var t=e.replace(rt,it);return function(e){return e.getAttribute("id")===t}}):(delete o.find.ID,o.filter.ID=function(e){var t=e.replace(rt,it);return function(e){var n=typeof e.getAttributeNode!==j&&e.getAttributeNode("id");return n&&n.value===t}}),o.find.TAG=r.getElementsByTagName?function(e,n){return typeof n.getElementsByTagName!==j?n.getElementsByTagName(e):t}:function(e,t){var n,r=[],i=0,o=t.getElementsByTagName(e);if("*"===e){while(n=o[i++])1===n.nodeType&&r.push(n);return r}return o},o.find.CLASS=r.getElementsByClassName&&function(e,n){return typeof n.getElementsByClassName!==j&&h?n.getElementsByClassName(e):t},m=[],g=[],(r.qsa=K.test(n.querySelectorAll))&&(ut(function(e){e.innerHTML="",e.querySelectorAll("[selected]").length||g.push("\\["+P+"*(?:value|"+B+")"),e.querySelectorAll(":checked").length||g.push(":checked")}),ut(function(e){var t=n.createElement("input");t.setAttribute("type","hidden"),e.appendChild(t).setAttribute("t",""),e.querySelectorAll("[t^='']").length&&g.push("[*^$]="+P+"*(?:''|\"\")"),e.querySelectorAll(":enabled").length||g.push(":enabled",":disabled"),e.querySelectorAll("*,:x"),g.push(",.*:")})),(r.matchesSelector=K.test(y=d.webkitMatchesSelector||d.mozMatchesSelector||d.oMatchesSelector||d.msMatchesSelector))&&ut(function(e){r.disconnectedMatch=y.call(e,"div"),y.call(e,"[s!='']:x"),m.push("!=",I)}),g=g.length&&RegExp(g.join("|")),m=m.length&&RegExp(m.join("|")),v=K.test(d.contains)||d.compareDocumentPosition?function(e,t){var n=9===e.nodeType?e.documentElement:e,r=t&&t.parentNode;return e===r||!(!r||1!==r.nodeType||!(n.contains?n.contains(r):e.compareDocumentPosition&&16&e.compareDocumentPosition(r)))}:function(e,t){if(t)while(t=t.parentNode)if(t===e)return!0;return!1},A=d.compareDocumentPosition?function(e,t){if(e===t)return S=!0,0;var i=t.compareDocumentPosition&&e.compareDocumentPosition&&e.compareDocumentPosition(t);return i?1&i||!r.sortDetached&&t.compareDocumentPosition(e)===i?e===n||v(w,e)?-1:t===n||v(w,t)?1:c?F.call(c,e)-F.call(c,t):0:4&i?-1:1:e.compareDocumentPosition?-1:1}:function(e,t){var r,i=0,o=e.parentNode,a=t.parentNode,s=[e],l=[t];if(e===t)return S=!0,0;if(!o||!a)return e===n?-1:t===n?1:o?-1:a?1:c?F.call(c,e)-F.call(c,t):0;if(o===a)return pt(e,t);r=e;while(r=r.parentNode)s.unshift(r);r=t;while(r=r.parentNode)l.unshift(r);while(s[i]===l[i])i++;return i?pt(s[i],l[i]):s[i]===w?-1:l[i]===w?1:0},n):f},at.matches=function(e,t){return at(e,null,null,t)},at.matchesSelector=function(e,t){if((e.ownerDocument||e)!==f&&p(e),t=t.replace(Y,"='$1']"),!(!r.matchesSelector||!h||m&&m.test(t)||g&&g.test(t)))try{var n=y.call(e,t);if(n||r.disconnectedMatch||e.document&&11!==e.document.nodeType)return n}catch(i){}return at(t,f,null,[e]).length>0},at.contains=function(e,t){return(e.ownerDocument||e)!==f&&p(e),v(e,t)},at.attr=function(e,n){(e.ownerDocument||e)!==f&&p(e);var i=o.attrHandle[n.toLowerCase()],a=i&&L.call(o.attrHandle,n.toLowerCase())?i(e,n,!h):t;return a===t?r.attributes||!h?e.getAttribute(n):(a=e.getAttributeNode(n))&&a.specified?a.value:null:a},at.error=function(e){throw Error("Syntax error, unrecognized expression: "+e)},at.uniqueSort=function(e){var t,n=[],i=0,o=0;if(S=!r.detectDuplicates,c=!r.sortStable&&e.slice(0),e.sort(A),S){while(t=e[o++])t===e[o]&&(i=n.push(o));while(i--)e.splice(n[i],1)}return e},a=at.getText=function(e){var t,n="",r=0,i=e.nodeType;if(i){if(1===i||9===i||11===i){if("string"==typeof e.textContent)return e.textContent;for(e=e.firstChild;e;e=e.nextSibling)n+=a(e)}else if(3===i||4===i)return e.nodeValue}else for(;t=e[r];r++)n+=a(t);return n},o=at.selectors={cacheLength:50,createPseudo:lt,match:Q,attrHandle:{},find:{},relative:{">":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(e){return e[1]=e[1].replace(rt,it),e[3]=(e[4]||e[5]||"").replace(rt,it),"~="===e[2]&&(e[3]=" "+e[3]+" "),e.slice(0,4)},CHILD:function(e){return e[1]=e[1].toLowerCase(),"nth"===e[1].slice(0,3)?(e[3]||at.error(e[0]),e[4]=+(e[4]?e[5]+(e[6]||1):2*("even"===e[3]||"odd"===e[3])),e[5]=+(e[7]+e[8]||"odd"===e[3])):e[3]&&at.error(e[0]),e},PSEUDO:function(e){var n,r=!e[5]&&e[2];return Q.CHILD.test(e[0])?null:(e[3]&&e[4]!==t?e[2]=e[4]:r&&J.test(r)&&(n=mt(r,!0))&&(n=r.indexOf(")",r.length-n)-r.length)&&(e[0]=e[0].slice(0,n),e[2]=r.slice(0,n)),e.slice(0,3))}},filter:{TAG:function(e){var t=e.replace(rt,it).toLowerCase();return"*"===e?function(){return!0}:function(e){return e.nodeName&&e.nodeName.toLowerCase()===t}},CLASS:function(e){var t=N[e+" "];return t||(t=RegExp("(^|"+P+")"+e+"("+P+"|$)"))&&N(e,function(e){return t.test("string"==typeof e.className&&e.className||typeof e.getAttribute!==j&&e.getAttribute("class")||"")})},ATTR:function(e,t,n){return function(r){var i=at.attr(r,e);return null==i?"!="===t:t?(i+="","="===t?i===n:"!="===t?i!==n:"^="===t?n&&0===i.indexOf(n):"*="===t?n&&i.indexOf(n)>-1:"$="===t?n&&i.slice(-n.length)===n:"~="===t?(" "+i+" ").indexOf(n)>-1:"|="===t?i===n||i.slice(0,n.length+1)===n+"-":!1):!0}},CHILD:function(e,t,n,r,i){var o="nth"!==e.slice(0,3),a="last"!==e.slice(-4),s="of-type"===t;return 1===r&&0===i?function(e){return!!e.parentNode}:function(t,n,l){var u,c,p,f,d,h,g=o!==a?"nextSibling":"previousSibling",m=t.parentNode,y=s&&t.nodeName.toLowerCase(),v=!l&&!s;if(m){if(o){while(g){p=t;while(p=p[g])if(s?p.nodeName.toLowerCase()===y:1===p.nodeType)return!1;h=g="only"===e&&!h&&"nextSibling"}return!0}if(h=[a?m.firstChild:m.lastChild],a&&v){c=m[b]||(m[b]={}),u=c[e]||[],d=u[0]===T&&u[1],f=u[0]===T&&u[2],p=d&&m.childNodes[d];while(p=++d&&p&&p[g]||(f=d=0)||h.pop())if(1===p.nodeType&&++f&&p===t){c[e]=[T,d,f];break}}else if(v&&(u=(t[b]||(t[b]={}))[e])&&u[0]===T)f=u[1];else while(p=++d&&p&&p[g]||(f=d=0)||h.pop())if((s?p.nodeName.toLowerCase()===y:1===p.nodeType)&&++f&&(v&&((p[b]||(p[b]={}))[e]=[T,f]),p===t))break;return f-=i,f===r||0===f%r&&f/r>=0}}},PSEUDO:function(e,t){var n,r=o.pseudos[e]||o.setFilters[e.toLowerCase()]||at.error("unsupported pseudo: "+e);return r[b]?r(t):r.length>1?(n=[e,e,"",t],o.setFilters.hasOwnProperty(e.toLowerCase())?lt(function(e,n){var i,o=r(e,t),a=o.length;while(a--)i=F.call(e,o[a]),e[i]=!(n[i]=o[a])}):function(e){return r(e,0,n)}):r}},pseudos:{not:lt(function(e){var t=[],n=[],r=l(e.replace(z,"$1"));return r[b]?lt(function(e,t,n,i){var o,a=r(e,null,i,[]),s=e.length;while(s--)(o=a[s])&&(e[s]=!(t[s]=o))}):function(e,i,o){return t[0]=e,r(t,null,o,n),!n.pop()}}),has:lt(function(e){return function(t){return at(e,t).length>0}}),contains:lt(function(e){return function(t){return(t.textContent||t.innerText||a(t)).indexOf(e)>-1}}),lang:lt(function(e){return G.test(e||"")||at.error("unsupported lang: "+e),e=e.replace(rt,it).toLowerCase(),function(t){var n;do if(n=h?t.lang:t.getAttribute("xml:lang")||t.getAttribute("lang"))return n=n.toLowerCase(),n===e||0===n.indexOf(e+"-");while((t=t.parentNode)&&1===t.nodeType);return!1}}),target:function(t){var n=e.location&&e.location.hash;return n&&n.slice(1)===t.id},root:function(e){return e===d},focus:function(e){return e===f.activeElement&&(!f.hasFocus||f.hasFocus())&&!!(e.type||e.href||~e.tabIndex)},enabled:function(e){return e.disabled===!1},disabled:function(e){return e.disabled===!0},checked:function(e){var t=e.nodeName.toLowerCase();return"input"===t&&!!e.checked||"option"===t&&!!e.selected},selected:function(e){return e.parentNode&&e.parentNode.selectedIndex,e.selected===!0},empty:function(e){for(e=e.firstChild;e;e=e.nextSibling)if(e.nodeName>"@"||3===e.nodeType||4===e.nodeType)return!1;return!0},parent:function(e){return!o.pseudos.empty(e)},header:function(e){return tt.test(e.nodeName)},input:function(e){return et.test(e.nodeName)},button:function(e){var t=e.nodeName.toLowerCase();return"input"===t&&"button"===e.type||"button"===t},text:function(e){var t;return"input"===e.nodeName.toLowerCase()&&"text"===e.type&&(null==(t=e.getAttribute("type"))||t.toLowerCase()===e.type)},first:ht(function(){return[0]}),last:ht(function(e,t){return[t-1]}),eq:ht(function(e,t,n){return[0>n?n+t:n]}),even:ht(function(e,t){var n=0;for(;t>n;n+=2)e.push(n);return e}),odd:ht(function(e,t){var n=1;for(;t>n;n+=2)e.push(n);return e}),lt:ht(function(e,t,n){var r=0>n?n+t:n;for(;--r>=0;)e.push(r);return e}),gt:ht(function(e,t,n){var r=0>n?n+t:n;for(;t>++r;)e.push(r);return e})}},o.pseudos.nth=o.pseudos.eq;for(n in{radio:!0,checkbox:!0,file:!0,password:!0,image:!0})o.pseudos[n]=ft(n);for(n in{submit:!0,reset:!0})o.pseudos[n]=dt(n);function gt(){}gt.prototype=o.filters=o.pseudos,o.setFilters=new gt;function mt(e,t){var n,r,i,a,s,l,u,c=k[e+" "];if(c)return t?0:c.slice(0);s=e,l=[],u=o.preFilter;while(s){(!n||(r=X.exec(s)))&&(r&&(s=s.slice(r[0].length)||s),l.push(i=[])),n=!1,(r=U.exec(s))&&(n=r.shift(),i.push({value:n,type:r[0].replace(z," ")}),s=s.slice(n.length));for(a in o.filter)!(r=Q[a].exec(s))||u[a]&&!(r=u[a](r))||(n=r.shift(),i.push({value:n,type:a,matches:r}),s=s.slice(n.length));if(!n)break}return t?s.length:s?at.error(e):k(e,l).slice(0)}function yt(e){var t=0,n=e.length,r="";for(;n>t;t++)r+=e[t].value;return r}function vt(e,t,n){var r=t.dir,o=n&&"parentNode"===r,a=C++;return t.first?function(t,n,i){while(t=t[r])if(1===t.nodeType||o)return e(t,n,i)}:function(t,n,s){var l,u,c,p=T+" "+a;if(s){while(t=t[r])if((1===t.nodeType||o)&&e(t,n,s))return!0}else while(t=t[r])if(1===t.nodeType||o)if(c=t[b]||(t[b]={}),(u=c[r])&&u[0]===p){if((l=u[1])===!0||l===i)return l===!0}else if(u=c[r]=[p],u[1]=e(t,n,s)||i,u[1]===!0)return!0}}function bt(e){return e.length>1?function(t,n,r){var i=e.length;while(i--)if(!e[i](t,n,r))return!1;return!0}:e[0]}function xt(e,t,n,r,i){var o,a=[],s=0,l=e.length,u=null!=t;for(;l>s;s++)(o=e[s])&&(!n||n(o,r,i))&&(a.push(o),u&&t.push(s));return a}function wt(e,t,n,r,i,o){return r&&!r[b]&&(r=wt(r)),i&&!i[b]&&(i=wt(i,o)),lt(function(o,a,s,l){var u,c,p,f=[],d=[],h=a.length,g=o||Nt(t||"*",s.nodeType?[s]:s,[]),m=!e||!o&&t?g:xt(g,f,e,s,l),y=n?i||(o?e:h||r)?[]:a:m;if(n&&n(m,y,s,l),r){u=xt(y,d),r(u,[],s,l),c=u.length;while(c--)(p=u[c])&&(y[d[c]]=!(m[d[c]]=p))}if(o){if(i||e){if(i){u=[],c=y.length;while(c--)(p=y[c])&&u.push(m[c]=p);i(null,y=[],u,l)}c=y.length;while(c--)(p=y[c])&&(u=i?F.call(o,p):f[c])>-1&&(o[u]=!(a[u]=p))}}else y=xt(y===a?y.splice(h,y.length):y),i?i(null,a,y,l):M.apply(a,y)})}function Tt(e){var t,n,r,i=e.length,a=o.relative[e[0].type],s=a||o.relative[" "],l=a?1:0,c=vt(function(e){return e===t},s,!0),p=vt(function(e){return F.call(t,e)>-1},s,!0),f=[function(e,n,r){return!a&&(r||n!==u)||((t=n).nodeType?c(e,n,r):p(e,n,r))}];for(;i>l;l++)if(n=o.relative[e[l].type])f=[vt(bt(f),n)];else{if(n=o.filter[e[l].type].apply(null,e[l].matches),n[b]){for(r=++l;i>r;r++)if(o.relative[e[r].type])break;return wt(l>1&&bt(f),l>1&&yt(e.slice(0,l-1).concat({value:" "===e[l-2].type?"*":""})).replace(z,"$1"),n,r>l&&Tt(e.slice(l,r)),i>r&&Tt(e=e.slice(r)),i>r&&yt(e))}f.push(n)}return bt(f)}function Ct(e,t){var n=0,r=t.length>0,a=e.length>0,s=function(s,l,c,p,d){var h,g,m,y=[],v=0,b="0",x=s&&[],w=null!=d,C=u,N=s||a&&o.find.TAG("*",d&&l.parentNode||l),k=T+=null==C?1:Math.random()||.1;for(w&&(u=l!==f&&l,i=n);null!=(h=N[b]);b++){if(a&&h){g=0;while(m=e[g++])if(m(h,l,c)){p.push(h);break}w&&(T=k,i=++n)}r&&((h=!m&&h)&&v--,s&&x.push(h))}if(v+=b,r&&b!==v){g=0;while(m=t[g++])m(x,y,l,c);if(s){if(v>0)while(b--)x[b]||y[b]||(y[b]=q.call(p));y=xt(y)}M.apply(p,y),w&&!s&&y.length>0&&v+t.length>1&&at.uniqueSort(p)}return w&&(T=k,u=C),x};return r?lt(s):s}l=at.compile=function(e,t){var n,r=[],i=[],o=E[e+" "];if(!o){t||(t=mt(e)),n=t.length;while(n--)o=Tt(t[n]),o[b]?r.push(o):i.push(o);o=E(e,Ct(i,r))}return o};function Nt(e,t,n){var r=0,i=t.length;for(;i>r;r++)at(e,t[r],n);return n}function kt(e,t,n,i){var a,s,u,c,p,f=mt(e);if(!i&&1===f.length){if(s=f[0]=f[0].slice(0),s.length>2&&"ID"===(u=s[0]).type&&r.getById&&9===t.nodeType&&h&&o.relative[s[1].type]){if(t=(o.find.ID(u.matches[0].replace(rt,it),t)||[])[0],!t)return n;e=e.slice(s.shift().value.length)}a=Q.needsContext.test(e)?0:s.length;while(a--){if(u=s[a],o.relative[c=u.type])break;if((p=o.find[c])&&(i=p(u.matches[0].replace(rt,it),V.test(s[0].type)&&t.parentNode||t))){if(s.splice(a,1),e=i.length&&yt(s),!e)return M.apply(n,i),n;break}}}return l(e,f)(i,t,!h,n,V.test(e)),n}r.sortStable=b.split("").sort(A).join("")===b,r.detectDuplicates=S,p(),r.sortDetached=ut(function(e){return 1&e.compareDocumentPosition(f.createElement("div"))}),ut(function(e){return e.innerHTML="","#"===e.firstChild.getAttribute("href")})||ct("type|href|height|width",function(e,n,r){return r?t:e.getAttribute(n,"type"===n.toLowerCase()?1:2)}),r.attributes&&ut(function(e){return e.innerHTML="",e.firstChild.setAttribute("value",""),""===e.firstChild.getAttribute("value")})||ct("value",function(e,n,r){return r||"input"!==e.nodeName.toLowerCase()?t:e.defaultValue}),ut(function(e){return null==e.getAttribute("disabled")})||ct(B,function(e,n,r){var i;return r?t:(i=e.getAttributeNode(n))&&i.specified?i.value:e[n]===!0?n.toLowerCase():null}),x.find=at,x.expr=at.selectors,x.expr[":"]=x.expr.pseudos,x.unique=at.uniqueSort,x.text=at.getText,x.isXMLDoc=at.isXML,x.contains=at.contains}(e);var O={};function F(e){var t=O[e]={};return x.each(e.match(T)||[],function(e,n){t[n]=!0}),t}x.Callbacks=function(e){e="string"==typeof e?O[e]||F(e):x.extend({},e);var n,r,i,o,a,s,l=[],u=!e.once&&[],c=function(t){for(r=e.memory&&t,i=!0,a=s||0,s=0,o=l.length,n=!0;l&&o>a;a++)if(l[a].apply(t[0],t[1])===!1&&e.stopOnFalse){r=!1;break}n=!1,l&&(u?u.length&&c(u.shift()):r?l=[]:p.disable())},p={add:function(){if(l){var t=l.length;(function i(t){x.each(t,function(t,n){var r=x.type(n);"function"===r?e.unique&&p.has(n)||l.push(n):n&&n.length&&"string"!==r&&i(n)})})(arguments),n?o=l.length:r&&(s=t,c(r))}return this},remove:function(){return l&&x.each(arguments,function(e,t){var r;while((r=x.inArray(t,l,r))>-1)l.splice(r,1),n&&(o>=r&&o--,a>=r&&a--)}),this},has:function(e){return e?x.inArray(e,l)>-1:!(!l||!l.length)},empty:function(){return l=[],o=0,this},disable:function(){return l=u=r=t,this},disabled:function(){return!l},lock:function(){return u=t,r||p.disable(),this},locked:function(){return!u},fireWith:function(e,t){return!l||i&&!u||(t=t||[],t=[e,t.slice?t.slice():t],n?u.push(t):c(t)),this},fire:function(){return p.fireWith(this,arguments),this},fired:function(){return!!i}};return p},x.extend({Deferred:function(e){var t=[["resolve","done",x.Callbacks("once memory"),"resolved"],["reject","fail",x.Callbacks("once memory"),"rejected"],["notify","progress",x.Callbacks("memory")]],n="pending",r={state:function(){return n},always:function(){return i.done(arguments).fail(arguments),this},then:function(){var e=arguments;return x.Deferred(function(n){x.each(t,function(t,o){var a=o[0],s=x.isFunction(e[t])&&e[t];i[o[1]](function(){var e=s&&s.apply(this,arguments);e&&x.isFunction(e.promise)?e.promise().done(n.resolve).fail(n.reject).progress(n.notify):n[a+"With"](this===r?n.promise():this,s?[e]:arguments)})}),e=null}).promise()},promise:function(e){return null!=e?x.extend(e,r):r}},i={};return r.pipe=r.then,x.each(t,function(e,o){var a=o[2],s=o[3];r[o[1]]=a.add,s&&a.add(function(){n=s},t[1^e][2].disable,t[2][2].lock),i[o[0]]=function(){return i[o[0]+"With"](this===i?r:this,arguments),this},i[o[0]+"With"]=a.fireWith}),r.promise(i),e&&e.call(i,i),i},when:function(e){var t=0,n=g.call(arguments),r=n.length,i=1!==r||e&&x.isFunction(e.promise)?r:0,o=1===i?e:x.Deferred(),a=function(e,t,n){return function(r){t[e]=this,n[e]=arguments.length>1?g.call(arguments):r,n===s?o.notifyWith(t,n):--i||o.resolveWith(t,n)}},s,l,u;if(r>1)for(s=Array(r),l=Array(r),u=Array(r);r>t;t++)n[t]&&x.isFunction(n[t].promise)?n[t].promise().done(a(t,u,n)).fail(o.reject).progress(a(t,l,s)):--i;return i||o.resolveWith(u,n),o.promise()}}),x.support=function(t){var n,r,o,s,l,u,c,p,f,d=a.createElement("div");if(d.setAttribute("className","t"),d.innerHTML="
a",n=d.getElementsByTagName("*")||[],r=d.getElementsByTagName("a")[0],!r||!r.style||!n.length)return t;s=a.createElement("select"),u=s.appendChild(a.createElement("option")),o=d.getElementsByTagName("input")[0],r.style.cssText="top:1px;float:left;opacity:.5",t.getSetAttribute="t"!==d.className,t.leadingWhitespace=3===d.firstChild.nodeType,t.tbody=!d.getElementsByTagName("tbody").length,t.htmlSerialize=!!d.getElementsByTagName("link").length,t.style=/top/.test(r.getAttribute("style")),t.hrefNormalized="/a"===r.getAttribute("href"),t.opacity=/^0.5/.test(r.style.opacity),t.cssFloat=!!r.style.cssFloat,t.checkOn=!!o.value,t.optSelected=u.selected,t.enctype=!!a.createElement("form").enctype,t.html5Clone="<:nav>"!==a.createElement("nav").cloneNode(!0).outerHTML,t.inlineBlockNeedsLayout=!1,t.shrinkWrapBlocks=!1,t.pixelPosition=!1,t.deleteExpando=!0,t.noCloneEvent=!0,t.reliableMarginRight=!0,t.boxSizingReliable=!0,o.checked=!0,t.noCloneChecked=o.cloneNode(!0).checked,s.disabled=!0,t.optDisabled=!u.disabled;try{delete d.test}catch(h){t.deleteExpando=!1}o=a.createElement("input"),o.setAttribute("value",""),t.input=""===o.getAttribute("value"),o.value="t",o.setAttribute("type","radio"),t.radioValue="t"===o.value,o.setAttribute("checked","t"),o.setAttribute("name","t"),l=a.createDocumentFragment(),l.appendChild(o),t.appendChecked=o.checked,t.checkClone=l.cloneNode(!0).cloneNode(!0).lastChild.checked,d.attachEvent&&(d.attachEvent("onclick",function(){t.noCloneEvent=!1}),d.cloneNode(!0).click());for(f in{submit:!0,change:!0,focusin:!0})d.setAttribute(c="on"+f,"t"),t[f+"Bubbles"]=c in e||d.attributes[c].expando===!1;d.style.backgroundClip="content-box",d.cloneNode(!0).style.backgroundClip="",t.clearCloneStyle="content-box"===d.style.backgroundClip;for(f in x(t))break;return t.ownLast="0"!==f,x(function(){var n,r,o,s="padding:0;margin:0;border:0;display:block;box-sizing:content-box;-moz-box-sizing:content-box;-webkit-box-sizing:content-box;",l=a.getElementsByTagName("body")[0];l&&(n=a.createElement("div"),n.style.cssText="border:0;width:0;height:0;position:absolute;top:0;left:-9999px;margin-top:1px",l.appendChild(n).appendChild(d),d.innerHTML="
t
",o=d.getElementsByTagName("td"),o[0].style.cssText="padding:0;margin:0;border:0;display:none",p=0===o[0].offsetHeight,o[0].style.display="",o[1].style.display="none",t.reliableHiddenOffsets=p&&0===o[0].offsetHeight,d.innerHTML="",d.style.cssText="box-sizing:border-box;-moz-box-sizing:border-box;-webkit-box-sizing:border-box;padding:1px;border:1px;display:block;width:4px;margin-top:1%;position:absolute;top:1%;",x.swap(l,null!=l.style.zoom?{zoom:1}:{},function(){t.boxSizing=4===d.offsetWidth}),e.getComputedStyle&&(t.pixelPosition="1%"!==(e.getComputedStyle(d,null)||{}).top,t.boxSizingReliable="4px"===(e.getComputedStyle(d,null)||{width:"4px"}).width,r=d.appendChild(a.createElement("div")),r.style.cssText=d.style.cssText=s,r.style.marginRight=r.style.width="0",d.style.width="1px",t.reliableMarginRight=!parseFloat((e.getComputedStyle(r,null)||{}).marginRight)),typeof d.style.zoom!==i&&(d.innerHTML="",d.style.cssText=s+"width:1px;padding:1px;display:inline;zoom:1",t.inlineBlockNeedsLayout=3===d.offsetWidth,d.style.display="block",d.innerHTML="
",d.firstChild.style.width="5px",t.shrinkWrapBlocks=3!==d.offsetWidth,t.inlineBlockNeedsLayout&&(l.style.zoom=1)),l.removeChild(n),n=d=o=r=null)}),n=s=l=u=r=o=null,t +}({});var B=/(?:\{[\s\S]*\}|\[[\s\S]*\])$/,P=/([A-Z])/g;function R(e,n,r,i){if(x.acceptData(e)){var o,a,s=x.expando,l=e.nodeType,u=l?x.cache:e,c=l?e[s]:e[s]&&s;if(c&&u[c]&&(i||u[c].data)||r!==t||"string"!=typeof n)return c||(c=l?e[s]=p.pop()||x.guid++:s),u[c]||(u[c]=l?{}:{toJSON:x.noop}),("object"==typeof n||"function"==typeof n)&&(i?u[c]=x.extend(u[c],n):u[c].data=x.extend(u[c].data,n)),a=u[c],i||(a.data||(a.data={}),a=a.data),r!==t&&(a[x.camelCase(n)]=r),"string"==typeof n?(o=a[n],null==o&&(o=a[x.camelCase(n)])):o=a,o}}function W(e,t,n){if(x.acceptData(e)){var r,i,o=e.nodeType,a=o?x.cache:e,s=o?e[x.expando]:x.expando;if(a[s]){if(t&&(r=n?a[s]:a[s].data)){x.isArray(t)?t=t.concat(x.map(t,x.camelCase)):t in r?t=[t]:(t=x.camelCase(t),t=t in r?[t]:t.split(" ")),i=t.length;while(i--)delete r[t[i]];if(n?!I(r):!x.isEmptyObject(r))return}(n||(delete a[s].data,I(a[s])))&&(o?x.cleanData([e],!0):x.support.deleteExpando||a!=a.window?delete a[s]:a[s]=null)}}}x.extend({cache:{},noData:{applet:!0,embed:!0,object:"clsid:D27CDB6E-AE6D-11cf-96B8-444553540000"},hasData:function(e){return e=e.nodeType?x.cache[e[x.expando]]:e[x.expando],!!e&&!I(e)},data:function(e,t,n){return R(e,t,n)},removeData:function(e,t){return W(e,t)},_data:function(e,t,n){return R(e,t,n,!0)},_removeData:function(e,t){return W(e,t,!0)},acceptData:function(e){if(e.nodeType&&1!==e.nodeType&&9!==e.nodeType)return!1;var t=e.nodeName&&x.noData[e.nodeName.toLowerCase()];return!t||t!==!0&&e.getAttribute("classid")===t}}),x.fn.extend({data:function(e,n){var r,i,o=null,a=0,s=this[0];if(e===t){if(this.length&&(o=x.data(s),1===s.nodeType&&!x._data(s,"parsedAttrs"))){for(r=s.attributes;r.length>a;a++)i=r[a].name,0===i.indexOf("data-")&&(i=x.camelCase(i.slice(5)),$(s,i,o[i]));x._data(s,"parsedAttrs",!0)}return o}return"object"==typeof e?this.each(function(){x.data(this,e)}):arguments.length>1?this.each(function(){x.data(this,e,n)}):s?$(s,e,x.data(s,e)):null},removeData:function(e){return this.each(function(){x.removeData(this,e)})}});function $(e,n,r){if(r===t&&1===e.nodeType){var i="data-"+n.replace(P,"-$1").toLowerCase();if(r=e.getAttribute(i),"string"==typeof r){try{r="true"===r?!0:"false"===r?!1:"null"===r?null:+r+""===r?+r:B.test(r)?x.parseJSON(r):r}catch(o){}x.data(e,n,r)}else r=t}return r}function I(e){var t;for(t in e)if(("data"!==t||!x.isEmptyObject(e[t]))&&"toJSON"!==t)return!1;return!0}x.extend({queue:function(e,n,r){var i;return e?(n=(n||"fx")+"queue",i=x._data(e,n),r&&(!i||x.isArray(r)?i=x._data(e,n,x.makeArray(r)):i.push(r)),i||[]):t},dequeue:function(e,t){t=t||"fx";var n=x.queue(e,t),r=n.length,i=n.shift(),o=x._queueHooks(e,t),a=function(){x.dequeue(e,t)};"inprogress"===i&&(i=n.shift(),r--),i&&("fx"===t&&n.unshift("inprogress"),delete o.stop,i.call(e,a,o)),!r&&o&&o.empty.fire()},_queueHooks:function(e,t){var n=t+"queueHooks";return x._data(e,n)||x._data(e,n,{empty:x.Callbacks("once memory").add(function(){x._removeData(e,t+"queue"),x._removeData(e,n)})})}}),x.fn.extend({queue:function(e,n){var r=2;return"string"!=typeof e&&(n=e,e="fx",r--),r>arguments.length?x.queue(this[0],e):n===t?this:this.each(function(){var t=x.queue(this,e,n);x._queueHooks(this,e),"fx"===e&&"inprogress"!==t[0]&&x.dequeue(this,e)})},dequeue:function(e){return this.each(function(){x.dequeue(this,e)})},delay:function(e,t){return e=x.fx?x.fx.speeds[e]||e:e,t=t||"fx",this.queue(t,function(t,n){var r=setTimeout(t,e);n.stop=function(){clearTimeout(r)}})},clearQueue:function(e){return this.queue(e||"fx",[])},promise:function(e,n){var r,i=1,o=x.Deferred(),a=this,s=this.length,l=function(){--i||o.resolveWith(a,[a])};"string"!=typeof e&&(n=e,e=t),e=e||"fx";while(s--)r=x._data(a[s],e+"queueHooks"),r&&r.empty&&(i++,r.empty.add(l));return l(),o.promise(n)}});var z,X,U=/[\t\r\n\f]/g,V=/\r/g,Y=/^(?:input|select|textarea|button|object)$/i,J=/^(?:a|area)$/i,G=/^(?:checked|selected)$/i,Q=x.support.getSetAttribute,K=x.support.input;x.fn.extend({attr:function(e,t){return x.access(this,x.attr,e,t,arguments.length>1)},removeAttr:function(e){return this.each(function(){x.removeAttr(this,e)})},prop:function(e,t){return x.access(this,x.prop,e,t,arguments.length>1)},removeProp:function(e){return e=x.propFix[e]||e,this.each(function(){try{this[e]=t,delete this[e]}catch(n){}})},addClass:function(e){var t,n,r,i,o,a=0,s=this.length,l="string"==typeof e&&e;if(x.isFunction(e))return this.each(function(t){x(this).addClass(e.call(this,t,this.className))});if(l)for(t=(e||"").match(T)||[];s>a;a++)if(n=this[a],r=1===n.nodeType&&(n.className?(" "+n.className+" ").replace(U," "):" ")){o=0;while(i=t[o++])0>r.indexOf(" "+i+" ")&&(r+=i+" ");n.className=x.trim(r)}return this},removeClass:function(e){var t,n,r,i,o,a=0,s=this.length,l=0===arguments.length||"string"==typeof e&&e;if(x.isFunction(e))return this.each(function(t){x(this).removeClass(e.call(this,t,this.className))});if(l)for(t=(e||"").match(T)||[];s>a;a++)if(n=this[a],r=1===n.nodeType&&(n.className?(" "+n.className+" ").replace(U," "):"")){o=0;while(i=t[o++])while(r.indexOf(" "+i+" ")>=0)r=r.replace(" "+i+" "," ");n.className=e?x.trim(r):""}return this},toggleClass:function(e,t){var n=typeof e;return"boolean"==typeof t&&"string"===n?t?this.addClass(e):this.removeClass(e):x.isFunction(e)?this.each(function(n){x(this).toggleClass(e.call(this,n,this.className,t),t)}):this.each(function(){if("string"===n){var t,r=0,o=x(this),a=e.match(T)||[];while(t=a[r++])o.hasClass(t)?o.removeClass(t):o.addClass(t)}else(n===i||"boolean"===n)&&(this.className&&x._data(this,"__className__",this.className),this.className=this.className||e===!1?"":x._data(this,"__className__")||"")})},hasClass:function(e){var t=" "+e+" ",n=0,r=this.length;for(;r>n;n++)if(1===this[n].nodeType&&(" "+this[n].className+" ").replace(U," ").indexOf(t)>=0)return!0;return!1},val:function(e){var n,r,i,o=this[0];{if(arguments.length)return i=x.isFunction(e),this.each(function(n){var o;1===this.nodeType&&(o=i?e.call(this,n,x(this).val()):e,null==o?o="":"number"==typeof o?o+="":x.isArray(o)&&(o=x.map(o,function(e){return null==e?"":e+""})),r=x.valHooks[this.type]||x.valHooks[this.nodeName.toLowerCase()],r&&"set"in r&&r.set(this,o,"value")!==t||(this.value=o))});if(o)return r=x.valHooks[o.type]||x.valHooks[o.nodeName.toLowerCase()],r&&"get"in r&&(n=r.get(o,"value"))!==t?n:(n=o.value,"string"==typeof n?n.replace(V,""):null==n?"":n)}}}),x.extend({valHooks:{option:{get:function(e){var t=x.find.attr(e,"value");return null!=t?t:e.text}},select:{get:function(e){var t,n,r=e.options,i=e.selectedIndex,o="select-one"===e.type||0>i,a=o?null:[],s=o?i+1:r.length,l=0>i?s:o?i:0;for(;s>l;l++)if(n=r[l],!(!n.selected&&l!==i||(x.support.optDisabled?n.disabled:null!==n.getAttribute("disabled"))||n.parentNode.disabled&&x.nodeName(n.parentNode,"optgroup"))){if(t=x(n).val(),o)return t;a.push(t)}return a},set:function(e,t){var n,r,i=e.options,o=x.makeArray(t),a=i.length;while(a--)r=i[a],(r.selected=x.inArray(x(r).val(),o)>=0)&&(n=!0);return n||(e.selectedIndex=-1),o}}},attr:function(e,n,r){var o,a,s=e.nodeType;if(e&&3!==s&&8!==s&&2!==s)return typeof e.getAttribute===i?x.prop(e,n,r):(1===s&&x.isXMLDoc(e)||(n=n.toLowerCase(),o=x.attrHooks[n]||(x.expr.match.bool.test(n)?X:z)),r===t?o&&"get"in o&&null!==(a=o.get(e,n))?a:(a=x.find.attr(e,n),null==a?t:a):null!==r?o&&"set"in o&&(a=o.set(e,r,n))!==t?a:(e.setAttribute(n,r+""),r):(x.removeAttr(e,n),t))},removeAttr:function(e,t){var n,r,i=0,o=t&&t.match(T);if(o&&1===e.nodeType)while(n=o[i++])r=x.propFix[n]||n,x.expr.match.bool.test(n)?K&&Q||!G.test(n)?e[r]=!1:e[x.camelCase("default-"+n)]=e[r]=!1:x.attr(e,n,""),e.removeAttribute(Q?n:r)},attrHooks:{type:{set:function(e,t){if(!x.support.radioValue&&"radio"===t&&x.nodeName(e,"input")){var n=e.value;return e.setAttribute("type",t),n&&(e.value=n),t}}}},propFix:{"for":"htmlFor","class":"className"},prop:function(e,n,r){var i,o,a,s=e.nodeType;if(e&&3!==s&&8!==s&&2!==s)return a=1!==s||!x.isXMLDoc(e),a&&(n=x.propFix[n]||n,o=x.propHooks[n]),r!==t?o&&"set"in o&&(i=o.set(e,r,n))!==t?i:e[n]=r:o&&"get"in o&&null!==(i=o.get(e,n))?i:e[n]},propHooks:{tabIndex:{get:function(e){var t=x.find.attr(e,"tabindex");return t?parseInt(t,10):Y.test(e.nodeName)||J.test(e.nodeName)&&e.href?0:-1}}}}),X={set:function(e,t,n){return t===!1?x.removeAttr(e,n):K&&Q||!G.test(n)?e.setAttribute(!Q&&x.propFix[n]||n,n):e[x.camelCase("default-"+n)]=e[n]=!0,n}},x.each(x.expr.match.bool.source.match(/\w+/g),function(e,n){var r=x.expr.attrHandle[n]||x.find.attr;x.expr.attrHandle[n]=K&&Q||!G.test(n)?function(e,n,i){var o=x.expr.attrHandle[n],a=i?t:(x.expr.attrHandle[n]=t)!=r(e,n,i)?n.toLowerCase():null;return x.expr.attrHandle[n]=o,a}:function(e,n,r){return r?t:e[x.camelCase("default-"+n)]?n.toLowerCase():null}}),K&&Q||(x.attrHooks.value={set:function(e,n,r){return x.nodeName(e,"input")?(e.defaultValue=n,t):z&&z.set(e,n,r)}}),Q||(z={set:function(e,n,r){var i=e.getAttributeNode(r);return i||e.setAttributeNode(i=e.ownerDocument.createAttribute(r)),i.value=n+="","value"===r||n===e.getAttribute(r)?n:t}},x.expr.attrHandle.id=x.expr.attrHandle.name=x.expr.attrHandle.coords=function(e,n,r){var i;return r?t:(i=e.getAttributeNode(n))&&""!==i.value?i.value:null},x.valHooks.button={get:function(e,n){var r=e.getAttributeNode(n);return r&&r.specified?r.value:t},set:z.set},x.attrHooks.contenteditable={set:function(e,t,n){z.set(e,""===t?!1:t,n)}},x.each(["width","height"],function(e,n){x.attrHooks[n]={set:function(e,r){return""===r?(e.setAttribute(n,"auto"),r):t}}})),x.support.hrefNormalized||x.each(["href","src"],function(e,t){x.propHooks[t]={get:function(e){return e.getAttribute(t,4)}}}),x.support.style||(x.attrHooks.style={get:function(e){return e.style.cssText||t},set:function(e,t){return e.style.cssText=t+""}}),x.support.optSelected||(x.propHooks.selected={get:function(e){var t=e.parentNode;return t&&(t.selectedIndex,t.parentNode&&t.parentNode.selectedIndex),null}}),x.each(["tabIndex","readOnly","maxLength","cellSpacing","cellPadding","rowSpan","colSpan","useMap","frameBorder","contentEditable"],function(){x.propFix[this.toLowerCase()]=this}),x.support.enctype||(x.propFix.enctype="encoding"),x.each(["radio","checkbox"],function(){x.valHooks[this]={set:function(e,n){return x.isArray(n)?e.checked=x.inArray(x(e).val(),n)>=0:t}},x.support.checkOn||(x.valHooks[this].get=function(e){return null===e.getAttribute("value")?"on":e.value})});var Z=/^(?:input|select|textarea)$/i,et=/^key/,tt=/^(?:mouse|contextmenu)|click/,nt=/^(?:focusinfocus|focusoutblur)$/,rt=/^([^.]*)(?:\.(.+)|)$/;function it(){return!0}function ot(){return!1}function at(){try{return a.activeElement}catch(e){}}x.event={global:{},add:function(e,n,r,o,a){var s,l,u,c,p,f,d,h,g,m,y,v=x._data(e);if(v){r.handler&&(c=r,r=c.handler,a=c.selector),r.guid||(r.guid=x.guid++),(l=v.events)||(l=v.events={}),(f=v.handle)||(f=v.handle=function(e){return typeof x===i||e&&x.event.triggered===e.type?t:x.event.dispatch.apply(f.elem,arguments)},f.elem=e),n=(n||"").match(T)||[""],u=n.length;while(u--)s=rt.exec(n[u])||[],g=y=s[1],m=(s[2]||"").split(".").sort(),g&&(p=x.event.special[g]||{},g=(a?p.delegateType:p.bindType)||g,p=x.event.special[g]||{},d=x.extend({type:g,origType:y,data:o,handler:r,guid:r.guid,selector:a,needsContext:a&&x.expr.match.needsContext.test(a),namespace:m.join(".")},c),(h=l[g])||(h=l[g]=[],h.delegateCount=0,p.setup&&p.setup.call(e,o,m,f)!==!1||(e.addEventListener?e.addEventListener(g,f,!1):e.attachEvent&&e.attachEvent("on"+g,f))),p.add&&(p.add.call(e,d),d.handler.guid||(d.handler.guid=r.guid)),a?h.splice(h.delegateCount++,0,d):h.push(d),x.event.global[g]=!0);e=null}},remove:function(e,t,n,r,i){var o,a,s,l,u,c,p,f,d,h,g,m=x.hasData(e)&&x._data(e);if(m&&(c=m.events)){t=(t||"").match(T)||[""],u=t.length;while(u--)if(s=rt.exec(t[u])||[],d=g=s[1],h=(s[2]||"").split(".").sort(),d){p=x.event.special[d]||{},d=(r?p.delegateType:p.bindType)||d,f=c[d]||[],s=s[2]&&RegExp("(^|\\.)"+h.join("\\.(?:.*\\.|)")+"(\\.|$)"),l=o=f.length;while(o--)a=f[o],!i&&g!==a.origType||n&&n.guid!==a.guid||s&&!s.test(a.namespace)||r&&r!==a.selector&&("**"!==r||!a.selector)||(f.splice(o,1),a.selector&&f.delegateCount--,p.remove&&p.remove.call(e,a));l&&!f.length&&(p.teardown&&p.teardown.call(e,h,m.handle)!==!1||x.removeEvent(e,d,m.handle),delete c[d])}else for(d in c)x.event.remove(e,d+t[u],n,r,!0);x.isEmptyObject(c)&&(delete m.handle,x._removeData(e,"events"))}},trigger:function(n,r,i,o){var s,l,u,c,p,f,d,h=[i||a],g=v.call(n,"type")?n.type:n,m=v.call(n,"namespace")?n.namespace.split("."):[];if(u=f=i=i||a,3!==i.nodeType&&8!==i.nodeType&&!nt.test(g+x.event.triggered)&&(g.indexOf(".")>=0&&(m=g.split("."),g=m.shift(),m.sort()),l=0>g.indexOf(":")&&"on"+g,n=n[x.expando]?n:new x.Event(g,"object"==typeof n&&n),n.isTrigger=o?2:3,n.namespace=m.join("."),n.namespace_re=n.namespace?RegExp("(^|\\.)"+m.join("\\.(?:.*\\.|)")+"(\\.|$)"):null,n.result=t,n.target||(n.target=i),r=null==r?[n]:x.makeArray(r,[n]),p=x.event.special[g]||{},o||!p.trigger||p.trigger.apply(i,r)!==!1)){if(!o&&!p.noBubble&&!x.isWindow(i)){for(c=p.delegateType||g,nt.test(c+g)||(u=u.parentNode);u;u=u.parentNode)h.push(u),f=u;f===(i.ownerDocument||a)&&h.push(f.defaultView||f.parentWindow||e)}d=0;while((u=h[d++])&&!n.isPropagationStopped())n.type=d>1?c:p.bindType||g,s=(x._data(u,"events")||{})[n.type]&&x._data(u,"handle"),s&&s.apply(u,r),s=l&&u[l],s&&x.acceptData(u)&&s.apply&&s.apply(u,r)===!1&&n.preventDefault();if(n.type=g,!o&&!n.isDefaultPrevented()&&(!p._default||p._default.apply(h.pop(),r)===!1)&&x.acceptData(i)&&l&&i[g]&&!x.isWindow(i)){f=i[l],f&&(i[l]=null),x.event.triggered=g;try{i[g]()}catch(y){}x.event.triggered=t,f&&(i[l]=f)}return n.result}},dispatch:function(e){e=x.event.fix(e);var n,r,i,o,a,s=[],l=g.call(arguments),u=(x._data(this,"events")||{})[e.type]||[],c=x.event.special[e.type]||{};if(l[0]=e,e.delegateTarget=this,!c.preDispatch||c.preDispatch.call(this,e)!==!1){s=x.event.handlers.call(this,e,u),n=0;while((o=s[n++])&&!e.isPropagationStopped()){e.currentTarget=o.elem,a=0;while((i=o.handlers[a++])&&!e.isImmediatePropagationStopped())(!e.namespace_re||e.namespace_re.test(i.namespace))&&(e.handleObj=i,e.data=i.data,r=((x.event.special[i.origType]||{}).handle||i.handler).apply(o.elem,l),r!==t&&(e.result=r)===!1&&(e.preventDefault(),e.stopPropagation()))}return c.postDispatch&&c.postDispatch.call(this,e),e.result}},handlers:function(e,n){var r,i,o,a,s=[],l=n.delegateCount,u=e.target;if(l&&u.nodeType&&(!e.button||"click"!==e.type))for(;u!=this;u=u.parentNode||this)if(1===u.nodeType&&(u.disabled!==!0||"click"!==e.type)){for(o=[],a=0;l>a;a++)i=n[a],r=i.selector+" ",o[r]===t&&(o[r]=i.needsContext?x(r,this).index(u)>=0:x.find(r,this,null,[u]).length),o[r]&&o.push(i);o.length&&s.push({elem:u,handlers:o})}return n.length>l&&s.push({elem:this,handlers:n.slice(l)}),s},fix:function(e){if(e[x.expando])return e;var t,n,r,i=e.type,o=e,s=this.fixHooks[i];s||(this.fixHooks[i]=s=tt.test(i)?this.mouseHooks:et.test(i)?this.keyHooks:{}),r=s.props?this.props.concat(s.props):this.props,e=new x.Event(o),t=r.length;while(t--)n=r[t],e[n]=o[n];return e.target||(e.target=o.srcElement||a),3===e.target.nodeType&&(e.target=e.target.parentNode),e.metaKey=!!e.metaKey,s.filter?s.filter(e,o):e},props:"altKey bubbles cancelable ctrlKey currentTarget eventPhase metaKey relatedTarget shiftKey target timeStamp view which".split(" "),fixHooks:{},keyHooks:{props:"char charCode key keyCode".split(" "),filter:function(e,t){return null==e.which&&(e.which=null!=t.charCode?t.charCode:t.keyCode),e}},mouseHooks:{props:"button buttons clientX clientY fromElement offsetX offsetY pageX pageY screenX screenY toElement".split(" "),filter:function(e,n){var r,i,o,s=n.button,l=n.fromElement;return null==e.pageX&&null!=n.clientX&&(i=e.target.ownerDocument||a,o=i.documentElement,r=i.body,e.pageX=n.clientX+(o&&o.scrollLeft||r&&r.scrollLeft||0)-(o&&o.clientLeft||r&&r.clientLeft||0),e.pageY=n.clientY+(o&&o.scrollTop||r&&r.scrollTop||0)-(o&&o.clientTop||r&&r.clientTop||0)),!e.relatedTarget&&l&&(e.relatedTarget=l===e.target?n.toElement:l),e.which||s===t||(e.which=1&s?1:2&s?3:4&s?2:0),e}},special:{load:{noBubble:!0},focus:{trigger:function(){if(this!==at()&&this.focus)try{return this.focus(),!1}catch(e){}},delegateType:"focusin"},blur:{trigger:function(){return this===at()&&this.blur?(this.blur(),!1):t},delegateType:"focusout"},click:{trigger:function(){return x.nodeName(this,"input")&&"checkbox"===this.type&&this.click?(this.click(),!1):t},_default:function(e){return x.nodeName(e.target,"a")}},beforeunload:{postDispatch:function(e){e.result!==t&&(e.originalEvent.returnValue=e.result)}}},simulate:function(e,t,n,r){var i=x.extend(new x.Event,n,{type:e,isSimulated:!0,originalEvent:{}});r?x.event.trigger(i,null,t):x.event.dispatch.call(t,i),i.isDefaultPrevented()&&n.preventDefault()}},x.removeEvent=a.removeEventListener?function(e,t,n){e.removeEventListener&&e.removeEventListener(t,n,!1)}:function(e,t,n){var r="on"+t;e.detachEvent&&(typeof e[r]===i&&(e[r]=null),e.detachEvent(r,n))},x.Event=function(e,n){return this instanceof x.Event?(e&&e.type?(this.originalEvent=e,this.type=e.type,this.isDefaultPrevented=e.defaultPrevented||e.returnValue===!1||e.getPreventDefault&&e.getPreventDefault()?it:ot):this.type=e,n&&x.extend(this,n),this.timeStamp=e&&e.timeStamp||x.now(),this[x.expando]=!0,t):new x.Event(e,n)},x.Event.prototype={isDefaultPrevented:ot,isPropagationStopped:ot,isImmediatePropagationStopped:ot,preventDefault:function(){var e=this.originalEvent;this.isDefaultPrevented=it,e&&(e.preventDefault?e.preventDefault():e.returnValue=!1)},stopPropagation:function(){var e=this.originalEvent;this.isPropagationStopped=it,e&&(e.stopPropagation&&e.stopPropagation(),e.cancelBubble=!0)},stopImmediatePropagation:function(){this.isImmediatePropagationStopped=it,this.stopPropagation()}},x.each({mouseenter:"mouseover",mouseleave:"mouseout"},function(e,t){x.event.special[e]={delegateType:t,bindType:t,handle:function(e){var n,r=this,i=e.relatedTarget,o=e.handleObj;return(!i||i!==r&&!x.contains(r,i))&&(e.type=o.origType,n=o.handler.apply(this,arguments),e.type=t),n}}}),x.support.submitBubbles||(x.event.special.submit={setup:function(){return x.nodeName(this,"form")?!1:(x.event.add(this,"click._submit keypress._submit",function(e){var n=e.target,r=x.nodeName(n,"input")||x.nodeName(n,"button")?n.form:t;r&&!x._data(r,"submitBubbles")&&(x.event.add(r,"submit._submit",function(e){e._submit_bubble=!0}),x._data(r,"submitBubbles",!0))}),t)},postDispatch:function(e){e._submit_bubble&&(delete e._submit_bubble,this.parentNode&&!e.isTrigger&&x.event.simulate("submit",this.parentNode,e,!0))},teardown:function(){return x.nodeName(this,"form")?!1:(x.event.remove(this,"._submit"),t)}}),x.support.changeBubbles||(x.event.special.change={setup:function(){return Z.test(this.nodeName)?(("checkbox"===this.type||"radio"===this.type)&&(x.event.add(this,"propertychange._change",function(e){"checked"===e.originalEvent.propertyName&&(this._just_changed=!0)}),x.event.add(this,"click._change",function(e){this._just_changed&&!e.isTrigger&&(this._just_changed=!1),x.event.simulate("change",this,e,!0)})),!1):(x.event.add(this,"beforeactivate._change",function(e){var t=e.target;Z.test(t.nodeName)&&!x._data(t,"changeBubbles")&&(x.event.add(t,"change._change",function(e){!this.parentNode||e.isSimulated||e.isTrigger||x.event.simulate("change",this.parentNode,e,!0)}),x._data(t,"changeBubbles",!0))}),t)},handle:function(e){var n=e.target;return this!==n||e.isSimulated||e.isTrigger||"radio"!==n.type&&"checkbox"!==n.type?e.handleObj.handler.apply(this,arguments):t},teardown:function(){return x.event.remove(this,"._change"),!Z.test(this.nodeName)}}),x.support.focusinBubbles||x.each({focus:"focusin",blur:"focusout"},function(e,t){var n=0,r=function(e){x.event.simulate(t,e.target,x.event.fix(e),!0)};x.event.special[t]={setup:function(){0===n++&&a.addEventListener(e,r,!0)},teardown:function(){0===--n&&a.removeEventListener(e,r,!0)}}}),x.fn.extend({on:function(e,n,r,i,o){var a,s;if("object"==typeof e){"string"!=typeof n&&(r=r||n,n=t);for(a in e)this.on(a,n,r,e[a],o);return this}if(null==r&&null==i?(i=n,r=n=t):null==i&&("string"==typeof n?(i=r,r=t):(i=r,r=n,n=t)),i===!1)i=ot;else if(!i)return this;return 1===o&&(s=i,i=function(e){return x().off(e),s.apply(this,arguments)},i.guid=s.guid||(s.guid=x.guid++)),this.each(function(){x.event.add(this,e,i,r,n)})},one:function(e,t,n,r){return this.on(e,t,n,r,1)},off:function(e,n,r){var i,o;if(e&&e.preventDefault&&e.handleObj)return i=e.handleObj,x(e.delegateTarget).off(i.namespace?i.origType+"."+i.namespace:i.origType,i.selector,i.handler),this;if("object"==typeof e){for(o in e)this.off(o,n,e[o]);return this}return(n===!1||"function"==typeof n)&&(r=n,n=t),r===!1&&(r=ot),this.each(function(){x.event.remove(this,e,r,n)})},trigger:function(e,t){return this.each(function(){x.event.trigger(e,t,this)})},triggerHandler:function(e,n){var r=this[0];return r?x.event.trigger(e,n,r,!0):t}});var st=/^.[^:#\[\.,]*$/,lt=/^(?:parents|prev(?:Until|All))/,ut=x.expr.match.needsContext,ct={children:!0,contents:!0,next:!0,prev:!0};x.fn.extend({find:function(e){var t,n=[],r=this,i=r.length;if("string"!=typeof e)return this.pushStack(x(e).filter(function(){for(t=0;i>t;t++)if(x.contains(r[t],this))return!0}));for(t=0;i>t;t++)x.find(e,r[t],n);return n=this.pushStack(i>1?x.unique(n):n),n.selector=this.selector?this.selector+" "+e:e,n},has:function(e){var t,n=x(e,this),r=n.length;return this.filter(function(){for(t=0;r>t;t++)if(x.contains(this,n[t]))return!0})},not:function(e){return this.pushStack(ft(this,e||[],!0))},filter:function(e){return this.pushStack(ft(this,e||[],!1))},is:function(e){return!!ft(this,"string"==typeof e&&ut.test(e)?x(e):e||[],!1).length},closest:function(e,t){var n,r=0,i=this.length,o=[],a=ut.test(e)||"string"!=typeof e?x(e,t||this.context):0;for(;i>r;r++)for(n=this[r];n&&n!==t;n=n.parentNode)if(11>n.nodeType&&(a?a.index(n)>-1:1===n.nodeType&&x.find.matchesSelector(n,e))){n=o.push(n);break}return this.pushStack(o.length>1?x.unique(o):o)},index:function(e){return e?"string"==typeof e?x.inArray(this[0],x(e)):x.inArray(e.jquery?e[0]:e,this):this[0]&&this[0].parentNode?this.first().prevAll().length:-1},add:function(e,t){var n="string"==typeof e?x(e,t):x.makeArray(e&&e.nodeType?[e]:e),r=x.merge(this.get(),n);return this.pushStack(x.unique(r))},addBack:function(e){return this.add(null==e?this.prevObject:this.prevObject.filter(e))}});function pt(e,t){do e=e[t];while(e&&1!==e.nodeType);return e}x.each({parent:function(e){var t=e.parentNode;return t&&11!==t.nodeType?t:null},parents:function(e){return x.dir(e,"parentNode")},parentsUntil:function(e,t,n){return x.dir(e,"parentNode",n)},next:function(e){return pt(e,"nextSibling")},prev:function(e){return pt(e,"previousSibling")},nextAll:function(e){return x.dir(e,"nextSibling")},prevAll:function(e){return x.dir(e,"previousSibling")},nextUntil:function(e,t,n){return x.dir(e,"nextSibling",n)},prevUntil:function(e,t,n){return x.dir(e,"previousSibling",n)},siblings:function(e){return x.sibling((e.parentNode||{}).firstChild,e)},children:function(e){return x.sibling(e.firstChild)},contents:function(e){return x.nodeName(e,"iframe")?e.contentDocument||e.contentWindow.document:x.merge([],e.childNodes)}},function(e,t){x.fn[e]=function(n,r){var i=x.map(this,t,n);return"Until"!==e.slice(-5)&&(r=n),r&&"string"==typeof r&&(i=x.filter(r,i)),this.length>1&&(ct[e]||(i=x.unique(i)),lt.test(e)&&(i=i.reverse())),this.pushStack(i)}}),x.extend({filter:function(e,t,n){var r=t[0];return n&&(e=":not("+e+")"),1===t.length&&1===r.nodeType?x.find.matchesSelector(r,e)?[r]:[]:x.find.matches(e,x.grep(t,function(e){return 1===e.nodeType}))},dir:function(e,n,r){var i=[],o=e[n];while(o&&9!==o.nodeType&&(r===t||1!==o.nodeType||!x(o).is(r)))1===o.nodeType&&i.push(o),o=o[n];return i},sibling:function(e,t){var n=[];for(;e;e=e.nextSibling)1===e.nodeType&&e!==t&&n.push(e);return n}});function ft(e,t,n){if(x.isFunction(t))return x.grep(e,function(e,r){return!!t.call(e,r,e)!==n});if(t.nodeType)return x.grep(e,function(e){return e===t!==n});if("string"==typeof t){if(st.test(t))return x.filter(t,e,n);t=x.filter(t,e)}return x.grep(e,function(e){return x.inArray(e,t)>=0!==n})}function dt(e){var t=ht.split("|"),n=e.createDocumentFragment();if(n.createElement)while(t.length)n.createElement(t.pop());return n}var ht="abbr|article|aside|audio|bdi|canvas|data|datalist|details|figcaption|figure|footer|header|hgroup|mark|meter|nav|output|progress|section|summary|time|video",gt=/ jQuery\d+="(?:null|\d+)"/g,mt=RegExp("<(?:"+ht+")[\\s/>]","i"),yt=/^\s+/,vt=/<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/gi,bt=/<([\w:]+)/,xt=/\s*$/g,At={option:[1,""],legend:[1,"
","
"],area:[1,"",""],param:[1,"",""],thead:[1,"","
"],tr:[2,"","
"],col:[2,"","
"],td:[3,"","
"],_default:x.support.htmlSerialize?[0,"",""]:[1,"X
","
"]},jt=dt(a),Dt=jt.appendChild(a.createElement("div"));At.optgroup=At.option,At.tbody=At.tfoot=At.colgroup=At.caption=At.thead,At.th=At.td,x.fn.extend({text:function(e){return x.access(this,function(e){return e===t?x.text(this):this.empty().append((this[0]&&this[0].ownerDocument||a).createTextNode(e))},null,e,arguments.length)},append:function(){return this.domManip(arguments,function(e){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var t=Lt(this,e);t.appendChild(e)}})},prepend:function(){return this.domManip(arguments,function(e){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var t=Lt(this,e);t.insertBefore(e,t.firstChild)}})},before:function(){return this.domManip(arguments,function(e){this.parentNode&&this.parentNode.insertBefore(e,this)})},after:function(){return this.domManip(arguments,function(e){this.parentNode&&this.parentNode.insertBefore(e,this.nextSibling)})},remove:function(e,t){var n,r=e?x.filter(e,this):this,i=0;for(;null!=(n=r[i]);i++)t||1!==n.nodeType||x.cleanData(Ft(n)),n.parentNode&&(t&&x.contains(n.ownerDocument,n)&&_t(Ft(n,"script")),n.parentNode.removeChild(n));return this},empty:function(){var e,t=0;for(;null!=(e=this[t]);t++){1===e.nodeType&&x.cleanData(Ft(e,!1));while(e.firstChild)e.removeChild(e.firstChild);e.options&&x.nodeName(e,"select")&&(e.options.length=0)}return this},clone:function(e,t){return e=null==e?!1:e,t=null==t?e:t,this.map(function(){return x.clone(this,e,t)})},html:function(e){return x.access(this,function(e){var n=this[0]||{},r=0,i=this.length;if(e===t)return 1===n.nodeType?n.innerHTML.replace(gt,""):t;if(!("string"!=typeof e||Tt.test(e)||!x.support.htmlSerialize&&mt.test(e)||!x.support.leadingWhitespace&&yt.test(e)||At[(bt.exec(e)||["",""])[1].toLowerCase()])){e=e.replace(vt,"<$1>");try{for(;i>r;r++)n=this[r]||{},1===n.nodeType&&(x.cleanData(Ft(n,!1)),n.innerHTML=e);n=0}catch(o){}}n&&this.empty().append(e)},null,e,arguments.length)},replaceWith:function(){var e=x.map(this,function(e){return[e.nextSibling,e.parentNode]}),t=0;return this.domManip(arguments,function(n){var r=e[t++],i=e[t++];i&&(r&&r.parentNode!==i&&(r=this.nextSibling),x(this).remove(),i.insertBefore(n,r))},!0),t?this:this.remove()},detach:function(e){return this.remove(e,!0)},domManip:function(e,t,n){e=d.apply([],e);var r,i,o,a,s,l,u=0,c=this.length,p=this,f=c-1,h=e[0],g=x.isFunction(h);if(g||!(1>=c||"string"!=typeof h||x.support.checkClone)&&Nt.test(h))return this.each(function(r){var i=p.eq(r);g&&(e[0]=h.call(this,r,i.html())),i.domManip(e,t,n)});if(c&&(l=x.buildFragment(e,this[0].ownerDocument,!1,!n&&this),r=l.firstChild,1===l.childNodes.length&&(l=r),r)){for(a=x.map(Ft(l,"script"),Ht),o=a.length;c>u;u++)i=l,u!==f&&(i=x.clone(i,!0,!0),o&&x.merge(a,Ft(i,"script"))),t.call(this[u],i,u);if(o)for(s=a[a.length-1].ownerDocument,x.map(a,qt),u=0;o>u;u++)i=a[u],kt.test(i.type||"")&&!x._data(i,"globalEval")&&x.contains(s,i)&&(i.src?x._evalUrl(i.src):x.globalEval((i.text||i.textContent||i.innerHTML||"").replace(St,"")));l=r=null}return this}});function Lt(e,t){return x.nodeName(e,"table")&&x.nodeName(1===t.nodeType?t:t.firstChild,"tr")?e.getElementsByTagName("tbody")[0]||e.appendChild(e.ownerDocument.createElement("tbody")):e}function Ht(e){return e.type=(null!==x.find.attr(e,"type"))+"/"+e.type,e}function qt(e){var t=Et.exec(e.type);return t?e.type=t[1]:e.removeAttribute("type"),e}function _t(e,t){var n,r=0;for(;null!=(n=e[r]);r++)x._data(n,"globalEval",!t||x._data(t[r],"globalEval"))}function Mt(e,t){if(1===t.nodeType&&x.hasData(e)){var n,r,i,o=x._data(e),a=x._data(t,o),s=o.events;if(s){delete a.handle,a.events={};for(n in s)for(r=0,i=s[n].length;i>r;r++)x.event.add(t,n,s[n][r])}a.data&&(a.data=x.extend({},a.data))}}function Ot(e,t){var n,r,i;if(1===t.nodeType){if(n=t.nodeName.toLowerCase(),!x.support.noCloneEvent&&t[x.expando]){i=x._data(t);for(r in i.events)x.removeEvent(t,r,i.handle);t.removeAttribute(x.expando)}"script"===n&&t.text!==e.text?(Ht(t).text=e.text,qt(t)):"object"===n?(t.parentNode&&(t.outerHTML=e.outerHTML),x.support.html5Clone&&e.innerHTML&&!x.trim(t.innerHTML)&&(t.innerHTML=e.innerHTML)):"input"===n&&Ct.test(e.type)?(t.defaultChecked=t.checked=e.checked,t.value!==e.value&&(t.value=e.value)):"option"===n?t.defaultSelected=t.selected=e.defaultSelected:("input"===n||"textarea"===n)&&(t.defaultValue=e.defaultValue)}}x.each({appendTo:"append",prependTo:"prepend",insertBefore:"before",insertAfter:"after",replaceAll:"replaceWith"},function(e,t){x.fn[e]=function(e){var n,r=0,i=[],o=x(e),a=o.length-1;for(;a>=r;r++)n=r===a?this:this.clone(!0),x(o[r])[t](n),h.apply(i,n.get());return this.pushStack(i)}});function Ft(e,n){var r,o,a=0,s=typeof e.getElementsByTagName!==i?e.getElementsByTagName(n||"*"):typeof e.querySelectorAll!==i?e.querySelectorAll(n||"*"):t;if(!s)for(s=[],r=e.childNodes||e;null!=(o=r[a]);a++)!n||x.nodeName(o,n)?s.push(o):x.merge(s,Ft(o,n));return n===t||n&&x.nodeName(e,n)?x.merge([e],s):s}function Bt(e){Ct.test(e.type)&&(e.defaultChecked=e.checked)}x.extend({clone:function(e,t,n){var r,i,o,a,s,l=x.contains(e.ownerDocument,e);if(x.support.html5Clone||x.isXMLDoc(e)||!mt.test("<"+e.nodeName+">")?o=e.cloneNode(!0):(Dt.innerHTML=e.outerHTML,Dt.removeChild(o=Dt.firstChild)),!(x.support.noCloneEvent&&x.support.noCloneChecked||1!==e.nodeType&&11!==e.nodeType||x.isXMLDoc(e)))for(r=Ft(o),s=Ft(e),a=0;null!=(i=s[a]);++a)r[a]&&Ot(i,r[a]);if(t)if(n)for(s=s||Ft(e),r=r||Ft(o),a=0;null!=(i=s[a]);a++)Mt(i,r[a]);else Mt(e,o);return r=Ft(o,"script"),r.length>0&&_t(r,!l&&Ft(e,"script")),r=s=i=null,o},buildFragment:function(e,t,n,r){var i,o,a,s,l,u,c,p=e.length,f=dt(t),d=[],h=0;for(;p>h;h++)if(o=e[h],o||0===o)if("object"===x.type(o))x.merge(d,o.nodeType?[o]:o);else if(wt.test(o)){s=s||f.appendChild(t.createElement("div")),l=(bt.exec(o)||["",""])[1].toLowerCase(),c=At[l]||At._default,s.innerHTML=c[1]+o.replace(vt,"<$1>")+c[2],i=c[0];while(i--)s=s.lastChild;if(!x.support.leadingWhitespace&&yt.test(o)&&d.push(t.createTextNode(yt.exec(o)[0])),!x.support.tbody){o="table"!==l||xt.test(o)?""!==c[1]||xt.test(o)?0:s:s.firstChild,i=o&&o.childNodes.length;while(i--)x.nodeName(u=o.childNodes[i],"tbody")&&!u.childNodes.length&&o.removeChild(u)}x.merge(d,s.childNodes),s.textContent="";while(s.firstChild)s.removeChild(s.firstChild);s=f.lastChild}else d.push(t.createTextNode(o));s&&f.removeChild(s),x.support.appendChecked||x.grep(Ft(d,"input"),Bt),h=0;while(o=d[h++])if((!r||-1===x.inArray(o,r))&&(a=x.contains(o.ownerDocument,o),s=Ft(f.appendChild(o),"script"),a&&_t(s),n)){i=0;while(o=s[i++])kt.test(o.type||"")&&n.push(o)}return s=null,f},cleanData:function(e,t){var n,r,o,a,s=0,l=x.expando,u=x.cache,c=x.support.deleteExpando,f=x.event.special;for(;null!=(n=e[s]);s++)if((t||x.acceptData(n))&&(o=n[l],a=o&&u[o])){if(a.events)for(r in a.events)f[r]?x.event.remove(n,r):x.removeEvent(n,r,a.handle); +u[o]&&(delete u[o],c?delete n[l]:typeof n.removeAttribute!==i?n.removeAttribute(l):n[l]=null,p.push(o))}},_evalUrl:function(e){return x.ajax({url:e,type:"GET",dataType:"script",async:!1,global:!1,"throws":!0})}}),x.fn.extend({wrapAll:function(e){if(x.isFunction(e))return this.each(function(t){x(this).wrapAll(e.call(this,t))});if(this[0]){var t=x(e,this[0].ownerDocument).eq(0).clone(!0);this[0].parentNode&&t.insertBefore(this[0]),t.map(function(){var e=this;while(e.firstChild&&1===e.firstChild.nodeType)e=e.firstChild;return e}).append(this)}return this},wrapInner:function(e){return x.isFunction(e)?this.each(function(t){x(this).wrapInner(e.call(this,t))}):this.each(function(){var t=x(this),n=t.contents();n.length?n.wrapAll(e):t.append(e)})},wrap:function(e){var t=x.isFunction(e);return this.each(function(n){x(this).wrapAll(t?e.call(this,n):e)})},unwrap:function(){return this.parent().each(function(){x.nodeName(this,"body")||x(this).replaceWith(this.childNodes)}).end()}});var Pt,Rt,Wt,$t=/alpha\([^)]*\)/i,It=/opacity\s*=\s*([^)]*)/,zt=/^(top|right|bottom|left)$/,Xt=/^(none|table(?!-c[ea]).+)/,Ut=/^margin/,Vt=RegExp("^("+w+")(.*)$","i"),Yt=RegExp("^("+w+")(?!px)[a-z%]+$","i"),Jt=RegExp("^([+-])=("+w+")","i"),Gt={BODY:"block"},Qt={position:"absolute",visibility:"hidden",display:"block"},Kt={letterSpacing:0,fontWeight:400},Zt=["Top","Right","Bottom","Left"],en=["Webkit","O","Moz","ms"];function tn(e,t){if(t in e)return t;var n=t.charAt(0).toUpperCase()+t.slice(1),r=t,i=en.length;while(i--)if(t=en[i]+n,t in e)return t;return r}function nn(e,t){return e=t||e,"none"===x.css(e,"display")||!x.contains(e.ownerDocument,e)}function rn(e,t){var n,r,i,o=[],a=0,s=e.length;for(;s>a;a++)r=e[a],r.style&&(o[a]=x._data(r,"olddisplay"),n=r.style.display,t?(o[a]||"none"!==n||(r.style.display=""),""===r.style.display&&nn(r)&&(o[a]=x._data(r,"olddisplay",ln(r.nodeName)))):o[a]||(i=nn(r),(n&&"none"!==n||!i)&&x._data(r,"olddisplay",i?n:x.css(r,"display"))));for(a=0;s>a;a++)r=e[a],r.style&&(t&&"none"!==r.style.display&&""!==r.style.display||(r.style.display=t?o[a]||"":"none"));return e}x.fn.extend({css:function(e,n){return x.access(this,function(e,n,r){var i,o,a={},s=0;if(x.isArray(n)){for(o=Rt(e),i=n.length;i>s;s++)a[n[s]]=x.css(e,n[s],!1,o);return a}return r!==t?x.style(e,n,r):x.css(e,n)},e,n,arguments.length>1)},show:function(){return rn(this,!0)},hide:function(){return rn(this)},toggle:function(e){return"boolean"==typeof e?e?this.show():this.hide():this.each(function(){nn(this)?x(this).show():x(this).hide()})}}),x.extend({cssHooks:{opacity:{get:function(e,t){if(t){var n=Wt(e,"opacity");return""===n?"1":n}}}},cssNumber:{columnCount:!0,fillOpacity:!0,fontWeight:!0,lineHeight:!0,opacity:!0,order:!0,orphans:!0,widows:!0,zIndex:!0,zoom:!0},cssProps:{"float":x.support.cssFloat?"cssFloat":"styleFloat"},style:function(e,n,r,i){if(e&&3!==e.nodeType&&8!==e.nodeType&&e.style){var o,a,s,l=x.camelCase(n),u=e.style;if(n=x.cssProps[l]||(x.cssProps[l]=tn(u,l)),s=x.cssHooks[n]||x.cssHooks[l],r===t)return s&&"get"in s&&(o=s.get(e,!1,i))!==t?o:u[n];if(a=typeof r,"string"===a&&(o=Jt.exec(r))&&(r=(o[1]+1)*o[2]+parseFloat(x.css(e,n)),a="number"),!(null==r||"number"===a&&isNaN(r)||("number"!==a||x.cssNumber[l]||(r+="px"),x.support.clearCloneStyle||""!==r||0!==n.indexOf("background")||(u[n]="inherit"),s&&"set"in s&&(r=s.set(e,r,i))===t)))try{u[n]=r}catch(c){}}},css:function(e,n,r,i){var o,a,s,l=x.camelCase(n);return n=x.cssProps[l]||(x.cssProps[l]=tn(e.style,l)),s=x.cssHooks[n]||x.cssHooks[l],s&&"get"in s&&(a=s.get(e,!0,r)),a===t&&(a=Wt(e,n,i)),"normal"===a&&n in Kt&&(a=Kt[n]),""===r||r?(o=parseFloat(a),r===!0||x.isNumeric(o)?o||0:a):a}}),e.getComputedStyle?(Rt=function(t){return e.getComputedStyle(t,null)},Wt=function(e,n,r){var i,o,a,s=r||Rt(e),l=s?s.getPropertyValue(n)||s[n]:t,u=e.style;return s&&(""!==l||x.contains(e.ownerDocument,e)||(l=x.style(e,n)),Yt.test(l)&&Ut.test(n)&&(i=u.width,o=u.minWidth,a=u.maxWidth,u.minWidth=u.maxWidth=u.width=l,l=s.width,u.width=i,u.minWidth=o,u.maxWidth=a)),l}):a.documentElement.currentStyle&&(Rt=function(e){return e.currentStyle},Wt=function(e,n,r){var i,o,a,s=r||Rt(e),l=s?s[n]:t,u=e.style;return null==l&&u&&u[n]&&(l=u[n]),Yt.test(l)&&!zt.test(n)&&(i=u.left,o=e.runtimeStyle,a=o&&o.left,a&&(o.left=e.currentStyle.left),u.left="fontSize"===n?"1em":l,l=u.pixelLeft+"px",u.left=i,a&&(o.left=a)),""===l?"auto":l});function on(e,t,n){var r=Vt.exec(t);return r?Math.max(0,r[1]-(n||0))+(r[2]||"px"):t}function an(e,t,n,r,i){var o=n===(r?"border":"content")?4:"width"===t?1:0,a=0;for(;4>o;o+=2)"margin"===n&&(a+=x.css(e,n+Zt[o],!0,i)),r?("content"===n&&(a-=x.css(e,"padding"+Zt[o],!0,i)),"margin"!==n&&(a-=x.css(e,"border"+Zt[o]+"Width",!0,i))):(a+=x.css(e,"padding"+Zt[o],!0,i),"padding"!==n&&(a+=x.css(e,"border"+Zt[o]+"Width",!0,i)));return a}function sn(e,t,n){var r=!0,i="width"===t?e.offsetWidth:e.offsetHeight,o=Rt(e),a=x.support.boxSizing&&"border-box"===x.css(e,"boxSizing",!1,o);if(0>=i||null==i){if(i=Wt(e,t,o),(0>i||null==i)&&(i=e.style[t]),Yt.test(i))return i;r=a&&(x.support.boxSizingReliable||i===e.style[t]),i=parseFloat(i)||0}return i+an(e,t,n||(a?"border":"content"),r,o)+"px"}function ln(e){var t=a,n=Gt[e];return n||(n=un(e,t),"none"!==n&&n||(Pt=(Pt||x("