[proposal] Same-document same-context windows, declaratively via HTML <window> element

#1

There could be some use cases that become simple to achieve if we could have browser windows that rendered as part of the same document with the same JS context.

F.e., imagine the following opens a new window:

<body><!-- this renders in the parent window -->
  <div>Take a look at the content in the window.</div>
  <window opened width="800" height="600">
    <!-- this renders in a child window that pops open -->
    <div>I am content in another window, but in the same DOM and JS context.</div>
  </window>
</body>

Then in the same context, we can easily work with it:

const d = document.querySelector('window > div')
d.innerHTML = 'I am in another window and my content was edited from the parent window context.'

Not only would multi-window applications become super simple to orchestrate…

… It would also solve the complexity of rendering Custom Elements across windows (see this StackOverflow question).

It is (or at least seems) very difficult to make multi-window applications using the same (or subsets of) the custom elements that are in the parent Window.

With this feature, we can do the following with Custom Elements:

<script>
  class MyEL extends HTMLElement { /* ... */ }
  customElements.define('my-element', MyEL)
</script>
<body>
  <!-- my-el works in the parent window -->
  <my-el>Take a look at the content in the window.</my-el>
  <window opened width="800" height="600">
    <!-- my-el also works in the child window -->
    <my-el></my-el>
  </window>
</body>

Because we’re using the same DOM, note that CSS styling also applies in the child window.

With the current API (using window.open) styles need to be manually copied over or linked in the child window. Custom Elements need to be re-defined in the child window, event needs to be wired up via postMessage, etc. It’s a lot of pain and easy to do it wrong and leaves a big surface area for bugs to infest.

The new <window> feature would solve all of that. F.e., here’s a complete example, including CSS:

<style>
  my-el { background: pink }
</style>
<script>
  class MyEL extends HTMLElement { /* ... */ }
  customElements.define('my-element', MyEL)
</script>
<body>
  <!-- my-el works in the parent window, has a pink background -->
  <my-el>Take a look at the content in the window.</my-el>
  <window opened width="800" height="600">
    <!-- my-el also works in the child window, also has a pink background -->
    <my-el>I am content in another window, but in the same DOM and JS context.</my-el>
  </window>
</body>
#2

This would allow even more awesome things to be possible, f.e. imagine ShadowDOM distribution, where a component can distribute children into another window:

<!-- windowable-sidebar-layout has a <window> element in its shadow root -->
<windowable-sidebar-layout windowed>
  <menu slot="header">...</menu>
  <main slot="main">...</main>
  <aside slot="sidebar">...</aside>
</windowable-sidebar-layout>

which would distribute the <aside> element into the child window. To tell the web component to render the sidebar back in the main window, the user would just remove the windowed attribute.

#3

Basically, <window>s would open OS windows that are simply just render targets for the HTML engine. Portions from the main window’s DOM are simply painted inside the child window, nothing more. There’s no new DOM or JS context anywhere, there’s no new origin, it’s all the same context as far as user code goes.

#4

Oh! And events would be simple! You could click on an element, and catch the bubbling event anywhere in the main window’s DOM. It’d make multi-window apps super easy!

Example:

<style>
  my-el { background: pink }
</style>

<script>
  class MyEL extends HTMLElement { /* ... */ }
  customElements.define('my-element', MyEL)
</script>

<body>
  <!-- my-el works in the parent window, has a pink background -->
  <my-el>Take a look at the content in the window.</my-el>
  <window opened width="800" height="600">
    <!-- my-el also works in the child window, also has a pink background -->
    <my-el id="foo">I am content in another window, but in the same DOM and JS context.</my-el>
  </window>
</body>

<script>
  // do something when the user clicks on the element in the window!
  const el = document.querySelector('#foo')
  el.addEventListener('click', () => {
    console.log('user clicked inside the window')
  })
</script>

And also, clicking anywhere in the window:

  const win = document.querySelector('#foo')
  win.addEventListener('click', () => {
    console.log('user clicked on the window')
  })

and bubbling:

  document.addEventListener('click', e => {
    if (e.target.id === 'foo') {
      console.log('user clicked inside the window')
    }
  })
#5

I have mixed feelings. I can see the power this brings, but the window element does seem slightly strange. A few questions:

  1. What would happen when the window is closed? Is the element removed from the DOM?
  2. What happens when you add the window element to the DOM, but the browser has popups disabled?
#6

The opened state becomes false. Similar to checked on input elements. Note the opened attribute in the examples.

When the window gets closed, it renders relative to it’s location in the DOM (perhaps a window element is styled with display:content by default).

This leads to concepts like “windowable widgets” that can be popped out into windows, and popped back in, on demand.

It’s going to handle exactly the same as it does with window.open, which may be specific to each browser.

In Chrome, it opens in a new tab unless you specify width and height. Chrome has heuristics for detecting spammy ads. At worse, the blocked pop-up icon will appear in the address bar.

But because the window uses the same DOM and JS Context, that already eliminates a whole class of spam which opens windows with other origins.

In Chrome, a user can disable the pop-up blocking for a specific site if Chrome heuristics are wrong.

PWAs will be more trusted. If a user finds a PWA misbehaving, the user can uninstall it.

Etc

Browsers would have new rules specifically for windows opened from <window>s. F.e., maybe

  • A maximum of 10 can be opened at once, or something
  • When switching to another main window (or tab), perhaps all <window> windows of the previous main window are hidden
  • etc

Yeaaaas.

Imagine for example a tool panel, like those in well-known apps such as Photoshop or Gimp or Blender, and imagine individual sections in the tool bars can be popped into separate windows, dragged between windows, etc.

Gimp has a single-window mode and a multi-window mode that you can toggle, where panels pop in or out of the main window.

These sorts of things would super easy to achieve with this <window> feature.

#7

I updated my above reply, in case you’re reading email or already read it.

#8

By the way, this is somewhat possible to do with React components.

With React, we can use a “portal” (React.createPortal) to render to any arbitrary element, even one from another window.

The thing is, this works well when

  • not also using custom elements, because they then need to be defined in the child window
  • using a CSS-in-JS styling system which applies all styles via style attributes, that way we don’t need to think about how to copy styles over.
  • Using React components that do not rely on event bubbling (because they stop at the window boundary).

But this still involves opening the window manually, and also spins up a new context, and event bubbling would require more hacking, 3rd party libs like jQuery won’t simply work, etc. It’s just plain difficult.

#9

Could you describe the use cases?

What are the advantages of Gimp’s multi-window mode? Could you share a screenshot?