Подтвердить что ты не робот

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

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

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

Пример:

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

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

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

4b9b3361

Ответ 1

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

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

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

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

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

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

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

  • На низком уровне сделайте свой 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");
    

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

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

    Внутри content_script.js:

    // inject a script from the extension 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");
    
  • В наш процесс API есть еще одна морщина: myapi.js script не будет доступен точно во время загрузки. Вместо этого он будет загружен через некоторое время после загрузки страницы. Таким образом, обычная веб-страница должна знать, когда она может безопасно называть ваш 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...
    });
    
  • Другим решением проблемы доступности script во время загрузки является установка свойства run_at содержимого script в манифесте на "document_start" следующим образом:

    manifest.json:

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

    Выдержка из docs:

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

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

  • Чтобы отправить результаты на страницу, вам необходимо предоставить асинхронную функцию обратного вызова. Невозможно синхронно возвращать результат из вашего API, потому что запуск/прослушивание событий по сути является асинхронным (т.е. Функция API на стороне сайта завершается до того, как содержимое script когда-либо получит событие с запросом 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-интерфейс криптографии с доказательством концепции для школьного проекта (и впоследствии узнал основные связанные с ним проблемы безопасности).

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