Если вы хотите создать расширение Chrome со сложными функциями, скорее всего, вам нужно, чтобы ваши фоновые скрипты (background), скрипты контента (content) и скрипты всплывающих окон (popup) работали вместе и реагировали друг на друга.
Это небольшое руководстве о том, как мы можем отправлять данные между различными компонентами расширения Chrome с помощью разовых сообщений и долгоживущих соединений.
Прежде чем читать дальше и экспериментировать, вы должны понимать, как создавать расширение Chrome и загружать его в браузере для тестирования.
Примеры написаны для расширения, использующее Manifest v3. Его большое отличие от v2 - возможность использовать async/await. Однако если вы используете v2, просто применяйте обратные вызовы вместо async/await.
Краткий обзор архитектуры расширения
Чтобы понять, как работает передача сообщений между различными компонентами, важно понимать архитектуру расширения и знать, за что отвечает каждая часть.
В расширении нам будут важны 3 основных компонента:
- Элементы пользовательского интерфейса (popup.html + popup.js)
- Скрипт background
- Скрипт content
Элемент пользовательского интерфейса - это то, что отображается при нажатии на значок расширения. Как правило это некий попап-окошко поверх страницы, который может содержать файл popup.js, делающий его интерактивным для пользователя. Однако, поскольку логика JavaScript используется только для всплывающего окна, ее нельзя применить к фактической веб-странице, которую пользователь просматривает в данный момент.
Чтобы манипулировать самой веб-страницей, нам нужно использовать скрипт content.js. Этот скрипт будет выполняться в контексте страницы, загружаемой в браузер, поэтому он очень полезен, если мы хотим изменить определенную информацию или внешний вид сайта.
Наконец, скрипт background.js (он же сервис-воркер в v3) в основном используется для обработки событий. Он загружается один раз и остается бездействующим, если не происходит никакого интересного события. Он не может напрямую обращаться к DOM, но может быть очень полезен для таких целей, как перехват исходящих и входящих запросов к сайту.
Как видите, все 3 компонента имеют разные цели и имеют доступ к разным вещам в браузере. Поэтому нам нужно заставить их общаться друг с другом и реагировать на разные сообщения для достижения полноценной функциональности расширения.
Последняя ключевая вещь, которую следует напомнить, это то, что content-скрипт работает в контексте текущей активной вкладки. Поэтому каждый раз, когда мы хотим связаться со скриптом popup или background, нам нужно указать, на какую вкладку отправлять сообщение, и это касается как разовых сообщений, так и долгоживущих соединений.
Разовые сообщения
Разовые сообщения полезны, если вы хотите отправить одно сообщение в другие части расширения. Вы можете отправить разовый запрос из content-скрипта в popup или наоборот, и отреагировать ответным сообщением.
На высоком уровне идея состоит в том, что один из скриптов будет отправителем сообщения, а получатель настроит прослушиватель любых входящих сообщений. Когда сообщение получено, прослушиватель запускается и может дополнительно отправить ответ отправителю сообщения. Слушатель добавляется с помощью команды chrome.runtime.onMessage.addListener.
В зависимости от получателя, Chrome предоставляет нам 2 типа методов: chrome.runtime.sendMessage и chrome.tabs.sendMessage. Важно знать, когда какой метод использовать.
Когда использовать chrome.tabs.sendMessage
При отправке сообщения content-скрипту нам нужно указать, на какую вкладку его отправить. Поэтому нам нужно сначала получить информацию об активной вкладке, а затем использовать chrome.tabs.sendMessage. Чтобы использовать API вкладок и иметь доступ к активной вкладке, необходимо добавить поля tabs и activeTab в файле manifest.json.
// popup.js
const sendMessageButton = document.getElementById('sendMessage')
sendMessageButton.onclick = async function(e) {
let queryOptions = { active: true, currentWindow: true };
let tab = await chrome.tabs.query(queryOptions);
let message = {name: "Ivan"};
chrome.tabs.sendMessage(tab[0].id, message, function(response) {
console.log(response.status);
});
}
// content.js
chrome.runtime.onMessage.addListener(
function(request, sender, sendResponse) {
if (request.name === "Ivan") {
let user = document.getElementById("userName");
user.innerHTML = request.name;
sendResponse({status: "done"});
}
}
);
Приведенный выше код показывает, как скрипт расширения popup может вызвать изменение внешнего вида сайта. Когда пользователь нажимает sendMessageButton, обработчик запрашивает текущую активную вкладку и отправляет сообщение в скрипт content для этой вкладки с помощью chrome.tabs.sendMessage.
Метод принимает 3 параметра:
- ID активной вкладки
- Сообщение (может быть объектом JSON)
- Функция, которая запускается после ответа получателя
На стороне скрипта content, который является получателем, необходимо прослушивать входящие сообщения. Это делается с помощью команды chrome.runtime.onMessage.addListener. Функция слушателя - это функция, которая имеет три параметра: request, sender, sendResponse.
Параметр request - это отправленное сообщение, sender - это объект, содержащий информацию о контексте скрипта, который отправил сообщение, а sendResponse - это функция, которая принимает JSON-объект для ответа отправителю.
Когда использовать chrome.runtime.sendMessage
Если вы отправляете сообщения из content-скрипта в popup-скрипт, достаточно будет использовать chrome.runtime.sendMessage. Этот метод не принимает идентификатор вкладки в качестве первого параметра, но остальная часть сигнатуры функции такая же, как у chrome.tabs.sendMessage. Код стороны получателя остается прежним: chrome.runtime.onMessage.addListener.
У вас также может быть несколько элементов в popup.js, отправляющих разные сообщения получателю для различных действий на активном в данный момент сайте.
Долгоживущие соединения
Долгоживущие соединения позволяют открывать соединение, которое длится дольше, чем одиночный запрос. Это можно осуществить, используя chrome.runtime.connect и chrome.tabs.connect.
Пример открытия соединения из popup в content:
// popup.js
const sendMessageButton = document.getElementById('sendMessage');
sendMessageButton.onclick = async function(e) {
let queryOptions = { active: true, currentWindow: true };
let tabs = await chrome.tabs.query(queryOptions);
// Открываем соединение
const port = chrome.tabs.connect(tabs[0].id, {
name: "qwerty",
});
// Возьмем значение input и отправим его
const inputText = document.getElementById('input')
port.postMessage({
text: inputText.value
});
port.onMessage.addListener(function(msg) {
if (msg.exists) {
console.log('В слове больше 5ти букв');
} else {
console.log('В слове не больше 5ти букв');
}
})
}
Как видим, мы открываем соединение с content-скриптом активной вкладки и передаем объект с полем name. Это поле позволяет нам различать несколько открытых соединений (если они есть у расширения). Метод connect возвращает объект port и мы можем его использовать для отправки сообщений с помощью port.postMessage и, при желании, добавить прослушиватель onMessage для ответов.
Далее, мы отправляем сообщение принимающей стороне (content-скрипту). Сообщение представляет собой объект с любыми данными, которые вы хотите. В нашем случае это значение поля inputText.
В конце мы настраиваем прослушиватель onMessage, который будет запускаться при получении ответа. В примере будем ожидать значение поля exists, после чего выведем соответствующее сообщение.
// content.js
chrome.runtime.onConnect.addListener(function (port) {
port.onMessage.addListener(function (msg) {
if (port.name === "qwerty") {
const text = msg.text;
if (text.length > 5) {
port.postMessage({
exists: true,
});
} else {
port.postMessage({
exists: false,
});
}
}
});
});
Чтобы прослушивать входящие сообщения, необходимо реализовать функцию прослушивателя сообщений внутри прослушивателя событий chrome.runtime.onConnect.
Когда в скрипте popup запускается метод .connect, в скрипте content отрабатывает событие onConnect и объект port включается в прослушиватель. А его мы можем использовать для добавления прослушивателей сообщений с помощью chrome.onMessage.addListener или для ответа с помощью chrome.port.postMessage.
Также мы проверяем ключ name объекта port, поскольку у нас могут быть другие соединения с другими значениями name, и в зависимости от того, какой нам нужен, определяем дальнейшие действия.
В нашем примере, мы берем значение параметра text из принятого объекта и определяем количество его символов. В зависимости от этого, отправляем соответствующее сообщение обратно отправителю. Это запустит прослушиватель onMessage, определенный в popup.js, завершив цикл связи.
Комментарии (1)