Skip to content

Instantly share code, notes, and snippets.

@dglazkov
Last active August 29, 2015 13:58
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save dglazkov/ce96f673b0b2ce7b8c55 to your computer and use it in GitHub Desktop.
Save dglazkov/ce96f673b0b2ce7b8c55 to your computer and use it in GitHub Desktop.
Shadow DOM Imperative API Brainstorming

#Shadow DOM Imperative API Brainstorming

##Reduced test case:

<div>
  <template shadow> <!-- shadow root -->
    <details>
      <content></content>
    </details>
  </template>
  <summary>SUMMARY</summary>
  <p>ALSO STUFF</p>
</div>

<summary> and <details> are used here because their behavior is understood. One can imagine any widget with the same behavior in their place. For the sake of this exercise, the <details> has the following shadow tree:

<content id="summary-ui" select="summary:first-of-type"></content>
<div>
  <content id="everything-else"></content>
</div>

Steps:

  1. Suppose you start with no <summary> element in the tree
  2. At this point, the <details> sees nothing and displays nothing.
  3. The <summary> is created and inserted into the tree.
  4. Somewhere after this point, but prior to rendering, the two insertion points in <details> need to be informed that <summary> is now available as a distribution candidate.
  5. The content#summary-ui looks at the order of <summary> relative to other elements in the distribution pool and only picks it if it's the first <summary>.
  6. The content#everything-else grabs leftovers of the distribution pool.

##Rough Ideas

Here are the ideas we explored.

###Distribution Callback

The distribution callback is called at the time of distribution (maybe at the time of pool distribution):

function distributionCallback(pool) {
  return filter(pool);
}

What's unclear is when to run this callback.

div.appendChild(summary); // callback runs here?
var top = summary.offsetTop; // callback runs here?!
assert(top);

The preferred option is to run distribution callbacks as lazily as possible, to avoid churn of distribution on every DOM operation. Today, this timing in Blink coincides with style resolution, and it works well. Unfortunately, this means that function getters that force style updates will now run arbitrary JS code.

Formulated generally, the imperative API needs to be able to react to changes in composition and affect box tree construction.

###Passive Candidate Array

An array of items on each insertion point, which can be populated at will by the author. This array is then used as the candidate pool the time of distribution. It's the responsibility of the author to update the array in reaction to DOM changes.

In this situation, an insertion point needs to effectively re-implement distribution algorithms by hand:

When <summary> is inserted in steps above, the only way for <details> for to detect this and react to it is by installing mutation observers on the document. In the general case, a widget needs to always watch children of ancestors with shadow roots, then figuring out whether they will be distributed all the way down to the widget, and then update the pool accordingly. This seems extra-super-duper-bad.

###Selector-based Routing

Another idea would be to provide a more explicit way to define distributions via a selector-based routers:

var router == new DistributionRouter("summary:first-of-type");
router.takeChildrenFrom(div)
router.distributeTo(contentSummaryUI);

This is a bit of a cop-out, since it does not allow explicitly listing elements as candidates, but it does not have the drawbacks of the previously listed ideas.

@sicking
Copy link

sicking commented Apr 11, 2014

The simplest solution here seems to be to simply have an API like

partial interface HTMLContentElement { insertionFor(Element child); // To be bikeshed };

The caller can then choose to run this whenever is appropriate. I.e. by listening to mutation observers and/or layout related events.

I guess you'd also need to be able to be notified about when the list of nodes distributed into a changes. So that you can deal with nested insertion points... So the problem comes back to when such an event fires.

@dglazkov
Copy link
Author

Yup. You got it 😃

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment