Расширения для браузера всегда меня восхищали. В современном мире их ценность уже не столь очевидна, в особенности из-за перехода от десктопных версий браузеров к мобильным. Однако мне нравится настраивать посещаемые сайты под свой вкус, к примеру, расширяя степень приватности их использования.
Сегодня я расскажу, как создать полнофункциональное расширение для блокировки рекламы на LinkedIn всего за 10 минут.
На мой взгляд расширения относятся к простейшим в плане создания программам, и я предлагаю сразу перейти к делу.
К сведению: это расширение будет работать только для Chromium-браузеров вроде Chrome, Brave, Opera, Microsoft Edge (да-да, и он тоже) и т.д. Иначе говоря, оно не запустится на Firefox или Safari.
Как обычно, весь код можете найти на GitHub.
Для максимального упрощения наше расширение не будет иметь всплывающего окна, появляющегося при клике по иконке. Вместо этого оно будет просто работать в фоновом режиме.
В таком случае нам понадобится всего 3 файла: manifest.json
, background.js
и linkedin.js
. Итак…
Для начала создаем пустой репозиторий, а в нём и упомянутые файлы.
Теперь рассмотрим их содержимое.
manifest.json
— это единственный жизненно важный для расширения файл. В нашем случае он будет содержать метаданные, необходимые для работы расширения, а также скрипт, который это расширение будет выполнять в фоновом режиме. Вот как всё это будет выглядеть:
{
"manifest_version": 2,
"name": "LinkedIn AdBlocker",
"description": "Blocking ads.",
"version": "0.0.1",
"author": "<AUTHOR_NAME>",
"browser_action": {
"default_title": "LinkedIn AdBlocker"
},
"permissions": [
"tabs",
"webNavigation",
"https://www.linkedin.com/"
],
"background": {
"scripts": [
"extension.js"
]
}
}
Помимо метаданных, manifest настраивает для нас и другие важные элементы:
Permissions (разрешения)
permissions
определяют, что дозволено делать вашему расширению. Например, получать URL текущей страницы или добавлять на сайт JS-код.
Когда вы публикуете расширение в Chrome Web Store или аналогичных местах, то вас попросят обосновать каждое разрешение, к которому вы запрашиваете доступ. Это делается в целях обеспечения безопасности и приватности пользователей.
Здесь мы запрашиваем разрешения для tabs
и webNavigation
, чтобы определять посещение нового сайта и понимать, что это за сайт. Так мы сможем проверять, является ли этот сайт LinkedIn и при необходимости запускать блокирующий рекламу скрипт.
Последним мы запрашиваем разрешение на изменение страницы https://www.linkedin.com/, чтобы получить возможность вставить в неё свой скрипт.
Background
background
определяет скрипты, которые должны запускаться при срабатывании конкретных событий. Во многих случаях ключевым событием, прослушиваемым расширениями, является загрузка новой страницы.
В нашем случае мы будем прослушивать изменения в навигации и получать URL каждого запрашиваемого сайта.
В background.js
мы настроим слушателя событий так, чтобы он запускал действие при каждой загрузке пользователем новой страницы. Затем мы будем проверять, является ли этот сайт LinkedIn и, если да, то будем блокировать рекламу.
Вот содержимое этого файла:
/*
Это событие срабатывает в начале загрузки страницы.
В отличие от, например, webNavigation.onCompleted, оно происходит рано, давая нам возможность сразу приступить к удалению рекламы
*/
chrome.webNavigation.onCommitted.addListener(function (tab) {
// Запрещает запуск скрипта во время загрузки других фреймов
if (tab.frameId == 0) {
chrome.tabs.query({ active: true, lastFocusedWindow: true }, tabs => {
// Получает URL страницы
let url = tabs[0].url;
// Удаляет из URL необязательные определения протоколов и поддомен www
let parsedUrl = url.replace("https://", "")
.replace("http://", "")
.replace("www.", "")
// Удаляет путь и запросы, например linkedin.com/feed либо linkedin.com?query=value
// Нам нужен только базовый домен
let domain = parsedUrl.slice(0, parsedUrl.indexOf('/') == -1 ? parsedUrl.length : parsedUrl.indexOf('/'))
.slice(0, parsedUrl.indexOf('?') == -1 ? parsedUrl.length : parsedUrl.indexOf('?'));
try {
if (domain.length < 1 || domain === null || domain === undefined) {
return;
} else if (domain == "linkedin.com") {
runLinkedinScript();
return;
}
} catch (err) {
throw err;
}
});
}
});
function runLinkedinScript() {
// Встраивает в страницу скрипт из файла
chrome.tabs.executeScript({
file: 'linkedin.js'
});
return true;
}
Поскольку я оставил в файле ряд комментариев, то не вижу смысла углубляться в описание действий каждой строки.
Однако, если говорить в целом, то здесь мы получаем URL при загрузке новой страницы, удаляем из него ненужные составляющие и проверяем, совпадает ли он с linkedin.com.
Если да, то мы внедряем в этот сайт содержимое файла linkedin.js
, которое в итоге будет устранять рекламу. Если же не совпадает, то мы ничего не делаем.
Заключительный файл определяет то, как фактически будет блокироваться реклама. Я уже проделал небольшую работу, чтобы избавить вас от лишних хлопот.
Эта картинка демонстрирует скриншот моего канала LinkedIn, рассматриваемого через DevTools браузера.
Здесь вы можете заметить, что поскольку рекламные вставки должны быть соответственным образом отмечены, LinkedIn добавляет к посту строчку “Promoted”, которая указывает на её рекламный характер.
Тем не менее, в отличие от Facebook, LinkedIn не старается запутать HTML, чтобы усложнить обнаружение рекламы, поэтому простой просмотр страницы на наличие элементов, содержащих слово “Promoted”, позволит вам найти рекламу в JS.
Затем, чтобы её удалить, нам нужно найти родительский элемент, обёртывающий весь пост, и избавиться от него. При помощи DevTools вы можете просто кликнуть правой кнопкой по элементу и выбрать “Remove Element”. В нашем коде мы установим атрибут style
этого элемента как display: none:
, что в итоге и проделает нужную нам работу по удалению.
Вот как это будет выглядеть:
function removeAds() {
// Получает все элементы 'span' на странице
let spans = document.getElementsByTagName("span");
for (let i = 0; i < spans.length; ++i) {
// Проверяет, содержат ли они текст 'Promoted'
if (spans[i].innerHTML === "Promoted") {
// Получает div, который обёртывает рекламную вставку
let card = spans[i].closest(".feed-shared-update-v2");
// если класс изменился, и мы не можем найти его при помощи closest(), получает 6-го предка
if (card === null) {
// Может также быть card.parentNode.parentNode.parentNode.parentNode.parentNode.parentNode :D
let j = 0;
card = spans[i];
while (j < 6) {
card = card.parentNode;
++j;
}
}
// Удаляет рекламу!
card.setAttribute("style", "display: none !important;");
}
}
}
removeAds();
// Обеспечивает, чтобы реклама удалялась по мере прокрутки страницы
setInterval(function () {
removeAds();
}, 100)
Коротко говоря, мы перебираем все элементы span
в поиске, содержащие текст “Promoted”, пытаемся получить div
, обёртывающий рекламную вставку, двумя разными способами, а затем избавляемся от него установкой display: none;
.
Чтобы найти правильного предка мы сначала используем встроенный метод для HTML-элементов под названием closest
. Он перебирает предков нашего span
пока не найдёт первого с классом feed-shared-update-v2
. Я определил его как элемент, который нам нужно “удалить”.
Однако, если бы LinkedIn обновили свой UI и изменили этот класс на feed-shared-update-v3
, метод бы не сработал. На этот случай мы далее пытаемся получить 6-го предка, который должен быть эквивалентен тому же div
из другого метода.
Естественно, если LinkedIn изменит этот class
и структуру их “promoted’’ постов, наше расширение, скорее всего, не сработает. Но до тех пор всё должно быть прекрасно.
Мы также используем несложный способ (запуск скрипта каждые 100мс при помощи функции setInterval
) дляобеспечения динамической загрузки рекламы и её удаления по мере прокрутки страницы пользователем.
Вообще при использовании этого расширения я иногда успеваю увидеть первую рекламную вставку до её исчезновения, но последующие уже не вижу совсем.
Итак, по части расширения мы закончили. На всё про всё ушло около 100 строк кода с комментариями.
Используется же оно следующим образом:
Если вы всё сделали правильно, то среди установленных расширений должна появиться новая иконка (также справа вверху).
Поскольку мы не устанавливали для нашего расширения специальную иконку, она, вероятно, будет выглядеть как ‘L’ в рамке.
Теперь можете приступить к проверке и воспользоваться этой ссылкой на LinkedIn. (примечание — LinkedIn в РФ доступен только через VPN).
На этом всё. Я действительно считаю, что всё это можно сделать за 10 минут. Поскольку целью статьи было наскоро создать полезный инструмент, мы пожертвовали несколькими аспектами вроде наличия всплывающего окна и даже иконки. Тем не менее теперь вы можете создать для себя (а может и других) полезное ПО, просто за время очередного перерыва на чашку кофе.
Перевод статьи Giancarlo Buomprisco: API-less Prototyping with Angular
Комментарии