Проблема с расширением, когда к элементам обращаются напрямую по их идентификатору, а не по getElementById - PullRequest
0 голосов
/ 18 февраля 2020

Даже если это не рекомендуется, JavaScript позволяет получить доступ к элементам по их идентификатору без использования getElementById.

<iframe id="myframe" />
<script>
    var i = window.myframe;
</script>

Это создает проблему для расширений, которым необходим доступ к указанным элементам при обращении к ним самим. Способ сделать это - вызвать Object.defineProperty () для прототипа для getElementById, чтобы при его вызове вы могли получить доступ к элементу. Но когда ссылка на элемент напрямую getElementById не вызывается, следовательно, они являются слепым пятном для расширения. Я попробовал следующее, но безуспешно.

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

Кто-нибудь знает внутренний механизм, который JavaScript использует, когда на элемент ссылаются вот так? Мне любопытно, есть ли другая внутренняя функция, которая может быть подключена как getElementById. Или есть способ обеспечить выполнение кода сценария содержимого после загрузки DOM, но до запуска сценариев страницы?

1 Ответ

0 голосов
/ 18 февраля 2020

Или есть способ обеспечить выполнение кода сценария содержимого после загрузки DOM, но до запуска сценариев страницы?

Проблема в том, что сценарии страницы могут выполняться до DOM полностью загружен. Например, ниже:

<div id="foo"></div>
<script>
foo.textContent = 'foo';
</script>
<div id="bar"></div>

скрипт будет запускаться до создания элемента bar (до загрузки DOM).

Возможно найти все элементы с идентификаторами в настоящее время в DOM с [id], и можно переназначить эти свойства в окне, просто переназначив свойство - или, если вы хотите запустить код при получении элемента, вы можете использовать Object.defineProperty для превратить его в добытчик. Вы должны быть в состоянии найти идентификаторы и переназначить их до загрузки сценариев, что может быть выполнено с помощью поддерева MutationObserver - непосредственно перед вставкой <script>, итерации по [id] s в DOM и переназначения их:

console.log('(empty JS block inserted by Stack Snippet editor)');
<script>
  new MutationObserver((mutations) => {
    // Check to see if a script was inserted
    if (mutations.every(
      mutation => [...mutation.addedNodes].every(
        addedNode => addedNode.tagName !== 'SCRIPT'
      )
    )) {
      // No scripts inserted
      return;
    }
    console.log('id reassignment running');
    for (const elm of document.querySelectorAll('[id]')) {
      Object.defineProperty(window, elm.id, {
        get() {
          console.log('Custom code running');
          return elm;
        },
        configurable: true
      });
    }
  })
    .observe(document.body, { childList: true, subtree: true });
</script>
<div id="foo"></div>
<script>
console.log('Lower script about to reference foo');
foo.textContent = 'foo';
console.log('Lower script has finished referencing foo');
</script>
<div id="bar"></div>

Может быть полезна некоторая полировка, но это общая идея.

...