Перехват/проксирование обращений к элементу DOM-дерева в javaScript и обработчиков для него, варианты получения ссылки на элемент в javaScript

Гобелен - кликните для возврата на главную

Не прошло и месяца а Фурмановская ЦРБ в очередной раз попала в историю. На этот раз сотрудница клеветала на пациентов, а именно огласку получил случай когда сотрудница назвала пациента алкашём.

После того как в некоторых случаях перестала работать программа goodbydpi появились сложности с просмотром сайта YouTube. В конце мая 2025 года стало доступно расширение позволяющее разблокировать сайт YouTube в браузере Firefox на территории России.

Ровно 3 года назад произошло событие, которое подарило жителям планеты Змеля новый чистый праздник 6 апреля - в этот замечательный день земля забрала гнить негодяя и СПАМера Владимира Вольфовича Жириновского.

Как бы не обстояли дела в области культуры и IT-технологий, Самосвал писал статьи на связанные темы и планирует ещё написать.

На очередном конкурсе танцев, школьница отлично выступила.

Вот та самая информация: программа для продвижения канала YouTube, эта информация выложена уже несколько лет, при этом просмотров не много, а метод который описан в данном видео действительно помогает продвигать видео в YouTube.

Начал разбираться с информацией которая находится в HTTPS клиентском запросе рукопожатия.

Обратите внимание! Объект изображённый на гобилене может отличаться от общепринятого вида описанного объекта. Тут дело в том что художник видит именно так!

104 549 руб.

Описание товара

В втором вопросе этого чата я написал что будет несколько связанная тема с первым вопросом, как раз подошли к этому. Есть следующая ситуация - страница с тегом video и несколько скриптов обрабатывающих эту страницу большого размера(один из которых даже более 2-х мегабайт). В это видео как то с использованием скриптов добавляются видеоданные(которые приходят по Ajax в зашифрованном виде). Мне нужны эти видеоданные расшифрованные. Насколько я знаю у тега video нет возможности получить данные обратно(если не использовать src, которого в рассматриваемом случаи нет, поскольку данные добавляются через JavaScript как я уже описал). Моя идея следующая. ранее Вы уже описывали что можно добавить обработчик addEventListener который будет "логировать" все обращения. Так можно получить ссылки на события. Моя идея в том чтобы определённые события для тега video вызывались не напрямую а через некоторый "прокси", который поможет отследить какое именно событие передаёт видеоданные и поможет их получить. Эта технология включает в себя несколько вопросов, а именно, какими именно вариантами может получаться ссылка на элемент video(getElementsById, querySelector, возможно какие то иные), возможно есть способ переопределить эти методы, чтобы они возвращали не прямую ссылку на объект а "прокси" на него, который будет следить что именно сделано с объектом. Так же возможно требуется переопределить методы устанавливающие обработчики на объект и так же возвращать прокси. Я поясню - такой вариант рассматривается, поскольку как я уже написал скрипты имеют значительный размер - и поиск места добавления видео контента в коде может быть длительным(*к тому же скрипты зашифрованы), поэтому ищется обходной вариант.

Ваш подход с проксированием DOM-элементов и событий действительно может помочь перехватить видеоданные, даже если они динамически загружаются через зашифрованные Ajax-запросы. Давайте разберём эту стратегию по шагам, включая перехват методов доступа к элементам и событий.

1. Перехват доступа к элементу <video>

Чтобы "подменить" возвращаемый элемент на прокси-объект, можно переопределить стандартные методы DOM.

Варианты перехвата:

а) Переопределение document.getElementById и других методов

JavaScript
    
const originalGetElementById = document.getElementById;
document.getElementById = function(id) {
  const element = originalGetElementById.call(document, id);
  if (id === 'targetVideo') { // Проверяем, что это нужный элемент
    return createVideoProxy(element); // Возвращаем прокси
  }
  return element;
};

  

б) Перехват querySelector/querySelectorAll

JavaScript
    
const originalQuerySelector = Element.prototype.querySelector;
Element.prototype.querySelector = function(selector) {
  const element = originalQuerySelector.call(this, selector);
  if (element?.tagName === 'VIDEO') {
    return createVideoProxy(element);
  }
  return element;
};

  

в) Перехват getElementsByTagName

JavaScript
    
const originalGetElementsByTagName = document.getElementsByTagName;
document.getElementsByTagName = function(tagName) {
  const elements = originalGetElementsByTagName.call(document, tagName);
  if (tagName === 'video') {
    return Array.from(elements).map(el => createVideoProxy(el));
  }
  return elements;
};

  

2. Создание прокси для элемента <video>

Прокси-объект будет перехватывать все обращения к оригинальному элементу, включая:

  • установку обработчиков событий (addEventListener);
  • изменение свойств (например, src, srcObject);
  • вызов методов (play(), pause()).

Пример прокси:

JavaScript
    
function createVideoProxy(videoElement) {
  return new Proxy(videoElement, {
    get(target, prop) {
      if (prop === 'addEventListener') {
        return function(type, listener, options) {
          console.log(`Добавлен обработчик события: ${type}`);
          // Можно подменить listener на свой
          const wrappedListener = function(event) {
            console.log(`Событие ${type} вызвано`, event);
            if (type === 'progress' || type === 'loadeddata') {
              // Попытка перехватить видеоданные
              console.log('Возможная передача данных:', target.src || target.srcObject);
            }
            return listener.call(target, event);
          };
          return target.addEventListener(type, wrappedListener, options);
        };
      }
      // Перехват изменения src или srcObject
      if (prop === 'src' || prop === 'srcObject') {
        return target[prop];
      }
      if (prop === 'setAttribute') {
        return function(name, value) {
          console.log(`Установлен атрибут: ${name}=${value}`);
          if (name === 'src') {
            console.log('Обнаружена установка видео-источника:', value);
          }
          return target.setAttribute(name, value);
        };
      }
      return target[prop];
    },
    set(target, prop, value) {
      if (prop === 'src' || prop === 'srcObject') {
        console.log(`Попытка установки ${prop}:`, value);
        // Здесь можно перехватить URL или медиа-поток
      }
      target[prop] = value;
      return true;
    },
  });
}

  

3. Перехват событий, связанных с загрузкой видео

Видеоданные могут передаваться через:

  • события (progress, loadeddata, canplay);
  • свойства (videoElement.src, videoElement.srcObject);
  • методы (videoElement.load(), videoElement.play()).

Ключевые события для перехвата(событие и когда вызывается):

  • progress Загрузка данных (можно перехватить буфер).
  • loadeddata Первые кадры видео загружены.
  • canplay Видео готово к воспроизведению.
  • playing Воспроизведение началось.

Пример перехвата данных через srcObject

Если видео передаётся через MediaStream или Blob:

JavaScript
    
Object.defineProperty(HTMLVideoElement.prototype, 'srcObject', {
  set: function(stream) {
    console.log('Обнаружен видеопоток:', stream);
    // Здесь можно сохранить stream или его данные
    this._srcObject = stream;
    return this._srcObject;
  },
  get: function() {
    return this._srcObject;
  },
});

  

4. Перехват Ajax-запросов с видеоданными

Если видео загружается через Ajax (например, в ArrayBuffer), можно перехватить XMLHttpRequest или fetch:

а) Переопределение XMLHttpRequest

JavaScript
    
const originalXHR = window.XMLHttpRequest;
window.XMLHttpRequest = function() {
  const xhr = new originalXHR();
  const originalOpen = xhr.open;
  xhr.open = function(method, url) {
    if (url.includes('.mp4') || url.includes('video_data')) {
      console.log('Обнаружен запрос видео:', url);
    }
    return originalOpen.apply(xhr, arguments);
  };
  xhr.addEventListener('load', function() {
    if (xhr.responseType === 'arraybuffer' || xhr.responseType === 'blob') {
      console.log('Получены видеоданные:', xhr.response);
    }
  });
  return xhr;
};

  

б) Перехват fetch

JavaScript
    
const originalFetch = window.fetch;
window.fetch = function(url, options) {
  if (typeof url === 'string' && url.includes('video')) {
    console.log('Fetch-запрос видеоданных:', url);
  }
  return originalFetch(url, options).then(response => {
    if (response.headers.get('Content-Type')?.includes('video')) {
      response.clone().arrayBuffer().then(data => {
        console.log('Данные видео через fetch:', data);
      });
    }
    return response;
  });
};

  

5. Сборка всей системы

Переопределяем методы DOM (getElementById, querySelector и др.), чтобы возвращать прокси.

Проксируем элемент <video>, перехватывая:

Установку src/srcObject.

Добавление обработчиков событий.

Перехватываем Ajax-запросы, чтобы обнаружить загрузку видео.

Анализируем события (progress, loadeddata), чтобы получить момент передачи данных.

6. Ограничения и обходные пути

Если скрипты используют ShadowDOM — перехват усложняется (нужно лезть внутрь shadowRoot).

Минифицированный/обфусцированный код — может мешать анализу, но структура событий обычно сохраняется.

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

Вывод

Ваш подход с проксированием DOM и событий — один из самых надёжных способов перехватить динамически загружаемые видеоданные, особенно когда:

  • исходный код скриптов сложен для анализа;
  • данные передаются через зашифрованные Ajax-запросы;
  • видео подгружается через srcObject или ArrayBuffer;

Итоговый алгоритм:

  • перехватить доступ к <video> через прокси;
  • подменить addEventListener для ключевых событий;
  • мониторить установку src/srcObject;
  • перехватывать Ajax-запросы с видеоданными.

Это даст вам контроль над процессом загрузки видео и доступ к сырым данным до их воспроизведения.

Я на всякий случай уточню, существует ли вариант как то ещё получить ссылку на DOM-элемент кроме как использование querySelector и getElementsByTagName, я например знаю что есть ещё getElementById, хотя в данном случаи его можно не рассматривать так тег video про который идёт речь не имеет атрибута id. Может быть существуют ещё способы получить элемент? Например может быть есть вариант через получение потомков document или другого DOM-элемента а затем перебор их в цикле и поиск в нужного с рекурсией по поиску внутри элементов цикла. Возможно ещё какие то варианты.

Да, существует множество способов получить DOM-элементы, помимо стандартных querySelector, getElementsByTagName и getElementById. Некоторые из них могут быть полезны в вашем случае, особенно если скрипты используют неочевидные методы доступа к <video>. Рассмотрим все возможные варианты.

1. Основные методы доступа к DOM-элементам

1.1. Поиск по ID (getElementById)

JavaScript
    
document.getElementById('myVideo'); // Если есть id

  

В вашем случае не подходит, так как <video> не имеет id.

1.2. Поиск по тегу (getElementsByTagName)

JavaScript
    
document.getElementsByTagName('video'); // Все <video> на странице

  

1.3. Поиск по CSS-селектору (querySelector, querySelectorAll)

JavaScript
    
document.querySelector('video'); // Первый <video>
document.querySelectorAll('video'); // Все <video>

  

2. Альтернативные методы доступа к элементам

2.1. Поиск через document.all (устаревший, но работает)

JavaScript
    
const allElements = document.all; // Все элементы страницы
const videos = Array.from(allElements).filter(el => el.tagName === 'VIDEO');

  

Минусы:

  • document.all — устаревший метод, но поддерживается всеми браузерами;
  • возвращает все элементы, включая <html> <head>, <script> и т. д.

2.2. Поиск через document.getElementsByClassName

Если у <video> есть класс:

JavaScript
    
document.getElementsByClassName('video-player'); // Все элементы с классом
Если классы одинаковые у нескольких <video>, можно отфильтровать.

  

2.3. Поиск через document.evaluate (XPath)

Позволяет искать элементы с помощью XPath-запросов:

JavaScript
    
const result = document.evaluate(
  '//video', // XPath-запрос (все <video> в DOM)
  document,
  null,
  XPathResult.ORDERED_NODE_SNAPSHOT_TYPE,
  null
);
      
const videos = [];
for (let i = 0; i < result.snapshotLength; i++) {
  videos.push(result.snapshotItem(i));
}

  

Плюсы:

  • гибкость (можно искать по сложным условиям);
  • работает даже в ShadowDOM.

Минусы:

Синтаксис XPath сложнее CSS-селекторов.

3. Поиск через обход DOM-дерева (рекурсивный)

Если скрипты используют нестандартные методы, можно вручную перебрать всю DOM-структуру:

3.1. Рекурсивный обход childNodes

JavaScript
    
function findAllVideos(element) {
  const videos = [];
  if (element.tagName === 'VIDEO') {
    videos.push(element);
  }
  for (const child of element.childNodes) {
    videos.push(...findAllVideos(child));
  }
  return videos;
}
      
const allVideos = findAllVideos(document.documentElement);

  

Плюсы:

Находит даже <video> внутри ShadowDOM (если добавить проверку element.shadowRoot).

Минусы:

Медленнее, чем querySelectorAll.

3.2. Обход через TreeWalker

Более эффективный способ перебора DOM:

JavaScript
    
const treeWalker = document.createTreeWalker(
  document.body,
  NodeFilter.SHOW_ELEMENT,
  { acceptNode: (node) => node.tagName === 'VIDEO' ? NodeFilter.FILTER_ACCEPT : NodeFilter.FILTER_SKIP }
);
      
const videos = [];
while (treeWalker.nextNode()) {
  videos.push(treeWalker.currentNode);
}

  

Плюсы:

  • быстрее рекурсивного обхода;
  • можно гибко фильтровать элементы.

4. Доступ к элементам внутри ShadowDOM

Если <video> находится внутри ShadowDOM (например, в веб-компоненте), стандартные методы не сработают. Нужно лезть внутрь shadowRoot:

4.1. Рекурсивный поиск в ShadowDOM

JavaScript
    
function findVideosInShadowDOM(root) {
  const videos = Array.from(root.querySelectorAll('video'));
  for (const element of root.querySelectorAll('*')) {
    if (element.shadowRoot) {
      videos.push(...findVideosInShadowDOM(element.shadowRoot));
    }
  }
  return videos;
}
      
const allVideos = findVideosInShadowDOM(document);

  

4.2. Пример: Поиск <video> внутри YouTube-плеера

Многие плееры (например, YouTube) используют ShadowDOM:

JavaScript
    
const youtubePlayer = document.querySelector('youtube-player');
if (youtubePlayer?.shadowRoot) {
  const video = youtubePlayer.shadowRoot.querySelector('video');
}

  

5. Нестандартные методы (если скрипты используют хакеры)

Иногда разработчики используют неочевидные способы доступа к элементам:

5.1. Доступ через window.frames (если <video> в <iframe>)

JavaScript
    
const iframe = window.frames[0].document;
const video = iframe.querySelector('video');

  

5.2. Поиск через document.adoptNode (редко)

JavaScript
    
const orphanVideo = document.createElement('video');
document.adoptNode(orphanVideo); // Делает элемент частью DOM

  

5.3. Доступ через document.currentScript (если <video> создаётся скриптом)

JavaScript
    
const script = document.currentScript; // Текущий выполняемый скрипт
const video = script.previousElementSibling; // Если <video> перед скриптом

  

6. Вывод: какие методы подойдут в вашем случае(способ и когда имеет смысл использовать):

  • querySelectorAll - стандартный способ (лучший баланс скорости и удобства);
  • getElementsByTagName - если нужно быстро получить все <video>;
  • рекурсивный обход DOM - если <video> спрятаны в ShadowDOM или сложной структуре;
  • document.evaluate (XPath) - если нужно гибко искать по условиям;
  • перехват appendChild - если видео добавляется динамически (см. ниже).

7. Дополнительная тактика: перехват динамически созданных <video>

Если видео добавляется через JavaScript после загрузки страницы, можно перехватить методы DOM-модификации:

7.1. Переопределение appendChild

JavaScript
    
const originalAppend = Node.prototype.appendChild;
Node.prototype.appendChild = function(element) {
  if (element.tagName === 'VIDEO') {
    console.log('Обнаружен динамически добавленный <video>', element);
  }
  return originalAppend.call(this, element);
};

  

7.2. Перехват insertBefore

JavaScript
    
const originalInsert = Node.prototype.insertBefore;
Node.prototype.insertBefore = function(newNode, refNode) {
  if (newNode.tagName === 'VIDEO') {
    console.log('Обнаружен <video>, вставленный через insertBefore', newNode);
  }
  return originalInsert.call(this, newNode, refNode);
};

  

Итог

Если <video> не имеет id, можно использовать:

  • Стандартные методы (querySelectorAll, getElementsByTagName);
  • Обход DOM (рекурсивный или через TreeWalker);
  • Поиск в ShadowDOM (если видео внутри веб-компонентов).

Перехват динамического добавления (мониторинг appendChild, insertBefore).

Оптимальный вариант:

JavaScript
    
// Быстрый поиск всех <video> (включая ShadowDOM)
function findAllVideos() {
  const videos = Array.from(document.querySelectorAll('video'));
  document.querySelectorAll('*').forEach(el => {
    if (el.shadowRoot) {
      videos.push(...el.shadowRoot.querySelectorAll('video'));
    }
  });
  return videos;
}
      
const allVideos = findAllVideos();
console.log(allVideos); // Все <video> на странице

  

Это даст вам максимальный охват, даже если видео спрятано глубоко в DOM.

Другие товары

Вы так же можете прочитать следующие статьи: