前言
前不久,插件新增了一个 浏览器 功能,现在可以直接在 IDEA 中浏览网页了。
这时有人就要说了:“是Chrome不好用,还是你真的有啥‘大毛病’?”
可能就是有啥毛病吧。
开始!
以防 API 不兼容,请使用 2021.2 及以上版本的 IDEA !
插件安装
添加插件库 Plugins > 设置按钮 > Manage Plugin Repositories...
复制代码
搜索 “xechat” 安装
插件主界面
启动浏览器
使用命令 #open 1 开启浏览器
咱们来访问一下掘金社区:
右上边地址栏输入一下网站地址后回车
看看文章,学习一下“新技术”
逛逛沸点,提升一下“软技能”
使用说明
按键说明
✕:关闭浏览器
♨:跳到主页
←:后退一页
→:前进一页
⟳:刷新当前页
调整窗口大小
S:小
M:中
UA设置
缩放
输入数值后回车。
负值缩小,正值放大。
浏览器实现原理
该浏览器是基于 IntelliJ SDK 内置的 JBCefBrowser 实现的,其核心是 JCEF,参考:…
抽象
浏览器功能接口
浏览器基本功能的定义。
package cn.xeblog.; import java.awt.*; /** * @author anlingyi * @date 2022/8/15 2:08 PM */ public interface BrowserService { /** * 获取浏览器UI组件 * * @return */ Component getUI(); /** * 加载URL * * @param url */ void loadURL(String url); /** * 后退 */ void goBack(); /** * 前进 */ void goForward(); /** * 重新加载当前页面 */ void reload(); /** * 浏览器关闭 */ void close(); /** * 设置用户代理 * * @param UserAgent */ void setUserAgent(UserAgent userAgent); /** * 添加浏览器事件监听 * * @param listener */ void addEventListener(BrowserEventListener listener); } 复制代码
浏览器事件监听接口
目前只用到了两个事件:
- 浏览器地址变更事件:在浏览器地址变更之后,用于获取变更之后的地址。
- 浏览器关闭之前事件:在浏览器关闭之前释放一些资源。
package cn.xeblog.; /** * @author anlingyi * @date 2022/8/15 2:24 PM */ public interface BrowserEventListener { /** * 浏览器地址变更 * * @param url */ default void onAddressChange(String url) { } /** * 浏览器关闭之前 */ default void onBeforeClose() { } } 复制代码
UA定义
通过枚举类定义目前可支持的 UserAgent 设置
package cn.xeblog.; import lombok.AllArgsConstructor; import lombok.Getter; /** * @author anlingyi * @date 2022/8/15 5:15 PM */ @AllArgsConstructor public enum UserAgent { iPhone("iPhone") { @Override public String getValue() { return "Mozilla (iPhone; CPU iPhone OS 13_2_3 like Mac OS X) AppleWebKi (KHTML, like Gecko) Version Mobile/15E148 Safari;; } }, ANDROID("Android") { @Override public String getValue() { return "Mozilla (Linux; Android 8.0.0; SM-G955U Build/R16NW) AppleWebKi (KHTML, like Gecko) Chrome Mobile Safari;; } }, iPad("iPad") { @Override public String getValue() { return "Mozilla (iPad; CPU OS 13_3 like Mac OS X) AppleWebKi (KHTML, like Gecko) CriOS Mobile/15E148 Safari;; } }, WINDOWS("Windows") { @Override public String getValue() { return "Mozilla (Windows NT 10.0; WOW64) AppleWebKi (KHTML, like Gecko) Chrome Safari;; } }, MacOS("MacOS") { @Override public String getValue() { return "Mozilla (Macintosh; Intel Mac OS X 10_15_7) AppleWebKi (KHTML, like Gecko) Chrome Safari;; } }, NATIVE("本机") { @Override public String getValue() { return null; } }; @Getter private String name; public abstract String getValue(); public static UserAgent getUserAgent(String name) { for (UserAgent userAgent : values()) { if ().equals(name)) { return userAgent; } } return IPHONE; } } 复制代码
实现
浏览器功能实现
基本原理:先创建一个 JBCefBrowser 对象,通过这个对象可以获取到 CefBrowser 对象,浏览器大部分功能都是通过 CefBrowser 来完成的,再通过 CefBrowser 获取 CefClient,CefClient 可用于监听一些事件,像是浏览器地址变更事件、浏览器关闭事件、浏览器请求事件(可设置UA)等。
package cn.xeblog.; import cn.; import com.in; import org.cef.CefClient; import org.cef.brow; import org.cef.brow; import org.cef.handler.*; import org.cef.mi; import org.cef.ne; import java.awt.*; /** * @author anlingyi * @date 2022/8/15 2:06 PM */ public class JcefBrowserService implements BrowserService { private final JBCefBrowser jbCefBrowser; private final CefBrowser cefBrowser; private final CefClient client; private UserAgent userAgent; private BrowserEventListener eventListener; public JcefBrowserService(String url) { = new JBCefBrowser(url); = .getCefBrowser(); = .getClient(); = U; = new BrowserEventListener() { }; initAddEvent(); } private void initAddEvent() { .removeRequestHandler(); .addRequestHandler(new CefRequestHandlerAdapter() { @Override public CefResourceRequestHandler getResourceRequestHandler(CefBrowser browser, CefFrame frame, CefRequest request, boolean isNavigation, boolean isDownload, String requestInitiator, BoolRef disableDefaultHandling) { return new CefResourceRequestHandlerAdapter() { @Override public boolean onBeforeResourceLoad(CefBrowser browser, CefFrame frame, CefRequest request) { String ua = u(); if (ua != null) { reque("User-Agent", ua, true); } return false; } }; } }); .addDisplayHandler(new CefDisplayHandlerAdapter() { @Override public void onAddressChange(CefBrowser browser, CefFrame frame, String url) { if (!S(url, "http")) { return; } even(url); } }); .removeLifeSpanHandler(); .addLifeSpanHandler(new CefLifeSpanHandlerAdapter() { @Override public boolean onBeforePopup(CefBrowser browser, CefFrame frame, String target_url, String target_frame_name) { if (target_url, "jpg", "png", "gif", "svg", "pdf", "bmp", "webp")) { return false; } loadURL(target_url); return true; } @Override public void onBeforeClose(CefBrowser browser) { close(); even(); } }); } @Override public Component getUI() { return .getComponent(); } @Override public void loadURL(String url) { .loadURL(url); } @Override public void goBack() { if (.canGoBack()) { .goBack(); } } @Override public void goForward() { if (.canGoForward()) { .goForward(); } } @Override public void reload() { .reload(); } @Override public void close() { .dispose(); .dispose(); } @Override public void setUserAgent(UserAgent userAgent) { if (userAgent == null) { return; } = userAgent; } @Override public void addEventListener(BrowserEventListener listener) { if (listener == null) { return; } = listener; } } 复制代码
浏览器UI实现
界面这一块是通过 Swing 实现的,主要是浏览器的一些基本控制按钮。
package cn.xeblog.; import cn.; import cn.xeblog.; import cn.xeblog..BrowserEventListener; import cn.xeblog..BrowserService; import cn.xeblog..JcefBrowserService; import cn.xeblog..UserAgent; import com.in; import lombok.Getter; import javax.swing.*; import java.awt.*; import java.awt.event.KeyAdapter; import java.awt.event.KeyEvent; /** * @author anlingyi * @date 2022/8/14 11:48 AM */ public class BrowserUI extends JPanel { private final static String HOME_PAGE = ";; private BrowserService browserService; private String lastUrl; private WindowMode windowMode; private UserAgent userAgent; private Component browserUI; private JTextField urlField; private enum WindowMode { SMALL("S", 200, 250), MEDIUM("M", 400, 300); @Getter String name; @Getter int width; @Getter int height; WindowMode(String name, int width, int height) { = name; = width; = height; } public static WindowMode getMode(String name) { for (WindowMode mode : values()) { if ().equals(name)) { return mode; } } return WindowMode.SMALL; } } public BrowserUI() { = WindowMode.SMALL; = U; initPanel(); } private void initPanel() { removeAll(); String url = HOME_PAGE; if (lastUrl != null) { url = lastUrl; } if != null) { (); } = new JcefBrowserService(url); .setUserAgent(userAgent); browserUI = brow(); urlField = new JTextField(url); resize(); Dimension buttonDimension = new Dimension(50, 25); Box hbox = Box.createHorizontalBox(); JButton exitButton = new JButton("✕"); exi("退出"); exi(buttonDimension); exi(l -> Command.OVER.exec()); (exitButton); JButton homeButton = new JButton("♨"); ("主页"); (buttonDimension); (l -> brow(HOME_PAGE)); (homeButton); JButton backButton = new JButton("←"); backBu("后退"); backBu(buttonDimension); backBu(l -> brow()); (backButton); JButton forwardButton = new JButton("→"); forwardBu("前进"); forwardBu(buttonDimension); forwardBu(l -> brow()); (forwardButton); JButton refreshButton = new JButton("⟳"); refreshBu("刷新"); refreshBu(buttonDimension); refreshBu(l -> brow()); (refreshButton); (urlField); JPanel urlPanel = new JPanel(); urlPanel.add(hbox); Box h2Box = Box.createHorizontalBox(); (new JLabel("Window:")); ComboBox windowModeBox = new ComboBox(); windowModeBox.setPreferredSize(buttonDimension); for (WindowMode mode : WindowMode.values()) { windowModeBox.addItem()); } windowModeBox.setSelectedItem()); windowModeBox.addItemListener(l -> { windowMode = WindowMode.getMode().toString()); resize(); updateUI(); }); (windowModeBox); (5)); (new JLabel("UA:")); ComboBox uaBox = new ComboBox(); uaBox.setPreferredSize(new Dimension(100, 30)); for (UserAgent userAgent : U()) { uaBox.addItem()); } uaBox.setSelectedItem()); uaBox.addItemListener(l -> { userAgent = U().toString()); brow(userAgent); brow(); }); (uaBox); JPanel bottomPanel = new JPanel(); bo(h2Box); setLayout(new BorderLayout()); add(urlPanel, BorderLayout.NORTH); add(browserUI, BorderLayout.CENTER); add(bottomPanel, BorderLayout.SOUTH); add(10), BorderLayout.EAST); updateUI(); urlField.addKeyListener(new KeyAdapter() { @Override public void keyPressed(KeyEvent e) { if () == KeyEvent.VK_ENTER) { String url = urlField.getText(); if (!SAny(url, "https://", "http://")) { url = "https://" + url; } brow(url); } } }); .addEventListener(new BrowserEventListener() { @Override public void onAddressChange(String url) { lastUrl = url; urlField.setText(url); } @Override public void onBeforeClose() { SwingU(() -> initPanel()); } }); } private void resize() { int width = .getWidth(); int height = .getHeight(); urlField.setPreferredSize(new Dimension(width * 2 / 3, 30)); urlField.updateUI(); Dimension browserDimension = new Dimension(width, height); brow(null); brow(null); if (windowMode == WindowMode.SMALL) { brow(browserDimension); } else { brow(browserDimension); } } public void close() { (); } } 复制代码
浏览器入口
package cn.xeblog.; import cn.xeblog.; import cn.xeblog.; import cn.xeblog.; import cn.xeblog..BrowserUI; import java.awt.*; /** * @author anlingyi * @date 2022/8/14 11:12 AM */ @DoTool) public class Browser extends AbstractTool { private BrowserUI browserUI; @Override protected void init() { = new BrowserUI(); mainPanel.setLayout(new BorderLayout()); mainPanel.add(, BorderLayout.CENTER); } @Override public void over() { (); if (browserUI != null) { .close(); } } } 复制代码
完整代码:gi…