text: /**
   * Copyright 2017 The AMP HTML Authors. All Rights Reserved.
   *
   * Licensed under the Apache License, Version 2.0 (the "License");
   * you may not use this file except in compliance with the License.
   * You may obtain a copy of the License at
   *
   *      http://www.apache.org/licenses/LICENSE-2.0
   *
   * Unless required by applicable law or agreed to in writing, software
   * distributed under the License is distributed on an "AS-IS" BASIS,
   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   * See the License for the specific language governing permissions and
   * limitations under the License.
   */
  'use strict';
  
  const {isRenderDelayingExtension, isCustomElement} = require('../Extensions.js');
  const {applyLayout} = require('./ApplyLayout.js');
  
  class ServerSideRendering {
    constructor(config) {
      this.log_ = config.log.tag('ServerSideRendering');
    }
    // Determines whether the node |n| has an enclosing ancestor tag
    // identified as |tagname|.
    _hasAncestorWithTag(n, tagname) {
      for (let p = n.parent; p !== null; p = p.parent) {
        if (p.tagName === tagname) {
          return true;
        }
      }
      return false;
    }
  
    transform(tree) {
      const html = tree.root.firstChildByTag('html');
      const body = html.firstChildByTag('body');
      const head = html.firstChildByTag('head');
  
      // A simple check ensuring that the Server-side rendering is only applied once.
      if (typeof (html.attribs['i-amphtml-layout']) !== 'undefined' &&
        html.attribs['i-amphtml-layout'] !== null) {
        return;
      }
      html.attribs['i-amphtml-layout'] = '';
  
      // Within the loop we apply the layout to the custom tags (amp-foo...)
      // where possible, but while we're at this we also look for reasons
      // not to remove the boilerplate.
      let canRemoveBoilerplate = true;
      for (let node = body; node !== null; node = node.nextNode()) {
        // Skip tags that are not AMP custom elements.
        if (!isCustomElement(node)) {
          continue;
        }
  
        // Skip tags inside a template tag.
        if (this._hasAncestorWithTag(node, 'template')) {
          continue;
        }
  
        // If these attributes are used on any AMP custom element tags within
        // the document, we can't remove the boilerplate - they require the
        // boilerplate.
        if (node.attribs.heights || node.attribs.media || node.attribs.sizes) {
          this.log_.debug(
              'cannot remove boilerplate as either heights, media or sizes attribute is set:\n',
              node.attribs
          );
          canRemoveBoilerplate = false;
        }
  
        // amp-experiment is a render delaying extension iff the tag is used in
        // the doc. We check for that here rather than checking for the existence
        // of the amp-experiment script in IsRenderDelayingExtension below.
        if (node.tagName === 'amp-experiment') {
          canRemoveBoilerplate = false;
          this.log_.debug('cannot remove boilerplate: amp-experiment');
        }
  
        // amp-audio requires knowing the dimensions of the browser. Do not
        // remove the boilerplate or apply layout if amp-audio is present in the
        // document.
        if (node.tagName === 'amp-audio') {
          canRemoveBoilerplate = false;
          this.log_.debug('cannot remove boilerplate: amp-audio');
          continue;
        }
  
        // Now apply the layout to the custom elements. If we encounter
        // any unsupported layout, the applyLayout function returns
        // false and we can't remove the boilerplate.
        if (!applyLayout(node, tree, this.log_)) {
          this.log_.debug('cannot remove boilerplate: unsupported layout');
          canRemoveBoilerplate = false;
          continue;
        }
      }
  
      // Emit the amp-runtime marker to indicate that we're applying
      // server side rendering in the document.
      const ampRuntimeMarker = tree.createElement('style');
      ampRuntimeMarker.attribs['amp-runtime'] = '';
  
      const referenceNode = head.children && head.children.length ? head.children[0] : null;
      head.insertBefore(ampRuntimeMarker, referenceNode);
  
      for (let node = head.firstChild; node; node = node.nextSibling) {
        // amp-experiment is a render delaying extension iff the tag is used in
        // the doc, which we checked for above.
        if (node.tagName === 'script' && node.hasAttribute('custom-element') &&
            node.attribs['custom-element'] === 'amp-experiment') {
          continue;
        }
        if (isRenderDelayingExtension(node)) {
          this.log_.debug('cannot remove boilerplate: amp-dynamic-css-classes');
          canRemoveBoilerplate = false;
        }
      }
  
      // Below, we're only concerned about removing the boilerplate.
      // If we've already determined that we can't, we're done here.
      if (!canRemoveBoilerplate) {
        return;
      }
  
      // The boilerplate can be removed, note it on the <html> tag.
      html.attribs['i-amphtml-no-boilerplate'] = '';
  
      // Find the boilerplate and remove it.
      // The following code assumes that the <noscript>
      // tag in the head is only ever used for boilerplate.
      const toRemove = [];
      for (let node = head.firstChild; node; node = node.nextSibling) {
        if (node.tagName === 'noscript' ||
            (node.tagName === 'style' && node.hasAttribute('amp-boilerplate'))) {
          toRemove.push(node);
        }
      }
  
      for (const n of toRemove) {
        n.remove();
      }
    }
  }
  
  module.exports = ServerSideRendering;
  