目录

  • ★☆★ 写在前面 ★☆★
  • ★☆★ 本系列文章 ★☆★
  • ★☆★ 开源网址 ★☆★
    • 一、发现新大陆 - 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,弹出确认框,默认即可。

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

  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() 等方法将字符串转换成对象,将对象转换成字符串。

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

  1. 【java】本地客户端内嵌浏览器3 - Swing 使用 Spring 框架 + 打包项目 + 转exe + 源码

    目录 ★☆★ 写在前面 ★☆★ ★☆★ 本系列文章 ★☆★ ★☆★ 开源网址 ★☆★ 一.给 Swing 加上 Spring 0.前期努力 I. SpringBoot II. SpringMVC 1. ...

  2. C# WPF使用CefSharp客户端内嵌浏览器做一个开小差工具

    前言 CefSharp是一个C#客户端内嵌入chromium开源项目浏览器的工具,方便在客户端中自然的访问网页内容,十分好用.当然,网上有很多使用CefSharp的教程了,怎么使用都很详尽.我这里只是 ...

  3. java gui 嵌入浏览器_DJNativeSwing-SWT组件-Java GUI中内嵌浏览器

    Java项目中经常需要在GUI程序中嵌入浏览器,而Swing自带的组件对CSS.JS的支持不是很好,网上也有很多组件,参考 但是由于对各个平台的支持不是很好,笔者是在Mac系统下进行开发,很多组件只支 ...

  4. CEF:给客户端内嵌一个Chrome吧

    原文:http://yogurtcat.com/posts/cef/hello-cef.html 发表于: 2013-03-31 20:20   |  更多相关文章: browser CEF Chro ...

  5. Java实现内嵌浏览器

    创建项目 ---->   导入需要的jar ---->  代码实现 需要的jar: https://pan.baidu.com/s/1MEZ1S0LnKSMGQm24QWgmCw 代码: ...

  6. java使用swing实现内嵌浏览器

    java使用swing实现内嵌浏览器 1.使用swing内嵌浏览器需要导入3个jar包,第3个根据电脑版本选择 dj-native-swing-swt.jar     dj-native-swing. ...

  7. Java swing 做一个传统Web项目的桌面程序启动器(内嵌浏览器)

    背景:公司有个老项目,web项目,但是使用者都想要一个桌面应用程序.实际上,是web程序的启动较为麻烦.这里每次都需要启动Tomcat和浏览器. 想法:重写一个项目太麻烦,想想成本,人间不值得.于是我 ...

  8. PC游戏中用CEF3制作内嵌浏览器

    因为项目需要,需要将游戏手机助手中的朋友圈给移植到PC游戏中,而以前游戏中的内嵌浏览器采用的是IE6内核,满足不了我们的需求,于是决定把Cef3内嵌到游戏中,在完成正常工作之余,利用闲散时间不断地查找 ...

  9. java selenium div内嵌滚动条 网页长截图发邮件

    java selenium 网页内嵌滚动条截图发邮件 主要问题 下面展开说 由于公司要求做一个接口,请求这个接口进行网页截图并发送邮件的功能,本来前期是用python写好了,but似乎不太符合要求,那 ...

最新文章

  1. shader 3 rendering path
  2. c++ 字符串数组长度排序_C指针和字符串数组
  3. python 数据库 实战_干货!python与MySQL数据库的交互实战
  4. IIS 支持 php
  5. 《复杂》读书笔记(part2)--混沌与逻辑斯蒂映射
  6. Oracle Spatial中SDO_Geometry说明
  7. 简要概述网络I/O与并发
  8. Java 基础知识 【钢镚核恒】
  9. Facebook努力回归中国,微博会被人们放弃吗?
  10. 出生在商丘农村的80后的幸福童年
  11. 计算机用户名怎么注册,微软账号怎么注册 Microsoft帐户注册使用教程
  12. 洛谷2672 推销员
  13. mysql 乐观锁 超卖_秒杀系统之一:防止超卖(乐观锁)
  14. day64 url用法以及django的路由系统
  15. 鹏业安装三维算量软件——批量修改工程量
  16. 麦当劳可以免费添加可乐的!
  17. G - Nightmare Ⅱ (双向BFS)
  18. 最详细职场面试题目(一)
  19. 非视线成像:角膜成像系统
  20. 传输层 --- 面向连接的传输TCP

热门文章

  1. pypy加速python
  2. 离散傅里叶变换的主要性质
  3. 异常Could not load file or assembly 'XXX' or one of its dependencies. 参数出错...
  4. 名词解释第四讲:钱包
  5. Hive中行拆分操作
  6. VScode安装以及mingw64配置
  7. python alphago_AlphaGo 都在使用的 Python 语言,是最接近 AI 的编程语言?
  8. Android自定义View实战:简约风歌词控件,Android开发者值得深入思考的几个问题
  9. java 模板方法_设计模式(java实现)_模板方法模式(Template method)
  10. tp5 时间间隔查询问题