1. /**
    
  2.  * This file is compiled to a standalone browser script by rollup and loaded by Fizz
    
  3.  *  clients. Therefore, it should be fast and not have many external dependencies.
    
  4.  * @flow
    
  5.  */
    
  6. /* eslint-disable dot-notation */
    
  7. 
    
  8. // Imports are resolved statically by the closure compiler in release bundles
    
  9. // and by rollup in jest unit tests
    
  10. import {
    
  11.   clientRenderBoundary,
    
  12.   completeBoundaryWithStyles,
    
  13.   completeBoundary,
    
  14.   completeSegment,
    
  15. } from './fizz-instruction-set/ReactDOMFizzInstructionSetExternalRuntime';
    
  16. 
    
  17. if (!window.$RC) {
    
  18.   // TODO: Eventually remove, we currently need to set these globals for
    
  19.   // compatibility with ReactDOMFizzInstructionSet
    
  20.   window.$RC = completeBoundary;
    
  21.   window.$RM = new Map();
    
  22. }
    
  23. 
    
  24. if (document.body != null) {
    
  25.   if (document.readyState === 'loading') {
    
  26.     installFizzInstrObserver(document.body);
    
  27.   }
    
  28.   // $FlowFixMe[incompatible-cast]
    
  29.   handleExistingNodes((document.body: HTMLElement));
    
  30. } else {
    
  31.   // Document must be loading -- body may not exist yet if the fizz external
    
  32.   // runtime is sent in <head> (e.g. as a preinit resource)
    
  33.   // $FlowFixMe[recursive-definition]
    
  34.   const domBodyObserver = new MutationObserver(() => {
    
  35.     // We expect the body node to be stable once parsed / created
    
  36.     if (document.body != null) {
    
  37.       if (document.readyState === 'loading') {
    
  38.         installFizzInstrObserver(document.body);
    
  39.       }
    
  40.       // $FlowFixMe[incompatible-cast]
    
  41.       handleExistingNodes((document.body: HTMLElement));
    
  42. 
    
  43.       // We can call disconnect without takeRecord here,
    
  44.       // since we only expect a single document.body
    
  45.       domBodyObserver.disconnect();
    
  46.     }
    
  47.   });
    
  48.   // documentElement must already exist at this point
    
  49.   domBodyObserver.observe(document.documentElement, {childList: true});
    
  50. }
    
  51. 
    
  52. function handleExistingNodes(target: HTMLElement) {
    
  53.   const existingNodes = target.querySelectorAll('template');
    
  54.   for (let i = 0; i < existingNodes.length; i++) {
    
  55.     handleNode(existingNodes[i]);
    
  56.   }
    
  57. }
    
  58. 
    
  59. function installFizzInstrObserver(target: Node) {
    
  60.   const handleMutations = (mutations: Array<MutationRecord>) => {
    
  61.     for (let i = 0; i < mutations.length; i++) {
    
  62.       const addedNodes = mutations[i].addedNodes;
    
  63.       for (let j = 0; j < addedNodes.length; j++) {
    
  64.         if (addedNodes[j].parentNode) {
    
  65.           handleNode(addedNodes[j]);
    
  66.         }
    
  67.       }
    
  68.     }
    
  69.   };
    
  70. 
    
  71.   const fizzInstrObserver = new MutationObserver(handleMutations);
    
  72.   // We assume that instruction data nodes are eventually appended to the
    
  73.   // body, even if Fizz is streaming to a shell / subtree.
    
  74.   fizzInstrObserver.observe(target, {
    
  75.     childList: true,
    
  76.   });
    
  77.   window.addEventListener('DOMContentLoaded', () => {
    
  78.     handleMutations(fizzInstrObserver.takeRecords());
    
  79.     fizzInstrObserver.disconnect();
    
  80.   });
    
  81. }
    
  82. 
    
  83. function handleNode(node_: Node) {
    
  84.   // $FlowFixMe[incompatible-cast]
    
  85.   if (node_.nodeType !== 1 || !(node_: HTMLElement).dataset) {
    
  86.     return;
    
  87.   }
    
  88.   // $FlowFixMe[incompatible-cast]
    
  89.   const node = (node_: HTMLElement);
    
  90.   const dataset = node.dataset;
    
  91.   if (dataset['rxi'] != null) {
    
  92.     clientRenderBoundary(
    
  93.       dataset['bid'],
    
  94.       dataset['dgst'],
    
  95.       dataset['msg'],
    
  96.       dataset['stck'],
    
  97.     );
    
  98.     node.remove();
    
  99.   } else if (dataset['rri'] != null) {
    
  100.     // Convert styles here, since its type is Array<Array<string>>
    
  101.     completeBoundaryWithStyles(
    
  102.       dataset['bid'],
    
  103.       dataset['sid'],
    
  104.       JSON.parse(dataset['sty']),
    
  105.     );
    
  106.     node.remove();
    
  107.   } else if (dataset['rci'] != null) {
    
  108.     completeBoundary(dataset['bid'], dataset['sid']);
    
  109.     node.remove();
    
  110.   } else if (dataset['rsi'] != null) {
    
  111.     completeSegment(dataset['sid'], dataset['pid']);
    
  112.     node.remove();
    
  113.   }
    
  114. }