【java】本地客户端内嵌浏览器2 - chrome/chromium/cef/jcef

article/2025/10/3 1:02:05

目录

  • ★☆★ 写在前面 ★☆★
  • ★☆★ 本系列文章 ★☆★
  • ★☆★ 开源网址 ★☆★
    • 一、发现新大陆 - CEF/JCEF
        • 0、前言
        • 1、使用 jcef.jar 搭建项目
        • 2、启动包含 jcef.jar 的程序
        • 3、simple\MainFrame 注释翻译
    • 二、定制自己的项目之 Swing
        • 1、删除导航栏
        • 2、程序启动最大化窗口,设置最小窗口大小
        • 3、设置标题
        • 4、设置图标
        • 5、CefApp 启动失败,自定义显示内容
        • 6、点击关闭按钮二次确认是否退出软件(原代码并没有真正退出软件)
    • 三、定制自己的项目之 JCEF
        • 1、Browser 和 Main 分离。
        • 2、执行 javascript 代码(client -> browser)
        • 3、实现下载功能
            • I. 前言
            • II. 具体操作
        • 4、右键菜单
            • I. 初识右键菜单(删除/禁用右键菜单)
            • II. 自定义右键菜单之“图片另存为”
            • III. 自定义右键菜单之“开发者工具”
        • 5、js 请求 client(browser -> client)

★☆★ 写在前面 ★☆★

请通过目录,选择感兴趣的部分阅读。

★☆★ 本系列文章 ★☆★

【java】本地客户端内嵌浏览器1 - Swing、SWT、DJNativeSwing、javaFX
【java】本地客户端内嵌浏览器2 - chrome/chromium/cef/jcef
【java】本地客户端内嵌浏览器3 - Swing 使用 Spring 框架 + 打包项目 + 转exe + 源码

★☆★ 开源网址 ★☆★

https://github.com/supsunc/swing-jcef-spring

一、发现新大陆 - CEF/JCEF

0、前言

  1. 当我发现 javafx 也失败了之后,我心灰意冷,真的是太累了,网上的抄抄抄,不注明出处,没有任何原创精神,搞得我身心俱疲。
  2. 就在我即将放弃的时候,我发现了 CEF(忘记是怎么发现的了),于是直接百度搜索 CEF

哎呀呵,Google Chromium多平台支持有其他语言的移植版支持Webkit & Chrome中实现的HTML5的特性,这不正是我想要的么!
在这里插入图片描述

  1. 哈哈,进一步找到了 CEF 开源项目网址:https://code.google.com/archive/p/chromiumembedded/

??? google.com ???这,能打开吗?
事实证明,打不开!

  1. 于是,我放弃了....................两天。
  2. 第三天,我抱着不服输的心态,又研究了两天,搜了一大堆东西,参考着网友 2017 年 4 月份写的文章(此文章在后面两篇文章中有提及),生产出了两篇文章:非常详细的获取 JCEF 相关 jar 包的教程比较简单的获取 JCEF 相关 jar 包的教程

1、使用 jcef.jar 搭建项目

本节接上节末提到的两篇文章后开始
在这里说明一下,jcef 是基于 Swing 的,不过本文不需要多么懂 Swing,因为我就不是很懂。

  1. 新建个项目,目录结构为
  • src
    • main
      • java
      • lib
        • jcef
      • resources
    • test

目录结构

  1. 在 Project Structure 中设置文件夹类型。

设置文件夹类型

  1. 将 E:\java-cef\src\binary_distrib\win64\bin 中的 test 文件夹,拷贝到项目的 java 文件夹中。
  2. 将 E:\java-cef\src\binary_distrib\win64\bin 中除了 test 文件夹之外的所有文件及文件夹,拷贝到项目的 lib\jcef 文件夹中。(junittests 需要 junit 相关依赖,这里不介绍此部分相关内容,因此这个文件夹(包)删掉,后面不再提及)

在这里插入图片描述

  1. 右键单击 jcef 文件夹,点击 Add as library,弹出确认框,默认即可。

add as library

  1. 打开 Project Structure,点击 Library,或在 Modules 中的这个项目的 Dependencies中,点击加号,找到 lib\jcef\lib\win64 文件夹。

add Native Library Locations

  1. 添加完之后,应该是这样子的。

在这里插入图片描述

上述内容,如果第 5 步之后不做,则根本无法启动程序,因为 jar 包都没有添加依赖嘛。
上述内容,如果第 6 步之后不做,则启动程序失败,因为会报错:Exception in thread "main" java.lang.UnsatisfiedLinkError: no chrome_elf in java.library.path
在这里插入图片描述

2、启动包含 jcef.jar 的程序

  1. 打开刚刚拷贝到 java 文件夹中的 simple\MainFrame。
  2. 修改文件最后面的一个语句:
    new MainFrame("http://www.google.com", useOsr, false); 变成
    new MainFrame("https://www.baidu.com", useOsr, false);
  3. 启动。

在这里插入图片描述

  1. 哇,真棒。测试我自己的项目,全部都没问题,哈哈哈。

在这里插入图片描述

3、simple\MainFrame 注释翻译

虽然用的百度翻译,但好歹是中文,,,有需要的可以直接拿走。

// 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 tests.simple;import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.KeyboardFocusManager;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.FocusAdapter;
import java.awt.event.FocusEvent;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;import javax.swing.JFrame;
import javax.swing.JTextField;import org.cef.CefApp;
import org.cef.CefApp.CefAppState;
import org.cef.CefClient;
import org.cef.CefSettings;
import org.cef.OS;
import org.cef.browser.CefBrowser;
import org.cef.browser.CefFrame;
import org.cef.handler.CefAppHandlerAdapter;
import org.cef.handler.CefDisplayHandlerAdapter;
import org.cef.handler.CefFocusHandlerAdapter;/*** This is a simple example application using JCEF.* It displays a JFrame with a JTextField at its top and a CefBrowser in its* center. The JTextField is used to enter and assign an URL to the browser UI.* No additional handlers or callbacks are used in this example.** The number of used JCEF classes is reduced (nearly) to its minimum and should* assist you to get familiar with JCEF.** For a more feature complete example have also a look onto the example code* within the package "tests.detailed".*/
public class MainFrame extends JFrame {private static final long serialVersionUID = -5570653778104813836L;private final JTextField address_;private final CefApp cefApp_;private final CefClient client_;private final CefBrowser browser_;private final Component browerUI_;private boolean browserFocus_ = true;/*** To display a simple browser window, it suffices completely to create an* instance of the class CefBrowser and to assign its UI component to your* application (e.g. to your content pane).* 要显示一个简单的浏览器窗口,只需创建一个类 CefBrowser 的实例并将其 UI 组件* 分配给应用程序(例如,分配给内容窗格)就足够了。* <p>* But to be more verbose, this CTOR keeps an instance of each object on the* way to the browser UI.* 但是,为了更详细,这个 CTOR 将每个对象的一个实例保存在通往浏览器 UI 的路上。*/private MainFrame(String startURL, boolean useOSR, boolean isTransparent) {// (1) The entry point to JCEF is always the class CefApp. There is only one//     instance per application and therefore you have to call the method//     "getInstance()" instead of a CTOR.//     JCEF 的入口点总是类 CefApp。每个应用程序只有一个实例,因此必须调用//     方法"getInstance()"而不是一个 CTOR。//     CefApp is responsible for the global CEF context. It loads all//     required native libraries, initializes CEF accordingly, starts a//     background task to handle CEF's message loop and takes care of//     shutting down CEF after disposing it.//     CefApp 负责全局 CEF 上下文。它加载所有必需的本地库,相应地初始化 CEF,//     启动后台任务来处理 CEF 的消息循环,并在处理完后关闭 CEF。CefApp.addAppHandler(new CefAppHandlerAdapter(null) {@Overridepublic void stateHasChanged(org.cef.CefApp.CefAppState state) {// Shutdown the app if the native CEF part is terminated// 如果本机 CEF 部分终止,则关闭应用程序if (state == CefAppState.TERMINATED) System.exit(0);}});CefSettings settings = new CefSettings();settings.windowless_rendering_enabled = useOSR;cefApp_ = CefApp.getInstance(settings);// (2) JCEF can handle one to many browser instances simultaneous. These//     browser instances are logically grouped together by an instance of//     the class CefClient. In your application you can create one to many//     instances of CefClient with one to many CefBrowser instances per//     client. To get an instance of CefClient you have to use the method//     "createClient()" of your CefApp instance. Calling an CTOR of//     CefClient is not supported.//     JCEF 可以同时处理一到多个浏览器实例。这些浏览器实例按类 CefClient 的实例在逻辑上分组在一起。//     在您的应用程序中,您可以创建一到多个 CefClient 实例,每个客户端有一到多个 CefBrowser 实例。//     要获取 CefClient 的实例,必须使用 CefApp 实例的方法"createClient()"。不支持调用 CefClient 的 CTOR。////     CefClient is a connector to all possible events which come from the//     CefBrowser instances. Those events could be simple things like the//     change of the browser title or more complex ones like context menu//     events. By assigning handlers to CefClient you can control the//     behavior of the browser. See tests.detailed.MainFrame for an example//     of how to use these handlers.//     CefClient 是连接来自 CefBrowser 实例的所有可能事件的连接器。//     这些事件可以是诸如更改浏览器标题之类的简单事件,也可以是诸如上下文菜单事件之类的更复杂事件。//     通过将处理程序分配给 CefClient,您可以控制浏览器的行为。有关如何使用这些处理程序的示例,请参见 tests.detailed.MainFrame。client_ = cefApp_.createClient();// (3) One CefBrowser instance is responsible to control what you'll see on//     the UI component of the instance. It can be displayed off-screen//     rendered or windowed rendered. To get an instance of CefBrowser you//     have to call the method "createBrowser()" of your CefClient//     instances.//     一个 CefBrowser 实例负责控制在该实例的 UI 组件上看到的内容。//     它可以显示屏幕外渲染或窗口渲染。要获取 CefBrowser 实例,必须调用 CefClient 实例的方法"createBrowser()"。////     CefBrowser has methods like "goBack()", "goForward()", "loadURL()",//     and many more which are used to control the behavior of the displayed//     content. The UI is held within a UI-Compontent which can be accessed//     by calling the method "getUIComponent()" on the instance of CefBrowser.//     The UI component is inherited from a java.awt.Component and therefore//     it can be embedded into any AWT UI.//     CefBrowser 有"goBack()"、"goForward()"、"loadURL()"等方法,这些方法用于控制显示内容的行为。//     该 UI 保存在 UI 组件中,可以通过调用 CefBrowser 实例上的方法"getUIComponent()"来访问该 UI 组件。//     UI 组件继承自java.awt.Component,因此可以嵌入到任何 AWT UI 中。browser_ = client_.createBrowser(startURL, useOSR, isTransparent);browerUI_ = browser_.getUIComponent();// (4) For this minimal browser, we need only a text field to enter an URL//     we want to navigate to and a CefBrowser window to display the content//     of the URL. To respond to the input of the user, we're registering an//     anonymous ActionListener. This listener is performed each time the//     user presses the "ENTER" key within the address field.//     If this happens, the entered value is passed to the CefBrowser//     instance to be loaded as URL.//     对于这个最小的浏览器,我们只需要一个文本字段来输入我们要导航到的 url,以及一个 CefBrowser 窗口来显示 url 的内容。//     为了响应用户的输入,我们注册了一个匿名 ActionListener。每当用户在地址字段中按“回车”键时,就会执行此侦听器。//     如果发生这种情况,则将输入的值传递给要作为 url 加载的 CefBrowser 实例。address_ = new JTextField(startURL, 100);address_.addActionListener(new ActionListener() {@Overridepublic void actionPerformed(ActionEvent e) {browser_.loadURL(address_.getText());}});// Update the address field when the browser URL changes.// 当浏览器 URL 更改时更新地址字段。client_.addDisplayHandler(new CefDisplayHandlerAdapter() {@Overridepublic void onAddressChange(CefBrowser browser, CefFrame frame, String url) {address_.setText(url);}});// Clear focus from the browser when the address field gains focus.// 当地址字段获得焦点时,从浏览器中清除焦点。address_.addFocusListener(new FocusAdapter() {@Overridepublic void focusGained(FocusEvent e) {if (!browserFocus_) return;browserFocus_ = false;KeyboardFocusManager.getCurrentKeyboardFocusManager().clearGlobalFocusOwner();address_.requestFocus();}});// Clear focus from the address field when the browser gains focus.// 当浏览器获得焦点时,从地址字段中清除焦点。client_.addFocusHandler(new CefFocusHandlerAdapter() {@Overridepublic void onGotFocus(CefBrowser browser) {if (browserFocus_) return;browserFocus_ = true;KeyboardFocusManager.getCurrentKeyboardFocusManager().clearGlobalFocusOwner();browser.setFocus(true);}@Overridepublic void onTakeFocus(CefBrowser browser, boolean next) {browserFocus_ = false;}});// (5) All UI components are assigned to the default content pane of this//     JFrame and afterwards the frame is made visible to the user.//     所有 UI 组件都被分配给这个 JFrame 的默认内容窗格,然后这个框架对用户可见。getContentPane().add(address_, BorderLayout.NORTH);getContentPane().add(browerUI_, BorderLayout.CENTER);pack();setSize(800, 600);setVisible(true);// (6) To take care of shutting down CEF accordingly, it's important to call//     the method "dispose()" of the CefApp instance if the Java//     application will be closed. Otherwise you'll get asserts from CEF.//     要相应地关闭 CEF,如果 Java 应用程序将被关闭,那么调用 CefApp 实例的方法"dispose()"非常重要。否则你会得到 CEF 的指控。addWindowListener(new WindowAdapter() {@Overridepublic void windowClosing(WindowEvent e) {CefApp.getInstance().dispose();dispose();}});}public static void main(String[] args) {// Perform startup initialization on platforms that require it.// 在需要的平台上执行启动初始化。if (!CefApp.startup()) {System.out.println("Startup initialization failed!");return;}// The simple example application is created as anonymous class and points// to Google as the very first loaded page. Windowed rendering mode is used by// default. If you want to test OSR mode set |useOsr| to true and recompile.// 这个简单的示例应用程序被创建为匿名类,并指向 Google 作为第一个加载的页面。默认情况下使用窗口渲染模式。如果要测试OSR模式,请将 |useOsr| 设置为 true 并重新编译。boolean useOsr = false;new MainFrame("http://www.google.com", useOsr, false);}
}

二、定制自己的项目之 Swing

1、删除导航栏

很简单,就是把源代码中的 address_ 变量相关语句全部删掉,以及 Focus 相关的代码也删掉。这里直接分享源代码(为节省篇幅,我将注释全部删除了)

package tests.simple;import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.KeyboardFocusManager;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.FocusAdapter;
import java.awt.event.FocusEvent;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;import javax.swing.JFrame;
import javax.swing.JTextField;import org.cef.CefApp;
import org.cef.CefApp.CefAppState;
import org.cef.CefClient;
import org.cef.CefSettings;
import org.cef.browser.CefBrowser;
import org.cef.browser.CefFrame;
import org.cef.handler.CefAppHandlerAdapter;
import org.cef.handler.CefDisplayHandlerAdapter;
import org.cef.handler.CefFocusHandlerAdapter;public class MainFrame extends JFrame {private static final long serialVersionUID = -5570653778104813836L;private final CefApp cefApp_;private final CefClient client_;private final CefBrowser browser_;private final Component browerUI_;private boolean browserFocus_ = true;private MainFrame(String startURL, boolean useOSR, boolean isTransparent) {CefApp.addAppHandler(new CefAppHandlerAdapter(null) {@Overridepublic void stateHasChanged(org.cef.CefApp.CefAppState state) {if (state == CefAppState.TERMINATED) System.exit(0);}});CefSettings settings = new CefSettings();settings.windowless_rendering_enabled = useOSR;cefApp_ = CefApp.getInstance(settings);client_ = cefApp_.createClient();browser_ = client_.createBrowser(startURL, useOSR, isTransparent);browerUI_ = browser_.getUIComponent();getContentPane().add(browerUI_, BorderLayout.CENTER);pack();setSize(800, 600);setVisible(true);addWindowListener(new WindowAdapter() {@Overridepublic void windowClosing(WindowEvent e) {CefApp.getInstance().dispose();dispose();}});}public static void main(String[] args) {if (!CefApp.startup()) {System.out.println("Startup initialization failed!");return;}boolean useOsr = false;new MainFrame("https://www.baidu.com", useOsr, false);}
}

2、程序启动最大化窗口,设置最小窗口大小

  1. 删除两条语句
pack();
setSize(800, 600);
  1. 新增两条语句
setMinimumSize(new Dimension(1366, 738));    // 设置最小窗口大小
setExtendedState(JFrame.MAXIMIZED_BOTH);    // 默认窗口全屏

3、设置标题

很简单,就一条语句

setTitle("MyBrowser");

4、设置图标

  1. 现在 resources 中创建文件夹 images,然后放进去图标文件(直接放进去也行)。

在这里插入图片描述

  1. 还是很简单,一条语句
setIconImage(Toolkit.getDefaultToolkit().getImage(getClass().getResource("/images/icon.png")));
  1. 如果报空指针错误之类的,可以先 Rebuild Project 一下,再重新启动。

5、CefApp 启动失败,自定义显示内容

  1. 将 main 方法中的 System.out.println("Startup initialization failed!"); 替换成如下代码(以下代码用到了一个图片文件,请自行放置,或删除相关代码)
JFrame jFrame = new JFrame("MyBrowser");
jFrame.setMinimumSize(new Dimension(1366, 738));    // 设置最小窗口大小
jFrame.setExtendedState(JFrame.MAXIMIZED_BOTH);    // 默认窗口全屏JLabel error = new JLabel("<html><body>&nbsp;&nbsp;&nbsp;&nbsp;在启动这个应用程序时,发生了一些错误,请关闭并重启这个应用程序。<br>There is something wrong when this APP start up, please close and restart it.</body></html>");
error.setFont(new Font("宋体/Arial", Font.PLAIN, 28));
error.setIcon(new ImageIcon(jFrame.getClass().getResource("/images/error.png")));
error.setForeground(Color.red);
error.setHorizontalAlignment(SwingConstants.CENTER);jFrame.getContentPane().setBackground(Color.white);
jFrame.getContentPane().add(error, BorderLayout.CENTER);
jFrame.setVisible(true);
  1. 让我们来瞧一瞧效果,感觉还不错。(Chinese English)

效果

6、点击关闭按钮二次确认是否退出软件(原代码并没有真正退出软件)

  1. 修改 addWindowListener 方法的入参中的 windowClosing 方法:
addWindowListener(new WindowAdapter() {@Overridepublic void windowClosing(WindowEvent e) {int i;String language = "en-us";if (language.equals("en-us"))i = JOptionPane.showOptionDialog(null, "Do you really want to quit this software?", "Exit", JOptionPane.YES_NO_OPTION, JOptionPane.QUESTION_MESSAGE, null, new String[]{"Yes", "No"}, "Yes");else if (language.equals("zh-cn"))i = JOptionPane.showOptionDialog(null, "你真的想退出这个软件吗?", "Exit", JOptionPane.YES_NO_OPTION, JOptionPane.QUESTION_MESSAGE, null, new String[]{"是的", "不"}, "是的");elsei = JOptionPane.showOptionDialog(null, "你真的想退出这个软件吗?\nDo you really want to quit this software?", "Exit", JOptionPane.YES_NO_OPTION, JOptionPane.QUESTION_MESSAGE, null, new String[]{"是的(Yes)", "不(No)"}, "是的(Yes)");if (i == JOptionPane.YES_OPTION) {CefApp.getInstance().dispose();dispose();System.exit(0);}}
});
  1. 让我们来瞧一瞧效果,感觉还不错。

在这里插入图片描述

三、定制自己的项目之 JCEF

1、Browser 和 Main 分离。

上一部分是直接在 simple/MainFrame 上面改的,本部分重新建包,从头干起。(之前的文件可以不必删,删了也行,建议看完本文再删。。。)

  1. 在 java 文件夹中新建 package:my.client.browsermy.client.main,并新建两个 class 叫做 MyBrowserMain

在这里插入图片描述

  1. 将 simple/MainFrame 中构造方法与 jcef 相关的代码 copy 到 MyBrowser 的构造方法中,并提供 getter 返回相关对象,这里直接分享源代码:
package my.client.browser;import org.cef.CefApp;
import org.cef.CefClient;
import org.cef.CefSettings;
import org.cef.browser.CefBrowser;
import org.cef.handler.CefAppHandlerAdapter;import java.awt.*;public class MyBrowser {private final CefApp cefApp_;private final CefClient client_;private final CefBrowser browser_;private final Component browserUI_;public MyBrowser(String startURL, boolean useOSR, boolean isTransparent) {CefApp.addAppHandler(new CefAppHandlerAdapter(null) {@Overridepublic void stateHasChanged(org.cef.CefApp.CefAppState state) {if (state == CefApp.CefAppState.TERMINATED) System.exit(0);}});CefSettings settings = new CefSettings();settings.windowless_rendering_enabled = useOSR;cefApp_ = CefApp.getInstance(settings);client_ = cefApp_.createClient();browser_ = client_.createBrowser(startURL, useOSR, isTransparent);browserUI_ = browser_.getUIComponent();}public CefApp getCefApp() {return cefApp_;}public CefClient getClient() {return client_;}public CefBrowser getBrowser() {return browser_;}public Component getBrowserUI() {return browserUI_;}
}
  1. 在 Main 类中建立 main 方法,并建立私有方法 init(),在 main 方法中调用 init() 方法。(为什么“脱裤子放屁-费二遍事”创建新方法,后面会有提及)
package my.client.main;public class Main {public static void main(String[] args) {init();}private static void init() {}
}
  1. 在 init 方法中创建 JFrame 并从 simple/MainFrame 中 copy 过来相关代码:

EventQueue.invokeLater 的作用及其相关知识请自行了解。
这里的很多代码和上一部分中的修改是相关的

package my.client.main;import my.client.browser.MyBrowser;
import org.cef.CefApp;import javax.swing.*;
import java.awt.*;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;public class Main {public static void main(String[] args) {init();}private static void init() {EventQueue.invokeLater(() -> {JFrame jFrame = new JFrame("MyBrowser");jFrame.setMinimumSize(new Dimension(1366, 738));    // 设置最小窗口大小jFrame.setExtendedState(JFrame.MAXIMIZED_BOTH);    // 默认窗口全屏jFrame.setIconImage(Toolkit.getDefaultToolkit().getImage(jFrame.getClass().getResource("/images/icon.png")));if (!CefApp.startup()) {    // 初始化失败JLabel error = new JLabel("<html><body>&nbsp;&nbsp;&nbsp;&nbsp;在启动这个应用程序时,发生了一些错误,请关闭并重启这个应用程序。<br>There is something wrong when this APP start up, please close and restart it.</body></html>");error.setFont(new Font("宋体/Arial", Font.PLAIN, 28));error.setIcon(new ImageIcon(jFrame.getClass().getResource("/images/error.png")));error.setForeground(Color.red);error.setHorizontalAlignment(SwingConstants.CENTER);jFrame.getContentPane().setBackground(Color.white);jFrame.getContentPane().add(error, BorderLayout.CENTER);jFrame.setVisible(true);return;}MyBrowser myBrowser = new MyBrowser("https://www.baidu.com", false, false);// // // // // // // // // // // // // // // // // // // // // // // // // // // //// TODO: 后面的步骤不再 po 全部代码了,如果说“在 init() 方法中插入”,则全是插入在这里 //// // // // // // // // // // // // // // // // // // // // // // // // // // // //jFrame.getContentPane().add(myBrowser.getBrowserUI(), BorderLayout.CENTER);jFrame.setVisible(true);jFrame.addWindowListener(new WindowAdapter() {@Overridepublic void windowClosing(WindowEvent e) {int i;String language = "en-us";if (language.equals("en-us"))i = JOptionPane.showOptionDialog(null, "Do you really want to quit this software?", "Exit", JOptionPane.YES_NO_OPTION, JOptionPane.QUESTION_MESSAGE, null, new String[]{"Yes", "No"}, "Yes");else if (language.equals("zh-cn"))i = JOptionPane.showOptionDialog(null, "你真的想退出这个软件吗?", "Exit", JOptionPane.YES_NO_OPTION, JOptionPane.QUESTION_MESSAGE, null, new String[]{"是的", "不"}, "是的");elsei = JOptionPane.showOptionDialog(null, "你真的想退出这个软件吗?\nDo you really want to quit this software?", "Exit", JOptionPane.YES_NO_OPTION, JOptionPane.QUESTION_MESSAGE, null, new String[]{"是的(Yes)", "不(No)"}, "是的(Yes)");if (i == JOptionPane.YES_OPTION) {myBrowser.getCefApp().dispose();jFrame.dispose();System.exit(0);}}});});}
}

2、执行 javascript 代码(client -> browser)

实例化出 MyBrowser 对象,加载完网页之后,就可以向网页上执行 js 代码了。为保证加载完网页,我们将相关代码写到一个新线程中,并 sleep 一秒。直接分享源代码:

MyBrowser myBrowser = new MyBrowser("https://www.baidu.com", false, false);
new Thread(new Runnable() {@Overridepublic void run() {try {// 让线程 sleep 一秒保证 executeJavaScript 方法能够执行Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}// 第一个参数是 js 代码,第二、三个参数是控制台打印所附带的信息,并不是指向网页执行代码。// 第二个参数是 url,一旦报错,就会打印其相关信息,以供开发人员阅读。myBrowser.getBrowser().executeJavaScript("console.log(123)", "http://whatever", 123);// 第三个参数是行号,一旦报错,就会打印其相关信息,以供开发人员阅读。myBrowser.getBrowser().executeJavaScript("document.write(123456)", "http://whatever", 1);}
}).start();

在这里插入图片描述

3、实现下载功能

I. 前言

jcef 这个东西,很多表现功能都要求自己去实现,比如下载功能,需要主动写一个类继承其特定的 Adapter,然后重写相关方法,才能完成这个功能。

II. 具体操作
  1. 在 init() 方法中插入”两条语句,其中的 DownloadHandler 类,由下一步创建。
CefClient client = myBrowser.getClient();
client.addContextMenuHandler(new DownloadHandler());
  1. my.client 中创建一个 Package 叫做 handler,创建一个 DownloadHandler 类,继承自 CefDownloadHandlerAdapter,重写 onBeforeDownloadonDownloadUpdated 方法。

此处可以参考 jcef 提供的 detailed 实例,里面有相关代码。

  1. 主要是重写 onBeforeDownload() 方法,添加很重要的一条语句:callback.Continue(fileName, true);
  2. 这里可以将下载进度之类的信息传递给网页,或者手动实现 Swing 组件用来展示下载进度。我这边是传递给网页,这里直接分享源代码:
package my.client.handler;import org.cef.browser.CefBrowser;
import org.cef.callback.CefBeforeDownloadCallback;
import org.cef.callback.CefDownloadItem;
import org.cef.callback.CefDownloadItemCallback;
import org.cef.handler.CefDownloadHandlerAdapter;public class DownloadHandler extends CefDownloadHandlerAdapter {@Overridepublic void onBeforeDownload(CefBrowser browser, CefDownloadItem item, String fileName, CefBeforeDownloadCallback callback) {callback.Continue(fileName, true);	// 通过此方法让下载正常进行}@Overridepublic void onDownloadUpdated(CefBrowser browser, CefDownloadItem item, CefDownloadItemCallback callback) {// 判断当前状态正在进行中、没有被取消、没有完成状态if (item.isInProgress() && !item.isCanceled() && !item.isComplete()) {// 如果没有开始下载(选择下载存放路径时),item.getPercentComplete() 返回值是 -1int percent = item.getPercentComplete() == -1 ? 0 : item.getPercentComplete();StringBuilder sb = new StringBuilder();// 判断当前网址是“英文网址” 还是“中文网址”if (browser.getURL().contains("en-us"))sb.append("It is downloading, ").append(percent).append("% completed.");elsesb.append("正在下载,完成度:").append(percent).append("%。");// 下载完毕让网页的下载窗口 dom 元素出现,并修改其中的文本信息browser.executeJavaScript("$download.show(); pDownload.innerText='" + sb + "';", item.getURL(), 1);} else {// 下载完毕让网页的下载窗口 dom 元素隐藏browser.executeJavaScript("setTimeout(() => $download.fadeOut('fast'), 1000);", item.getURL(), 2);}}
}

4、右键菜单

I. 初识右键菜单(删除/禁用右键菜单)
  1. 在 init() 方法中插入”一条语句:client.addContextMenuHandler(new MenuHandler());,其中的 MenuHandler 类,由下一步创建。
  2. my.client.handler中,创建一个 MenuHandler 类,继承自 CefContextMenuHandlerAdapter,重写 onBeforeContextMenuonContextMenuCommand 方法。

此处参考 jcef 提供的 detailed 实例,里面有相关代码。

package my.client.handler;import org.cef.browser.CefBrowser;
import org.cef.browser.CefFrame;
import org.cef.callback.CefContextMenuParams;
import org.cef.callback.CefMenuModel;
import org.cef.handler.CefContextMenuHandlerAdapter;public class MenuHandler extends CefContextMenuHandlerAdapter {@Overridepublic void onBeforeContextMenu(CefBrowser browser, CefFrame frame, CefContextMenuParams params, CefMenuModel model) {}@Overridepublic boolean onContextMenuCommand(CefBrowser browser, CefFrame frame, CefContextMenuParams params, int commandId, int eventFlags) {}
}
  1. 如果想要 删除/禁用右键菜单,很简单,就在 onBeforeContextMenu 方法中写一条语句就可以了:model.clear();
  2. 如果要自定义菜单,那么就要写一堆代码了:
package my.client.handler;import org.cef.browser.CefBrowser;
import org.cef.browser.CefFrame;
import org.cef.callback.CefContextMenuParams;
import org.cef.callback.CefMenuModel;
import org.cef.callback.CefMenuModel.MenuId;
import org.cef.handler.CefContextMenuHandlerAdapter;public class MenuHandler extends CefContextMenuHandlerAdapter {private final static int MENU_ID_MORE = 10001;@Overridepublic void onBeforeContextMenu(CefBrowser browser, CefFrame frame, CefContextMenuParams params, CefMenuModel model) {// 清除菜单项model.clear();//剪切、复制、粘贴model.addItem(MenuId.MENU_ID_COPY, "copy");model.addItem(MenuId.MENU_ID_CUT, "cut");model.addItem(MenuId.MENU_ID_PASTE, "paste");model.setEnabled(MenuId.MENU_ID_PASTE, false);model.addSeparator();CefMenuModel more = model.addSubMenu(MENU_ID_MORE, "more");more.addItem(MenuId.MENU_ID_PRINT,"print");more.addItem(MenuId.MENU_ID_VIEW_SOURCE,"view source");model.addSeparator();model.addItem(MenuId.MENU_ID_RELOAD, "reload");}@Overridepublic boolean onContextMenuCommand(CefBrowser browser, CefFrame frame, CefContextMenuParams params, int commandId, int eventFlags) {switch (commandId) {case MenuId.MENU_ID_RELOAD:browser.reload();return true;}return false;}
}
  1. 这里解释一下:
onBeforeContextMenu 方法:
* model.clear();    // 清除菜单项。
* MenuId.MENU_ID_COPY  // 是 MenuId 中定义好的一个值,使用特定值会触发默认的特定事件,也可以自定义,建议不要与 MenuId 类中已定义的值冲突。
* model.setEnabled(MenuId.MENU_ID_PASTE, false);  // 是将这个按钮禁用,因为每次右键单击的时候都会触发这个方法,所以可以通过一些变量控制其是否被禁用。
* model.addSeparator();  // 是在菜单栏中添加一条分割线。
* model.addSubMenu(MENU_ID_MORE, "more");	// 创建下级菜单,返回值是一个 CefMenuModel 对象,通过这个对象继续 addItem 添加下级菜单项目。onContextMenuCommand 方法:
* switch (commandId)  // 可以通过 commandId 获取点击项目设置的 Id,然后去匹配,去实现相关功能。
* return true;  // 阻止默认事件。
* return false;  // 默认事件可以触发,如 print、copy、cut、paste 等都有默认事件,见名思意即可。
  1. 让我们来瞧一瞧效果,感觉还不错。

在这里插入图片描述

II. 自定义右键菜单之“图片另存为”
  1. 思路:首先应该判断右键单击处是否是个图片,然后再添加相关菜单。
  2. 直接分享源代码:
package my.client.handler;import org.cef.browser.CefBrowser;
import org.cef.browser.CefFrame;
import org.cef.callback.CefContextMenuParams;
import org.cef.callback.CefMenuModel;
import org.cef.handler.CefContextMenuHandlerAdapter;public class MenuHandler extends CefContextMenuHandlerAdapter {private final static int MENU_ID_SAVE_PICTURE = 10001;@Overridepublic void onBeforeContextMenu(CefBrowser browser, CefFrame frame, CefContextMenuParams params, CefMenuModel model) {//清除菜单项model.clear();if (params.hasImageContents() && params.getSourceUrl() != null) {model.addItem(MENU_ID_SAVE_PICTURE, "图片另存为/save picture as...");model.addSeparator();}}@Overridepublic boolean onContextMenuCommand(CefBrowser browser, CefFrame frame, CefContextMenuParams params, int commandId, int eventFlags) {switch (commandId) {case MENU_ID_SAVE_PICTURE:browser.startDownload(params.getSourceUrl());return true;}return false;}
}
  1. 此代码仅对 img 标签这种有 Url 路径的有效,对于 canvas 这种,params.hasImageContents() 方法能够返回 true,但是 params.getSourceUrl() 返回的是空字符串,所以执行 browser.startDownload("") 时不会发生任何事情。
III. 自定义右键菜单之“开发者工具”
  1. 首先在 my.client 中创建一个 Package 叫做 dialog,创建一个 DevToolsDialog 类,继承自 JDialog,直接分享源代码:
package my.client.dialog;import org.cef.browser.CefBrowser;import javax.swing.*;
import java.awt.*;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;public class DevToolsDialog extends JDialog {private final CefBrowser devTools_;// 一般使用这个构造方法public DevToolsDialog(Frame owner, String title, CefBrowser browser) {this(owner, title, browser, null);}public DevToolsDialog(Frame owner, String title, CefBrowser browser, Point inspectAt) {super(owner, title, false);setLayout(new BorderLayout());	// 设置布局Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();	// 拿到屏幕尺寸setSize(screenSize.width / 2,screenSize.height / 2);	//设置大小为屏幕尺寸的一半,可以自定大小setLocation(owner.getLocation().x + 20, owner.getLocation().y + 20);	// 设置左上角点的位置,是指定 Frame 的左上角点的偏移 20px 位置devTools_ = browser.getDevTools(inspectAt);	// 获取到 browser 的 DevToolsadd(devTools_.getUIComponent());	// 将其 UIComponent 添加上去// 添加相关监听addComponentListener(new ComponentAdapter() {@Overridepublic void componentHidden(ComponentEvent e) {dispose();}});}@Overridepublic void dispose() {devTools_.close(true);	// 关闭的时候触发此方法,关闭 DevToolssuper.dispose();}
}
  1. 修改 MenuHandler 类,直接分享源代码:
package my.client.handler;import my.client.dialog.DevToolsDialog;
import org.cef.browser.CefBrowser;
import org.cef.browser.CefFrame;
import org.cef.callback.CefContextMenuParams;
import org.cef.callback.CefMenuModel;
import org.cef.callback.CefMenuModel.MenuId;
import org.cef.handler.CefContextMenuHandlerAdapter;import java.awt.*;public class MenuHandler extends CefContextMenuHandlerAdapter {private final Frame owner;public MenuHandler(Frame owner) {this.owner = owner;}private final static int MENU_ID_SHOW_DEV_TOOLS = 10000;@Overridepublic void onBeforeContextMenu(CefBrowser browser, CefFrame frame, CefContextMenuParams params, CefMenuModel model) {//清除菜单项model.clear();model.addItem(MENU_ID_SHOW_DEV_TOOLS, "开发者选项");}@Overridepublic boolean onContextMenuCommand(CefBrowser browser, CefFrame frame, CefContextMenuParams params, int commandId, int eventFlags) {switch (commandId) {case MENU_ID_SHOW_DEV_TOOLS:// 打开开发者选项DevToolsDialog devToolsDlg = new DevToolsDialog(owner, "开发者选项", browser);devToolsDlg.setVisible(true);return true;}return false;}
}
  1. 修改 main 方法中的 MenuHandler 实例化方法,入参加上 jFrame 对象:client.addContextMenuHandler(new MenuHandler(jFrame));
  2. 让我们来瞧一瞧效果,感觉还不错。

在这里插入图片描述

5、js 请求 client(browser -> client)

此处参考 jcef 提供的 detailed 实例,里面有相关代码。

  1. my.client.handler中,创建一个 MessageRouterHandler 类,继承自 CefMessageRouterHandlerAdapter,重写 onQuery。这里直接分享源代码:
package my.client.handler;import org.cef.browser.CefBrowser;
import org.cef.browser.CefFrame;
import org.cef.callback.CefQueryCallback;
import org.cef.handler.CefMessageRouterHandlerAdapter;public class MessageRouterHandler extends CefMessageRouterHandlerAdapter {@Overridepublic boolean onQuery(CefBrowser browser, CefFrame frame, long query_id, String request, boolean persistent, CefQueryCallback callback) {// 请求信息以 "click:" 开头if (request.indexOf("click:") == 0) {String msg = request.substring(6).trim();callback.success(msg + " create new message(cnm)");	// 返回对应信息到前端 success 回调函数return true;}// 请求信息以 "custom:" 开头if (request.indexOf("custom:") == 0) {// 将后面的字符串按 ,:- 切割String[] method = request.substring(7).trim().split("[,:\\-]");switch (method[0].trim()) {case "search":callback.success("This is the result of search.");	// 返回对应信息到前端 success 回调函数break;case "connect":System.out.println(method[1].trim());callback.success("This is the result of connect.");	// 返回对应信息到前端 success 回调函数break;default:callback.failure(404, "This is the result of failure.");	// 返回对应信息到前端 failure 回调函数break;}return true;}// Not handled.return false;	// 如果返回 false 则会自动执行一个 alert 弹出框提示没有 handled}@Overridepublic void onQueryCanceled(CefBrowser browser, CefFrame frame, long query_id) {}
}
  1. 在 init() 方法中插入”三条语句:
// 这里的 cef 和 cefCancel 是自定义字符串,前端通过调用这两个字符串表示的方法来访问 client,
// 即对应的 onQuery 和 onQueryCanceled 方法。
CefMessageRouter cmr = CefMessageRouter.create(new CefMessageRouter.CefMessageRouterConfig("cef", "cefCancel"));
cmr.addHandler(new MessageRouterHandler(), true);
client.addMessageRouter(cmr);
  1. 后台部分完成,开始前端页面的代码
function sendMessage() {// 这里的 cef 就是 client 创建 CefMessageRouter 对象的入参涉及到的字符串window.cef({request: 'click:' + document.getElementById("message").value,onSuccess(response) {console.log(response);},onFailure(error_code, error_message) {console.log(error_code, error_message);}});
}
function sendCustom() {// 这里的 cef 就是 client 创建 CefMessageRouter 对象的入参涉及到的字符串window.cef({request: 'custom: connect-192.168.1.1',onSuccess(response) {console.log(response);},onFailure(error_code, error_message) {console.log(error_code, error_message);}});window.cef({request: 'custom: search-' + JSON.stringify({a: 1, b: "str"}),onSuccess(response) {console.log(response);},onFailure(error_code, error_message) {console.log(error_code, error_message);}});
}

★ 前端用 JSON.stringify() 将对象转换成字符串传输到 client,同样,后台接收过来的 response 数据用 JSON.parse() 转换成对象。
★ 后台则使用 net.sf.json 的 JSONObject.from()、JSONArray.from()、JSONArray.from().toString() 等方法将字符串转换成对象,将对象转换成字符串。


http://chatgpt.dhexx.cn/article/OQtKje0X.shtml

相关文章

C#嵌入谷歌浏览器内核

1.右击项目&#xff0c;选择.net框架为4.5以上&#xff1a; 2.右击项目&#xff0c;选择“管理Nuget程序包”&#xff0c;点击“浏览”&#xff0c;搜索“CefSharp”&#xff0c;选择“CefSharp WinForms”下载安装。 安装之后到项目的引用下查看&#xff0c;会出现&#xff…

WinFrom内嵌chrome浏览器

选中项目&#xff0c;右键&#xff0c;下拉列表里选择“管理Nuget程序包&#xff08;N&#xff09;”选项&#xff0c;打开如图&#xff1a; 按照步骤装上这个nuget包&#xff0c;装上以后你的工具箱就有这个了&#xff1a; 不用拖拉控件&#xff0c;直接代码绑定把&#xff…

在网页中内嵌网页

目录&#xff1a; 文章目录 前言代码展示主页代码展示作品的代码球体运动方块旋转 结果演示 前言 在制作个人网站时&#xff0c;经常遇到一个问题&#xff0c;就是如何让自己的作品动态的显示在主页上而本文就是找到了解决办法&#xff0c;利用<embed src"xx.html&qu…

Qt实现 内嵌CEF制作浏览器(首篇)

介绍 cef支持跨平台&#xff0c;是基于Chromium的开源浏览器控件&#xff0c;全称Chromium Embedded Framework&#xff0c;因为其跨平台性&#xff0c;被广泛使用于制作浏览器。 本文主要介绍如何下载cef以及编译windows下的cef项目&#xff0c;并运行查看浏览器显示效果。 …

Qt 内嵌浏览器几种办法

1.使用axWidget QT axcontainer 然后ui里面就可以出现QAxWidget 直接拖入就可以 ui->axWidget->setControl(QString::fromUtf8("{8856F961-340A-11D0-A96B-00C04FD705A2}")); ui->axWidget->setFocusPolicy(Qt::StrongFocus); ui->axWidget…

CEF内嵌浏览器 编译

CEF github 笔记 https://github.com/fanfeilong/cefutil/blob/master/doc/CEF%20General%20Usage-zh-cn.md#using-binray 介绍 CEF全称Chromium Embedded Framework&#xff0c;是一个基于Google Chromium 的开源项目。Google Chromium项目主要是为Google Chrome应用开发的…

Unity内嵌浏览器插件(Android、iOS、Windows)

文章目录 一. Embedded Browser插件下载2. 使用 二. UniWebView插件1. 下载2. 使用 三、我自制的迷你浏览器 一. Embedded Browser插件 下载 平台&#xff1a;Windows 链接&#xff1a;https://pan.baidu.com/s/1h2oyGW6FsvPlOGCob0VrfA 提取码&#xff1a;02tu 2. 使用 导…

QT内嵌浏览器与JS通讯

QT内嵌浏览器与JS通讯 1. 概述2. JS调用QT方法2.1 QT代码2.2 HTML/代码 3 WebEngineView示例4. 效果展示 1. 概述 QT内嵌浏览器支持拦截请求、获取cookie、js代码注入及js调用QT方法。本篇主要介绍js调用QT方法其他方式的使用QT WebEngineView 拦截请求、获取cookie&#xff0…

Unity 工具之 内嵌网页/浏览器 web view / browser 插件的整理大全(包括Window Mac Android iOS 等)

Unity 工具之 内嵌网页/浏览器 web view / browser 插件的整理大全&#xff08;包括Window Mac Android iOS 等&#xff09; 目录 Unity 工具之 内嵌网页/浏览器 web view / browser 插件的整理大全&#xff08;包括Window Mac Android iOS 等&#xff09; 一、简单介绍 二、…

Unity 工具之 内嵌网页/浏览器插件使用和学习笔记

1、Embedded Browser 插件&#xff08;文件夹名ZFBrowserUnity&#xff09; 优点&#xff1a;设置简单&#xff0c;功能强大&#xff1a;输入url地址&#xff0c;拉取网页信息&#xff0c;可设置页面尺寸&#xff0c;可显示透明背景的网页&#xff0c;可与显示的页面进行互动&…

java内嵌浏览器的几种方式

最近遇到一个特殊的项目需求&#xff0c;就是需要在一个屏幕上打开多个窗口大小不同的浏览器、并且显示不同的页面。因为是需要浏览器无边框的&#xff0c;在网上找了好多资料&#xff0c;发现前端好像很难实现。所以就打算采用java后台内嵌浏览器&#xff0c;然后实现无边框的…

winform内嵌浏览器的2种实现方式

可使用WebBrowser或axWebBrowser实现winform窗体内嵌浏览器 一 使用axWebBrowser打开浏览器 1.新建个winform项目 2.添加axWebBrowser控件 打开工具箱,右键空白处,点击选择项 选择COM组件,勾上Microsoft Web Browser 把控件拉拽到winform窗体上 3.使用axWebBrowser打开浏览器 …

云表中表单配置内嵌浏览器

给大家分享一个在表单里也能嵌入网页的一个功能&#xff0c;云表的内嵌浏览器 1.首先我们先添加一个云表内嵌浏览器&#xff0c;在模板设计的右边点击表格面板点击下拉后可以先将浏览器&#xff0c;这个浏览器是需要一整个表格的 2.添加好浏览器后&#xff0c;我们表单设置…

Android开发实用小技巧九——内嵌WebView的使用(内置浏览器)

文章目录 前言一、效果展示二、代码1.样式布局2.活动页面 总结 前言 内嵌WebView的使用&#xff08;内置浏览器&#xff09;。 一、效果展示 二、代码 1.样式布局 res/layout/activity_browser.xml &#xff1a; <?xml version"1.0" encoding"utf-8"…

springboot 调用Jxbrowser内嵌浏览器

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 前言一、Jxbrowser是什么&#xff1f;二、使用步骤1.下载jar包一、在jxbrowser的启动类中加入如下代码&#xff1a;二、在resources目录下新建META-INF/teamdev.lic…

javaFX实现桌面应用程序内嵌浏览器(一、框架建设)

一、jdk不匹配问题 修改jdk版本不成功&#xff1a; 1、注意环境变量是否更改 2、jdk路径已经更改成功但cmd打开输入Java -version还是原先版本&#xff1a;在PATH的那一溜里将把JAVA_HOME放到最前面去 3、idea修改jdk版本 IDEA修改JDK版本完整版 以及Modules中的Sources&#…

IDM下载工具

安装的时候一直next就好了&#xff0c;尽量将idm安装在c盘里面 下面这个链接时绿色版&#xff0c;不需要安装 然后用idm免注册脚本运行一下 下载链接&#xff08;传不上来&#xff0c;发邮箱我给你传一份&#xff0c;这个阿里云盘有点low啊&#xff09; 尽量在关闭360等工具运…

idm 的使用

一:首先在chrome中添加IDM插件: http://www.internetdownloadmanager.com/ 首先进入IDM官网-->Support-->FAQ,点击BROWSER INTEGRATION QUESTIONS 然后点击第8条: 然后点击链接安装Chrome插件: 再然后, 启用该插件. 二、再下载IDMv.6.333 链接&#xff1a;https://do…

IDM的介绍、下载、注册激活使用教程详解 V6.38.2021

IDM是“Internet Download Manager”的简称&#xff0c;意思是“互联网下载管理器”&#xff0c;既是一类软件的统称&#xff0c;也专指一个非常知名的互联网下载器&#xff0c;这个软件的名字就叫IDM&#xff0c;被誉为地表最强下载器&#xff0c;屌丝救星&#xff0c;小电影神…

Internet Download Manager6.41提速下载器安装下载教程

很多人都知道Internet Download Manager(以下简称IDM)是一款非常优秀的下载提速软件。它功能强大&#xff0c;几乎能下载网页中的所有数据&#xff08;包括视频、音频、图片等&#xff09;&#xff0c;且适用于现在市面上几乎所有的浏览器&#xff0c;非常受大家欢迎。 Intern…