扩展上线啦, 欢迎大家安装使用: 百度热搜过滤器_chrome应用商店
前言
作为一个面向百度, 面向谷歌编程的程序员, 百度 谷歌是我平时使用最频繁的两个搜索引擎, 百度主要查找一些中文资料, 谷歌更多的用来查找英文资料
使用百度的时候我老是会被它右侧的热搜内容给吸引, 然后就会花不少时间去浏览这些热搜, 这分走了我很多的注意力, 于是我就在想, 有没有什么办法不让它显示呢?
我想到了开发一款谷歌浏览器的插件来帮我完成这件事, 于是就有了这篇文章
首先附上这次开发的谷歌浏览器插件的github地址: github.com/SilentFlute…
什么是谷歌浏览器扩展?
笔者使用的是win10的系统, 然后浏览器版本是104.0.5112.102(正式版本) (64 位)
首先附上谷歌浏览器扩展的开发文档, 个人理解, 谷歌浏览器的扩展是一个能让我们按自己的意愿提高我们使用谷歌浏览器体验的程序, 扩充谷歌浏览器的能力, 比如各种开发工具, 它们能提高我们的生产力:
以及我个人非常喜欢的查看markdown的扩展: JSON Viewer, 它能极大提升我们阅读markdown文件时候的体验
谷歌浏览器扩展是如何运行的?
它和我们前端开发熟悉的网页是一样的, 都是由html, css和js来编写的程序, 它在独立的沙盒环境中运行, 而且可以使用我们已知的API和页面内容产生交互, 甚至还能使用谷歌浏览器提供的chrome API来和谷歌浏览器交互, 相当于在用户, 网页和浏览器之间搭起了一个桥梁
谷歌浏览器扩展的文件结构是怎样的?
这是谷歌浏览器扩展的官方示例仓库: chrome-extensions-samples, 仓库中/examples目录下有多个扩展示例, 这里我们以/examples/bookmarks书签扩展为例, 这个扩展它的结构如下:
third-party: 里面是jQuery和它的一些插件的js和css文件, 在popup.html中使用icon.png: 是扩展的图标, 这个图标的显示位置可以通过配置来调整, 后面会详细解释manifest.json: 该扩展的配置文件:
{
"name": "My Bookmarks", //扩展的名称
"description": "A browser action with a popup dump of all bookmarks, including search, add, edit and delete.",
//扩展的描述
"version": "1.1", //扩展的版本
"manifest_version": 3,
//配置的版本, 官方文档上介绍说这个版本在隐私 安全 性能上都有极大提升, 并且使得开发者能使用诸如promise等现代web技术
"permissions": [
"bookmarks"
], //权限列表, 该扩展可以使用的谷歌浏览器的API
"action": { //谷歌浏览器右上角工具栏中扩展显示内容的相关配置
"default_title": "My Bookmarks", //鼠标悬停时候显示的提示信息
"default_icon": "icon.png", //工具栏中显示的扩展的图标
"default_popup": "popup.html" //点击工具栏中扩展所弹出的弹窗
},
"key": "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAy5Hjn5BDYtngVzsXekpvAPQW7SFpKIM2z/xV4py5XTfOAT5Ln/rEnYfwGNrw6WEZAPo2OsFnSg1ZOpCe7E5NmQVyqbKul9sDwu7wtQ288W8nu2/OoeDfTrfUPSKswTPpA8DbFBLAyYsqLABvg8hpfWRBlKywGSNOAfciJCH13tyUDCMeDWrAbrpdjZDOjTXu7AXLEc5qrAPyLlDisK50THyyCU6BbLttJmIFo3Ymen8UrKkpKsp0ewr5AEDEm4HGgM6/rnAc2Ivpdnt3H3jKv/Ttge9/AkBFvPG0eEgilzkLfydgs1TZp/n4W5Az87LqNH9gN6tJz4z6h+ez3hbmiwIDAQAB"
//这个字段我在文档中没找到, 可能是看漏了, 个人推测应该是标识唯一性的一个字段
}
这里的action.default_icon是工具栏的图标, 和action平级的还有一个叫icons的字段, 这个字段配置的是扩展的管理界面, 也就是这个界面: chrome://extensions/里扩展的图标, 如果不设置的话默认会使用扩展名字的第一个字母来作为扩展管理界面的图标
更多关于
manifest.json字段的解释可查看文档: Manifest file format
popup.css: 弹窗页面的样式文件popup.html: 弹窗页面popup.js: 弹窗页面的js文件, 需要用到chrome.bookmarks相关的API, 以及要使用这个API必须先在manifest.json的permissions字段中配置. 这个文件主要是对谷歌浏览器的书签进行CRUD的操作, 只是数据的来源不是远程接口, 而是书签, 比如获取书签数据使用chrome.bookmarks.getTree, 删除书签使用chrome.bookmarks.remove, 新建书签使用chrome.bookmarks.create, 更新书签使用chrome.bookmarks.update
如何体验谷歌浏览器的示例扩展?
- 点击右上角竖着排列的三个点 > 更多工具 > 扩展程序
或者
- 点击右上角拼图图标 > 弹窗最下方的管理扩展程序
在扩展程序页面(chrome://extensions/)右上角打开开发者模式, 然后界面左上方会出现三个按钮:
点击加载已解压的扩展程序之后选择/examples/bookmarks, 此时插件就安装好了, 接着点击浏览器右上角的拼图图标, 再击My Bookmarks就可以体验了
实际上不止是示例扩展, 我们在开发的时候也是通过这个方式来加载扩展程序的
调试扩展的方式
加载完扩展, 对扩展有调试需求的话一般会先编辑代码, 编辑完代码记得点刷新按钮更新扩展, 以使编辑生效:
同时调试的方式有以下两种:
1. 有弹窗页面的扩展
还是以书签扩展为例, 点击工具栏小拼图图标, 将扩展固定到工具栏, 然后右击扩展图标, 在显示出来的菜单中选择审查弹出内容即可:
此时会弹出一个我们前端开发者所熟悉的调试窗口, 就是F12打开的调试窗口, 只是此时这个调试窗口是popup.html的调试窗口
2. 没有弹窗的扩展
这种情形就是调试service_worker了, 比如我们再看一个扩展: /examples/page-redder, 它里面一共两个文件: background.js和manifest.json, 和上面提到的bookmarks扩展相比, 这个扩展的manifest.json比bookmarks的多了一个字段:
"background": {
"service_worker": "background.js"
}
那这个字段是干什么用的呢?
background字段的作用
个人理解, 这字段里面所声明的js相当于我们平时开发网页时候的main.js或者App.js这样的文件, 所有的代码都会先执行这个, 然后遇到不同的路由, 再去执行不同路由中的代码. 也就是说这个background字段中声明的js会像一个全局的文件一样, 浏览器装上了扩展就会执行
加载/examples/page-redder:
此时我们看到在扩展管理界面page-redder扩展这里有一行字, 这行字在bookmarks扩展中是没有的, 点击之后也会打开一个调试窗口, 只是这个调试窗口的Elements选项卡中是空的, 这个调试窗口就是用来调试page-redder/background.js的了, 我们在里面加个输出语句, 然后保存, 接着刷新扩展, 在点击Service Worker就能看到控制台将我们的输出语句中输出的值显示在了console选项卡中
开发一个自己的浏览器扩展
浏览了下谷歌浏览器扩展的开发文档, 以及体验了一下示例扩展之后, 终于可以开始着手开发我们自己的扩展了
这第一步, 自然是 新建文件夹了 先分析一下需求: 过滤百度热搜, 首先是百度, 然后再处理热搜, 如果是千度和万度我们就不处理, 紧接着就是把其中的热搜内容给处理掉, 也就是有这么几个步骤:
1. 判断当前页面是否是目标页面
首先创建一个配置文件manifest.json:
{
"name": "Filter Baidu Issue",
"description": "Filter baidu issue for u, and gives u more focus to concentrate on what needs to be done",
"version": "1.0",
"manifest_version": 3,
"permissions": [],
"background": {
"service_worker": "background.js"
},
"action": {
"default_title": "Filter Baidu Issue"
}
}
permissions字段先空着, 需要用到什么权限再填什么权限, 一步步来
而我们检测当前页面是不是百度的页面, 这需要我们装上扩展就要执行, 每次打开新的页面都要检测, 而不需要我们手动触发, 因此检测代码写到background.js中就最适合不过啦
浏览并加载体验了所有的示例扩展之后, 我发现关于url的有这么几个(这里只贴关键代码):
hello-world/background.js:
let tab = await chrome.tabs.create({ url });
milestones/popup.js:
function getMilestone(tabs) {
const div = document.createElement("div");
document.body.appendChild(div);
const url = tabs[0].url;
page-redder/background.js:
chrome.action.onClicked.addListener((tab) => {
if(!tab.url.includes("chrome://")) {
它们都和tab相关, 于是我在API Reference里面找到了chrome.tabs的文档, 文档开始就告诉我说需要在permissions字段中注册tabs权限, 否则将无法访问tabs.Tab实例上的4个敏感属性: url, pendingUrl, title和favIconUrl, 于是我便注册了tabs权限:
manifest.json:
{
"name": "Filter Baidu Issue",
"description": "Filter baidu issue for u, and gives u more focus to concentrate on what needs to be done",
"version": "1.0",
"manifest_version": 3,
"permissions": [
"tabs"
],
"background": {
"service_worker": "background.js"
},
"action": {
"default_title": "Filter Baidu Issue"
}
}
紧接着就是编辑background.js文件了, 获取url, 在尝试了tabs#get方法和tabs#getCurrent方法, 以及onActivated事件之后, 我找到了tabs#onUpdated事件: 它会在tab更新的时候执行, tab页签的url变化, 或者刷新的时候都会执行这个事件, 这正是我所需要的
在结合文档中回调的参数, 最终完成了目标页面的判断:
background.js:
chrome.tabs.onUpdated.addListener((tabId, changeInfo, tab) => {
const isBaidu = /baidu.com\/s/.test(tab.url);
if(!isBaidu) return;
const { status } = changeInfo;
if(status !== 'complete') return;
});
首选判断是不是百度的搜索结果页, 然后再判断页面是否加载完毕, 以便后续将热搜内容隐藏
2. 隐藏页面元素
作为一个前端开发, 这个我可太(大)熟(意)了, 找到页面相关元素的类名, 立刻编辑了一下代码:
background.js:
chrome.tabs.onUpdated.addListener((tabId, changeInfo, tab) => {
const isBaidu = /baidu.com\/s/.test(tab.url);
if(!isBaidu) return;
const { status } = changeInfo;
if(status !== 'complete') return;
document.querySelector('#content_right').setAttribute('style', 'display:none;');
});
加了这句: document.querySelector('#content_right').setAttribute('style', 'display:none;');, 然后保存, 刷新扩展, 看效果, 这动作一气呵成, 结果就报错了:
Error in event handler: ReferenceError: document is not defined
于是乎我去/examples中查找document关键字, 发现有两种js中使用了这个我最为熟悉的api:
- 一种是有弹窗的页面, 这个容易理解, 毕竟弹窗就是一个
html文件, 在html文件中使用document这个api再寻常不过了 - 而另一种是没有弹窗的页面, 它们是在
service_worker中使用这个api, 也就是在background.js中使用
在background.js中使用document这个api的示例是我们的page-redder, 仔细观察后发现它在manifest.json中注册了一个scripting的权限, 由此看来是权限问题了, 于是我这边也增加这个权限:
manifest.json:
{
"name": "Filter Baidu Issue",
"description": "Filter baidu issue for u, and gives u more focus to concentrate on what needs to be done",
"version": "1.0",
"manifest_version": 3,
"permissions": [
"tabs",
"scripting"
],
"background": {
"service_worker": "background.js"
},
"action": {
"default_title": "Filter Baidu Issue"
}
}
然而结果还是一样, 也报错, 报的也是同一个错, 此时我查阅了chrome.scripting文档并再一次阅读/examples/page-redder/background.js之后发现了在页面中执行js的方法, 准确地说是往网页之后注入js和css的方法: 使用chrome.scripting这个api, 具体做法如下:
background.js:
chrome.tabs.onUpdated.addListener((tabId, changeInfo, tab) => {
const isBaidu = /baidu.com\/s/.test(tab.url);
if(!isBaidu) return;
const { status } = changeInfo;
if(status !== 'complete') return;
chrome.scripting.executeScript({
target: { tabId },
func() {
document.querySelector('#content_right').setAttribute('style', 'display:none;');
}
});
});
然而令我意想不到的事发生了, 它又报了另外一个错:
Uncaught (in promise) Error: Cannot access contents of url "https://www.baidu.com/s?ie=UTF-8&wd=bruce%20lee". Extension manifest must request permission to access this host.
无法访问url xxx中的内容. 扩展清单配置必须申请访问该主机的权限
这让我想到了/examples/milestones, 这是一个显示Chromium代码合并里程碑的扩展, 里面有个字段叫: host_permissions, 再次修改配置文件:
manifest.json:
{
"name": "Filter Baidu Issue",
"description": "Filter baidu issue for u, and gives u more focus to concentrate on what needs to be done",
"version": "1.0",
"manifest_version": 3,
"permissions": [
"tabs",
"scripting"
],
"host_permissions": [
"https://www.baidu.com/"
],
"background": {
"service_worker": "background.js"
},
"action": {
"default_title": "Filter Baidu Issue"
}
}
并编辑background.js文件:
chrome.tabs.onUpdated.addListener((tabId, changeInfo, tab) => {
const isBaidu = /baidu.com\/s/.test(tab.url);
if(!isBaidu) return;
const { status } = changeInfo;
if(status !== 'complete') return;
chrome.scripting.executeScript({
target: { tabId },
func() {
document.querySelector('#content_right').setAttribute('style', 'display:none;');
}
});
});
此时又报了一个错:
Uncaught SyntaxError: Unexpected token '{'
点看一看发现:
(func() {
document.querySelector('#content_right').setAttribute('style', 'display:none;');
})()
也就意味着ES6方法的简写无法使用, 但写成箭头函数就可以了:
chrome.scripting.executeScript({
target: { tabId },
func: () => {
document.querySelector('#content_right').setAttribute('style', 'display:none;');
}
});
当然, 直接写成函数声明的形式也是可以的:
func: function() {
document.querySelector('#content_right').setAttribute('style', 'display:none;');
}
具体原因我推测可能是chrome把这个func toString之后执行导致的, 比如:
const obj = {
func() {
console.log(123);
}
};
eval(`(${obj.func.toString()})()`);
此时就会报一样的错, 而换成箭头函数或者是函数声明就没问题了, 因为此时再toString, 获得的将是合法的函数的定义, 接着再通过eval执行就不会报错了
最终代码如下:
manifest.json:
{
"name": "Filter Baidu Issue",
"description": "Filter baidu issue for u, and gives u more focus to concentrate on what needs to be done",
"version": "1.0",
"manifest_version": 3,
"permissions": [
"tabs",
"scripting"
],
"host_permissions": [
"https://www.baidu.com/"
],
"background": {
"service_worker": "background.js"
},
"action": {
"default_title": "Filter Baidu Issue"
}
}
background.js:
chrome.tabs.onUpdated.addListener((tabId, changeInfo, tab) => {
const isBaidu = /baidu.com\/s/.test(tab.url);
if(!isBaidu) return;
const { status } = changeInfo;
if(status !== 'complete') return;
chrome.scripting.executeScript({
target: { tabId },
func: () => {
document.querySelector('#content_right').setAttribute('style', 'display:none;');
}
});
});
3. 优化体验
平时网页开发的习惯导致我做了一个status为complete的判断, 这使得页面在浏览器中加载完毕了再去隐藏内容, 但这会导致页面会有抖动: 热搜先显示出来然后突然消失, 体验很差, 哪怕删除这个判断也还是会有抖动
有没有一种方法, 可以像SSR那样在服务端, 还没到客户端(浏览器)的时候就对html内容做一个处理, 比如我们这里的删除某些内容, 然后到浏览器的时候就已经是删除了某些内容之后的了, 此时就不会出现页面的抖动了
答案当然是有的! 这里就要涉及到注入脚本的两种方式了
注入脚本的两种方式
programmatically injected(编程注入)
这种方式就是上面通过service_worker, 利用chrome.scripting.executeScript注入的形式
declared statically(静态声明)
这个就是接下来要介绍的, 通过匹配页面url来注入的静态声明的形式, 具体来说就是先匹配页面的url, 匹配到了, 则注入文件即可, 文档在这: Content scripts
由于隐藏页面元素本质上是给页面的元素添加display: none;属性, 于是我直接注入了一个css文件, 而且这个方式不需要注册任何permissions, 也不需要host_permissions, 只需一点点配置和准备就可以了:
-
在
manifest.json中新增一个content_scripts字段, 并删除permissionshost_permissions和background字段(background.js可以保留, 不影响最终结果, 毕竟background字段已经被我们删除了):manifest.json:{ "name": "Filter Baidu Issue", "description": "Filter baidu issue for u, and gives u more focus to concentrate on what needs to be done", "version": "1.0", "manifest_version": 3, "content_scripts": [ { "matches": [ "https://www.baidu.com/s*" ], "css": [ "content-style.css" ] } ], "action": { "default_title": "Filter Baidu Issue" } }这里的
matches字段需要加末尾那个通配符才能匹配到搜索页 -
新增一个
content-style.css文件, 并在其中写入:#content_right { display: none; }
打完收工
刷新扩展, 百度一下, 右侧的热搜不见了, 啊, 有种世界终于清净了的感觉, 哈哈哈~~
是不是特别简洁? 配置一下匹配规则, 然后添加一个匹配上之后需要注入的文件就好了, 而且页面不会发生抖动, 因为Inject with static declarations(静态声明注入)中在解释content_scripts中css字段的时候有这样一段描述:
The list of CSS files to be injected into matching pages. These are injected in the order they appear in this array, before any DOM is constructed or displayed for the page.
要注入到匹配页面的CSS文件的列表。这些文件是按照它们在这个数组中出现的顺序注入的,在为页面构建或显示任何DOM之前。
before any DOM is constructed or displayed for the page, 这正是我想要的. 最终查看页面元素的时候发现注入的样式是以这样的形式生效的:
injected stylesheet, 这就像我们熟悉的user agent stylesheet一样, 这些样式就像是在浏览器中'内置'的一样, 在html加载渲染之前就存在, 生效时间最快, 就像返回的html文件中本身就有这段css一样, DOM构建的时候就生效, 而不是像上面那样在DOM构建之后再操作页面元素, 从而导致页面元素的闪现, 影响体验
关于background和content_scripts
个人理解, background相当于常驻后台运行的js文件, 全局的文件(比如上面提到的那样), 谷歌浏览器扩展开发文档中的代码基本都是运行在这个js中, 同时它有一部分功能和content_scripts有重合, 就是注入脚本的时候, 只是它是以programmatically injected(编程注入)的方式注入脚本
content_scripts的作用是注入脚本, 确切地说是注入文件, 正如我们上面提到的, 不只是js, css也可以注入, 同时这也是另一种注入脚本的方式: declared statically(静态声明), 同时它在注入脚本的时候功能更强大, 在注入js文件的时候, 文档中提到一个Run time的参数, 这个参数可以控制js脚本的注入时机, 而background通过编程方式注入只能在DOM渲染的时候注入, 类似页面加载之后打开控制台在console选项卡中执行js文件
发布扩展
接下来就到了最后一步了, 就是发布我们开发的扩展: Publish in the Chrome Web Store
跟着一步步做就能发布了, 但是需要有一张VISA信用卡, 因为需要支付5刀的费用(只需要支付一次)
以上就是这篇文章的全部内容了, 如果你觉得这篇文章写得还不错, 别忘了给我点个赞, 如果你觉得对你有帮助, 可以点个收藏, 以备不时之需
参考文献: