Module: Applitools::Selenium::Scripts

Defined in:
lib/applitools/selenium/scripts/get_element_xpath.rb,
lib/applitools/selenium/scripts/process_page_and_poll.rb,
lib/applitools/selenium/scripts/process_page_and_serialize.rb

Constant Summary collapse

GET_ELEMENT_XPATH_JS =
<<'END'
        var el = arguments[0];
        var xpath = '';
        do {
          var parent = el.parentElement;
          var index = 1;
          if (parent !== null) {
            var children = parent.children;
            for (var childIdx in children) {
              var child = children[childIdx];
              if (child === el) break;
              if (child.tagName === el.tagName) index++;
            }
          }
          xpath = '/' + el.tagName + '[' + index + ']' + xpath;
          el = parent;
        } while (el !== null);
        return '/' + xpath;
END
PROCESS_PAGE_AND_POLL =
<<'END'
/* @applitools/[email protected] */

function __processPageAndPoll() {
  var processPageAndPoll = (function () {
  'use strict';

  // This code was copied and modified from https://github.com/beatgammit/base64-js/blob/bf68aaa277/index.js
  // License: https://github.com/beatgammit/base64-js/blob/bf68aaa277d9de7007cc0c58279c411bb10670ac/LICENSE

  function arrayBufferToBase64(ab) {
    const lookup = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'.split('');

    const uint8 = new Uint8Array(ab);
    const len = uint8.length;
    const extraBytes = len % 3; // if we have 1 byte left, pad 2 bytes
    const parts = [];
    const maxChunkLength = 16383; // must be multiple of 3

    let tmp;

    // go through the array every three bytes, we'll deal with trailing stuff later
    for (let i = 0, len2 = len - extraBytes; i < len2; i += maxChunkLength) {
      parts.push(encodeChunk(i, i + maxChunkLength > len2 ? len2 : i + maxChunkLength));
    }

    // pad the end with zeros, but make sure to not forget the extra bytes
    if (extraBytes === 1) {
      tmp = uint8[len - 1];
      parts.push(lookup[tmp >> 2] + lookup[(tmp << 4) & 0x3f] + '==');
    } else if (extraBytes === 2) {
      tmp = (uint8[len - 2] << 8) + uint8[len - 1];
      parts.push(lookup[tmp >> 10] + lookup[(tmp >> 4) & 0x3f] + lookup[(tmp << 2) & 0x3f] + '=');
    }

    return parts.join('');

    function tripletToBase64(num) {
      return (
        lookup[(num >> 18) & 0x3f] +
        lookup[(num >> 12) & 0x3f] +
        lookup[(num >> 6) & 0x3f] +
        lookup[num & 0x3f]
      );
    }

    function encodeChunk(start, end) {
      let tmp;
      const output = [];
      for (let i = start; i < end; i += 3) {
        tmp = ((uint8[i] << 16) & 0xff0000) + ((uint8[i + 1] << 8) & 0xff00) + (uint8[i + 2] & 0xff);
        output.push(tripletToBase64(tmp));
      }
      return output.join('');
    }
  }

  var arrayBufferToBase64_1 = arrayBufferToBase64;

  function extractLinks(doc = document) {
    const srcsetUrls = Array.from(doc.querySelectorAll('img[srcset],source[srcset]'))
      .map(srcsetEl =>
        srcsetEl
          .getAttribute('srcset')
          .split(', ')
          .map(str => str.trim().split(/\s+/)[0]),
      )
      .reduce((acc, urls) => acc.concat(urls), []);

    const srcUrls = Array.from(
      doc.querySelectorAll('img[src],source[src],input[type="image"][src]'),
    ).map(srcEl => srcEl.getAttribute('src'));

    const imageUrls = Array.from(doc.querySelectorAll('image,use'))
      .map(hrefEl => hrefEl.getAttribute('href') || hrefEl.getAttribute('xlink:href'))
      .filter(u => u && u[0] !== '#');

    const objectUrls = Array.from(doc.querySelectorAll('object'))
      .map(el => el.getAttribute('data'))
      .filter(Boolean);

    const cssUrls = Array.from(doc.querySelectorAll('link[rel="stylesheet"]')).map(link =>
      link.getAttribute('href'),
    );

    const videoPosterUrls = Array.from(doc.querySelectorAll('video[poster]')).map(videoEl =>
      videoEl.getAttribute('poster'),
    );

    return Array.from(srcsetUrls)
      .concat(Array.from(srcUrls))
      .concat(Array.from(imageUrls))
      .concat(Array.from(cssUrls))
      .concat(Array.from(videoPosterUrls))
      .concat(Array.from(objectUrls));
  }

  var extractLinks_1 = extractLinks;

  function uuid() {
    return window.crypto.getRandomValues(new Uint32Array(1))[0];
  }

  var uuid_1 = uuid;

  function isInlineFrame(frame) {
    return (
      !/^https?:.+/.test(frame.src) ||
      (frame.contentDocument &&
        frame.contentDocument.location &&
        frame.contentDocument.location.href === 'about:blank')
    );
  }

  var isInlineFrame_1 = isInlineFrame;

  function isAccessibleFrame(frame) {
    try {
      const doc = frame.contentDocument;
      return !!(doc && doc.defaultView && doc.defaultView.frameElement);
    } catch (err) {
      // for CORS frames
    }
  }

  var isAccessibleFrame_1 = isAccessibleFrame;

  function absolutizeUrl(url, absoluteUrl) {
    return new URL(url, absoluteUrl).href;
  }

  var absolutizeUrl_1 = absolutizeUrl;

  function domNodesToCdt(docNode, baseUrl) {
    const cdt = [{nodeType: Node.DOCUMENT_NODE}];
    const docRoots = [docNode];
    const canvasElements = [];
    const inlineFrames = [];

    cdt[0].childNodeIndexes = childrenFactory(cdt, docNode.childNodes);
    return {cdt, docRoots, canvasElements, inlineFrames};

    function childrenFactory(cdt, elementNodes) {
      if (!elementNodes || elementNodes.length === 0) return null;

      const childIndexes = [];
      Array.prototype.forEach.call(elementNodes, elementNode => {
        const index = elementNodeFactory(cdt, elementNode);
        if (index !== null) {
          childIndexes.push(index);
        }
      });
      return childIndexes;
    }

    function elementNodeFactory(cdt, elementNode) {
      let node, manualChildNodeIndexes, dummyUrl;
      const {nodeType} = elementNode;

      if ([Node.ELEMENT_NODE, Node.DOCUMENT_FRAGMENT_NODE].includes(nodeType)) {
        if (elementNode.nodeName !== 'SCRIPT') {
          if (
            elementNode.nodeName === 'STYLE' &&
            elementNode.sheet &&
            elementNode.sheet.cssRules.length
          ) {
            cdt.push(getCssRulesNode(elementNode));
            manualChildNodeIndexes = [cdt.length - 1];
          }

          node = getBasicNode(elementNode);
          node.childNodeIndexes =
            manualChildNodeIndexes ||
            (elementNode.childNodes.length ? childrenFactory(cdt, elementNode.childNodes) : []);

          if (elementNode.shadowRoot) {
            node.shadowRootIndex = elementNodeFactory(cdt, elementNode.shadowRoot);
            docRoots.push(elementNode.shadowRoot);
          }

          if (elementNode.nodeName === 'CANVAS') {
            dummyUrl = absolutizeUrl_1(`applitools-canvas-${uuid_1()}.png`, baseUrl);
            node.attributes.push({name: 'data-applitools-src', value: dummyUrl});
            canvasElements.push({element: elementNode, url: dummyUrl});
          }

          if (
            elementNode.nodeName === 'IFRAME' &&
            isAccessibleFrame_1(elementNode) &&
            isInlineFrame_1(elementNode)
          ) {
            dummyUrl = absolutizeUrl_1(`?applitools-iframe=${uuid_1()}`, baseUrl);
            node.attributes.push({name: 'data-applitools-src', value: dummyUrl});
            inlineFrames.push({element: elementNode, url: dummyUrl});
          }
        } else {
          node = getScriptNode(elementNode);
        }
      } else if (nodeType === Node.TEXT_NODE) {
        node = getTextNode(elementNode);
      } else if (nodeType === Node.DOCUMENT_TYPE_NODE) {
        node = getDocNode(elementNode);
      }

      if (node) {
        cdt.push(node);
        return cdt.length - 1;
      } else {
        return null;
      }
    }

    function nodeAttributes({attributes = {}}) {
      return Object.keys(attributes).filter(k => attributes[k] && attributes[k].name);
    }

    function getCssRulesNode(elementNode) {
      return {
        nodeType: Node.TEXT_NODE,
        nodeValue: Array.from(elementNode.sheet.cssRules)
          .map(rule => rule.cssText)
          .join(''),
      };
    }

    function getBasicNode(elementNode) {
      const node = {
        nodeType: elementNode.nodeType,
        nodeName: elementNode.nodeName,
        attributes: nodeAttributes(elementNode).map(key => {
          let value = elementNode.attributes[key].value;
          const name = elementNode.attributes[key].name;
          if (/^blob:/.test(value)) {
            value = value.replace(/^blob:/, '');
          }
          return {
            name,
            value,
          };
        }),
      };

      if (elementNode.tagName === 'INPUT' && ['checkbox', 'radio'].includes(elementNode.type)) {
        if (elementNode.attributes.checked && !elementNode.checked) {
          const idx = node.attributes.findIndex(a => a.name === 'checked');
          node.attributes.splice(idx, 1);
        }
        if (!elementNode.attributes.checked && elementNode.checked) {
          node.attributes.push({name: 'checked'});
        }
      }

      if (
        elementNode.tagName === 'INPUT' &&
        elementNode.type === 'text' &&
        (elementNode.attributes.value && elementNode.attributes.value.value) !== elementNode.value
      ) {
        const nodeAttr = node.attributes.find(a => a.name === 'value');
        if (nodeAttr) {
          nodeAttr.value = elementNode.value;
        } else {
          node.attributes.push({name: 'value', value: elementNode.value});
        }
      }
      return node;
    }

    function getScriptNode(elementNode) {
      return {
        nodeType: Node.ELEMENT_NODE,
        nodeName: 'SCRIPT',
        attributes: nodeAttributes(elementNode)
          .map(key => ({
            name: elementNode.attributes[key].name,
            value: elementNode.attributes[key].value,
          }))
          .filter(attr => attr.name !== 'src'),
        childNodeIndexes: [],
      };
    }

    function getTextNode(elementNode) {
      return {
        nodeType: Node.TEXT_NODE,
        nodeValue: elementNode.nodeValue,
      };
    }

    function getDocNode(elementNode) {
      return {
        nodeType: Node.DOCUMENT_TYPE_NODE,
        nodeName: elementNode.nodeName,
      };
    }
  }

  var domNodesToCdt_1 = domNodesToCdt;

  function uniq(arr) {
    const result = [];
    new Set(arr).forEach(v => v && result.push(v));
    return result;
  }

  var uniq_1 = uniq;

  function aggregateResourceUrlsAndBlobs(resourceUrlsAndBlobsArr) {
    return resourceUrlsAndBlobsArr.reduce(
      ({resourceUrls: allResourceUrls, blobsObj: allBlobsObj}, {resourceUrls, blobsObj}) => ({
        resourceUrls: uniq_1(allResourceUrls.concat(resourceUrls)),
        blobsObj: Object.assign(allBlobsObj, blobsObj),
      }),
      {resourceUrls: [], blobsObj: {}},
    );
  }

  var aggregateResourceUrlsAndBlobs_1 = aggregateResourceUrlsAndBlobs;

  function makeGetResourceUrlsAndBlobs({processResource, aggregateResourceUrlsAndBlobs}) {
    return function getResourceUrlsAndBlobs({documents, urls, forceCreateStyle = false}) {
      return Promise.all(
        urls.map(url => processResource({url, documents, getResourceUrlsAndBlobs, forceCreateStyle})),
      ).then(resourceUrlsAndBlobsArr => aggregateResourceUrlsAndBlobs(resourceUrlsAndBlobsArr));
    };
  }

  var getResourceUrlsAndBlobs = makeGetResourceUrlsAndBlobs;

  function filterInlineUrl(absoluteUrl) {
    return /^(blob|https?):/.test(absoluteUrl);
  }

  var filterInlineUrl_1 = filterInlineUrl;

  function toUnAnchoredUri(url) {
    const m = url && url.match(/(^[^#]*)/);
    const res = (m && m[1]) || url;
    return (res && res.replace(/\?\s*$/, '')) || url;
  }

  var toUnAnchoredUri_1 = toUnAnchoredUri;

  var noop = () => {};

  function flat(arr) {
    return arr.reduce((flatArr, item) => flatArr.concat(item), []);
  }

  var flat_1 = flat;

  function makeProcessResource({
    fetchUrl,
    findStyleSheetByUrl,
    getCorsFreeStyleSheet,
    extractResourcesFromStyleSheet,
    extractResourcesFromSvg,
    sessionCache,
    cache = {},
    log = noop,
  }) {
    return function processResource({
      url,
      documents,
      getResourceUrlsAndBlobs,
      forceCreateStyle = false,
    }) {
      if (!cache[url]) {
        if (sessionCache && sessionCache.getItem(url)) {
          const resourceUrls = getDependencies(url);
          log('doProcessResource from sessionStorage', url, 'deps:', resourceUrls.slice(1));
          cache[url] = Promise.resolve({resourceUrls});
        } else {
          const now = Date.now();
          cache[url] = doProcessResource(url).then(result => {
            log('doProcessResource', `[${Date.now() - now}ms]`, url);
            return result;
          });
        }
      }
      return cache[url];

      function doProcessResource(url) {
        log('fetching', url);
        const now = Date.now();
        return fetchUrl(url)
          .catch(e => {
            if (probablyCORS(e)) {
              return {probablyCORS: true, url};
            } else {
              throw e;
            }
          })
          .then(({url, type, value, probablyCORS}) => {
            if (probablyCORS) {
              sessionCache && sessionCache.setItem(url, []);
              return {resourceUrls: [url]};
            }

            log('fetched', `[${Date.now() - now}ms]`, url);

            const thisBlob = {[url]: {type, value}};
            let dependentUrls;
            if (/text\/css/.test(type)) {
              let styleSheet = findStyleSheetByUrl(url, documents);
              if (styleSheet || forceCreateStyle) {
                const {corsFreeStyleSheet, cleanStyleSheet} = getCorsFreeStyleSheet(
                  value,
                  styleSheet,
                );
                dependentUrls = extractResourcesFromStyleSheet(corsFreeStyleSheet, documents[0]);
                cleanStyleSheet();
              }
            } else if (/image\/svg/.test(type)) {
              try {
                dependentUrls = extractResourcesFromSvg(value);
                forceCreateStyle = !!dependentUrls;
              } catch (e) {
                console.log('could not parse svg content', e);
              }
            }

            if (dependentUrls) {
              const absoluteDependentUrls = dependentUrls
                .map(resourceUrl => absolutizeUrl_1(resourceUrl, url.replace(/^blob:/, '')))
                .map(toUnAnchoredUri_1)
                .filter(filterInlineUrl_1);

              sessionCache && sessionCache.setItem(url, absoluteDependentUrls);

              return getResourceUrlsAndBlobs({
                documents,
                urls: absoluteDependentUrls,
                forceCreateStyle,
              }).then(({resourceUrls, blobsObj}) => ({
                resourceUrls,
                blobsObj: Object.assign(blobsObj, thisBlob),
              }));
            } else {
              sessionCache && sessionCache.setItem(url, []);
              return {blobsObj: thisBlob};
            }
          })
          .catch(err => {
            log('error while fetching', url, err);
            sessionCache && clearFromSessionStorage();
            return {};
          });
      }

      function probablyCORS(err) {
        const msg =
          err.message &&
          (err.message.includes('Failed to fetch') || err.message.includes('Network request failed'));
        const name = err.name && err.name.includes('TypeError');
        return msg && name;
      }

      function getDependencies(url) {
        const dependentUrls = sessionCache.getItem(url);
        return [url].concat(dependentUrls ? uniq_1(flat_1(dependentUrls.map(getDependencies))) : []);
      }

      function clearFromSessionStorage() {
        log('clearing from sessionStorage:', url);
        sessionCache.keys().forEach(key => {
          const dependentUrls = sessionCache.getItem(key);
          sessionCache.setItem(key, dependentUrls.filter(dep => dep !== url));
        });
        log('cleared from sessionStorage:', url);
      }
    };
  }

  var processResource = makeProcessResource;

  function getUrlFromCssText(cssText) {
    const re = /url\((?!['"]?:)['"]?([^'")]*)['"]?\)/g;
    const ret = [];
    let result;
    while ((result = re.exec(cssText)) !== null) {
      ret.push(result[1]);
    }
    return ret;
  }

  var getUrlFromCssText_1 = getUrlFromCssText;

  function makeExtractResourcesFromSvg({parser, decoder, extractResourceUrlsFromStyleTags}) {
    return function(svgArrayBuffer) {
      const decooder = decoder || new TextDecoder('utf-8');
      const svgStr = decooder.decode(svgArrayBuffer);
      const domparser = parser || new DOMParser();
      const doc = domparser.parseFromString(svgStr, 'image/svg+xml');

      const fromHref = Array.from(doc.querySelectorAll('image,use,link[rel="stylesheet"]')).map(
        e => e.getAttribute('href') || e.getAttribute('xlink:href'),
      );
      const fromObjects = Array.from(doc.getElementsByTagName('object')).map(e =>
        e.getAttribute('data'),
      );
      const fromStyleTags = extractResourceUrlsFromStyleTags(doc, false);
      const fromStyleAttrs = urlsFromStyleAttrOfDoc(doc);

      return fromHref
        .concat(fromObjects)
        .concat(fromStyleTags)
        .concat(fromStyleAttrs)
        .filter(u => u[0] !== '#');
    };
  }

  function urlsFromStyleAttrOfDoc(doc) {
    return flat_1(
      Array.from(doc.querySelectorAll('*[style]'))
        .map(e => e.style.cssText)
        .map(getUrlFromCssText_1)
        .filter(Boolean),
    );
  }

  var makeExtractResourcesFromSvg_1 = makeExtractResourcesFromSvg;

  /* global window */

  function fetchUrl(url, fetch = window.fetch) {
    return fetch(url, {cache: 'force-cache', credentials: 'same-origin'}).then(resp =>
      resp.status === 200
        ? resp.arrayBuffer().then(buff => ({
            url,
            type: resp.headers.get('Content-Type'),
            value: buff,
          }))
        : Promise.reject(`bad status code ${resp.status}`),
    );
  }

  var fetchUrl_1 = fetchUrl;

  function makeFindStyleSheetByUrl({styleSheetCache}) {
    return function findStyleSheetByUrl(url, documents) {
      const allStylesheets = flat_1(documents.map(d => Array.from(d.styleSheets)));
      return (
        styleSheetCache[url] ||
        allStylesheets.find(styleSheet => styleSheet.href && toUnAnchoredUri_1(styleSheet.href) === url)
      );
    };
  }

  var findStyleSheetByUrl = makeFindStyleSheetByUrl;

  function makeExtractResourcesFromStyleSheet({styleSheetCache}) {
    return function extractResourcesFromStyleSheet(styleSheet, doc) {
      const win = doc.defaultView || (doc.ownerDocument && doc.ownerDocument.defaultView) || window;
      const urls = uniq_1(
        Array.from(styleSheet.cssRules || []).reduce((acc, rule) => {
          if (rule instanceof win.CSSImportRule) {
            styleSheetCache[rule.styleSheet.href] = rule.styleSheet;
            return acc.concat(rule.href);
          } else if (rule instanceof win.CSSFontFaceRule) {
            return acc.concat(getUrlFromCssText_1(rule.cssText));
          } else if (
            (win.CSSSupportsRule && rule instanceof win.CSSSupportsRule) ||
            rule instanceof win.CSSMediaRule
          ) {
            return acc.concat(extractResourcesFromStyleSheet(rule, doc));
          } else if (rule instanceof win.CSSStyleRule) {
            for (let i = 0, ii = rule.style.length; i < ii; i++) {
              const urls = getUrlFromCssText_1(rule.style.getPropertyValue(rule.style[i]));
              urls.length && (acc = acc.concat(urls));
            }
          }
          return acc;
        }, []),
      );
      return urls.filter(u => u[0] !== '#');
    };
  }

  var extractResourcesFromStyleSheet = makeExtractResourcesFromStyleSheet;

  function extractResourceUrlsFromStyleAttrs(cdt) {
    return cdt.reduce((acc, node) => {
      if (node.nodeType === 1) {
        const styleAttr =
          node.attributes && node.attributes.find(attr => attr.name.toUpperCase() === 'STYLE');

        if (styleAttr) acc = acc.concat(getUrlFromCssText_1(styleAttr.value));
      }
      return acc;
    }, []);
  }

  var extractResourceUrlsFromStyleAttrs_1 = extractResourceUrlsFromStyleAttrs;

  function makeExtractResourceUrlsFromStyleTags(extractResourcesFromStyleSheet) {
    return function extractResourceUrlsFromStyleTags(doc, onlyDocStylesheet = true) {
      return uniq_1(
        Array.from(doc.querySelectorAll('style')).reduce((resourceUrls, styleEl) => {
          const styleSheet = onlyDocStylesheet
            ? Array.from(doc.styleSheets).find(styleSheet => styleSheet.ownerNode === styleEl)
            : styleEl.sheet;
          return styleSheet
            ? resourceUrls.concat(extractResourcesFromStyleSheet(styleSheet, doc))
            : resourceUrls;
        }, []),
      );
    };
  }

  var extractResourceUrlsFromStyleTags = makeExtractResourceUrlsFromStyleTags;

  function createTempStylsheet(cssArrayBuffer) {
    const cssText = new TextDecoder('utf-8').decode(cssArrayBuffer);
    const head = document.head || document.querySelectorAll('head')[0];
    const style = document.createElement('style');
    style.type = 'text/css';
    style.setAttribute('data-desc', 'Applitools tmp variable created by DOM SNAPSHOT');
    head.appendChild(style);

    // This is required for IE8 and below.
    if (style.styleSheet) {
      style.styleSheet.cssText = cssText;
    } else {
      style.appendChild(document.createTextNode(cssText));
    }
    return style.sheet;
  }

  var createTempStyleSheet = createTempStylsheet;

  function getCorsFreeStyleSheet(cssArrayBuffer, styleSheet) {
    let corsFreeStyleSheet;
    if (styleSheet) {
      try {
        styleSheet.cssRules;
        corsFreeStyleSheet = styleSheet;
      } catch (e) {
        console.log(
          `[dom-snapshot] could not access cssRules for ${styleSheet.href} ${e}\ncreating temp style for access.`,
        );
        corsFreeStyleSheet = createTempStyleSheet(cssArrayBuffer);
      }
    } else {
      corsFreeStyleSheet = createTempStyleSheet(cssArrayBuffer);
    }

    return {corsFreeStyleSheet, cleanStyleSheet};

    function cleanStyleSheet() {
      if (corsFreeStyleSheet !== styleSheet) {
        corsFreeStyleSheet.ownerNode.parentNode.removeChild(corsFreeStyleSheet.ownerNode);
      }
    }
  }

  var getCorsFreeStyleSheet_1 = getCorsFreeStyleSheet;

  function base64ToArrayBuffer(base64) {
    var binary_string = window.atob(base64);
    var len = binary_string.length;
    var bytes = new Uint8Array(len);
    for (var i = 0; i < len; i++) {
      bytes[i] = binary_string.charCodeAt(i);
    }
    return bytes.buffer;
  }

  var base64ToArrayBuffer_1 = base64ToArrayBuffer;

  function buildCanvasBlobs(canvasElements) {
    return canvasElements.map(({url, element}) => {
      const data = element.toDataURL('image/png');
      const value = base64ToArrayBuffer_1(data.split(',')[1]);
      return {url, type: 'image/png', value};
    });
  }

  var buildCanvasBlobs_1 = buildCanvasBlobs;

  function extractFrames(documents = [document]) {
    const iframes = flat_1(
      documents.map(d => Array.from(d.querySelectorAll('iframe[src]:not([src=""])'))),
    );

    return iframes.filter(f => isAccessibleFrame_1(f) && !isInlineFrame_1(f)).map(f => f.contentDocument);
  }

  var extractFrames_1 = extractFrames;

  const getBaesUrl = function(doc) {
    const baseUrl = doc.querySelectorAll('base')[0] && doc.querySelectorAll('base')[0].href;
    if (baseUrl && isUrl(baseUrl)) {
      return baseUrl;
    }
  };

  function isUrl(url) {
    return url && !/^(about:blank|javascript:void|blob:)/.test(url);
  }

  var getBaseUrl = getBaesUrl;

  function toUriEncoding(url) {
    const result =
      (url &&
        url.replace(/(\\[0-9a-fA-F]{1,6}\s?)/g, s => {
          const int = parseInt(s.substr(1).trim(), 16);
          return String.fromCodePoint(int);
        })) ||
      url;
    return result;
  }

  var toUriEncoding_1 = toUriEncoding;

  function makeLog(referenceTime) {
    return function log() {
      const args = ['[dom-snapshot]', `[+${Date.now() - referenceTime}ms]`].concat(
        Array.from(arguments),
      );
      console.log.apply(console, args);
    };
  }

  var log = makeLog;

  const RESOURCE_STORAGE_KEY = '__process_resource';

  function makeSessionCache({log, sessionStorage}) {
    let sessionStorageCache;
    try {
      sessionStorage = sessionStorage || window.sessionStorage;
      const sessionStorageCacheStr = sessionStorage.getItem(RESOURCE_STORAGE_KEY);
      sessionStorageCache = sessionStorageCacheStr ? JSON.parse(sessionStorageCacheStr) : {};
    } catch (ex) {
      log('error creating session cache', ex);
    }

    return {
      getItem,
      setItem,
      keys,
      persist,
    };

    function getItem(key) {
      if (sessionStorageCache) {
        return sessionStorageCache[key];
      }
    }

    function setItem(key, value) {
      if (sessionStorageCache) {
        log('saving to in-memory sessionStorage, key:', key, 'value:', value);
        sessionStorageCache[key] = value;
      }
    }

    function keys() {
      if (sessionStorageCache) {
        return Object.keys(sessionStorageCache);
      } else {
        return [];
      }
    }

    function persist() {
      if (sessionStorageCache) {
        sessionStorage.setItem(RESOURCE_STORAGE_KEY, JSON.stringify(sessionStorageCache));
      }
    }
  }

  var sessionCache = makeSessionCache;

  function processPage(doc = document, {showLogs, useSessionCache} = {}) {
    const log$$1 = showLogs ? log(Date.now()) : noop;
    log$$1('processPage start');
    const sessionCache$$1 = useSessionCache && sessionCache({log: log$$1});
    const styleSheetCache = {};
    const extractResourcesFromStyleSheet$$1 = extractResourcesFromStyleSheet({styleSheetCache});
    const findStyleSheetByUrl$$1 = findStyleSheetByUrl({styleSheetCache});
    const extractResourceUrlsFromStyleTags$$1 = extractResourceUrlsFromStyleTags(
      extractResourcesFromStyleSheet$$1,
    );

    const extractResourcesFromSvg = makeExtractResourcesFromSvg_1({extractResourceUrlsFromStyleTags: extractResourceUrlsFromStyleTags$$1});
    const processResource$$1 = processResource({
      fetchUrl: fetchUrl_1,
      findStyleSheetByUrl: findStyleSheetByUrl$$1,
      getCorsFreeStyleSheet: getCorsFreeStyleSheet_1,
      extractResourcesFromStyleSheet: extractResourcesFromStyleSheet$$1,
      extractResourcesFromSvg,
      absolutizeUrl: absolutizeUrl_1,
      log: log$$1,
      sessionCache: sessionCache$$1,
    });

    const getResourceUrlsAndBlobs$$1 = getResourceUrlsAndBlobs({
      processResource: processResource$$1,
      aggregateResourceUrlsAndBlobs: aggregateResourceUrlsAndBlobs_1,
    });

    return doProcessPage(doc).then(result => {
      log$$1('processPage end');
      return result;
    });

    function doProcessPage(doc, pageUrl = doc.location.href) {
      const baseUrl = getBaseUrl(doc) || pageUrl;
      const {cdt, docRoots, canvasElements, inlineFrames} = domNodesToCdt_1(doc, baseUrl);

      const linkUrls = flat_1(docRoots.map(extractLinks_1));
      const styleTagUrls = flat_1(docRoots.map(extractResourceUrlsFromStyleTags$$1));
      const absolutizeThisUrl = getAbsolutizeByUrl(baseUrl);
      const urls = uniq_1(
        Array.from(linkUrls)
          .concat(Array.from(styleTagUrls))
          .concat(extractResourceUrlsFromStyleAttrs_1(cdt)),
      )
        .map(toUriEncoding_1)
        .map(absolutizeThisUrl)
        .map(toUnAnchoredUri_1)
        .filter(filterInlineUrlsIfExisting);

      const resourceUrlsAndBlobsPromise = getResourceUrlsAndBlobs$$1({documents: docRoots, urls}).then(
        result => {
          sessionCache$$1 && sessionCache$$1.persist();
          return result;
        },
      );
      const canvasBlobs = buildCanvasBlobs_1(canvasElements);
      const frameDocs = extractFrames_1(docRoots);

      const processFramesPromise = frameDocs.map(f =>
        doProcessPage(f, f.defaultView.frameElement.src),
      );
      const processInlineFramesPromise = inlineFrames.map(({element, url}) =>
        doProcessPage(element.contentDocument, url),
      );

      const srcAttr =
        doc.defaultView &&
        doc.defaultView.frameElement &&
        doc.defaultView.frameElement.getAttribute('src');

      return Promise.all(
        [resourceUrlsAndBlobsPromise].concat(processFramesPromise).concat(processInlineFramesPromise),
      ).then(function(resultsWithFrameResults) {
        const {resourceUrls, blobsObj} = resultsWithFrameResults[0];
        const framesResults = resultsWithFrameResults.slice(1);
        return {
          cdt,
          url: pageUrl,
          srcAttr,
          resourceUrls: resourceUrls.map(url => url.replace(/^blob:/, '')),
          blobs: blobsObjToArray(blobsObj).concat(canvasBlobs),
          frames: framesResults,
        };
      });
    }
  }

  function getAbsolutizeByUrl(url) {
    return function(someUrl) {
      try {
        return absolutizeUrl_1(someUrl, url);
      } catch (err) {
        // can't do anything with a non-absolute url
      }
    };
  }

  function blobsObjToArray(blobsObj) {
    return Object.keys(blobsObj).map(blobUrl =>
      Object.assign(
        {
          url: blobUrl.replace(/^blob:/, ''),
        },
        blobsObj[blobUrl],
      ),
    );
  }

  function filterInlineUrlsIfExisting(absoluteUrl) {
    return absoluteUrl && filterInlineUrl_1(absoluteUrl);
  }

  var processPage_1 = processPage;

  function processPageAndSerialize() {
    return processPage_1.apply(this, arguments).then(serializeFrame);
  }

  function serializeFrame(frame) {
    frame.blobs = frame.blobs.map(({url, type, value}) => ({
      url,
      type,
      value: arrayBufferToBase64_1(value),
    }));
    frame.frames.forEach(serializeFrame);
    return frame;
  }

  var processPageAndSerialize_1 = processPageAndSerialize;

  const EYES_NAME_SPACE = '__EYES__APPLITOOLS__';

  function processPageAndPoll(doc) {
    if (!window[EYES_NAME_SPACE]) {
      window[EYES_NAME_SPACE] = {};
    }
    if (!window[EYES_NAME_SPACE].processPageAndSerializeResult) {
      window[EYES_NAME_SPACE].processPageAndSerializeResult = {
        status: 'WIP',
        value: null,
        error: null,
      };
      processPageAndSerialize_1(doc)
        .then(r => ((resultObject.status = 'SUCCESS'), (resultObject.value = r)))
        .catch(e => ((resultObject.status = 'ERROR'), (resultObject.error = e.message)));
    }

    const resultObject = window[EYES_NAME_SPACE].processPageAndSerializeResult;
    if (resultObject.status === 'SUCCESS') {
      window[EYES_NAME_SPACE].processPageAndSerializeResult = null;
    }

    return JSON.stringify(resultObject);
  }

  var processPageAndPoll_1 = processPageAndPoll;

  return processPageAndPoll_1;

}());

  return processPageAndPoll.apply(this, arguments);
}
END
PROCESS_RESOURCES =
<<'END'
// @applitools/[email protected]
function __processPageAndSerialize() {
  var processPageAndSerialize = (function () {
  'use strict';

  // This code was copied and modified from https://github.com/beatgammit/base64-js/blob/bf68aaa277/index.js
  // License: https://github.com/beatgammit/base64-js/blob/bf68aaa277d9de7007cc0c58279c411bb10670ac/LICENSE

  function arrayBufferToBase64(ab) {
    const lookup = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'.split('');

    const uint8 = new Uint8Array(ab);
    const len = uint8.length;
    const extraBytes = len % 3; // if we have 1 byte left, pad 2 bytes
    const parts = [];
    const maxChunkLength = 16383; // must be multiple of 3

    let tmp;

    // go through the array every three bytes, we'll deal with trailing stuff later
    for (let i = 0, len2 = len - extraBytes; i < len2; i += maxChunkLength) {
      parts.push(encodeChunk(i, i + maxChunkLength > len2 ? len2 : i + maxChunkLength));
    }

    // pad the end with zeros, but make sure to not forget the extra bytes
    if (extraBytes === 1) {
      tmp = uint8[len - 1];
      parts.push(lookup[tmp >> 2] + lookup[(tmp << 4) & 0x3f] + '==');
    } else if (extraBytes === 2) {
      tmp = (uint8[len - 2] << 8) + uint8[len - 1];
      parts.push(lookup[tmp >> 10] + lookup[(tmp >> 4) & 0x3f] + lookup[(tmp << 2) & 0x3f] + '=');
    }

    return parts.join('');

    function tripletToBase64(num) {
      return (
        lookup[(num >> 18) & 0x3f] +
        lookup[(num >> 12) & 0x3f] +
        lookup[(num >> 6) & 0x3f] +
        lookup[num & 0x3f]
      );
    }

    function encodeChunk(start, end) {
      let tmp;
      const output = [];
      for (let i = start; i < end; i += 3) {
        tmp = ((uint8[i] << 16) & 0xff0000) + ((uint8[i + 1] << 8) & 0xff00) + (uint8[i + 2] & 0xff);
        output.push(tripletToBase64(tmp));
      }
      return output.join('');
    }
  }

  var arrayBufferToBase64_1 = arrayBufferToBase64;

  function extractLinks(doc = document) {
    const srcsetUrls = [...doc.querySelectorAll('img[srcset],source[srcset]')]
      .map(srcsetEl =>
        srcsetEl
          .getAttribute('srcset')
          .split(',')
          .map(str => str.trim().split(/\s+/)[0]),
      )
      .reduce((acc, urls) => acc.concat(urls), []);

    const srcUrls = [...doc.querySelectorAll('img[src],source[src]')].map(srcEl =>
      srcEl.getAttribute('src'),
    );

    const cssUrls = [...doc.querySelectorAll('link[rel="stylesheet"]')].map(link =>
      link.getAttribute('href'),
    );

    const videoPosterUrls = [...doc.querySelectorAll('video[poster]')].map(videoEl =>
      videoEl.getAttribute('poster'),
    );

    return [...srcsetUrls, ...srcUrls, ...cssUrls, ...videoPosterUrls];
  }

  var extractLinks_1 = extractLinks;

  /* eslint-disable no-use-before-define */

  function domNodesToCdt(docNode) {
    const NODE_TYPES = {
      ELEMENT: 1,
      TEXT: 3,
      DOCUMENT: 9,
      DOCUMENT_TYPE: 10,
      DOCUMENT_FRAGMENT_NODE: 11,
    };

    const domNodes = [
      {
        nodeType: NODE_TYPES.DOCUMENT,
      },
    ];
    domNodes[0].childNodeIndexes = childrenFactory(domNodes, docNode.childNodes);
    return domNodes;

    function childrenFactory(domNodes, elementNodes) {
      if (!elementNodes || elementNodes.length === 0) return null;

      const childIndexes = [];
      elementNodes.forEach(elementNode => {
        const index = elementNodeFactory(domNodes, elementNode);
        if (index !== null) {
          childIndexes.push(index);
        }
      });

      return childIndexes;
    }

    function elementNodeFactory(domNodes, elementNode) {
      let node;
      const {nodeType} = elementNode;
      if ([NODE_TYPES.ELEMENT, NODE_TYPES.DOCUMENT_FRAGMENT_NODE].includes(nodeType)) {
        if (elementNode.nodeName !== 'SCRIPT') {
          if (
            elementNode.nodeName === 'STYLE' &&
            !elementNode.textContent &&
            elementNode.sheet &&
            elementNode.sheet.cssRules.length
          ) {
            elementNode.appendChild(
              docNode.createTextNode(
                [...elementNode.sheet.cssRules].map(rule => rule.cssText).join(''),
              ),
            );
          }

          node = {
            nodeType: nodeType,
            nodeName: elementNode.nodeName,
            attributes: Object.keys(elementNode.attributes || {}).map(key => {
              let value = elementNode.attributes[key].value;
              const name = elementNode.attributes[key].localName;

              if (/^blob:/.test(value)) {
                value = value.replace(/^blob:/, '');
              }

              return {
                name,
                value,
              };
            }),
            childNodeIndexes: elementNode.childNodes.length
              ? childrenFactory(domNodes, elementNode.childNodes)
              : [],
          };

          if (elementNode.shadowRoot) {
            node.shadowRootIndex = elementNodeFactory(domNodes, elementNode.shadowRoot);
          }

          if (elementNode.checked && !elementNode.attributes.checked) {
            node.attributes.push({name: 'checked', value: 'checked'});
          }
          if (
            elementNode.value !== undefined &&
            elementNode.attributes.value === undefined &&
            elementNode.tagName === 'INPUT'
          ) {
            node.attributes.push({name: 'value', value: elementNode.value});
          }
        }
      } else if (nodeType === NODE_TYPES.TEXT) {
        node = {
          nodeType: NODE_TYPES.TEXT,
          nodeValue: elementNode.nodeValue,
        };
      } else if (nodeType === NODE_TYPES.DOCUMENT_TYPE) {
        node = {
          nodeType: NODE_TYPES.DOCUMENT_TYPE,
          nodeName: elementNode.nodeName,
        };
      }

      if (node) {
        domNodes.push(node);
        return domNodes.length - 1;
      } else {
        // console.log(`Unknown nodeType: ${nodeType}`);
        return null;
      }
    }
  }

  var domNodesToCdt_1 = domNodesToCdt;
  var NODE_TYPES = {
    ELEMENT: 1,
    TEXT: 3,
    DOCUMENT: 9,
    DOCUMENT_TYPE: 10,
  };
  domNodesToCdt_1.NODE_TYPES = NODE_TYPES;

  function extractFrames(doc = document) {
    return [...doc.querySelectorAll('iframe[src]:not([src=""])')]
      .map(srcEl => {
        try {
          const contentDoc = srcEl.contentDocument;
          return (
            contentDoc &&
            /^https?:$/.test(contentDoc.location.protocol) &&
            contentDoc.defaultView &&
            contentDoc.defaultView.frameElement &&
            contentDoc
          );
        } catch (err) {
          //for CORS frames
        }
      })
      .filter(x => !!x);
  }

  var extractFrames_1 = extractFrames;

  function uniq(arr) {
    const result = [];
    new Set(arr).forEach(v => v && result.push(v));
    return result;
  }

  var uniq_1 = uniq;

  function aggregateResourceUrlsAndBlobs(resourceUrlsAndBlobsArr) {
    return resourceUrlsAndBlobsArr.reduce(
      ({resourceUrls: allResourceUrls, blobsObj: allBlobsObj}, {resourceUrls, blobsObj}) => ({
        resourceUrls: uniq_1(allResourceUrls.concat(resourceUrls)),
        blobsObj: Object.assign(allBlobsObj, blobsObj),
      }),
      {resourceUrls: [], blobsObj: {}},
    );
  }

  var aggregateResourceUrlsAndBlobs_1 = aggregateResourceUrlsAndBlobs;

  function makeGetResourceUrlsAndBlobs({processResource, aggregateResourceUrlsAndBlobs}) {
    return function getResourceUrlsAndBlobs(doc, baseUrl, urls) {
      return Promise.all(
        urls.map(url => processResource(url, doc, baseUrl, getResourceUrlsAndBlobs.bind(null, doc))),
      ).then(resourceUrlsAndBlobsArr => aggregateResourceUrlsAndBlobs(resourceUrlsAndBlobsArr));
    };
  }

  var getResourceUrlsAndBlobs = makeGetResourceUrlsAndBlobs;

  function filterInlineUrl(absoluteUrl) {
    return /^(blob|https?):/.test(absoluteUrl);
  }

  var filterInlineUrl_1 = filterInlineUrl;

  function absolutizeUrl(url, absoluteUrl) {
    return new URL(url, absoluteUrl).href;
  }

  var absolutizeUrl_1 = absolutizeUrl;

  function makeProcessResource({
    fetchUrl,
    findStyleSheetByUrl,
    extractResourcesFromStyleSheet,
    isSameOrigin,
    cache = {},
  }) {
    return function processResource(absoluteUrl, doc, baseUrl, getResourceUrlsAndBlobs) {
      return cache[absoluteUrl] || (cache[absoluteUrl] = doProcessResource(absoluteUrl));

      function doProcessResource(url) {
        return fetchUrl(url)
          .catch(e => {
            if (probablyCORS(e, url)) {
              return {probablyCORS: true, url};
            } else {
              throw e;
            }
          })
          .then(({url, type, value, probablyCORS}) => {
            if (probablyCORS) {
              return {resourceUrls: [url]};
            }
            const result = {blobsObj: {[url]: {type, value}}};
            if (/text\/css/.test(type)) {
              const styleSheet = findStyleSheetByUrl(url, doc);
              if (!styleSheet) {
                return result;
              }
              const resourceUrls = extractResourcesFromStyleSheet(styleSheet, doc.defaultView)
                .map(resourceUrl => absolutizeUrl_1(resourceUrl, url.replace(/^blob:/, '')))
                .filter(filterInlineUrl_1);
              return getResourceUrlsAndBlobs(baseUrl, resourceUrls).then(
                ({resourceUrls, blobsObj}) => ({
                  resourceUrls,
                  blobsObj: Object.assign(blobsObj, {[url]: {type, value}}),
                }),
              );
            } else {
              return result;
            }
          })
          .catch(err => {
            console.log('[dom-snapshot] error while fetching', url, err);
            return {};
          });
      }

      function probablyCORS(err, url) {
        const msgCORS = err.message && err.message.includes('Failed to fetch');
        const nameCORS = err.name && err.name.includes('TypeError');
        return msgCORS && nameCORS && !isSameOrigin(url, baseUrl);
      }
    };
  }

  var processResource = makeProcessResource;

  /* global window */

  function fetchUrl(url, fetch = window.fetch) {
    return fetch(url, {cache: 'force-cache', credentials: 'same-origin'}).then(resp =>
      resp.arrayBuffer().then(buff => ({
        url,
        type: resp.headers.get('Content-Type'),
        value: buff,
      })),
    );
  }

  var fetchUrl_1 = fetchUrl;

  function makeFindStyleSheetByUrl({styleSheetCache}) {
    return function findStyleSheetByUrl(url, doc) {
      return styleSheetCache[url] || [...doc.styleSheets].find(styleSheet => styleSheet.href === url);
    };
  }

  var findStyleSheetByUrl = makeFindStyleSheetByUrl;

  function getUrlFromCssText(cssText) {
    const re = /url\((?!['"]?:)['"]?([^'")]*)['"]?\)/g;
    const ret = [];
    let result;
    while ((result = re.exec(cssText)) !== null) {
      ret.push(result[1]);
    }
    return ret;
  }

  var getUrlFromCssText_1 = getUrlFromCssText;

  // NOTE this code is very similar to the node part of visual-grid-client, but there is a different related to the browser's cssom with import rules
  function makeExtractResourcesFromStyleSheet({styleSheetCache}) {
    return function extractResourcesFromStyleSheet(styleSheet, win = window) {
      return uniq_1(
        [...(styleSheet.cssRules || [])].reduce((acc, rule) => {
          if (rule instanceof win.CSSImportRule) {
            styleSheetCache[rule.styleSheet.href] = rule.styleSheet;
            return acc.concat(rule.href);
          } else if (rule instanceof win.CSSFontFaceRule) {
            return acc.concat(getUrlFromCssText_1(rule.style.getPropertyValue('src')));
          } else if (rule instanceof win.CSSSupportsRule || rule instanceof win.CSSMediaRule) {
            return acc.concat(extractResourcesFromStyleSheet(rule));
          } else if (rule instanceof win.CSSStyleRule) {
            for (let i = 0, ii = rule.style.length; i < ii; i++) {
              const urls = getUrlFromCssText_1(rule.style.getPropertyValue(rule.style[i]));
              urls.length && (acc = acc.concat(urls));
            }
          }
          return acc;
        }, []),
      );
    };
  }

  var extractResourcesFromStyleSheet = makeExtractResourcesFromStyleSheet;

  function extractResourceUrlsFromStyleAttrs(cdt) {
    return cdt.reduce((acc, node) => {
      if (node.nodeType === 1) {
        const styleAttr =
          node.attributes && node.attributes.find(attr => attr.name.toUpperCase() === 'STYLE');

        if (styleAttr) acc = acc.concat(getUrlFromCssText_1(styleAttr.value));
      }
      return acc;
    }, []);
  }

  var extractResourceUrlsFromStyleAttrs_1 = extractResourceUrlsFromStyleAttrs;

  function makeExtractResourceUrlsFromStyleTags(extractResourcesFromStyleSheet) {
    return function extractResourceUrlsFromStyleTags(doc) {
      return uniq_1(
        [...doc.getElementsByTagName('style')].reduce((resourceUrls, styleEl) => {
          const styleSheet = [...doc.styleSheets].find(
            styleSheet => styleSheet.ownerNode === styleEl,
          );
          return resourceUrls.concat(extractResourcesFromStyleSheet(styleSheet, doc.defaultView));
        }, []),
      );
    };
  }

  var extractResourceUrlsFromStyleTags = makeExtractResourceUrlsFromStyleTags;

  function isSameOrigin(url, baseUrl) {
    const blobOrData = /^(blob|data):/;
    if (blobOrData.test(url)) return true;
    if (blobOrData.test(baseUrl)) return false;

    const {origin} = new URL(url, baseUrl);
    const {origin: baseOrigin} = new URL(baseUrl);
    return origin === baseOrigin;
  }

  var isSameOrigin_1 = isSameOrigin;

  function processPage(doc = document) {
    const styleSheetCache = {};
    const extractResourcesFromStyleSheet$$1 = extractResourcesFromStyleSheet({styleSheetCache});
    const findStyleSheetByUrl$$1 = findStyleSheetByUrl({styleSheetCache});
    const processResource$$1 = processResource({
      fetchUrl: fetchUrl_1,
      findStyleSheetByUrl: findStyleSheetByUrl$$1,
      extractResourcesFromStyleSheet: extractResourcesFromStyleSheet$$1,
      absolutizeUrl: absolutizeUrl_1,
      isSameOrigin: isSameOrigin_1,
    });

    const getResourceUrlsAndBlobs$$1 = getResourceUrlsAndBlobs({
      processResource: processResource$$1,
      aggregateResourceUrlsAndBlobs: aggregateResourceUrlsAndBlobs_1,
    });

    const extractResourceUrlsFromStyleTags$$1 = extractResourceUrlsFromStyleTags(
      extractResourcesFromStyleSheet$$1,
    );

    return doProcessPage(doc);

    function doProcessPage(doc) {
      const frameElement = doc.defaultView && doc.defaultView.frameElement;
      const url = frameElement ? frameElement.src : doc.location.href;

      const cdt = domNodesToCdt_1(doc);

      const links = uniq_1(
        extractLinks_1(doc)
          .concat(extractResourceUrlsFromStyleAttrs_1(cdt))
          .concat(extractResourceUrlsFromStyleTags$$1(doc)),
      )
        .map(absolutizeThisUrl)
        .filter(filterInlineUrlsIfExisting);

      const resourceUrlsAndBlobsPromise = getResourceUrlsAndBlobs$$1(doc, url, links);

      const frameDocs = extractFrames_1(doc);
      const processFramesPromise = frameDocs.map(doProcessPage);

      return Promise.all([resourceUrlsAndBlobsPromise, ...processFramesPromise]).then(
        ([{resourceUrls, blobsObj}, ...framesResults]) => ({
          cdt,
          url,
          resourceUrls,
          blobs: blobsObjToArray(blobsObj),
          frames: framesResults,
          srcAttr: frameElement ? frameElement.getAttribute('src') : undefined,
        }),
      );

      function absolutizeThisUrl(someUrl) {
        try {
          return absolutizeUrl_1(someUrl, url);
        } catch (err) {
          // can't do anything with a non-absolute url
        }
      }
    }
  }

  function blobsObjToArray(blobsObj) {
    return Object.keys(blobsObj).map(blobUrl =>
      Object.assign(
        {
          url: blobUrl.replace(/^blob:/, ''),
        },
        blobsObj[blobUrl],
      ),
    );
  }

  function filterInlineUrlsIfExisting(absoluteUrl) {
    return absoluteUrl && filterInlineUrl_1(absoluteUrl);
  }

  var processPage_1 = processPage;

  function processPageAndSerialize(doc) {
    return processPage_1(doc).then(serializeFrame);
  }

  function serializeFrame(frame) {
    frame.blobs = frame.blobs.map(({url, type, value}) => ({
      url,
      type,
      value: arrayBufferToBase64_1(value),
    }));
    frame.frames.forEach(serializeFrame);
    return frame;
  }

  var processPageAndSerialize_1 = processPageAndSerialize;

  return processPageAndSerialize_1;

}());

  return processPageAndSerialize.apply(this, arguments);
}
END