18

Я новичок в разработке расширений браузера, и я понимаю концепцию расширений браузера, изменяющую страницу и вводящие в нее коды.Может ли сайт ссылаться на расширение браузера?

Есть ли способ, которым это направление можно обойти? Я пишу расширение, предоставляющее набор API, и веб-сайты, которые хотят использовать мое расширение, могут обнаружить его присутствие, и если он присутствует, веб-сайт может вызвать мои методы API, такие как var extension = Extenion(foo, bar). Возможно ли это в Chrome, Firefox и Safari?

Пример:

  1. Google создал новое расширение под названием BeautifierExtension. Он имеет набор API как объекты JS.

  2. Пользователь переходит на reddit.com. Reddit.com обнаруживает BeautifierExtension и вызов API, вызвав beautifer = Beautifier();

см # 2 - обычно это расширение, которое обнаруживает совпадающие сайты и изменять страницы. Мне интересно узнать, возможно ли 2.

ответ

54

Поскольку Chrome представил externally_connectable, это довольно легко сделать в Chrome. Во-первых, указать разрешенный домен в вашем manifest.json файле:

"externally_connectable": { 
    "matches": ["*://*.example.com/*"] 
} 

Использование chrome.runtime.sendMessage отправить сообщение со страницы:

chrome.runtime.sendMessage(editorExtensionId, {openUrlInEditor: url}, 
    function(response) { 
    // ... 
    }); 

Наконец, слушать фоновому страницу с chrome.runtime.onMessageExternal:

chrome.runtime.onMessageExternal.addListener(
    function(request, sender, sendResponse) { 
    // verify `sender.url`, read `request` object, reply with `sednResponse(...)`... 
    }); 

Если у вас нет доступа к externally_connectable, исходный ответ следующий:

Я отвечу с точки зрения Chrome, хотя описанные здесь принципы (вставки сценариев веб-страницы, длинные фоновые скрипты, передача сообщений) применимы практически для всех рамок расширения браузера ,

С высокого уровня, что вы хотите сделать, это ввести content script на каждую веб-страницу, которая добавляет API, доступный для веб-страницы. Когда сайт вызывает API, API запускает сценарий содержимого, чтобы что-то сделать, например, отправлять сообщения на фоновый рисунок и/или отправлять результат обратно на скрипт контента через асинхронный обратный вызов.

Основная трудность заключается в том, что контент-скрипты, которые «вводятся» в веб-страницу, не могут напрямую изменять JavaScript execution environment на странице. Они делят DOM, поэтому события и Изменения в структуре DOM совместно используются сценарием контента и веб-страницей, но функции и переменные не используются совместно. Примеры:

  • DOM манипуляции: Если сценарий содержание добавляет <div> элемент на страницу, которая будет работать, как ожидалось. Оба сценария контента и страница будут видеть новый <div>.

  • События: Если скрипт контента устанавливает приемник событий, например, для кликов по элементу, слушатель успешно срабатывает при возникновении события. Если страница настраивает слушателя для пользовательских событий, запущенных из сценария контента, они будут успешно получены, когда сценарий содержимого запускает эти события.

  • Функции: Если сценарий содержание определяет новую глобальную функцию foo() (как вы могли бы попробовать при настройке нового API). Страница не может видеть или исполнять foo, потому что foo существует только в среде исполнения сценария контента, а не в среде страницы.

Итак, как вы можете создать надлежащий API? Ответ приходит во многих шагов:

  1. На низком уровне, сделать свой API event-based. Веб-страница запускает пользовательские события DOM с dispatchEvent, а скрипты контента прослушивают их с помощью addEventListener, принимая меры, когда они получены. Вот простое событие на основе API хранения которых веб-страницы можно использовать, чтобы иметь расширение для хранения данных для него:

    content_script.js (в расширении):

    // an object used to store things passed in from the API 
    internalStorage = {}; 
    
    // listen for myStoreEvent fired from the page with key/value pair data 
    document.addEventListener('myStoreEvent', function(event) { 
        var dataFromPage = event.detail; 
        internalStorage[dataFromPage.key] = dataFromPage.value 
    }); 
    

    Non-расширение веб-страницы, с помощью событий на основе API:

    function sendDataToExtension(key, value) { 
        var dataObj = {"key":key, "value":value}; 
        var storeEvent = new CustomEvent('myStoreEvent', {"detail":dataObj}); 
        document.dispatchEvent(storeEvent); 
    } 
    sendDataToExtension("hello", "world"); 
    

    Как вы можете видеть, обычные веб-страницы стрельбы события, сценарий содержание может видеть и реагировать, потому что они разделяют т он ДОМ. События имеют прикрепленные данные, добавленные в CustomEvent constructor. Мой пример здесь жалко прост - вы, очевидно, можете сделать гораздо больше в своем сценарии контента, когда у него есть данные со страницы (скорее всего, pass it до background page для дальнейшей обработки).

  2. Однако это только половина битвы. В моем примере выше обычная веб-страница должна была создать sendDataToExtension. Создание и запуск пользовательских событий довольно многословно (мой код занимает 3 строки и относительно короткий). Вы не хотите заставлять сайт писать тайный код для запуска событий только для использования вашего API. Решение - это немного неприятный взлом: добавьте тег <script> к вашей общей DOM, который добавляет код запуска события в среду выполнения главной страницы.

    Внутри content_script.js:

    // inject a script from the extension's files 
    // into the execution environment of the main page 
    var s = document.createElement('script'); 
    s.src = chrome.extension.getURL("myapi.js"); 
    document.documentElement.appendChild(s); 
    

    Любые функции, которые определены в myapi.js станут доступны на главной странице. (Если вы используете "manifest_version":2, вам необходимо включить myapi.js в список вашего манифеста web_accessible_resources).

    описание товара myapi.ЯШ:

    function sendDataToExtension(key, value) { 
        var dataObj = {"key":key, "value":value}; 
        var storeEvent = new CustomEvent('myStoreEvent', {"detail":dataObj}); 
        document.dispatchEvent(storeEvent); 
    } 
    

    Теперь обычная веб-страницаможет просто сделать:

    sendDataToExtension("hello", "world"); 
    
  3. Существует один дальнейший морщин к нашему процессу API: myapi.js скрипт не будет доступен точно время загрузки. Вместо этого он будет загружен через некоторое время после загрузки страницы. Таким образом, обычная веб-страница должна знать, когда она может безопасно называть ваш API. Вы можете решить эту проблему, указав myapi.js «событие, готовое к API», которое ваша страница прослушивает.

    myapi.js:

    function sendDataToExtension(key, value) { 
        // as above 
    } 
    
    // since this script is running, myapi.js has loaded, so let the page know 
    var customAPILoaded = new CustomEvent('customAPILoaded'); 
    document.dispatchEvent(customAPILoaded); 
    

    Обычная веб-страница с использованием API:

    document.addEventListener('customAPILoaded', function() { 
        sendDataToExtension("hello", "world"); 
        // all API interaction goes in here, now that the API is loaded... 
    }); 
    
  4. Другим решением проблемы доступности сценария во время загрузки устанавливает run_at свойства содержания сценарий в манифесте до "document_start" следующим образом:

    manifest.json:

    "content_scripts": [ 
         { 
         "matches": ["https://example.com/*"], 
         "js": [ 
          "myapi.js" 
         ], 
         "run_at": "document_start" 
         } 
        ], 
    

    Отрывок из docs:

    В случае "document_start", файлы впрыскивают после любых файлов с помощью CSS, но прежде, чем любой другой DOM построен или любой другой скрипт запускается.

    Для некоторых статей, которые могут быть более подходящими и менее трудоемкими, чем событие, загруженное API.

  5. Для отправки результатов назад на страницу вам необходимо предоставить асинхронную функцию обратного вызова. Невозможно синхронно возвращать результат из вашего API, потому что запуск/прослушивание событий по сути асинхронно (т. Е. Функция API на стороне сайта завершается до того, как скрипт контента когда-либо получает событие с запросом API).

    myapi.js:

    function getDataFromExtension(key, callback) { 
        var reqId = Math.random().toString(); // unique ID for this request 
        var dataObj = {"key":key, "reqId":reqId}; 
        var fetchEvent = new CustomEvent('myFetchEvent', {"detail":dataObj}); 
        document.dispatchEvent(fetchEvent); 
    
        // get ready for a reply from the content script 
        document.addEventListener('fetchResponse', function respListener(event) { 
         var data = event.detail; 
    
         // check if this response is for this request 
         if(data.reqId == reqId) { 
          callback(data.value); 
          document.removeEventListener('fetchResponse', respListener); 
         } 
        } 
    } 
    

    content_script.js (в расширении):

    // listen for myFetchEvent fired from the page with key 
    // then fire a fetchResponse event with the reply 
    document.addEventListener('myStoreEvent', function(event) { 
        var dataFromPage = event.detail; 
        var responseData = {"value":internalStorage[dataFromPage.key], "reqId":data.reqId}; 
        var fetchResponse = new CustomEvent('fetchResponse', {"detail":responseData}); 
        document.dispatchEvent(fetchResponse); 
    }); 
    

    обычная веб-страница:

    document.addEventListener('customAPILoaded', function() { 
        getDataFromExtension("hello", function(val) { 
         alert("extension says " + val); 
        }); 
    }); 
    

    reqId необходим, если у вас сразу несколько запросов, так что они не читают неправильные ответы.

И я думаю, что это все!Таким образом, не для слабонервных и, возможно, не стоит того, когда вы считаете, что другие расширения могут также связывать слушателей с вашими событиями, чтобы подслушивать, как страница использует ваш API. Я знаю только все это, потому что я сделал API-интерфейс криптографии с доказательством концепции для школьного проекта (и впоследствии узнал основные связанные с ним проблемы безопасности).

Сумма: Скрипт контента может прослушивать пользовательские события с обычной веб-страницы, а сценарий также может вставлять скриптовый файл с функциями, облегчающими запуск веб-страниц для этих событий. Скрипт содержимого может передавать сообщения на фоновый рисунок, который затем сохраняет, преобразует или передает данные из сообщения.

+1

Спасибо за подробные объяснения! +1 для указания среды выполнения. – keewooi

+1

Рассмотрите возможность обновления ответа с помощью конструктора 'CustomEvent'. Его синтаксис выглядит намного лучше, чем устаревший метод document.createEvent. –

+0

Есть ли способ проверить информацию о безопасности страницы/статус HSTS перед инъекцией скрипта? – r3m0t

Смежные вопросы