惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

雷峰网
雷峰网
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
大猫的无限游戏
大猫的无限游戏
Google DeepMind News
Google DeepMind News
V
V2EX
T
The Blog of Author Tim Ferriss
H
Hackread – Cybersecurity News, Data Breaches, AI and More
Hugging Face - Blog
Hugging Face - Blog
Stack Overflow Blog
Stack Overflow Blog
I
InfoQ
博客园_首页
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
Last Week in AI
Last Week in AI
Recent Announcements
Recent Announcements
Vercel News
Vercel News
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
T
Tailwind CSS Blog
美团技术团队
Martin Fowler
Martin Fowler
宝玉的分享
宝玉的分享
Blog — PlanetScale
Blog — PlanetScale
GbyAI
GbyAI
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
J
Java Code Geeks

Jake Archibald's blog

Controlling when CSS custom properties are computed Fixing my tooltip accessibility mistake The Goldilocks customizable select height Importing vs fetching JSON The present and potential future of progressive image rendering Fetch streams are great, but not for measuring upload/download progress Making XML human-readable without XSLT Give footnotes the boot Animating zooming using CSS: transform order is important… sometimes How should <​selectedoption​> work? Video with alpha transparency on the web Garbage collection and closures
Firefox + custom elements + iframes bug
2025-02-14 · via Jake Archibald's blog

Over at Shopify we've been building a bunch of web components to use internally and in third party contexts. All of a sudden, we found some strange errors in our logs, all from Firefox. This is the post I wish existed when we discovered it.

Update: This is now fixed, and should land in Firefox 151.

The bug

The bug happens when a custom element (or web component) is moved to a document from another JavaScript Realm [spooky noises]. A Realm is a separate JavaScript context with its own global, own implementation of Array etc etc. An iframe or popup window provides a document in a new JavaScript Realm.

The result of the bug is that the element's custom prototype is lost, and things like instance methods disappear.

Ok ok, sorry, I'm trying to hit all the terms that people might search for to find a fix for this issue. Anyway, here's a simple custom element:

class MyElement extends HTMLElement {
  say(message) {
    console.log(message);
  }
  connectedCallback() {
    this.say('hello!');
  }
  disconnectedCallback() {
    this.say('goodbye!');
  }
}

customElements.define('my-element', MyElement);

And I'm going to create an instance of the element, and put it in an iframe:

// Create the iframe
const iframe = document.createElement('iframe');
document.body.append(iframe);

// Create the element, and put it in the iframe
const myElement = document.createElement('my-element');
iframe.contentDocument.body.append(myElement);

This fails in Firefox with "this.say is not a function", within connectedCallback. In fact, Firefox has lost all of the instance methods of the custom element. It's 'downgraded' to an instance of HTMLElement in the iframe Realm, rather than MyElement.

It's kinda funny, because the error happens within connectedCallback, which is an instance method, but even that instance method has gone. I assume the calling of connectedCallback was queued up before the prototype was lost.

If I put the element in the main document before moving it to the iframe, it fails in disconnectedCallback for the same reason.

Unfortunately this has been a known issue for 6 years.

The fix

The fix is kinda simple. Just… put the prototype back. As in, make it an instance of MyElement again.

class CustomElementBase extends HTMLElement {
  // This is called when the element is moved to a new document.
  // This is where we solve the bug if the element is moved to an iframe
  // without first being put into the main document.
  adoptedCallback() {
    rescueElementPrototype(this);
  }

  // This is called when the element is disconnected from a document.
  // This happens whenever the element is moved around the DOM,
  // but it also happens when the element is moved to a new document.
  // This happens before adoptedCallback,
  // so we need to fix it here,
  // to avoid the bug in subclass disconnectedCallback calls.
  disconnectedCallback() {
    rescueElementPrototype(this);
  }
}

function rescueElementPrototype(element) {
  // Return if everything looks as expected.
  if (element instanceof CustomElementBase) return;

  // Otherwise, get the intended constructor…
  const constructor = customElements.get(element.tagName.toLowerCase());

  // …and set the prototype.
  Object.setPrototypeOf(element, constructor.prototype);
}

Thanks to my colleague Anthony Frehner who realised customElements.get is a simple way to get the original constructor back, rather than the mad WeakMap hack I was using.

Now, make sure your custom elements extend this base class, and ensure you call super methods:

class MyElement extends CustomElementBase {
  say(message) {
    console.log(message);
  }
  connectedCallback() {
    super.connectedCallback?.();
    this.say('hello!');
  }
  disconnectedCallback() {
    super.disconnectedCallback?.();
    this.say('goodbye!');
  }
}

And that's it! The bug is undone, and everything works as expected.

View this page on GitHub