0%

浏览器插件

什么是浏览器插件(扩展)?

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

为什么学习浏览器插件开发?

增强浏览器功能,“定制”自己专属浏览器
丰富的api

  • 书签控制;
  • 下载控制;
  • 窗口控制;
  • 标签控制;
  • 网络请求控制,各类事件监听;
  • 自定义原生菜单;
  • 完善的通信机制;
  • 等等

Chrome extension vs Firefox extension?
Firefox插件只能在firefox浏览器中使用。
Chrome除了Chrome、Edge浏览器之外,还可以运行在所有webkit内核的国产浏览器,比如360极速浏览器、360安全浏览器、搜狗浏览器、QQ浏览器等等,Firefox浏览器也对Chrome插件的运行提供了一定的支持。

怎么进行开发调试?

chrome://extensions
edge://extensions/ + 开发者模式

manifest.json + 普通的web开发工具即可

5个核心概念

  1. manifest.json
    详细配置项参见:https://developer.chrome.com/docs/extensions/mv2/manifest/

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    {
    // 清单文件的版本
    "manifest_version": 2,
    // 插件的名称(取package.json中的名称)
    "name": "__MSG_extName__",
    // 插件的版本
    "version": "1.0.0",
    // 插件描述
    "description": "鼠标取词,简化查询流程,即时获取贯众安全威胁情报,提升分析效率。",
    // 网站主页,不要浪费了这个免费广告位
    "homepage_url": "https://tip.komect.com:9099/",
    // 图标
    "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”],
    // 覆盖浏览器默认页面
    "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"
    }
  2. content-scripts
    Chrome插件中向页面注入脚本的一种形式(虽然名为script,其实还可以包括css的),借助content-scripts我们可以实现通过配置的方式轻松向指定页面注入JS和CSS(如果需要动态注入,可以参考下文),最常见的比如:广告屏蔽、页面CSS定制
    run_at为document_start(默认为document_idle)

    1
    2
    3
    4
    document.addEventListener('DOMContentLoaded', async function() {
    initDialog()
    initSearchIcon()
    })
  3. background scripts(后台脚本)
    默认会创建一个background.html,可以理解为常驻的后台页面。
    扩展程序是基于事件的程序,用于修改或增强 Chrome 浏览体验。 事件是浏览器触发器,例如导航到新页面、移除书签或关闭选项卡。 扩展在它们的后台脚本中监视这些事件,然后对指定的指令做出反应。
    后台脚本可以通过配置(persistent: true/false)表示是否一直活跃。保持后台脚本持续活跃的唯一情况是扩展程序使用 chrome.webRequest API 来阻止或修改网络请求。

  4. popup (弹窗)
    browser_action或者page_action中配置

    生命周期短,打开时重新渲染,失去焦点立即关闭。可以直接通过chrome.extension.getBackgroundPage()获取background的window对象

  5. homepage_url (插件主页,天然广告位)

Chrome插件的8种展示形式

  1. browserAction(浏览器右上角)

    popup.html+tooltip(title)+badge(icon)

  2. pageAction(浏览器右上角)

    和browserAction区别在于,不可用是灰色,能根据不同页面个性化启用。(访问百度的时候可用,其他置灰不可用)

3、右键菜单
主要是通过chrome.contextMenusAPI实现

1
2
3
4
5
6
7
8
9
10
11
12
// manifest.json
{"permissions": ["contextMenus"]}

// background.js
chrome.contextMenus.create({
title: “evernote web clipper”,
onclick: function(){
alert('您点击了右键菜单!');
// 新建tab使用百度搜索,注意不能使用location.href,因为location是属于background的window对象
chrome.tabs.create({url: 'https://www.baidu.com/s?ie=utf-8&wd=' + encodeURI(params.selectionText)});
}
});


4、override(覆盖特定页面)
扩展可以替代如下页面:

  • 历史记录:从工具菜单上点击历史记录时访问的页面,或者从地址栏直接输入 chrome://history
  • 新标签页:当创建新标签的时候访问的页面,或者从地址栏直接输入 chrome://newtab
  • 书签:浏览器的书签,或者直接输入 chrome://bookmarks
    1
    2
    3
    4
    5
    6
    "chrome_url_overrides":
    {
    "newtab": "newtab.html",
    "history": "history.html",
    "bookmarks": "bookmarks.html"
    }

5、devtools

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

  • chrome.devtools.panels:面板相关;
  • chrome.devtools.inspectedWindow:获取被审查窗口的有关信息;
  • chrome.devtools.network:获取有关网络请求的信息;
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    // manifest.json
    {
    // 只能指向一个HTML文件,不能是JS文件
    "devtools_page": "devtools.html"
    }

    //devtools.html
    <!DOCTYPE html>
    <html>
    <head></head>
    <body>
    <script type="text/javascript" src="js/devtools.js"></script>
    </body>
    </html>

    //devtools.js
    // 创建自定义面板,同一个插件可以创建多个自定义面板
    // 几个参数依次为: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!'}); // 直接设置显示某个对象
    });

6、option(选项页)
插件的设置页面

1
2
3
4
{
// Chrome40以前的插件配置页写法
"options_page": "options.html",
}

7、omnibox
注册某个关键字+tab,以触发插件自己的搜索建议界面,然后可以任意发挥了。

监听onInputChanged/onInputEntered等 —> 微博(新浪、腾讯、搜狐)—>获取activetab —> 手动改url

8、桌面通知
Chrome提供了一个chrome.notificationsAPI以便插件推送桌面通知。

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

消息通信
Content-script植入页面,直接发送请求涉及跨域。所以需要通过于其他部分通信


通常通过backgroundjs进行通信
popup可以直接调用background中的JS方法,也可以直接访问background的DOM

短连接

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// 发送请求
// content-script 发送请求
chrome.runtime.sendMessage({greeting: "hello"}, function(response) {
console.log(response.farewell);
});
// 插件向content-script发送请求
chrome.tabs.query({active: true, currentWindow: true}, function(tabs) {
chrome.tabs.sendMessage(tabs[0].id, {greeting: "hello"}, function(response) {
console.log(response.farewell);
});
});


//接受请求
chrome.runtime.onMessage.addListener(
function(request, sender, sendResponse) {
console.log(sender.tab ?
"from a content script:" + sender.tab.url :
"from the extension");
if (request.greeting === "hello")
sendResponse({farewell: "goodbye"});
// return true;(异步,否则同步)
}
);


如果有多个页面监听消息(onMessage),谁最先执行sendResponse谁就赢,其余监听的回调会被忽略

长连接(类似WebSocket会一直建立连接,双方可以随时互发消息。)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// 发起长连接runtime.connect 或者 tabs.connect或者runtime.connectNative
// content-script发起
var port = chrome.runtime.connect({name: "knockknock"});
port.postMessage({joke: "Knock knock"});
port.onMessage.addListener(function(msg) {
if (msg.question === "Who's there?")
port.postMessage({answer: "Madame"});
else if (msg.question === "Madame who?")
port.postMessage({answer: "Madame... Bovary"});
});

//监听长连接
chrome.runtime.onConnect.addListener(function(port) {
console.assert(port.name === "knockknock");
port.onMessage.addListener(function(msg) {
if (msg.joke === "Knock knock")
port.postMessage({question: "Who's there?"});
else if (msg.answer === "Madame")
port.postMessage({question: "Madame who?"});
else if (msg.answer === "Madame... Bovary")
port.postMessage({question: "I don't get it."});
});
});

port被设计为浏览器扩展的不同部分之间的双向通信方法。发起一个长连接(runtime.connect 或者 tabs.connect或者runtime.connectNative)时,就会生成一个port

插件之间通信
chrome.runtime.onMessageExternal,需要知道插件的id
chrome.runtime.onConnectExternal
页面之间通信
和本地应用之间通信

存储
chrome.storage.sync
chrome.storage.local
// 监听变化
chrome.storage.onChanged.addListener(function (changes, namespace) {
for (let [key, { oldValue, newValue }] of Object.entries(changes)) {
console.log(
Storage key "${key}" in namespace "${namespace}" changed.,
Old value was "${oldValue}", new value is "${newValue}".
);
}
});

// 获取
chrome.storage.sync.get(‘options’, (data) => {
Object.assign(options, data.options);
optionsForm.debug.checked = Boolean(options.debug);;
});

// 设置
chrome.storage.sync.set({options});

调试
1、background.html
2、popup.html(保持popup不关闭)
3.1、css

3.2 js


常见API总结
比较常用用的一些API系列:

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

打包和发布

生成.crx文件
谷歌账号+谷歌开发者(5$)上传到应用商店。