vue (十二) | 搭建谷歌插件vue项目

Scroll Down

前言

hello,大家好,前段时间给大家带来了如何搭建一个vue项目,今天给大家带来一个如何搭建谷歌插件项目,是不是很nice,这里会分为两个版本,html版和vue版

什么是谷歌插件

Chrome插件是一个用Web技术开发、用来增强浏览器功能的软件,它其实就是一个由HTML、CSS、JS、图片等资源组成的一个.crx后缀的压缩包.

一、功能

Chrome插件提供了很多实用API供我们使用,包括但不限于:
书签控制;
下载控制;
窗口控制;
标签控制;
网络请求控制,各类事件监听;
自定义原生菜单;
完善的通信机制;
等等;

二、HTML版

2.1、步骤

  • 创建文件夹
    随便建一个目录:例如my-google

  • 创建json核心文件manifest.json

"name": "Hello Extensions",
  "description" : "Hello world Extension",
  "version": "1.0",
  "manifest_version": 2,
  "browser_action": {
    "default_popup": "hello.html",
    "default_icon": "hello_extensions.png"
  }
  • 创建页面:hello.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<h1>Hello Extensions</h1>
</body>
</html>

2.2、安装

  • 设置–》更多工具–》拓展程序
    image-1652688756749
  • 点击加载解压的拓展程序–》选择开发的文件夹
    image-1652688818278

好了到这里就可以访问你的第一个小程序了,如果你觉得可以,那就给作者秀总的秀基宝插件一个好评,谢谢。后面继续讲进击篇

三、进击篇VUE版

上面只是简单的介绍了开发插件的hello world,实际中,我们需要的功能远不止这么简单。需要更强大的功能,更多的库支持才能够胜任。下面介绍如何做出功能更完善的插件。我们从一个开源的插件脚手架做案例。

3.1、搭建

  • 创建脚手架
vue init kocal/vue-web-extension#v1 my-extension
cd my-extension
npm install

到了这里你的脚手架就结束了,是不是很简单。到这里就可以启动安装了。

  • 启动
npm run watch	-- 启动
npm run build	-- 打包,打包时候才执行
  • 安装【步骤同上】
  • 效果
    image-1652689400310

3.2、目录介绍

  • dist 打包文件
  • src 开发目录
    • icons 图片资源
    • options 设置页面
    • popup 插件主页面
    • store
    • background.js 后台永驻js
    • manifest.json 核心配置文件
  • package.json vue依赖管理
  • package-lock.json
  • webpack.config.js vue框架配置

3.3、开发

到这里基本就是结束了,因为后面就是你安装element、axio等插件,然后就是写功能了。重点下面我来详细介绍每个文件意思以及开发,都是跟插件相关的东西

四、核心介绍

  • manifest.json
{
    // 清单文件的版本,这个必须写,而且必须是2
    "manifest_version": 2,
    // 插件的名称
    "name": "demo",
    // 插件的版本
    "version": "1.0.0",
    // 插件描述
    "description": "简单的Chrome扩展demo",
    // 图标,一般偷懒全部用一个尺寸的也没问题
    "icons":
    {
        "16": "img/icon.png",
        "48": "img/icon.png",
        "128": "img/icon.png"
    },
    // 会一直常驻的后台JS或后台页面
    "background":
    {
        // 2种指定方式,如果指定JS,那么会自动生成一个背景页
        "page": "background.html"
        //"scripts": ["js/background.js"]
    },
    // 浏览器右上角图标设置,browser_action、page_action、app必须三选一
    "browser_action":
    {
        "default_icon": "img/icon.png",
        // 图标悬停时的标题,可选
        "default_title": "这是一个示例Chrome插件",
        "default_popup": "popup.html"
    },
    // 当某些特定页面打开才显示的图标
    /*"page_action":
    {
        "default_icon": "img/icon.png",
        "default_title": "我是pageAction",
        "default_popup": "popup.html"
    },*/
    // 需要直接注入页面的JS
    "content_scripts":
    [
        {
            //"matches": ["http://*/*", "https://*/*"],
            // "<all_urls>" 表示匹配所有地址
            "matches": ["<all_urls>"],
            // 多个JS按顺序注入
            "js": ["js/jquery-1.8.3.js", "js/content-script.js"],
            // JS的注入可以随便一点,但是CSS的注意就要千万小心了,因为一不小心就可能影响全局样式
            "css": ["css/custom.css"],
            // 代码注入的时间,可选值: "document_start", "document_end", or "document_idle",最后一个表示页面空闲时,默认document_idle
            "run_at": "document_start"
        },
        // 这里仅仅是为了演示content-script可以配置多个规则
        {
            "matches": ["*://*/*.png", "*://*/*.jpg", "*://*/*.gif", "*://*/*.bmp"],
            "js": ["js/show-image-content-size.js"]
        }
    ],
    // 权限申请
    "permissions":
    [
        "contextMenus", // 右键菜单
        "tabs", // 标签
        "notifications", // 通知
        "webRequest", // web请求
        "webRequestBlocking",
        "storage", // 插件本地存储
        "http://*/*", // 可以通过executeScript或者insertCSS访问的网站
        "https://*/*" // 可以通过executeScript或者insertCSS访问的网站
    ],
    // 普通页面能够直接访问的插件资源列表,如果不设置是无法直接访问的
    "web_accessible_resources": ["js/inject.js"],
    // 插件主页,这个很重要,不要浪费了这个免费广告位
    "homepage_url": "https://www.baidu.com",
    // 覆盖浏览器默认页面
    "chrome_url_overrides":
    {
        // 覆盖浏览器默认的新标签页
        "newtab": "newtab.html"
    },
    // Chrome40以前的插件配置页写法
    "options_page": "options.html",
    // Chrome40以后的插件配置页写法,如果2个都写,新版Chrome只认后面这一个
    "options_ui":
    {
        "page": "options.html",
        // 添加一些默认的样式,推荐使用
        "chrome_style": true
    },
    // 向地址栏注册一个关键字以提供搜索建议,只能设置一个关键字
    "omnibox": { "keyword" : "go" },
    // 默认语言
    "default_locale": "zh_CN",
    // devtools页面入口,注意只能指向一个HTML文件,不能是JS文件
    "devtools_page": "devtools.html"
}
  • content-scripts
{
    // 需要直接注入页面的JS
    "content_scripts":
    [
        {
            //"matches": ["http://*/*", "https://*/*"],
            // "<all_urls>" 表示匹配所有地址
            "matches": ["<all_urls>"],
            // 多个JS按顺序注入
            "js": ["js/jquery-1.8.3.js", "js/content-script.js"],
            // JS的注入可以随便一点,但是CSS的注意就要千万小心了,因为一不小心就可能影响全局样式
            "css": ["css/custom.css"],
            // 代码注入的时间,可选值: "document_start", "document_end", or "document_idle",最后一个表示页面空闲时,默认document_idle
            "run_at": "document_start"
        }
    ],
}
  • background.js

常驻的页面,它的生命周期是插件中所有类型页面中最长的,它随着浏览器的打开而打开,随着浏览器的关闭而关闭,所以通常把需要一直运行的、启动就运行的、全局的代码放在background里面。

background的权限非常高,几乎可以调用所有的Chrome扩展API(除了devtools),而且它可以无限制跨域,也就是可以跨域访问任何网站而无需要求对方设置CORS。

{
    // 会一直常驻的后台JS或后台页面
    "background":
    {
        // 2种指定方式,如果指定JS,那么会自动生成一个背景页
        "page": "background.html"
        //"scripts": ["js/background.js"]
    },
}

配置中,background可以通过page指定一张网页,也可以通过scripts直接指定一个JS,Chrome会自动为这个JS生成一个默认的网页:

  • popup

popup是点击browser_action或者page_action图标时打开的一个小窗口网页,焦点离开网页就立即关闭,一般用来做一些临时性的交互。
image-1652691659863

{
    "browser_action":
    {
        "default_icon": "img/icon.png",
        // 图标悬停时的标题,可选
        "default_title": "这是一个示例Chrome插件",
        "default_popup": "popup.html"
    }
}
  • homepage_url

开发者或者插件主页设置

五、Chrome插件的8种展示形式

5.1、browserAction(浏览器右上角)

"browser_action":
{
    "default_icon": "img/icon.png",
    "default_title": "这是一个示例Chrome插件",
    "default_popup": "popup.html"
}

这里有几个细节

  • 5.1.1、图标
    image-1652691905263

  • 5.1.2、tooltip
    如上图,就是一个提示

  • 5.1.3、badge
    image-1652692006393

5.2、pageAction(地址栏右侧)

具体是从哪一版本开始改的没去仔细考究,反正知道v50.0的时候还是前者,v58.0的时候已改为后者。
调整之后的pageAction我们可以简单地把它看成是可以置灰的browserAction。

  • chrome.pageAction.show(tabId) 显示图标;
  • chrome.pageAction.hide(tabId) 隐藏图标;
// manifest.json
{
    "page_action":
    {
        "default_icon": "img/icon.png",
        "default_title": "我是pageAction",
        "default_popup": "popup.html"
    },
    "permissions": ["declarativeContent"]
}

// background.js
chrome.runtime.onInstalled.addListener(function(){
    chrome.declarativeContent.onPageChanged.removeRules(undefined, function(){
        chrome.declarativeContent.onPageChanged.addRules([
            {
                conditions: [
                    // 只有打开百度才显示pageAction
                    new chrome.declarativeContent.PageStateMatcher({pageUrl: {urlContains: 'baidu.com'}})
                ],
                actions: [new chrome.declarativeContent.ShowPageAction()]
            }
        ]);
    });
});

image-1652692409274

5.3、右键菜单

Chrome插件可以自定义浏览器的右键菜单,主要是通过chrome.contextMenusAPI实现,右键菜单可以出现在不同的上下文,比如普通页面、选中的文字、图片、链接,等等,如果有同一个插件里面定义了多个菜单,Chrome会自动组合放到以插件名字命名的二级菜单里,如下:
image-1652692471898

  • 添加右键百度搜索
// manifest.json
{"permissions": ["contextMenus", "tabs"]}

// background.js
chrome.contextMenus.create({
    title: '使用度娘搜索:%s', // %s表示选中的文字
    contexts: ['selection'], // 只有当选中文字时才会出现此右键菜单
    onclick: function(params)
    {
        // 注意不能使用location.href,因为location是属于background的window对象
        chrome.tabs.create({url: 'https://www.baidu.com/s?ie=utf-8&wd=' + encodeURI(params.selectionText)});
    }
});

image-1652692589255

  • 语法说明
chrome.contextMenus.create({
    type: 'normal', // 类型,可选:["normal", "checkbox", "radio", "separator"],默认 normal
    title: '菜单的名字', // 显示的文字,除非为“separator”类型否则此参数必需,如果类型为“selection”,可以使用%s显示选定的文本
    contexts: ['page'], // 上下文环境,可选:["all", "page", "frame", "selection", "link", "editable", "image", "video", "audio"],默认page
    onclick: function(){}, // 单击时触发的方法
    parentId: 1, // 右键菜单项的父菜单项ID。指定父菜单项将会使此菜单项成为父菜单项的子菜单
    documentUrlPatterns: 'https://*.baidu.com/*' // 只在某些页面显示此右键菜单
});
// 删除某一个菜单项
chrome.contextMenus.remove(menuItemId);
// 删除所有自定义右键菜单
chrome.contextMenus.removeAll();
// 更新某一个菜单项
chrome.contextMenus.update(menuItemId, updateProperties);

5.4、override(覆盖特定页面)

使用override页可以将Chrome默认的一些特定页面替换掉,改为使用扩展提供的页面。

  • 历史记录:从工具菜单上点击历史记录时访问的页面,或者从地址栏直接输入 chrome://history

  • 新标签页:当创建新标签的时候访问的页面,或者从地址栏直接输入 chrome://newtab

  • 书签:浏览器的书签,或者直接输入 chrome://bookmarks
    注意:

  • 一个扩展只能替代一个页面;

  • 不能替代隐身窗口的新标签页;

  • 网页必须设置title,否则用户可能会看到网页的URL,造成困扰;

"chrome_url_overrides":
{
    "newtab": "newtab.html",
    "history": "history.html",
    "bookmarks": "bookmarks.html"
}

5.5、devtools(开发者工具)

Chrome允许插件在开发者工具(devtools)上动手脚,主要表现在:

  • 自定义一个和多个和Elements、Console、Sources等同级别的面板;
  • 自定义侧边栏(sidebar),目前只能自定义Elements面板的侧边栏;
    image-1652692860908

打开一个开发者工具窗口,都会创建devtools页面的实例,F12窗口关闭,页面也随着关闭,所以devtools页面的生命周期和devtools窗口是一致的。devtools页面可以访问一组特有的DevTools API以及有限的扩展API,这组特有的DevTools API只有devtools页面才可以访问,background都无权访问,这些API包括:

  • chrome.devtools.panels:面板相关;

  • chrome.devtools.inspectedWindow:获取被审查窗口的有关信息;

  • chrome.devtools.network:获取有关网络请求的信息;

  • 实例一

{
    // 只能指向一个HTML文件,不能是JS文件
    "devtools_page": "devtools.html"
}

这个devtools.html里面一般什么都没有,就引入一个js:

<!DOCTYPE html>
<html>
<head></head>
<body>
    <script type="text/javascript" src="js/devtools.js"></script>
</body>
</html>
// 创建自定义面板,同一个插件可以创建多个自定义面板
// 几个参数依次为:panel标题、图标(其实设置了也没地方显示)、要加载的页面、加载成功后的回调
chrome.devtools.panels.create('MyPanel', 'img/icon.png', 'mypanel.html', function(panel)
{
    console.log('自定义面板创建成功!'); // 注意这个log一般看不到
});

// 创建自定义侧边栏
chrome.devtools.panels.elements.createSidebarPane("Images", function(sidebar)
{
    // sidebar.setPage('../sidebar.html'); // 指定加载某个页面
    sidebar.setExpression('document.querySelectorAll("img")', 'All Images'); // 通过表达式来指定
    //sidebar.setObject({aaa: 111, bbb: 'Hello World!'}); // 直接设置显示某个对象
});
  • 实例二:详细功能
// 检测jQuery
document.getElementById('check_jquery').addEventListener('click', function()
{
    // 访问被检查的页面DOM需要使用inspectedWindow
    // 简单例子:检测被检查页面是否使用了jQuery
    chrome.devtools.inspectedWindow.eval("jQuery.fn.jquery", function(result, isException)
    {
        var html = '';
        if (isException) html = '当前页面没有使用jQuery。';
        else html = '当前页面使用了jQuery,版本为:'+result;
        alert(html);
    });
});

// 打开某个资源
document.getElementById('open_resource').addEventListener('click', function()
{
    chrome.devtools.inspectedWindow.eval("window.location.href", function(result, isException)
    {
        chrome.devtools.panels.openResource(result, 20, function()
        {
            console.log('资源打开成功!');
        });
    });
});

// 审查元素
document.getElementById('test_inspect').addEventListener('click', function()
{
    chrome.devtools.inspectedWindow.eval("inspect(document.images[0])", function(result, isException){});
});

// 获取所有资源
document.getElementById('get_all_resources').addEventListener('click', function()
{
    chrome.devtools.inspectedWindow.getResources(function(resources)
    {
        alert(JSON.stringify(resources));
    });
});

image-1652693057334

由于devtools本身就是开发者工具页面,所以几乎没有方法可以直接调试它,直接用 chrome-extension://extid/devtools.html"的方式打开页面肯定报错,因为不支持相关特殊API,只能先自己写一些方法屏蔽这些错误,调试通了再放开。

5.6、option(选项页)

所谓options页,就是插件的设置页面,有2个入口,一个是右键图标有一个“选项”菜单,还有一个在插件管理页面:

{
    "options_ui":
    {
        "page": "options.html",
        // 添加一些默认的样式,推荐使用
        "chrome_style": true
    },
}

几点注意:

  • 为了兼容,建议2种都写,如果都写了,Chrome40以后会默认读取新版的方式;
  • 新版options中不能使用alert;
  • 数据存储建议用chrome.storage,因为会随用户自动同步;

5.7、omnibox

omnibox是向用户提供搜索建议的一种方式。先来看个gif图以便了解一下这东西到底是个什么鬼:
image-1652693447962

  • 配置
{
    // 向地址栏注册一个关键字以提供搜索建议,只能设置一个关键字
    "omnibox": { "keyword" : "go" },
}
  • background.js中注册监听事件:
// omnibox 演示
chrome.omnibox.onInputChanged.addListener((text, suggest) => {
    console.log('inputChanged: ' + text);
    if(!text) return;
    if(text == '美女') {
        suggest([
            {content: '中国' + text, description: '你要找“中国美女”吗?'},
            {content: '日本' + text, description: '你要找“日本美女”吗?'},
            {content: '泰国' + text, description: '你要找“泰国美女或人妖”吗?'},
            {content: '韩国' + text, description: '你要找“韩国美女”吗?'}
        ]);
    }
    else if(text == '微博') {
        suggest([
            {content: '新浪' + text, description: '新浪' + text},
            {content: '腾讯' + text, description: '腾讯' + text},
            {content: '搜狐' + text, description: '搜索' + text},
        ]);
    }
    else {
        suggest([
            {content: '百度搜索 ' + text, description: '百度搜索 ' + text},
            {content: '谷歌搜索 ' + text, description: '谷歌搜索 ' + text},
        ]);
    }
});

// 当用户接收关键字建议时触发
chrome.omnibox.onInputEntered.addListener((text) => {
    console.log('inputEntered: ' + text);
    if(!text) return;
    var href = '';
    if(text.endsWith('美女')) href = 'http://image.baidu.com/search/index?tn=baiduimage&ie=utf-8&word=' + text;
    else if(text.startsWith('百度搜索')) href = 'https://www.baidu.com/s?ie=UTF-8&wd=' + text.replace('百度搜索 ', '');
    else if(text.startsWith('谷歌搜索')) href = 'https://www.google.com.tw/search?q=' + text.replace('谷歌搜索 ', '');
    else href = 'https://www.baidu.com/s?ie=UTF-8&wd=' + text;
    openUrlCurrentTab(href);
});
// 获取当前选项卡ID
function getCurrentTabId(callback)
{
    chrome.tabs.query({active: true, currentWindow: true}, function(tabs)
    {
        if(callback) callback(tabs.length ? tabs[0].id: null);
    });
}

// 当前标签打开某个链接
function openUrlCurrentTab(url)
{
    getCurrentTabId(tabId => {
        chrome.tabs.update(tabId, {url: url});
    })
}

5.8、桌面通知

Chrome提供了一个chrome.notificationsAPI以便插件推送桌面通知,暂未找到chrome.notifications和HTML5自带的Notification的显著区别及优势。

chrome.notifications.create(null, {
    type: 'basic',
    iconUrl: 'img/icon.png',
    title: '这是标题',
    message: '您刚才点击了自定义右键菜单!'
});

六、5种类型的JS对比

Chrome插件的JS主要可以分为这5类:injected script、content-script、popup js、background js和devtools js,

6.1、权限对比

JS种类 可访问的API DOM访问情况 JS访问情况 直接跨域
injected 和普通JS无任何差别 可以 可以 不可以
content 只能访问 extension、runtime等部分API 可以访问 不可以 不可以
popup 可访问绝大部分API,除了devtools系列 不可直接访问 不可以 可以
background 可访问绝大部分API,除了devtools系列 不可直接访问 不可以 可以
devtools 只能访问 devtools、extension、runtime等部分API 可以 可以 不可以

七、消息通信

通信主页:https://developer.chrome.com/extensions/messaging

前面我们介绍了Chrome插件中存在的5种JS,那么它们之间如何互相通信呢?下面先来系统概况一下,然后再分类细说。需要知道的是,popup和background其实几乎可以视为一种东西,因为它们可访问的API都一样、通信机制一样、都可以跨域。

7.1、通信详细介绍

1、popup和background

  • popup可以直接调用background中的JS方法,也可以直接访问background的DOM:
// background.js
function test()
{
    alert('我是background!');
}

// popup.js
var bg = chrome.extension.getBackgroundPage();
bg.test(); // 访问bg的函数
alert(bg.document.body.innerHTML); // 访问bg的DOM
  • 至于background访问popup如下(前提是popup已经打开)
    var views = chrome.extension.getViews({type:‘popup’});
    if(views.length > 0) {
    console.log(views[0].location.href);
    }

2、popup或者bg向content主动发送消息

function sendMessageToContentScript(message, callback)
{
    chrome.tabs.query({active: true, currentWindow: true}, function(tabs)
    {
        chrome.tabs.sendMessage(tabs[0].id, message, function(response)
        {
            if(callback) callback(response);
        });
    });
}
sendMessageToContentScript({cmd:'test', value:'你好,我是popup!'}, function(response)
{
    console.log('来自content的回复:'+response);
});

content-script.js接收:

chrome.runtime.onMessage.addListener(function(request, sender, sendResponse)
{
    // console.log(sender.tab ?"from a content script:" + sender.tab.url :"from the extension");
    if(request.cmd == 'test') alert(request.value);
    sendResponse('我收到了你的消息!');
});

3、content-script主动发消息给后台

content-script.js:

chrome.runtime.sendMessage({greeting: '你好,我是content-script呀,我主动发消息给后台!'}, function(response) {
    console.log('收到来自后台的回复:' + response);
});

background.js 或者 popup.js:

// 监听来自content-script的消息
chrome.runtime.onMessage.addListener(function(request, sender, sendResponse)
{
    console.log('收到来自content-script的消息:');
    console.log(request, sender, sendResponse);
    sendResponse('我是后台,我已收到你的消息:' + JSON.stringify(request));
});

注意事项:

  • content_scripts向popup主动发消息的前提是popup必须打开!否则需要利用background作中转;
  • 如果background和popup同时监听,那么它们都可以同时收到消息,但是只有一个可以sendResponse,一个先发送了,那么另外一个再发送就无效;

4、injected script和content-script

content-script和页面内的脚本(injected-script自然也属于页面内的脚本)之间唯一共享的东西就是页面的DOM元素,有2种方法可以实现二者通讯:

  • 可以通过window.postMessage和window.addEventListener来实现二者消息通讯;
  • 通过自定义DOM事件来实现;

这里将第一种,也是推荐的
injected-script中:

window.postMessage({"test": '你好!'}, '*');

content script中:

window.addEventListener("message", function(e)
{
    console.log(e.data);
}, false);

7.2、长连接和短连接

短连接的话就是挤牙膏一样,我发送一下,你收到了再回复一下,如果对方不回复,你只能重新发,而长连接类似WebSocket会一直建立连接,双方可以随时互发消息。

其实上面已经涉及到了,这里再单独说明一下。Chrome插件中有2种通信方式,一个是短连接(chrome.tabs.sendMessage和chrome.runtime.sendMessage),一个是长连接(chrome.tabs.connect和chrome.runtime.connect)。

popup.js:

getCurrentTabId((tabId) => {
    var port = chrome.tabs.connect(tabId, {name: 'test-connect'});
    port.postMessage({question: '你是谁啊?'});
    port.onMessage.addListener(function(msg) {
        alert('收到消息:'+msg.answer);
        if(msg.answer && msg.answer.startsWith('我是'))
        {
            port.postMessage({question: '哦,原来是你啊!'});
        }
    });
});

content-script.js:

// 监听长连接
chrome.runtime.onConnect.addListener(function(port) {
    console.log(port);
    if(port.name == 'test-connect') {
        port.onMessage.addListener(function(msg) {
            console.log('收到长连接消息:', msg);
            if(msg.question == '你是谁啊?') port.postMessage({answer: '我是你爸!'});
        });
    }
});

八、其它补充

虽然在background和popup中无法直接访问页面DOM,但是可以通过chrome.tabs.executeScript来执行脚本,从而实现访问web页面的DOM(注意,这种方式也不能直接访问页面JS)。

8.1、动态注入或执行JS

示例manifest.json配置:

{
    "name": "动态JS注入演示",
    ...
    "permissions": [
        "tabs", "http://*/*", "https://*/*"
    ],
    ...
}

js

// 动态执行JS代码
chrome.tabs.executeScript(tabId, {code: 'document.body.style.backgroundColor="red"'});
// 动态执行JS文件
chrome.tabs.executeScript(tabId, {file: 'some-script.js'});

8.2、动态注入CSS

示例manifest.json配置:

{
    "name": "动态CSS注入演示",
    ...
    "permissions": [
        "tabs", "http://*/*", "https://*/*"
    ],
    ...
}
// 动态执行CSS代码,TODO,这里有待验证
chrome.tabs.insertCSS(tabId, {code: 'xxx'});
// 动态执行CSS文件
chrome.tabs.insertCSS(tabId, {file: 'some-style.css'});

8.3、获取当前窗口ID

chrome.windows.getCurrent(function(currentWindow)
{
    console.log('当前窗口ID:' + currentWindow.id);
});

8.4、获取当前标签页ID

  • 第一种
// 获取当前选项卡ID
function getCurrentTabId(callback)
{
    chrome.tabs.query({active: true, currentWindow: true}, function(tabs)
    {
        if(callback) callback(tabs.length ? tabs[0].id: null);
    });
}
  • 第二种
// 获取当前选项卡ID
function getCurrentTabId2()
{
    chrome.windows.getCurrent(function(currentWindow)
    {
        chrome.tabs.query({active: true, windowId: currentWindow.id}, function(tabs)
        {
            if(callback) callback(tabs.length ? tabs[0].id: null);
        });
    });
}

8.5、本地存储

本地存储建议用chrome.storage而不是普通的localStorage,区别有好几点,个人认为最重要的2点区别是:

  • chrome.storage是针对插件全局的,即使你在background中保存的数据,在content-script也能获取到;
  • chrome.storage.sync可以跟随当前登录用户自动同步,这台电脑修改的设置会自动同步到其它电脑,很方便,如果没有登录或者未联网则先保存到本地,等登录了再同步至网络;
    需要声明storage权限,有chrome.storage.sync和chrome.storage.local2种方式可供选择,使用示例如下:
// 读取数据,第一个参数是指定要读取的key以及设置默认值
chrome.storage.sync.get({color: 'red', age: 18}, function(items) {
    console.log(items.color, items.age);
});
// 保存数据
chrome.storage.sync.set({color: 'blue'}, function() {
    console.log('保存成功!');
});

8.6、webRequest

通过webRequest系列API可以对HTTP请求进行任性地修改、定制,这里通过beforeRequest来简单演示一下它的冰山一角:

//manifest.json
{
    // 权限申请
    "permissions":
    [
        "webRequest", // web请求
        "webRequestBlocking", // 阻塞式web请求
        "storage", // 插件本地存储
        "http://*/*", // 可以通过executeScript或者insertCSS访问的网站
        "https://*/*" // 可以通过executeScript或者insertCSS访问的网站
    ],
}


// background.js
// 是否显示图片
var showImage;
chrome.storage.sync.get({showImage: true}, function(items) {
    showImage = items.showImage;
});
// web请求监听,最后一个参数表示阻塞式,需单独声明权限:webRequestBlocking
chrome.webRequest.onBeforeRequest.addListener(details => {
    // cancel 表示取消本次请求
    if(!showImage && details.type == 'image') return {cancel: true};
    // 简单的音视频检测
    // 大部分网站视频的type并不是media,且视频做了防下载处理,所以这里仅仅是为了演示效果,无实际意义
    if(details.type == 'media') {
        chrome.notifications.create(null, {
            type: 'basic',
            iconUrl: 'img/icon.png',
            title: '检测到音视频',
            message: '音视频地址:' + details.url,
        });
    }
}, {urls: ["<all_urls>"]}, ["blocking"]);

8.7、国际化

插件根目录新建一个名为_locales的文件夹,再在下面新建一些语言的文件夹,如en、zh_CN、zh_TW,然后再在每个文件夹放入一个messages.json,同时必须在清单文件中设置default_locale。

  • _locales\en\messages.json内容:
{
    "pluginDesc": {"message": "A simple chrome extension demo"},
    "helloWorld": {"message": "Hello World!"}
}
  • _locales\zh_CN\messages.json内容:
{
    "pluginDesc": {"message": "一个简单的Chrome插件demo"},
    "helloWorld": {"message": "你好啊,世界!"}
}
  • 在manifest.json和CSS文件中通过__MSG_messagename__引入,如:
{
    "description": "__MSG_pluginDesc__",
    // 默认语言
    "default_locale": "zh_CN",
}

JS中则直接chrome.i18n.getMessage(“helloWorld”)。

测试时,通过给chrome建立一个不同的快捷方式chrome.exe --lang=en来切换语言,如:
image-1652695383500

8.8、API总结

chrome.tabs
chrome.runtime
chrome.webRequest
chrome.window
chrome.storage
chrome.contextMenus
chrome.devtools
chrome.extension

九、经验

查看已安装插件路径

已安装的插件源码路径:C:\Users\用户名\AppData\Local\Google\Chrome\User Data\Default\Extensions,每一个插件被放在以插件ID为名的文件夹里面,想要学习某个插件的某个功能是如何实现的,看人家的源码是最好的方法了:

不知道这个ID,就勾选开发这模式即可
image-1652695520996

特别注意background的报错

很多时候你发现你的代码会莫名其妙的失效,找来找去又找不到原因,这时打开background的控制台才发现原来某个地方写错了导致代码没生效.
正式由于background报错的隐蔽性(需要主动打开对应的控制台才能看到错误),所以特别注意这点。

如何让popup页面不关闭

在对popup页面审查元素的时候popup会被强制打开无法关闭,只有控制台关闭了才可以关闭popup,原因很简单:如果popup关闭了控制台就没用了。这种方法在某些情况下很实用!

不支持内联JavaScript的执行

也就是不支持将js直接写在html中,比如:

<input id="btn" type="button" value="收藏" onclick="test()"/>
报错如下:

Refused to execute inline event handler because it violates the following Content Security Policy directive: "script-src 'self' blob: filesystem: chrome-extension-resource:". Either the 'unsafe-inline' keyword, a hash ('sha256-...'), or a nonce ('nonce-...') is required to enable inline execution.
解决方法就是用JS绑定事件:
$('#btn').on('click', function(){alert('测试')});

注入CSS的时候必须小心

由于通过content_scripts注入的CSS优先级非常高,几乎仅次于浏览器默认样式,稍不注意可能就会影响一些网站的展示效果,所以尽量不要写一些影响全局的样式。

之所以强调这个,是因为这个带来的问题非常隐蔽,不太容易找到,可能你正在写某个网页,昨天样式还是好好的,怎么今天就突然不行了?然后你辛辛苦苦找来找去,找了半天才发现竟然是因为插件里面的一个样式影响的!

后尾

到了这里就结束了,如果你觉得能够帮助到你,请帮作者的插件给个五星好评
最后贴一张高清谷歌图

image-1652695812863