Может ли сайт вызвать расширение браузера?

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

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

пример:

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

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

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

1 ответов


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

"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, хотя принципы, описанные здесь (инъекции скриптов веб-страниц, длительные фоновые скрипты, передача сообщений) применимы практически ко всем фреймворкам расширения браузера.

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

основная трудность здесь заключается в том, что скрипты контента, которые "вводятся" на веб-страницу, не могут напрямую изменять JavaScript среда исполнения страницы. Они делят дом, так что событий и изменения в структуре DOM являются общими для скрипта содержимого и веб-страницы, но функции и переменные не являются общими. Примеры:

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

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

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

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

  1. на низком уровне сделайте свой API событие на основе. Веб-страница запускает пользовательские события 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
    });
    

    веб-страница без расширения, используя API на основе событий:

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

    как вы можете видеть, обычная веб-страница запускает события, которые сценарий содержимого может видеть и реагировать, потому что они разделяют DOM. События имеют прикрепленные данные, добавленные в CustomEvent конструктор. Мой пример здесь жалко прост - вы, очевидно, можете сделать гораздо больше в своем скрипте контента, как только у него есть данные со страницы (скорее всего передать его до фон страницы для дальнейшей обработки).

  2. однако, это только половина битвы. В моем примере выше обычная веб-страница должна была создать . Создание и запуск пользовательских событий довольно многословно (мой код занимает 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.js:

    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 ready", которое ваша страница прослушивает для.

    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" такой:

    манифест.в JSON:

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

    фрагмент docs:

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

    для некоторых contentscripts, которые могут быть более подходящими и меньшими усилиями, чем событие "API loaded".

  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 криптографии доказательства концепции для школьного проекта (и впоследствии узнал основные подводные камни безопасности, связанные с ним).

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