
Эта статья изначально была опубликована в моем личном блоге.
В этом руководстве мы расскажем, как сделать снимок экрана в расширении Chrome и сохранить его на компьютере пользователя. Это руководство требует от новичка навыков работы с Javascript.
Мы создадим расширение, которое позволит пользователю сделать снимок экрана, просто щелкнув значок на панели инструментов. Пользователь может сделать снимок экрана всего экрана, только окна или текущей вкладки.
Обратите внимание, что это расширение будет использовать Manifest V3. Я буду давать несколько подсказок о различиях между V3 и V2 на протяжении всего руководства, но если вы хотите узнать больше о различиях между двумя версиями, вы можете ознакомиться с этим руководством.
Вы можете найти код этого руководства в этом репозитории GitHub.
Создание расширения
Мы не будем вдаваться в подробности о том, как создать расширение Chrome, поскольку это не является целью. Если вам нужно узнать больше об этом, вы можете ознакомиться с этим руководством.
Createmanifest.jsonв корне каталога расширений со следующим содержимым:
{
"name": "Screenshots",
"version": "0.0.1",
"description": "Take screenshots",
"manifest_version": 3,
"action": {
"default_title": "Take a Screenshot"
},
"icons": {
"16": "/assets/icon-16.png",
"32": "/assets/icon-32.png",
"48": "/assets/icon-48.png",
"128": "/assets/icon-128.png"
}
}
Иконки, которые мы используем для этого расширения, принадлежат BZZRICON Studio на Iconscout.
Для Manifest V2 убедитесь, что для themanifest_version установлено значение 2:
"manifest_version": 2
и обязательно replaceactionwithbrowser_action:
"browser_action": {
"default_title": "Take a Screenshot"
}
Затем создайте zip-архив, перейдите в chrome: // extensions, включите режим разработчика в правом верхнем углу, если он не включен, нажмите «Загрузить распакованный» с помощью кнопок слева и выберите каталог расширения. Наше расширение будет успешно добавлено.
Добавить Service Worker (или фоновый скрипт)
Чтобы определить, когда пользователь нажимает на значок расширения, нам нужно прикрепить прослушиватель событий кchrome.action.onClicked. Для этого нам нужно добавить сервис-воркера (или фоновый скрипт для V2).
Чтобы добавить сервис-воркера, добавьте следующий inmanifest.json:
"background": {
"service_worker": "background.js"
},
Или следующее для V2:
"background": {
"scripts": ["background.js"],
"persistent": false
},
Затем создайте в корне расширения createbackground.js со следующим содержимым:
chrome.action.onClicked.addListener(function (tab) {
})
для V2 должно быть следующее:
chrome.browserAction.onClicked.addListener(function (tab) {
})
Обратите внимание: если у вас нетactionkey inmanifest.json, вы не сможете добавить слушателя вonClicked.
Затем мы начнем процесс сделать снимок экрана. Для этого воспользуемся Desktop Capture API. В частности, мы будем использовать methodchrome.desktopCapture.chooseDesktopMediawhich принимает 3 параметра: первый - это массив строк источников захвата, которые могут быть screen, window, tab и audio. Второй параметр - это целевая вкладка, которая является необязательной, однако в некоторых случаях, если целевая вкладка не передана, Chrome выдает сбой. Третий параметр - это обратный вызов, который возвращает идентификатор потока, который мы будем использовать позже, чтобы получить снимок экрана.
добавьте в слушателя следующее:
chrome.desktopCapture.chooseDesktopMedia([
"screen",
"window",
"tab"
], tab, (streamId) => {
//check whether the user canceled the request or not
if (streamId && streamId.length) {
}
})
Обратите внимание, что мы передаем первый параметр screen, window и tab в качестве разрешенных типов источников. Второй параметр - это thetabparameter, переданный слушателю, а третий - функция обратного вызова. Мы проверяем, не является ли ifstreamId пустым, поскольку оно будетempty, если пользователь отменит запрос.
Однако, прежде чем мы сможем использовать это, нам нужно добавить некоторые разрешения в themanifest.json. Разрешения позволяют пользователю понять, что делает расширение, и согласиться с ним до того, как оно будет установлено в его браузере.
Добавьте вmanifest.json следующее:
"permissions":["desktopCapture","tabs"],
Причина, по которой нам также нужно thetabspermission, заключается в том, что, если у нас нет разрешения, объектtab, переданный слушателю theonClickedevent, не будет иметьurlparameter, который требуется дляchooseDesktopMedia при передаче этой вкладки в качестве параметра.
Итак, если вы перезагрузите расширение сейчас и нажмете значок, вы увидите, что оно спросит вас, какой экран вы хотите записать, и все. Далее нам нужно использовать thestreamId, чтобы получить снимок экрана.
Добавить сценарий содержимого
Чтобы получить поток от thestreamId, нам нужно использовать getUserMedia. Однако в сервис-воркере это недоступно. Итак, нам нужно создать сценарий содержимого, который получает сообщение от работника службы с идентификатором потока, а затем получает снимок экрана из потока.
Чтобы добавить сценарий содержимого, добавьте вmanifest.json следующую строку:
"content_scripts": [
{
"matches": ["<all_urls>"],
"js": ["content_script.js"]
}
]
Затем создайте в корне расширения createcontent_script.js со следующим содержимым:
chrome.runtime.onMessage.addListener((message, sender, senderResponse) => {
if (message.name === 'stream' && message.streamId) {
}
});
Этот код прослушивает событие «onMessage» и проверяет, имеет ли themessagereceivednameproperty, равноеstream, и имеет лиstreamIdproperty, затем мы получим поток и сделаем его снимок экрана.
Внутри if мы используем usegetUserMediawhich, который возвращает обещание, которое преобразуется в MediaStream:
let track, canvas
navigator.mediaDevices.getUserMedia({
video: {
mandatory: {
chromeMediaSource: 'desktop',
chromeMediaSourceId: message.streamId
},
}
}).then((stream) => {
})
Обратите внимание, что параметр, который мы передали togetUserMedia, принимает объект параметров. Мы передаем thechromeMediaSourcewhich равняется todesktop, andchromeMediaSourceIdwhich равняется идентификатору потока, который мы получили.
Затем внутри функции обратного вызова для обработанного обещания мы получим MediaStreamTrack, а затем сделаем из него снимок экрана с помощью API ImageCapture:
track = stream.getVideoTracks()[0]
const imageCapture = new ImageCapture(track)
return imageCapture.grabFrame()
В конце концов, мы возвращаем значениеimageCapture.grabFrame, которое возвращает Promise, которое разрешается в ImageBitmap. Обратите внимание, что мы не использовали thetakePhotomethod of theImageCaptureAPI. Причина этого в том, что известны случаи возникновения исключения DOMException при его использовании, и это обходной путь для этого.
Затем мы присоединим anotherthenmethod для обработки возвращенного обещания fromimageCapture.grabFrame. Функция обратного вызова остановит поток, создаст холст и нарисует в нем ImageBitmap, а затем получит URL-адрес данных холста:
.then((bitmap) => {
track.stop();
canvas = document.createElement('canvas');
canvas.width = bitmap.width;
canvas.height = bitmap.height;
let context = canvas.getContext('2d');
context.drawImage(bitmap, 0, 0, bitmap.width, bitmap.height);
return canvas.toDataURL();
})
Обратите внимание, что важно установить ширину и высоту холста равными значению thebitmap. Если мы этого не сделаем, высота и ширина холста по умолчанию будут равны200px, а если ширина или высота растрового изображения больше этого значения, снимок экрана будет обрезан.
В итоге мы возвращаемcanvas.toDataUrl. Мы присоединим lastthenmethod, который принимает URL-адрес, возвращаемый в качестве параметра. Этот URL-адрес будет использоваться для загрузки изображения на устройство пользователя:
.then((url) => {
//TODO download the image from the URL
}).catch((err) => {
alert("Could not take screenshot")
senderResponse({success: false, message: err})
})
Обратите внимание, что мы также добавилиcatch, чтобы отлавливать любые ошибки. Как вы можете видеть в thecatchcallback, мы вызываем functionsenderResponse. Эту функцию мы передаем от сервис-воркера или фонового скрипта в контентный скрипт при отправке сообщения.
В конце theifblock мы добавим следующее:
return true;
В прослушивателе onMessageevent, если прослушиватель возвращает истину, это означает, что позже мы вернем ответ отправителю, используя функцию обратного вызова, которую они передали при отправке сообщения.
Скачать снимок экрана
Чтобы скачать скриншот, воспользуемся API Загрузки. Он предоставляет множество методов для управления загрузками, таких как поиск, открытие, удаление и многое другое.
Прежде чем мы сможем использовать какой-либо из методов, нам нужно добавить thedownloadspermission в thepermissionsarray inmanifest.json:
"permissions": [
"desktopCapture",
"tabs",
"downloads"
],
Теперь мы можем использовать методы API загрузок. Мы будем использовать methodchrome.downloads.downloadwhich принимает массив параметров в качестве первого параметра и функцию обратного вызова в качестве второго.
Однако этот метод нельзя вызвать из сценария содержимого. Нам нужно вызвать его из сервис-воркера / фонового скрипта. Итак, когда мы дойдем до theTODOpart в нашем коде ранее, нам нужно отправить сообщение работнику службы с URL-адресом, который мы хотим загрузить.
Чтобы отправить сообщение в расширении, мы используем thechrome.runtime.sendMessagewhich, который принимает в качестве первого параметра сообщение для отправки (которое может быть любого типа) и дополнительную функцию обратного вызова в качестве второго параметра, который является функцией, которую должен получить получатель сообщения. позвоните, чтобы доставить ответ.
Добавьте следующий код вместо theTODOcomment:
.then((url) => {
chrome.runtime.sendMessage({name: 'download', url}, (response) => {
if (response.success) {
alert("Screenshot saved");
} else {
alert("Could not save screenshot")
}
canvas.remove()
senderResponse({success: true})
})
})
Обратите внимание, что мы отправляем message{name: 'download', url} получателю. Поскольку сообщение отправляется каждому слушателю в расширении, хорошо включить свойство сообщения в отправляемое сообщение, чтобы иметь возможность обрабатывать различные сообщения. Мы также отправляем URL-адрес для загрузки изображения.
Вернемся к нашему сервис-работнику. Во-первых, давайте отправим сообщение скрипту содержимого из функции fromchooseDesktopMediacallback, которую мы ранее выполнили:
//check whether the user canceled the request or not
if (streamId && streamId.length) {
setTimeout(() => {
chrome.tabs.sendMessage(tab.id, {name: "stream", streamId}, (response) => console.log(response))
}, 200)
}
Обратите внимание, что для отправки сообщения сценарию содержимого мы используемchrome.tabs.sendMessage. Разница между этим и chrome.runtime.sendMessage заключается в том, что первый отправляет сообщение сценариям содержимого на определенной вкладке, тогда как первый отправляет сообщение всем сценариям в расширении, которые прослушивают theonMessagehandler.
Затем мы добавим слушателя к theonMessageevent, чтобы получить сообщение thedownloadmessage и загрузить файл на компьютер пользователя:
chrome.runtime.onMessage.addListener((message, sender, senderResponse) => {
if (message.name === 'download' && message.url) {
chrome.downloads.download({
filename: 'screenshot.png',
url: message.url
}, (downloadId) => {
senderResponse({success: true})
})
return true;
}
})
Сначала мы проверяем, совпадает лиnameproperty сообщения сdownload, чтобы убедиться, что полученное сообщение является правильным. Затем мы загружаем файл с помощьюchrome.downloads.download, передавая ему объект параметров, который имеет здесь два параметра: filename, который является именем файла для загрузки, иurl, который является URL-адресом для загрузки. В обратном вызове thedownloadsmethod мы вызываем функцию обратного вызова, переданную отправителем.
Наше расширение готово. Снова перейдите в chrome: // extensions и перезагрузите расширение. Затем перейдите на любую страницу, щелкните значок расширения. Вам будет предложено выбрать весь экран, окно или вкладку. После того, как вы выберете, будет сделан снимок экрана и сохранен на вашем компьютере.
Заключение
В этом руководстве мы вскоре узнали, как сделать снимок экрана, и познакомились с некоторыми концепциями расширения Chrome. Если вы хотите узнать больше о расширениях Chrome, обязательно ознакомьтесь с остальными моими уроками о расширениях браузера.
Если вы хотите подключиться и поговорить больше об этой статье или программировании в целом, вы можете найти меня в моей учетной записи Twitter @shahednasserr