From 5a4b955ee193d3082b2a1b12e700fd5d341b0579 Mon Sep 17 00:00:00 2001 From: Anton Tarasov Date: Wed, 15 Apr 2020 19:11:34 +0300 Subject: [PATCH] IDEA-237780 address JBR-2282 [jcef] update to JCEF/80.0.4+g74f7b0c+chromium-80.0.3987.122 GitOrigin-RevId: 3c21bc5d20d3b6a0eb5585a418160b2e1564181e --- .idea/libraries/jcef.xml | 6 +- .../src/com/intellij/ui/jcef/JBCefApp.java | 21 +++- .../com/intellij/ui/jcef/JBCefBrowser.java | 28 +++-- .../src/com/intellij/ui/jcef/JBCefClient.java | 61 +++------- .../ui/jcef/JBCefFileSchemeHandler.java | 36 ++++-- .../ui/jcef/JBCefHtmlStringSchemeHandler.java | 104 ++++++++++++++++++ 6 files changed, 178 insertions(+), 78 deletions(-) create mode 100644 platform/platform-api/src/com/intellij/ui/jcef/JBCefHtmlStringSchemeHandler.java diff --git a/.idea/libraries/jcef.xml b/.idea/libraries/jcef.xml index d881e46aca0b..f7133d54c689 100644 --- a/.idea/libraries/jcef.xml +++ b/.idea/libraries/jcef.xml @@ -1,12 +1,12 @@ - + - + - + \ No newline at end of file diff --git a/platform/platform-api/src/com/intellij/ui/jcef/JBCefApp.java b/platform/platform-api/src/com/intellij/ui/jcef/JBCefApp.java index 2b58a92dde82..bb96de044ff6 100644 --- a/platform/platform-api/src/com/intellij/ui/jcef/JBCefApp.java +++ b/platform/platform-api/src/com/intellij/ui/jcef/JBCefApp.java @@ -34,6 +34,7 @@ import java.util.Locale; import java.util.concurrent.atomic.AtomicBoolean; import static com.intellij.ui.jcef.JBCefFileSchemeHandler.FILE_SCHEME_NAME; +import static com.intellij.ui.jcef.JBCefHtmlStringSchemeHandler.HTML_STRING_SCHEME_NAME; /** * A wrapper over {@link CefApp}. @@ -60,7 +61,7 @@ public abstract class JBCefApp { private static final List ourSchemeHandlerFactoryList = Collections.synchronizedList(new ArrayList<>()); private JBCefApp() { - CefApp.startup(); + CefApp.startup(ArrayUtil.EMPTY_STRING_ARRAY); //noinspection AbstractMethodCallInConstructor CefAppConfig config = getCefAppConfig(); config.mySettings.windowless_rendering_enabled = false; @@ -176,19 +177,20 @@ public abstract class JBCefApp { @Override protected CefAppConfig getCefAppConfig() { String ALT_CEF_FRAMEWORK_DIR = System.getenv("ALT_CEF_FRAMEWORK_DIR"); - String ALT_CEF_BROWSER_SUBPROCESS = System.getenv("ALT_CEF_BROWSER_SUBPROCESS"); - if (ALT_CEF_FRAMEWORK_DIR == null || ALT_CEF_BROWSER_SUBPROCESS == null) { + String ALT_CEF_HELPER_APP_DIR = System.getenv("ALT_CEF_HELPER_APP_DIR"); + if (ALT_CEF_FRAMEWORK_DIR == null || ALT_CEF_HELPER_APP_DIR == null) { String CONTENTS_PATH = System.getProperty("java.home") + "/.."; if (ALT_CEF_FRAMEWORK_DIR == null) { ALT_CEF_FRAMEWORK_DIR = CONTENTS_PATH + "/Frameworks/Chromium Embedded Framework.framework"; } - if (ALT_CEF_BROWSER_SUBPROCESS == null) { - ALT_CEF_BROWSER_SUBPROCESS = CONTENTS_PATH + "/Helpers/jcef Helper.app/Contents/MacOS/jcef Helper"; + if (ALT_CEF_HELPER_APP_DIR == null) { + ALT_CEF_HELPER_APP_DIR = CONTENTS_PATH + "/Frameworks/jcef Helper.app"; } } return new CefAppConfig(new CefSettings(), new String[] { "--framework-dir-path=" + normalize(ALT_CEF_FRAMEWORK_DIR), - "--browser-subprocess-path=" + normalize(ALT_CEF_BROWSER_SUBPROCESS), + "--browser-subprocess-path=" + normalize(ALT_CEF_HELPER_APP_DIR + "/Contents/MacOS/jcef Helper"), + "--main-bundle-path=" + normalize(ALT_CEF_HELPER_APP_DIR), "--disable-in-process-stack-traces", "--use-mock-keychain" }); @@ -296,6 +298,13 @@ public abstract class JBCefApp { return FILE_SCHEME_NAME.equals(schemeName) ? new JBCefFileSchemeHandler(browser, frame) : null; } }); + + getInstance().myCefApp.registerSchemeHandlerFactory(HTML_STRING_SCHEME_NAME, "", new CefSchemeHandlerFactory() { + @Override + public CefResourceHandler create(CefBrowser browser, CefFrame frame, String schemeName, CefRequest request) { + return HTML_STRING_SCHEME_NAME.equals(schemeName) ? new JBCefHtmlStringSchemeHandler(browser, frame) : null; + } + }); } } } diff --git a/platform/platform-api/src/com/intellij/ui/jcef/JBCefBrowser.java b/platform/platform-api/src/com/intellij/ui/jcef/JBCefBrowser.java index 4dc31d0e6882..e9b42798bf7a 100644 --- a/platform/platform-api/src/com/intellij/ui/jcef/JBCefBrowser.java +++ b/platform/platform-api/src/com/intellij/ui/jcef/JBCefBrowser.java @@ -11,7 +11,6 @@ import org.cef.callback.CefMenuModel; import org.cef.handler.*; import org.cef.network.CefCookieManager; import org.jetbrains.annotations.ApiStatus; -import org.jetbrains.annotations.Contract; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -35,6 +34,9 @@ import static org.cef.callback.CefMenuModel.MenuId.MENU_ID_USER_LAST; public class JBCefBrowser implements JBCefDisposable { private static final String BLANK_URI = "about:blank"; + private static final String JBCEFBROWSER_INSTANCE_PROP = "JBCefBrowser.instance"; + private static final String HTML_STRING_URL = JBCefHtmlStringSchemeHandler.HTML_STRING_SCHEME_NAME + "://jbcefbrowser/loadstring"; + @NotNull private final JBCefClient myCefClient; @NotNull private final MyComponent myComponent; @NotNull private final CefBrowser myCefBrowser; @@ -74,7 +76,7 @@ public class JBCefBrowser implements JBCefDisposable { SwingUtilities.invokeLater( myHtml == null ? () -> browser.loadURL(myUrl) : - () -> browser.loadString(myHtml, myUrl)); + () -> loadString(browser, myHtml, myUrl)); } } @@ -105,7 +107,9 @@ public class JBCefBrowser implements JBCefDisposable { myCefBrowser = cefBrowser != null ? cefBrowser : myCefClient.getCefClient().createBrowser(url != null ? url : BLANK_URI, false, false); - myComponent.add(myCefBrowser.getUIComponent(), BorderLayout.CENTER); + JComponent uiComp = (JComponent)myCefBrowser.getUIComponent(); + uiComp.putClientProperty(JBCEFBROWSER_INSTANCE_PROP, this); + myComponent.add(uiComp, BorderLayout.CENTER); if (cefBrowser == null) { myCefClient.addLifeSpanHandler(myLifeSpanHandler = new CefLifeSpanHandlerAdapter() { @@ -165,7 +169,7 @@ public class JBCefBrowser implements JBCefDisposable { */ public void loadHTML(@NotNull String html, @NotNull String url) { if (myIsCefBrowserCreated) { - myCefBrowser.loadString(html, url); + loadString(myCefBrowser, html, url); } else { myLoadDeferrer = LoadDeferrer.htmlDeferrer(html, url); @@ -179,6 +183,12 @@ public class JBCefBrowser implements JBCefDisposable { loadHTML(html, BLANK_URI); } + private static void loadString(CefBrowser cefBrowser, String html, String url) { + String loadUrl = HTML_STRING_URL + "?url=" + url; + JBCefHtmlStringSchemeHandler.registerRequest(cefBrowser, html, loadUrl); + cefBrowser.loadURL(loadUrl); + } + /** * Creates a browser with default {@link JBCefClient}. The default client is disposed with this browser and may not be used with other browsers. */ @@ -289,11 +299,11 @@ public class JBCefBrowser implements JBCefDisposable { return myDisposeHelper.isDisposed(); } - @SuppressWarnings("unused") - @Contract("null->null; !null->!null") - protected static JBCefBrowser getJBCefBrowser(CefBrowser browser) { - if (browser == null) return null; - return ((MyComponent)browser.getUIComponent().getParent()).getJBCefBrowser(); + /** + * Returns {@code JBCefBrowser} instance associated with this {@code CefBrowser}. + */ + public static JBCefBrowser getJBCefBrowser(@NotNull CefBrowser browser) { + return (JBCefBrowser)((JComponent)browser.getUIComponent()).getClientProperty(JBCEFBROWSER_INSTANCE_PROP); } protected class DefaultCefContextMenuHandler extends CefContextMenuHandlerAdapter { diff --git a/platform/platform-api/src/com/intellij/ui/jcef/JBCefClient.java b/platform/platform-api/src/com/intellij/ui/jcef/JBCefClient.java index 5bf901158331..5b6624207e1b 100644 --- a/platform/platform-api/src/com/intellij/ui/jcef/JBCefClient.java +++ b/platform/platform-api/src/com/intellij/ui/jcef/JBCefClient.java @@ -12,10 +12,7 @@ import org.cef.browser.CefFrame; import org.cef.callback.*; import org.cef.handler.*; 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; import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -420,56 +417,31 @@ public class JBCefClient implements JBCefDisposable { } @Override - public boolean onBeforeResourceLoad(CefBrowser browser, CefFrame frame, CefRequest request) { + public CefResourceRequestHandler getResourceRequestHandler(CefBrowser browser, + CefFrame frame, + CefRequest request, + boolean isNavigation, + boolean isDownload, + String requestInitiator, + BoolRef disableDefaultHandling) + { return myRequestHandler.handleNotNull(browser, handler -> { - return handler.onBeforeResourceLoad(browser, frame, request); - }); - } - - @Override - public CefResourceHandler getResourceHandler(CefBrowser browser, CefFrame frame, CefRequest request) { - return myRequestHandler.handle(browser, handler -> { - return handler.getResourceHandler(browser, frame, request); - }); - } - - @Override - public void onResourceRedirect(CefBrowser browser, CefFrame frame, CefRequest request, CefResponse response, StringRef new_url) { - myRequestHandler.handle(browser, handler -> { - handler.onResourceRedirect(browser, frame, request, response, new_url); - }); - } - - @Override - public boolean onResourceResponse(CefBrowser browser, CefFrame frame, CefRequest request, CefResponse response) { - return myRequestHandler.handleNotNull(browser, handler -> { - return handler.onResourceResponse(browser, frame, request, response); - }); - } - - @Override - public void onResourceLoadComplete(CefBrowser browser, - CefFrame frame, - CefRequest request, - CefResponse response, - CefURLRequest.Status status, - long receivedContentLength) { - myRequestHandler.handle(browser, handler -> { - handler.onResourceLoadComplete(browser, frame, request, response, status, receivedContentLength); + return handler.getResourceRequestHandler(browser, frame, request, isNavigation, isDownload, requestInitiator, disableDefaultHandling); }); } @Override public boolean getAuthCredentials(CefBrowser browser, - CefFrame frame, + String origin_url, boolean isProxy, String host, int port, String realm, String scheme, - CefAuthCallback callback) { + CefAuthCallback callback) + { return myRequestHandler.handleNotNull(browser, handler -> { - return handler.getAuthCredentials(browser, frame, isProxy, host, port, realm, scheme, callback); + return handler.getAuthCredentials(browser, origin_url, isProxy, host, port, realm, scheme, callback); }); } @@ -480,13 +452,6 @@ public class JBCefClient implements JBCefDisposable { }); } - @Override - public void onProtocolExecution(CefBrowser browser, String url, BoolRef allow_os_execution) { - myRequestHandler.handle(browser, handler -> { - handler.onProtocolExecution(browser, url, allow_os_execution); - }); - } - @Override public boolean onCertificateError(CefBrowser browser, CefLoadHandler.ErrorCode cert_error, diff --git a/platform/platform-api/src/com/intellij/ui/jcef/JBCefFileSchemeHandler.java b/platform/platform-api/src/com/intellij/ui/jcef/JBCefFileSchemeHandler.java index bc445ae87f59..8c00b21f6a52 100644 --- a/platform/platform-api/src/com/intellij/ui/jcef/JBCefFileSchemeHandler.java +++ b/platform/platform-api/src/com/intellij/ui/jcef/JBCefFileSchemeHandler.java @@ -30,15 +30,16 @@ class JBCefFileSchemeHandler extends CefResourceHandlerAdapter { public static final String FILE_SCHEME_NAME = "file"; - @NotNull private final CefBrowser myBrowser; - @NotNull private final CefFrame myFrame; + //@NotNull private final CefBrowser myBrowser; + //@NotNull private final CefFrame myFrame; @Nullable private Path myPath; @Nullable private InputStream myInputStream; + @SuppressWarnings("unused") JBCefFileSchemeHandler(@NotNull CefBrowser browser, @NotNull CefFrame frame) { - myBrowser = browser; - myFrame = frame; + //myBrowser = browser; + //myFrame = frame; } @Override @@ -78,14 +79,27 @@ class JBCefFileSchemeHandler extends CefResourceHandlerAdapter { } response.setStatus(myInputStream != null ? 200 : 404); } - @Override public boolean readResponse(byte@NotNull[] data_out, int bytes_to_read, IntRef bytes_read, CefCallback callback) { + boolean inProgress = readResponse(myInputStream, data_out, bytes_to_read, bytes_read, callback); + if (!inProgress) { + myPath = null; + myInputStream = null; + } + return inProgress; + } + + static boolean readResponse(@Nullable InputStream inputStream, + byte@NotNull[] data_out, + int bytes_to_read, + IntRef bytes_read, + @SuppressWarnings("unused") CefCallback callback) + { try { - int availableSize = myInputStream != null ? myInputStream.available() : 0; + int availableSize = inputStream != null ? inputStream.available() : 0; if (availableSize > 0) { int bytesToRead = Math.min(bytes_to_read, availableSize); - bytesToRead = myInputStream.read(data_out, 0, bytesToRead); + bytesToRead = inputStream.read(data_out, 0, bytesToRead); bytes_read.set(bytesToRead); return true; } @@ -94,20 +108,18 @@ class JBCefFileSchemeHandler extends CefResourceHandlerAdapter { LOG.error(e); } bytes_read.set(0); - myPath = null; - if (myInputStream != null) { + if (inputStream != null) { try { - myInputStream.close(); + inputStream.close(); } catch (IOException e) { LOG.error(e); } - myInputStream = null; } return false; } - private static boolean checkAccessAllowed(Path path) { + private static boolean checkAccessAllowed(@SuppressWarnings("unused") Path path) { // tav: todo Ask the user or query the settings for JCEF FS access. return true; } diff --git a/platform/platform-api/src/com/intellij/ui/jcef/JBCefHtmlStringSchemeHandler.java b/platform/platform-api/src/com/intellij/ui/jcef/JBCefHtmlStringSchemeHandler.java new file mode 100644 index 000000000000..5079d3f2b2ae --- /dev/null +++ b/platform/platform-api/src/com/intellij/ui/jcef/JBCefHtmlStringSchemeHandler.java @@ -0,0 +1,104 @@ +// Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. +package com.intellij.ui.jcef; + +import com.intellij.openapi.Disposable; +import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.util.Disposer; +import org.cef.browser.CefBrowser; +import org.cef.browser.CefFrame; +import org.cef.callback.CefCallback; +import org.cef.handler.CefLoadHandler; +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; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.io.ByteArrayInputStream; +import java.io.InputStream; +import java.nio.charset.Charset; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +/** + * A custom scheme handler for serving the {@link JBCefBrowser#loadHTML(String, String)} method. + * + * @author tav + */ +class JBCefHtmlStringSchemeHandler extends CefResourceHandlerAdapter implements Disposable { + private static final Logger LOG = Logger.getInstance(JBCefHtmlStringSchemeHandler.class); + + public static final String HTML_STRING_SCHEME_NAME = "jb-html-string"; + + private static final Map> LOAD_REQUEST_MAP = new HashMap<>(); + + @NotNull private final CefBrowser myBrowser; + @Nullable private InputStream myInputStream; + + JBCefHtmlStringSchemeHandler(@NotNull CefBrowser browser, @SuppressWarnings("unused") @NotNull CefFrame frame) { + myBrowser = browser; + Disposer.register(JBCefBrowser.getJBCefBrowser(browser), this); + } + + @Override + public void dispose() { + LOAD_REQUEST_MAP.remove(myBrowser); + } + + public static void registerRequest(@NotNull CefBrowser browser, @NotNull String html, @NotNull String url) { + getInitMap(browser).put(url, html); + } + + @NotNull + private static Map getInitMap(@NotNull CefBrowser browser) { + Map map = LOAD_REQUEST_MAP.get(browser); + if (map == null) { + synchronized (LOAD_REQUEST_MAP) { + map = LOAD_REQUEST_MAP.get(browser); + if (map == null) { + LOAD_REQUEST_MAP.put(browser, map = Collections.synchronizedMap(new HashMap<>())); + } + } + } + return map; + } + + @Override + public boolean processRequest(@NotNull CefRequest request, @NotNull CefCallback callback) { + Map map = LOAD_REQUEST_MAP.get(myBrowser); + if (map != null) { + String html = map.get(request.getURL()); + if (html != null) { + myInputStream = new ByteArrayInputStream(html.getBytes(Charset.defaultCharset())); + callback.Continue(); + return true; + } + } + return false; + } + + @Override + public void getResponseHeaders(@NotNull CefResponse response, IntRef response_length, StringRef redirectUrl) { + response.setMimeType("text/html"); + if (myInputStream == null) { + response.setError(CefLoadHandler.ErrorCode.ERR_INSUFFICIENT_RESOURCES); + response.setStatusText("The HTML string is null"); + LOG.error("JBCefHtmlStringSchemeHandler.getResponseHeaders: the HTML string is null"); + response.setStatus(400); + return; + } + response.setStatus(200); + } + + @Override + public boolean readResponse(byte@NotNull[] data_out, int bytes_to_read, IntRef bytes_read, CefCallback callback) { + boolean inProgress = JBCefFileSchemeHandler.readResponse(myInputStream, data_out, bytes_to_read, bytes_read, callback); + if (!inProgress) { + myInputStream = null; + } + return inProgress; + } +}