streamImgSpy.ts 1.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748
  1. /**
  2. * Records every camera stream src given to an <img>, and whether that img was
  3. * in the document at the moment (#3431).
  4. *
  5. * React sets props while it renders, before it commits anything, so a src
  6. * passed as a prop lands on an img that is not on the page yet -- and on the
  7. * img of every render React later throws away, which the browser opens all
  8. * the same. A stream src must only ever be set on an attached img.
  9. */
  10. export interface StreamSrcRecord {
  11. img: HTMLImageElement;
  12. src: string;
  13. attached: boolean;
  14. }
  15. export function spyOnStreamSrc(match = /\/camera\/(stream|snapshot)/) {
  16. const records: StreamSrcRecord[] = [];
  17. const proto = HTMLImageElement.prototype;
  18. const descriptor = Object.getOwnPropertyDescriptor(proto, 'src')!;
  19. const setAttribute = Element.prototype.setAttribute;
  20. const note = (img: HTMLImageElement, value: unknown) => {
  21. const src = String(value);
  22. if (match.test(src)) records.push({ img, src, attached: img.isConnected });
  23. };
  24. Object.defineProperty(proto, 'src', {
  25. ...descriptor,
  26. set(this: HTMLImageElement, value: string) {
  27. note(this, value);
  28. descriptor.set!.call(this, value);
  29. },
  30. });
  31. Element.prototype.setAttribute = function (this: Element, name: string, value: string) {
  32. if (this instanceof HTMLImageElement && name === 'src') note(this, value);
  33. return setAttribute.call(this, name, value);
  34. };
  35. return {
  36. records,
  37. /** Stream srcs set on an img that was not in the document. */
  38. detached: () => records.filter(r => !r.attached),
  39. /** Imgs outside the document that still hold a stream src: open connections. */
  40. leaked: () =>
  41. [...new Set(records.map(r => r.img))].filter(img => !img.isConnected && match.test(img.getAttribute('src') ?? '')),
  42. restore: () => {
  43. Object.defineProperty(proto, 'src', descriptor);
  44. Element.prototype.setAttribute = setAttribute;
  45. },
  46. };
  47. }