Разработка кроссбраузерного расширения (WebExtension API)

Наша компания занимается разработкой, поддержкой и обслуживанием сайтов любой сложности. От простых одностраничных сайтов до масштабных кластерных систем построенных на микро сервисах. Опыт разработчиков подтвержден сертификатами от вендоров.

Разработка и обслуживание любых видов сайтов:

Информационные сайты или веб-приложения
Сайты визитки, landing page, корпоративные сайты, онлайн каталоги, квиз, промо-сайты, блоги, новостные ресурсы, информационные порталы, форумы, агрегаторы
Сайты или веб-приложения электронной коммерции
Интернет-магазины, B2B-порталы, маркетплейсы, онлайн-обменники, кэшбэк-сайты, биржи, дропшиппинг-платформы, парсеры товаров
Веб-приложения для управления бизнес-процессами
CRM-системы, ERP-системы, корпоративные порталы, системы управления производством, парсеры информации
Сайты или веб-приложения электронных услуг
Доски объявлений, онлайн-школы, онлайн-кинотеатры, конструкторы сайтов, порталы предоставления электронных услуг, видеохостинги, тематические порталы

Это лишь некоторые из технических типов сайтов, с которыми мы работаем, и каждый из них может иметь свои специфические особенности и функциональность, а также быть адаптированным под конкретные потребности и цели клиента

Предлагаемые услуги
Показано 1 из 1 услугВсе 2065 услуг
Разработка кроссбраузерного расширения (WebExtension API)
Сложная
~2-3 недели
Часто задаваемые вопросы

Наши компетенции:

Этапы разработки

Последние работы

  • image_website-b2b-advance_0.png
    Разработка сайта компании B2B ADVANCE
    1262
  • image_web-applications_feedme_466_0.webp
    Разработка веб-приложения для компании FEEDME
    1171
  • image_websites_belfingroup_462_0.webp
    Разработка веб-сайта для компании БЕЛФИНГРУПП
    874
  • image_ecommerce_furnoro_435_0.webp
    Разработка интернет магазина для компании FURNORO
    1094
  • image_crm_enviok_479_0.webp
    Разработка веб-приложения для компании Enviok
    831
  • image_bitrix-bitrix-24-1c_fixper_448_0.png
    Разработка веб-сайта для компании ФИКСПЕР
    851

Разработка кроссбраузерного расширения (WebExtension API)

WebExtension API — это стандарт, который поддерживают Chrome, Firefox, Edge, Opera и (частично) Safari. Единая кодовая база с условными патчами под браузерные различия — правильный подход к кроссбраузерной разработке. Копировать и поддерживать отдельные версии для каждого браузера — антипаттерн.

Совместимость API по браузерам

API Chrome Firefox Edge Opera Safari
storage.local MV2/3 MV2/3 MV2/3 MV2/3 14+
storage.sync 15+
tabs 14+
scripting (MV3) 101+ 92+ 15.4+
declarativeNetRequest 113+ 92+ 15.4+
sidePanel 114+
webRequest MV2 only MV2 only MV2 only

Полифил: webextension-polyfill

Mozilla разработала webextension-polyfill — он преобразует chrome.* (Callback) в browser.* (Promise) и сглаживает различия между браузерами:

npm install webextension-polyfill
import browser from 'webextension-polyfill';

// Одинаковый код для Chrome и Firefox
const tabs = await browser.tabs.query({ active: true, currentWindow: true });
await browser.storage.local.set({ data: 'value' });
const result = await browser.storage.local.get('data');

Единый манифест: MV2 vs MV3

Поддерживать два манифеста параллельно — стандартная практика для максимального охвата:

// scripts/build.js
const fs = require('fs');
const target = process.env.TARGET_BROWSER; // 'chrome', 'firefox', 'safari'

const manifests = {
  chrome: {
    manifest_version: 3,
    background: { service_worker: 'background.js' },
    action: { default_popup: 'popup.html' },
  },
  firefox: {
    manifest_version: 2,
    background: { scripts: ['background.js'], persistent: false },
    browser_action: { default_popup: 'popup.html' },
    browser_specific_settings: {
      gecko: { id: '[email protected]', strict_min_version: '109.0' },
    },
  },
  safari: {
    manifest_version: 3,
    background: { service_worker: 'background.js' },
    action: { default_popup: 'popup.html' },
    browser_specific_settings: {
      safari: { strict_min_version: '15.4' },
    },
  },
};

const base = JSON.parse(fs.readFileSync('./manifest.base.json', 'utf8'));
const merged = { ...base, ...manifests[target] };
fs.writeFileSync('./dist/manifest.json', JSON.stringify(merged, null, 2));

Структура проекта

my-extension/
├── src/
│   ├── background/
│   │   ├── index.ts
│   │   └── handlers/
│   ├── content/
│   │   ├── index.ts
│   │   └── modules/
│   ├── popup/
│   │   ├── App.tsx
│   │   └── index.tsx
│   ├── options/
│   │   └── App.tsx
│   └── shared/
│       ├── storage.ts      # обёртка над browser.storage
│       ├── messaging.ts    # типизированные сообщения
│       └── browser.ts      # определение текущего браузера
├── manifest.base.json
├── manifests/
│   ├── mv2.json
│   └── mv3.json
├── vite.config.ts
└── package.json

Типизированные сообщения

Строгая типизация сообщений между контекстами — залог отсутствия silent-багов:

// shared/messages.ts
export type Message =
  | { type: 'GET_PAGE_DATA'; url: string }
  | { type: 'SET_BADGE'; count: number; color?: string }
  | { type: 'OPEN_OPTIONS' }
  | { type: 'EXTRACT_TEXT'; selector: string };

export type MessageResponse<T extends Message> =
  T extends { type: 'GET_PAGE_DATA' } ? { title: string; meta: Record<string, string> } :
  T extends { type: 'EXTRACT_TEXT' }  ? { text: string } :
  void;

// Типизированный sendMessage
export async function sendMessage<T extends Message>(
  message: T
): Promise<MessageResponse<T>> {
  return browser.runtime.sendMessage(message) as Promise<MessageResponse<T>>;
}

// Типизированный onMessage
export function onMessage<T extends Message['type']>(
  type: T,
  handler: (msg: Extract<Message, { type: T }>, sender: browser.runtime.MessageSender) => Promise<MessageResponse<Extract<Message, { type: T }>>>
) {
  browser.runtime.onMessage.addListener((msg, sender, sendResponse) => {
    if (msg.type === type) {
      handler(msg, sender).then(sendResponse);
      return true;
    }
  });
}

Определение браузера и условные пути

// shared/browser.ts
export type BrowserName = 'chrome' | 'firefox' | 'safari' | 'edge' | 'opera';

export function detectBrowser(): BrowserName {
  // @ts-ignore
  if (typeof browser !== 'undefined' && browser.runtime?.id) {
    const ua = navigator.userAgent;
    if (ua.includes('Firefox/')) return 'firefox';
    if (ua.includes('OPR/')) return 'opera';
    if (ua.includes('Edg/')) return 'edge';
    // Safari определяется через manifest
    if (!ua.includes('Chrome')) return 'safari';
  }
  return 'chrome';
}

export const IS_FIREFOX = detectBrowser() === 'firefox';
export const IS_SAFARI = detectBrowser() === 'safari';
export const SUPPORTS_SIDE_PANEL = detectBrowser() === 'chrome';

Сборка под несколько браузеров с Vite

// vite.config.ts
import { defineConfig } from 'vite';
import { crx } from '@crxjs/vite-plugin';

const browser = process.env.TARGET_BROWSER ?? 'chrome';
const manifestFile = browser === 'firefox'
  ? './manifests/mv2.json'
  : './manifests/mv3.json';

export default defineConfig({
  define: {
    __BROWSER__: JSON.stringify(browser),
    __IS_FIREFOX__: browser === 'firefox',
  },
  plugins: [crx({ manifest: require(manifestFile) })],
  build: {
    outDir: `dist/${browser}`,
  },
});
// package.json scripts
{
  "build:chrome":  "TARGET_BROWSER=chrome  vite build",
  "build:firefox": "TARGET_BROWSER=firefox vite build",
  "build:edge":    "TARGET_BROWSER=edge    vite build",
  "build:safari":  "TARGET_BROWSER=safari  vite build",
  "build:all":     "npm run build:chrome && npm run build:firefox && npm run build:edge"
}

CI/CD: сборка и публикация всех версий

# .github/workflows/publish.yml
name: Publish Extensions
on:
  push:
    tags: ['v*']

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - run: npm ci
      - run: npm run build:all

      - name: Publish to Chrome Web Store
        uses: trstringer/manual-approval@v1
        env:
          CHROME_CLIENT_ID: ${{ secrets.CHROME_CLIENT_ID }}
          CHROME_CLIENT_SECRET: ${{ secrets.CHROME_CLIENT_SECRET }}
          CHROME_REFRESH_TOKEN: ${{ secrets.CHROME_REFRESH_TOKEN }}

      - name: Sign & Publish to Firefox AMO
        run: |
          npx web-ext sign \
            --source-dir dist/firefox \
            --api-key ${{ secrets.AMO_JWT_ISSUER }} \
            --api-secret ${{ secrets.AMO_JWT_SECRET }} \
            --channel listed

Тестирование кроссбраузерного расширения

// tests/messaging.test.ts (с @jest-environment jsdom)
import { sendMessage, onMessage } from '../src/shared/messaging';

// Мокируем browser.runtime
global.chrome = {
  runtime: {
    sendMessage: jest.fn((msg, cb) => cb({ data: 'test' })),
    onMessage: { addListener: jest.fn() },
  },
} as any;

test('sendMessage GET_PAGE_DATA returns data', async () => {
  const result = await sendMessage({ type: 'GET_PAGE_DATA', url: 'https://example.com' });
  expect(result).toHaveProperty('title');
});

Для E2E — playwright + официальный пример запуска расширений:

const { chromium } = require('playwright');
const path = require('path');

const browser = await chromium.launchPersistentContext('', {
  headless: false,
  args: [
    `--disable-extensions-except=${path.resolve('./dist/chrome')}`,
    `--load-extension=${path.resolve('./dist/chrome')}`,
  ],
});

Сроки

Кроссбраузерное расширение (Chrome + Firefox + Edge) с единой кодовой базой, типизацией, автосборкой и публикацией в три магазина — 10–16 рабочих дней в зависимости от функциональности. Добавление Safari (Xcode-обёртка + App Store) прибавляет 5–8 дней. Сроки ревью магазинов — отдельно и параллельно.