Вопрос по javascript – Может ли сайт вызывать расширение для браузера?

21

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

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

Пример:

  1. Google created a new extension called BeautifierExtension. It has a set of APIs as JS objects.

  2. User goes to reddit.com. Reddit.com detects BeautifierExtension and invoke the API by calling beautifer = Beautifier();

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

Ваш Ответ

1   ответ
59

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

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

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

DOM manipulation: If a content script adds a <div> element to a page, that will work as expected. Both content script and page will see the new <div>.

Events: If a content script sets up an event listener, e.g., for clicks on an element, the listener will successfully fire when the event occurs. If the page sets up a listener for custom events fired from the content script, they will be successfully received when the content script fires those events.

Functions: If the content script defines a new global function foo() (as you might try when setting up a new API). The page cannot see or execute foo, because foo exists only in the content script's execution environment, not in the page's environment.

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

At a low-level, make your API event-based. The web page fires custom DOM events with dispatchEvent, and the content scripts listens for them with addEventListener, taking action when they are received. Here's a simple event-based storage API which a web page can use to have the extension to store data for it:

content_script.js (in your extension):

// 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-extension web page, using your event-based API:

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

As you can see, the ordinary web page is firing events that the content script can see and react to, because they share the DOM. The events have data attached, added in the CustomEvent constructor. My example here is pitifully simple -- you can obviously do much more in your content script once it has the data from the page (most likely pass it to the background page for further processing).

However, this is only half the battle. In my example above, the ordinary web page had to create sendDataToExtension itself. Creating and firing custom events is quite verbose (my code takes up 3 lines and is relatively brief). You don't want to force a site to write arcane event-firing code just to use your API. The solution is a bit of a nasty hack: append a <script> tag to your shared DOM which adds the event-firing code to the main page's execution environment.

Inside 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);

Any functions that are defined in myapi.js will become accessible to the main page. (If you are using "manifest_version":2, you'll need to include myapi.js in your manifest's list of 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);
}

Now the plain web page can simply do:

sendDataToExtension("hello", "world");

There is one further wrinkle to our API process: the myapi.js script will not be available exactly at load time. Instead, it will be loaded some time after page-load time. Therefore, the plain web page needs to know when it can safely call your API. You can solve this by having myapi.js fire an "API ready" event, which your page listens for.

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);

Plain web page using API:

document.addEventListener('customAPILoaded', function() {
    sendDataToExtension("hello", "world");
    // all API interaction goes in here, now that the API is loaded...
});

Another solution to the problem of script availability at load time is setting run_at property of content script in manifest to "document_start" like this:

manifest.json:

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

Excerpt from docs:

In the case of "document_start", the files are injected after any files from css, but before any other DOM is constructed or any other script is run.

For some contentscripts that could be more appropriate and of less effort than having "API loaded" event.

In order to send results back to the page, you need to provide an asynchronous callback function. There is no way to synchronously return a result from your API, because event firing/listening is inherently asynchronous (i.e., your site-side API function terminates before the content script ever gets the event with the API request).

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 (in your extension):

// 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);
});

ordinary web page:

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

The reqId is necessary in case you have multiple requests out at once, so that they don't read the wrong responses.

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

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

Error: User Rate Limit ExceededCustomEventError: User Rate Limit Exceededdocument.createEventError: User Rate Limit Exceeded
Error: User Rate Limit ExceededCustomEventError: User Rate Limit Exceeded
Error: User Rate Limit Exceeded keewooi
Error: User Rate Limit ExceededmaybeError: User Rate Limit ExceededwebRequestError: User Rate Limit Exceeded
Error: User Rate Limit Exceeded

Похожие вопросы