<commit_before>import csstree from 'css-tree'
import debug from 'debug'
import pruneNonCriticalSelectors from './browser-sandbox/pruneNonCriticalSelectors'
import replacePageCss from './browser-sandbox/replacePageCss'
import cleanupAst from './postformatting'
import buildSelectorProfile from './selectors-profile'
import nonMatchingMediaQueryRemover from './non-matching-media-query-remover'

const debuglog = debug('penthouse:core')

const PUPPETEER_PAGE_UNLOADED_DURING_EXECUTION_ERROR_REGEX = /(Cannot find context with specified id|Execution context was destroyed)/
export const PAGE_UNLOADED_DURING_EXECUTION_ERROR_MESSAGE =
  'PAGE_UNLOADED_DURING_EXECUTION: Critical css generation script could not be executed.\n\nThis can happen if Penthouse was killed during execution, OR otherwise most commonly if the page navigates away after load, via setting window.location, meta tag refresh directive or similar. For the critical css generation to work the loaded page must stay: remove any redirects or move them to the server. You can also disable them on your end just for the critical css generation, for example via a query parameter.'

function blockinterceptedRequests (interceptedRequest) {
  const isJsRequest = /\.js(\?.*)?$/.test(interceptedRequest.url())
  if (isJsRequest) {
    interceptedRequest.abort()
  } else {
    interceptedRequest.continue()
  }
}

function loadPage (
  page,
  url,
  pageGotoOptions,
  timeout,
  pageLoadSkipTimeout,
  allowedResponseCode
) {
  debuglog('page load start')
  let waitingForPageLoad = true
  let loadPagePromise = page.goto(url, pageGotoOptions)
  if (pageLoadSkipTimeout) {
    loadPagePromise = Promise.race([
      loadPagePromise,
      new Promise(resolve => {
        // _abort_ page load after X time,
        // in order to deal with spammy pages that keep sending non-critical requests
        // (tracking etc), which would otherwise never load.
        // With JS disabled it just shouldn't take that many seconds to load what's needed
        // for critical viewport.
        setTimeout(() => {
          if (waitingForPageLoad) {
            debuglog(
              'page load waiting ABORTED after ' +
                pageLoadSkipTimeout / 1000 +
                's. '
            )
            resolve()
          }
        }, pageLoadSkipTimeout)
      })
    ])
  }
  return loadPagePromise.then(response => {
    if (typeof allowedResponseCode !== 'undefined') {
      checkResponseStatus(allowedResponseCode, response)
    }

    waitingForPageLoad = false
    debuglog('page load DONE')
  })
}

function checkResponseStatus (allowedResponseCode, response) {
  var errorMessage
  if (
    typeof allowedResponseCode === 'number' &&
    response.status() !== allowedResponseCode
  ) {
    errorMessage = `Server response status ${response.status()} isn't matching allowedResponseCode: ${allowedResponseCode}.`
  } else if (
    typeof allowedResponseCode === 'object' &&
    allowedResponseCode.constructor.name === 'RegExp' &&
    !response
      .status()
      .toString()
      .match(allowedResponseCode)
  ) {
    errorMessage = `Server response status ${response.status()} isn't matching allowedResponseCode: ${allowedResponseCode.toString()}.`
  } else if (
    typeof allowedResponseCode === 'function' &&
    !allowedResponseCode.call(this, response)
  ) {
    errorMessage = `Server response status ${response.status()} isn't matching allowedResponseCode.`
  }

  if (errorMessage) {
    throw new Error(errorMessage)
  }
}

function setupBlockJsRequests (page) {
  page.on('request', blockinterceptedRequests)
  return page.setRequestInterception(true)
}

async function astFromCss ({ cssString, strict }) {
  // breaks puppeteer
  const css = cssString.replace(//g, '\f042')

  const parsingErrors = []
  debuglog('parse ast START')
  const ast = csstree.parse(css, {
    onParseError: error => parsingErrors.push(error.formattedMessage)
  })
  debuglog(`parse ast DONE (with ${parsingErrors.length} errors)`)

  if (parsingErrors.length && strict === true) {
    // NOTE: only informing about first error, even if there were more than one.
    const parsingErrorMessage = parsingErrors[0]
    throw new Error(
      `AST parser (css-tree) found ${parsingErrors.length} errors in CSS.
      Breaking because in strict mode.
      The first error was:
      ` + parsingErrorMessage
    )
  }
  return ast
}

async function preparePage ({
  page,
  pagePromise,
  width,
  height,
  cookies,
  userAgent,
  customPageHeaders,
  blockJSRequests,
  cleanupAndExit,
  getHasExited
}) {
  let reusedPage
  try {
    const pagePromiseResult = await pagePromise
    page = pagePromiseResult.page
    reusedPage = pagePromiseResult.reused
  } catch (e) {
    debuglog('unexpected: could not get an open browser page' + e)
    return
  }
  // we already exited while page was opening, stop execution
  // (strict mode ast css parsing erros)
  if (getHasExited()) {
    return
  }
  debuglog('open page ready in browser')

  // We set the viewport size in the browser when it launches,
  // and then re-use it for each page (to avoid extra work).
  // Only if later pages use a different viewport size do we need to
  // update it here.
  let setViewportPromise = Promise.resolve()
  const currentViewport = page.viewport()
  if (!currentViewport || currentViewport.width !== width || currentViewport.height !== height) {
    setViewportPromise = page
      .setViewport({ width, height })
      .then(() => debuglog('viewport size updated'))
  }

  const setUserAgentPromise = page
    .setUserAgent(userAgent)
    .then(() => debuglog('userAgent set'))

  let setCustomPageHeadersPromise = Promise.resolve()
  if (customPageHeaders && Object.keys(customPageHeaders).length) {
    try {
      setCustomPageHeadersPromise = page
        .setExtraHTTPHeaders(customPageHeaders)
        .then(() =>
          debuglog('customPageHeaders set:' + JSON.stringify(customPageHeaders))
        )
    } catch (e) {
      debuglog('failed setting extra http headers: ' + e)
    }
  }

  let setCookiesPromise = Promise.resolve()
  if (cookies) {
    try {
      setCookiesPromise = page
        .setCookie(...cookies)
        .then(() => debuglog('cookie(s) set: ' + JSON.stringify(cookies)))
    } catch (e) {
      debuglog('failed to set cookies: ' + e)
    }
  }

  // assumes the page was already configured from previous call!
  if (reusedPage) {
    return Promise.all([
      setViewportPromise,
      setUserAgentPromise,
      setCustomPageHeadersPromise,
      setCookiesPromise
    ]).then(() => {
      debuglog('preparePage DONE')
      return page
    })
  }

  // disable Puppeteer navigation timeouts;
  // Penthouse tracks these internally instead.
  page.setDefaultNavigationTimeout(0)

  let blockJSRequestsPromise
  if (blockJSRequests) {
    // NOTE: with JS disabled we cannot use JS timers inside page.evaluate
    // (setTimeout, setInterval), however requestAnimationFrame works.
    blockJSRequestsPromise = Promise.all([
      page.setJavaScriptEnabled(false),
      setupBlockJsRequests(page)
    ]).then(() => {
      debuglog('blocking js requests DONE')
    })
  }

  page.on('error', error => {
    debuglog('page error: ' + error)
    cleanupAndExit({ error })
  })
  page.on('console', msg => {
    const text = msg.text
      ? typeof msg.text === 'function'
        ? msg.text()
        : msg.text
      : msg
    // pass through log messages
    // - the ones sent by penthouse for debugging has 'debug: ' prefix.
    if (/^debug: /.test(text)) {
      debuglog(text.replace(/^debug: /, ''))
    }
  })
  debuglog('page event listeners set')

  return Promise.all([
    setViewportPromise,
    setUserAgentPromise,
    setCustomPageHeadersPromise,
    setCookiesPromise,
    blockJSRequestsPromise
  ]).then(() => {
    debuglog('preparePage DONE')
    return page
  })
}

async function grabPageScreenshot ({
  type,
  page,
  screenshots,
  screenshotExtension,
  debuglog
}) {
  const path = screenshots.basePath + `-${type}` + screenshotExtension
  debuglog(`take ${type} screenshot, START`)
  return page
    .screenshot({
      ...screenshots,
      path
    })
    .then(() => debuglog(`take ${type} screenshot DONE, path: ${path}`))
}

async function pruneNonCriticalCssLauncher ({
  pagePromise,
  url,
  pageGotoOptions,
  cssString,
  width,
  height,
  forceInclude,
  forceExclude,
  strict,
  userAgent,
  renderWaitTime,
  timeout,
  pageLoadSkipTimeout,
  blockJSRequests,
  customPageHeaders,
  cookies,
  screenshots,
  propertiesToRemove,
  maxEmbeddedBase64Length,
  keepLargerMediaQueries,
  maxElementsToCheckPerSelector,
  unstableKeepBrowserAlive,
  allowedResponseCode
}) {
  let _hasExited = false
  // hacky to get around _hasExited only available in the scope of this function
  const getHasExited = () => _hasExited

  const takeScreenshots = screenshots && screenshots.basePath
  const screenshotExtension =
    takeScreenshots && screenshots.type === 'jpeg' ? '.jpg' : '.png'

  // NOTE: would need a refactor to killTimeout logic to be able to remove promise here.
  /* eslint-disable no-async-promise-executor */
  return new Promise(async (resolve, reject) => {
    /* eslint-enable no-async-promise-executor */
    debuglog('Penthouse core start')
    let page = null
    let killTimeout = null
    async function cleanupAndExit ({ error, returnValue }) {
      if (_hasExited) {
        return
      }
      debuglog('cleanupAndExit')
      _hasExited = true
      clearTimeout(killTimeout)

      if (error) {
        return reject(error)
      }

      if (page) {
        const resetPromises = []
        // reset page headers and cookies,
        // since we re-use the page
        if (customPageHeaders && Object.keys(customPageHeaders).length) {
          try {
            resetPromises.push(
              page
                .setExtraHTTPHeaders({})
                .then(() => debuglog('customPageHeaders reset'))
            )
          } catch (e) {
            debuglog('failed resetting extra http headers: ' + e)
          }
        }
        // reset cookies
        if (cookies && cookies.length) {
          try {
            resetPromises.push(
              page
                .deleteCookie(...cookies)
                .then(() => debuglog('cookie(s) reset: '))
            )
          } catch (e) {
            debuglog('failed to reset cookies: ' + e)
          }
        }
        await Promise.all(resetPromises)
      }

      return resolve(returnValue)
    }
    killTimeout = setTimeout(() => {
      cleanupAndExit({
        error: new Error('Penthouse timed out after ' + timeout / 1000 + 's. ')
      })
    }, timeout)

    // 1. start preparing a browser page (tab) [NOT BLOCKING]
    const updatedPagePromise = preparePage({
      page,
      pagePromise,
      width,
      height,
      userAgent,
      cookies,
      customPageHeaders,
      blockJSRequests,
      cleanupAndExit,
      getHasExited
    })

    // 2. parse ast
    // -> [BLOCK FOR] AST parsing
    let ast
    try {
      ast = await astFromCss({
        cssString,
        strict
      })
    } catch (e) {
      cleanupAndExit({ error: e })
      return
    }

    // 3. Further process the ast [BLOCKING]
    // Strip out non matching media queries.
    // Need to be done before buildSelectorProfile;
    // (very fast but could be done together/in parallel in future)
    nonMatchingMediaQueryRemover(ast, width, height, keepLargerMediaQueries)
    debuglog('stripped out non matching media queries')

    // -> [BLOCK FOR] page preparation
    page = await updatedPagePromise
    if (!page) {
      cleanupAndExit({ error: 'Could not open page in browser' })
      return
    }

    // load the page (slow) [NOT BLOCKING]
    const loadPagePromise = loadPage(
      page,
      url,
      pageGotoOptions,
      timeout,
      pageLoadSkipTimeout,
      allowedResponseCode
    )

    // turn css to formatted selectorlist [NOT BLOCKING]
    debuglog('turn css to formatted selectorlist START')
    const buildSelectorProfilePromise = buildSelectorProfile(
      ast,
      forceInclude && forceInclude.length ? forceInclude : null,
      forceExclude && forceExclude.length ? forceExclude : null
    ).then(res => {
      debuglog('turn css to formatted selectorlist DONE')
      return res
    })

    // -> [BLOCK FOR] page load
    try {
      await loadPagePromise
    } catch (e) {
      cleanupAndExit({ error: e })
      return
    }
    if (!page) {
      // in case we timed out
      debuglog('page load TIMED OUT')
      cleanupAndExit({ error: new Error('Page load timed out') })
      return
    }
    if (_hasExited) return

    // Penthouse waits for the `load` event to fire
    // (before loadPagePromise resolves; except for very slow loading pages)
    // (via default puppeteer page.goto options.waitUntil setting,
    //  https://github.com/GoogleChrome/puppeteer/blob/v1.8.0/docs/api.md#pagegotourl-options)
    // This means "all of the objects in the document are in the DOM, and all the images...
    // have finished loading".
    // This is necessary for Penthouse to know the correct layout of the critical viewport
    // (well really, we would only need to load the critical viewport.. not possible?)

    // However, @font-face's can be available later,
    // and for this reason it can be useful to delay further - if screenshots are used.
    // For this `renderWaitTime` can be used.

    // Note: `renderWaitTime` is not a very good name,
    // and just setting a time is also not the most effective solution to f.e. wait for fonts.
    // In future probably deprecate and allow for a custom function instead (returning a promise).

    // -> [BLOCK FOR] renderWaitTime - needs to be done before we take any screenshots
    await new Promise(resolve => {
      setTimeout(() => {
        debuglog('waited for renderWaitTime: ' + renderWaitTime)
        resolve()
      }, renderWaitTime)
    })

    // take before screenshot (optional) [NOT BLOCKING]
    const beforeScreenshotPromise = takeScreenshots
      ? grabPageScreenshot({
          type: 'before',
          page,
          screenshots,
          screenshotExtension,
          debuglog
        })
      : Promise.resolve()

    // -> [BLOCK FOR] css into formatted selectors list with "sourcemap"
    // latter used to map back to full css rule
    const { selectors, selectorNodeMap } = await buildSelectorProfilePromise

    if (getHasExited()) {
      return
    }

    // -> [BLOCK FOR] critical css selector pruning (in browser)
    let criticalSelectors
    try {
      criticalSelectors = await page
        .evaluate(pruneNonCriticalSelectors, {
          selectors,
          renderWaitTime,
          maxElementsToCheckPerSelector
        })
        .then(criticalSelectors => {
          debuglog('pruneNonCriticalSelectors done')
          return criticalSelectors
        })
    } catch (err) {
      debuglog('pruneNonCriticalSelector threw an error: ' + err)
      const errorDueToPageUnloaded = PUPPETEER_PAGE_UNLOADED_DURING_EXECUTION_ERROR_REGEX.test(
        err
      )
      cleanupAndExit({
        error: errorDueToPageUnloaded
          ? new Error(PAGE_UNLOADED_DURING_EXECUTION_ERROR_MESSAGE)
          : err
      })
      return
    }
    if (getHasExited()) {
      return
    }

    // -> [BLOCK FOR] clean up final ast for critical css
    debuglog('AST cleanup START')

    // NOTE: this function mutates the AST
    cleanupAst({
      ast,
      selectorNodeMap,
      criticalSelectors,
      propertiesToRemove,
      maxEmbeddedBase64Length
    })
    debuglog('AST cleanup DONE')

    // -> [BLOCK FOR] generate final critical css from critical ast
    const css = csstree.generate(ast)
    debuglog('generated CSS from AST')

    // take after screenshot (optional) [BLOCKING]
    if (takeScreenshots) {
      // wait for the before screenshot, before start modifying the page
      await beforeScreenshotPromise
      debuglog('inline critical styles for after screenshot')
      await page.evaluate(replacePageCss, { css }).then(() => {
        return grabPageScreenshot({
          type: 'after',
          page,
          screenshots,
          screenshotExtension,
          debuglog
        })
      })
    }
    debuglog('generateCriticalCss DONE')

    cleanupAndExit({ returnValue: css })
  })
}

export default pruneNonCriticalCssLauncher
<commit_msg>fix uncaughtException<commit_after>import csstree from 'css-tree'
import debug from 'debug'
import pruneNonCriticalSelectors from './browser-sandbox/pruneNonCriticalSelectors'
import replacePageCss from './browser-sandbox/replacePageCss'
import cleanupAst from './postformatting'
import buildSelectorProfile from './selectors-profile'
import nonMatchingMediaQueryRemover from './non-matching-media-query-remover'

const debuglog = debug('penthouse:core')

const PUPPETEER_PAGE_UNLOADED_DURING_EXECUTION_ERROR_REGEX = /(Cannot find context with specified id|Execution context was destroyed)/
export const PAGE_UNLOADED_DURING_EXECUTION_ERROR_MESSAGE =
  'PAGE_UNLOADED_DURING_EXECUTION: Critical css generation script could not be executed.\n\nThis can happen if Penthouse was killed during execution, OR otherwise most commonly if the page navigates away after load, via setting window.location, meta tag refresh directive or similar. For the critical css generation to work the loaded page must stay: remove any redirects or move them to the server. You can also disable them on your end just for the critical css generation, for example via a query parameter.'

function blockinterceptedRequests (interceptedRequest) {
  const isJsRequest = /\.js(\?.*)?$/.test(interceptedRequest.url())
  if (isJsRequest) {
    interceptedRequest.abort()
  } else {
    interceptedRequest.continue()
  }
}

function loadPage (
  page,
  url,
  pageGotoOptions,
  timeout,
  pageLoadSkipTimeout,
  allowedResponseCode
) {
  debuglog('page load start')
  let waitingForPageLoad = true
  let loadPagePromise = page.goto(url, pageGotoOptions)
  if (pageLoadSkipTimeout) {
    loadPagePromise = Promise.race([
      loadPagePromise,
      new Promise(resolve => {
        // _abort_ page load after X time,
        // in order to deal with spammy pages that keep sending non-critical requests
        // (tracking etc), which would otherwise never load.
        // With JS disabled it just shouldn't take that many seconds to load what's needed
        // for critical viewport.
        setTimeout(() => {
          if (waitingForPageLoad) {
            debuglog(
              'page load waiting ABORTED after ' +
                pageLoadSkipTimeout / 1000 +
                's. '
            )
            resolve()
          }
        }, pageLoadSkipTimeout)
      })
    ])
  }
  return loadPagePromise.then(response => {
    if (typeof allowedResponseCode !== 'undefined') {
      checkResponseStatus(allowedResponseCode, response)
    }

    waitingForPageLoad = false
    debuglog('page load DONE')
  })
}

function checkResponseStatus (allowedResponseCode, response) {
  var errorMessage
  if (
    typeof allowedResponseCode === 'number' &&
    response.status() !== allowedResponseCode
  ) {
    errorMessage = `Server response status ${response.status()} isn't matching allowedResponseCode: ${allowedResponseCode}.`
  } else if (
    typeof allowedResponseCode === 'object' &&
    allowedResponseCode.constructor.name === 'RegExp' &&
    !response
      .status()
      .toString()
      .match(allowedResponseCode)
  ) {
    errorMessage = `Server response status ${response.status()} isn't matching allowedResponseCode: ${allowedResponseCode.toString()}.`
  } else if (
    typeof allowedResponseCode === 'function' &&
    !allowedResponseCode.call(this, response)
  ) {
    errorMessage = `Server response status ${response.status()} isn't matching allowedResponseCode.`
  }

  if (errorMessage) {
    throw new Error(errorMessage)
  }
}

function setupBlockJsRequests (page) {
  page.on('request', blockinterceptedRequests)
  return page.setRequestInterception(true)
}

async function astFromCss ({ cssString, strict }) {
  // breaks puppeteer
  const css = cssString.replace(//g, '\f042')

  const parsingErrors = []
  debuglog('parse ast START')
  const ast = csstree.parse(css, {
    onParseError: error => parsingErrors.push(error.formattedMessage)
  })
  debuglog(`parse ast DONE (with ${parsingErrors.length} errors)`)

  if (parsingErrors.length && strict === true) {
    // NOTE: only informing about first error, even if there were more than one.
    const parsingErrorMessage = parsingErrors[0]
    throw new Error(
      `AST parser (css-tree) found ${parsingErrors.length} errors in CSS.
      Breaking because in strict mode.
      The first error was:
      ` + parsingErrorMessage
    )
  }
  return ast
}

async function preparePage ({
  page,
  pagePromise,
  width,
  height,
  cookies,
  userAgent,
  customPageHeaders,
  blockJSRequests,
  cleanupAndExit,
  getHasExited
}) {
  let reusedPage
  try {
    const pagePromiseResult = await pagePromise
    page = pagePromiseResult.page
    reusedPage = pagePromiseResult.reused
  } catch (e) {
    debuglog('unexpected: could not get an open browser page' + e)
    return
  }
  // we already exited while page was opening, stop execution
  // (strict mode ast css parsing erros)
  if (getHasExited()) {
    return
  }
  debuglog('open page ready in browser')

  // We set the viewport size in the browser when it launches,
  // and then re-use it for each page (to avoid extra work).
  // Only if later pages use a different viewport size do we need to
  // update it here.
  let setViewportPromise = Promise.resolve()
  const currentViewport = page.viewport()
  if (!currentViewport || currentViewport.width !== width || currentViewport.height !== height) {
    setViewportPromise = page
      .setViewport({ width, height })
      .then(() => debuglog('viewport size updated'))
  }

  const setUserAgentPromise = page
    .setUserAgent(userAgent)
    .then(() => debuglog('userAgent set'))

  let setCustomPageHeadersPromise = Promise.resolve()
  if (customPageHeaders && Object.keys(customPageHeaders).length) {
    try {
      setCustomPageHeadersPromise = page
        .setExtraHTTPHeaders(customPageHeaders)
        .then(() =>
          debuglog('customPageHeaders set:' + JSON.stringify(customPageHeaders))
        )
    } catch (e) {
      debuglog('failed setting extra http headers: ' + e)
    }
  }

  let setCookiesPromise = Promise.resolve()
  if (cookies) {
    try {
      setCookiesPromise = page
        .setCookie(...cookies)
        .then(() => debuglog('cookie(s) set: ' + JSON.stringify(cookies)))
    } catch (e) {
      debuglog('failed to set cookies: ' + e)
    }
  }

  // assumes the page was already configured from previous call!
  if (reusedPage) {
    return Promise.all([
      setViewportPromise,
      setUserAgentPromise,
      setCustomPageHeadersPromise,
      setCookiesPromise
    ]).then(() => {
      debuglog('preparePage DONE')
      return page
    })
  }

  // disable Puppeteer navigation timeouts;
  // Penthouse tracks these internally instead.
  page.setDefaultNavigationTimeout(0)

  let blockJSRequestsPromise
  if (blockJSRequests) {
    // NOTE: with JS disabled we cannot use JS timers inside page.evaluate
    // (setTimeout, setInterval), however requestAnimationFrame works.
    blockJSRequestsPromise = Promise.all([
      page.setJavaScriptEnabled(false),
      setupBlockJsRequests(page)
    ]).then(() => {
      debuglog('blocking js requests DONE')
    })
  }

  page.on('error', error => {
    debuglog('page error: ' + error)
    cleanupAndExit({ error })
  })
  page.on('console', msg => {
    const text = msg.text
      ? typeof msg.text === 'function'
        ? msg.text()
        : msg.text
      : msg
    // pass through log messages
    // - the ones sent by penthouse for debugging has 'debug: ' prefix.
    if (/^debug: /.test(text)) {
      debuglog(text.replace(/^debug: /, ''))
    }
  })
  debuglog('page event listeners set')

  return Promise.all([
    setViewportPromise,
    setUserAgentPromise,
    setCustomPageHeadersPromise,
    setCookiesPromise,
    blockJSRequestsPromise
  ]).then(() => {
    debuglog('preparePage DONE')
    return page
  })
}

async function grabPageScreenshot ({
  type,
  page,
  screenshots,
  screenshotExtension,
  debuglog
}) {
  const path = screenshots.basePath + `-${type}` + screenshotExtension
  debuglog(`take ${type} screenshot, START`)
  return page
    .screenshot({
      ...screenshots,
      path
    })
    .then(() => debuglog(`take ${type} screenshot DONE, path: ${path}`))
}

async function pruneNonCriticalCssLauncher ({
  pagePromise,
  url,
  pageGotoOptions,
  cssString,
  width,
  height,
  forceInclude,
  forceExclude,
  strict,
  userAgent,
  renderWaitTime,
  timeout,
  pageLoadSkipTimeout,
  blockJSRequests,
  customPageHeaders,
  cookies,
  screenshots,
  propertiesToRemove,
  maxEmbeddedBase64Length,
  keepLargerMediaQueries,
  maxElementsToCheckPerSelector,
  unstableKeepBrowserAlive,
  allowedResponseCode
}) {
  let _hasExited = false
  // hacky to get around _hasExited only available in the scope of this function
  const getHasExited = () => _hasExited

  const takeScreenshots = screenshots && screenshots.basePath
  const screenshotExtension =
    takeScreenshots && screenshots.type === 'jpeg' ? '.jpg' : '.png'

  // NOTE: would need a refactor to killTimeout logic to be able to remove promise here.
  /* eslint-disable no-async-promise-executor */
  return new Promise(async (resolve, reject) => {
    // ensure there is no uncaughtException
    try {
      /* eslint-enable no-async-promise-executor */
      debuglog('Penthouse core start')
      let page = null
      let killTimeout = null
      async function cleanupAndExit ({ error, returnValue }) {
        if (_hasExited) {
          return
        }
        debuglog('cleanupAndExit')
        _hasExited = true
        clearTimeout(killTimeout)

        if (error) {
          return reject(error)
        }

        if (page) {
          const resetPromises = []
          // reset page headers and cookies,
          // since we re-use the page
          if (customPageHeaders && Object.keys(customPageHeaders).length) {
            try {
              resetPromises.push(
                page
                  .setExtraHTTPHeaders({})
                  .then(() => debuglog('customPageHeaders reset'))
              )
            } catch (e) {
              debuglog('failed resetting extra http headers: ' + e)
            }
          }
          // reset cookies
          if (cookies && cookies.length) {
            try {
              resetPromises.push(
                page
                  .deleteCookie(...cookies)
                  .then(() => debuglog('cookie(s) reset: '))
              )
            } catch (e) {
              debuglog('failed to reset cookies: ' + e)
            }
          }
          await Promise.all(resetPromises)
        }

        return resolve(returnValue)
      }
      killTimeout = setTimeout(() => {
        cleanupAndExit({
          error: new Error('Penthouse timed out after ' + timeout / 1000 + 's. ')
        })
      }, timeout)

      // 1. start preparing a browser page (tab) [NOT BLOCKING]
      const updatedPagePromise = preparePage({
        page,
        pagePromise,
        width,
        height,
        userAgent,
        cookies,
        customPageHeaders,
        blockJSRequests,
        cleanupAndExit,
        getHasExited
      })

      // 2. parse ast
      // -> [BLOCK FOR] AST parsing
      let ast
      try {
        ast = await astFromCss({
          cssString,
          strict
        })
      } catch (e) {
        cleanupAndExit({ error: e })
        return
      }

      // 3. Further process the ast [BLOCKING]
      // Strip out non matching media queries.
      // Need to be done before buildSelectorProfile;
      // (very fast but could be done together/in parallel in future)
      nonMatchingMediaQueryRemover(ast, width, height, keepLargerMediaQueries)
      debuglog('stripped out non matching media queries')

      // -> [BLOCK FOR] page preparation
      page = await updatedPagePromise
      if (!page) {
        cleanupAndExit({ error: 'Could not open page in browser' })
        return
      }

      // load the page (slow) [NOT BLOCKING]
      const loadPagePromise = loadPage(
        page,
        url,
        pageGotoOptions,
        timeout,
        pageLoadSkipTimeout,
        allowedResponseCode
      )

      // turn css to formatted selectorlist [NOT BLOCKING]
      debuglog('turn css to formatted selectorlist START')
      const buildSelectorProfilePromise = buildSelectorProfile(
        ast,
        forceInclude && forceInclude.length ? forceInclude : null,
        forceExclude && forceExclude.length ? forceExclude : null
      ).then(res => {
        debuglog('turn css to formatted selectorlist DONE')
        return res
      })

      // -> [BLOCK FOR] page load
      try {
        await loadPagePromise
      } catch (e) {
        cleanupAndExit({ error: e })
        return
      }
      if (!page) {
        // in case we timed out
        debuglog('page load TIMED OUT')
        cleanupAndExit({ error: new Error('Page load timed out') })
        return
      }
      if (_hasExited) return

      // Penthouse waits for the `load` event to fire
      // (before loadPagePromise resolves; except for very slow loading pages)
      // (via default puppeteer page.goto options.waitUntil setting,
      //  https://github.com/GoogleChrome/puppeteer/blob/v1.8.0/docs/api.md#pagegotourl-options)
      // This means "all of the objects in the document are in the DOM, and all the images...
      // have finished loading".
      // This is necessary for Penthouse to know the correct layout of the critical viewport
      // (well really, we would only need to load the critical viewport.. not possible?)

      // However, @font-face's can be available later,
      // and for this reason it can be useful to delay further - if screenshots are used.
      // For this `renderWaitTime` can be used.

      // Note: `renderWaitTime` is not a very good name,
      // and just setting a time is also not the most effective solution to f.e. wait for fonts.
      // In future probably deprecate and allow for a custom function instead (returning a promise).

      // -> [BLOCK FOR] renderWaitTime - needs to be done before we take any screenshots
      await new Promise(resolve => {
        setTimeout(() => {
          debuglog('waited for renderWaitTime: ' + renderWaitTime)
          resolve()
        }, renderWaitTime)
      })

      // take before screenshot (optional) [NOT BLOCKING]
      const beforeScreenshotPromise = takeScreenshots
        ? grabPageScreenshot({
            type: 'before',
            page,
            screenshots,
            screenshotExtension,
            debuglog
          })
        : Promise.resolve()

      // -> [BLOCK FOR] css into formatted selectors list with "sourcemap"
      // latter used to map back to full css rule
      const { selectors, selectorNodeMap } = await buildSelectorProfilePromise

      if (getHasExited()) {
        return
      }

      // -> [BLOCK FOR] critical css selector pruning (in browser)
      let criticalSelectors
      try {
        criticalSelectors = await page
          .evaluate(pruneNonCriticalSelectors, {
            selectors,
            renderWaitTime,
            maxElementsToCheckPerSelector
          })
          .then(criticalSelectors => {
            debuglog('pruneNonCriticalSelectors done')
            return criticalSelectors
          })
      } catch (err) {
        debuglog('pruneNonCriticalSelector threw an error: ' + err)
        const errorDueToPageUnloaded = PUPPETEER_PAGE_UNLOADED_DURING_EXECUTION_ERROR_REGEX.test(
          err
        )
        cleanupAndExit({
          error: errorDueToPageUnloaded
            ? new Error(PAGE_UNLOADED_DURING_EXECUTION_ERROR_MESSAGE)
            : err
        })
        return
      }
      if (getHasExited()) {
        return
      }

      // -> [BLOCK FOR] clean up final ast for critical css
      debuglog('AST cleanup START')

      // NOTE: this function mutates the AST
      cleanupAst({
        ast,
        selectorNodeMap,
        criticalSelectors,
        propertiesToRemove,
        maxEmbeddedBase64Length
      })
      debuglog('AST cleanup DONE')

      // -> [BLOCK FOR] generate final critical css from critical ast
      const css = csstree.generate(ast)
      debuglog('generated CSS from AST')

      // take after screenshot (optional) [BLOCKING]
      if (takeScreenshots) {
        // wait for the before screenshot, before start modifying the page
        await beforeScreenshotPromise
        debuglog('inline critical styles for after screenshot')
        await page.evaluate(replacePageCss, { css }).then(() => {
          return grabPageScreenshot({
            type: 'after',
            page,
            screenshots,
            screenshotExtension,
            debuglog
          })
        })
      }
      debuglog('generateCriticalCss DONE')

      cleanupAndExit({ returnValue: css })
    } catch (err) {
			reject(err);
		}
  })
}

export default pruneNonCriticalCssLauncher
<|endoftext|>


<commit_before>/**
 * Copyright 2015 The Incremental DOM 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.
 */

import {
  createElement,
  createText,
  getChild,
  registerChild
} from './nodes';
import { getData } from './node_data';
import { Context } from './context';
import { symbols } from './symbols';
import {
  assertInPatch,
  assertKeyedTagMatches,
  assertNoUnclosedTags,
  assertNotInAttributes,
  assertVirtualAttributesClosed,
  assertNoChildrenDeclaredYet,
  assertPatchElementNotEmpty,
  assertPatchElementNoExtras,
  setInAttributes,
  setInSkip
} from './assertions';
import { notifications } from './notifications';

/** @type {?Context} */
var context = null;

/** @type {?Node} */
var currentNode;

/** @type {?Node} */
var currentParent;

/** @type {?Node} */
var previousNode;

/** @type {?Element|?DocumentFragment} */
var root;

/** @type {?Document} */
var doc;

/**
 * Sets up and restores a patch context, running the patch function with the
 * provided data.
 * @param {!Element|!DocumentFragment} node The Element or Document
 *     where the patch should start.
 * @param {!function(T)} fn The patching function.
 * @param {T=} data An argument passed to fn.
 * @template T
 */
var runPatch = function(node, fn, data) {
  var prevContext = context;
  var prevRoot = root;
  var prevDoc = doc;
  var prevCurrentNode = currentNode;
  var prevCurrentParent = currentParent;
  var prevPreviousNode = previousNode;
  var previousInAttributes = false;
  var previousInSkip = false;

  context = new Context();
  root = node;
  doc = node.ownerDocument;
  currentNode = node;
  currentParent = node.parentNode;
  previousNode = null;

  if (process.env.NODE_ENV !== 'production') {
    previousInAttributes = setInAttributes(false);
    previousInSkip = setInSkip(false);
  }

  fn(data);

  if (process.env.NODE_ENV !== 'production') {
    assertVirtualAttributesClosed();
    setInAttributes(previousInAttributes);
    setInSkip(previousInSkip);
  }

  context.notifyChanges();

  context = prevContext;
  root = prevRoot;
  doc = prevDoc;
  currentNode = prevCurrentNode;
  currentParent = prevCurrentParent;
  previousNode = prevPreviousNode;
};

/**
 * Patches the document starting at node with the provided function. This
 * function may be called during an existing patch operation.
 * @param {!Element|!DocumentFragment} node The Element or Document
 *     to patch.
 * @param {!function(T)} fn A function containing elementOpen/elementClose/etc.
 *     calls that describe the DOM.
 * @param {T=} data An argument passed to fn to represent DOM state.
 * @template T
 */
var patchInner = function(node, fn, data) {
  runPatch(node, function(data) {
    enterNode();
    fn(data);
    exitNode();

    if (process.env.NODE_ENV !== 'production') {
      assertNoUnclosedTags(previousNode, node);
    }
  }, data);
};

/**
 * Patches an Element with the the provided function. Exactly one top level
 * element call should be made corresponding to `node`.
 * @param {!Element} node The Element where the patch should start.
 * @param {!function(T)} fn A function containing elementOpen/elementClose/etc.
 *     calls that describe the DOM. This should have at most one top level
 *     element call.
 * @param {T=} data An argument passed to fn to represent DOM state.
 * @template T
 */
var patchOuter = function(node, fn, data) {
  runPatch(node, function(data) {
    fn(data);

    if (process.env.NODE_ENV !== 'production') {
      assertPatchElementNotEmpty(node, currentNode);
      assertPatchElementNoExtras(node, previousNode);
    }
  }, data);
};

/**
 * Checks whether or not the current node matches the specified nodeName and
 * key.
 *
 * @param {?string} nodeName The nodeName for this node.
 * @param {?string=} key An optional key that identifies a node.
 * @return {boolean} True if the node matches, false otherwise.
 */
var matches = function(nodeName, key) {
  var data = getData(currentNode);

  // Key check is done using double equals as we want to treat a null key the
  // same as undefined. This should be okay as the only values allowed are
  // strings, null and undefined so the == semantics are not too weird.
  return nodeName === data.nodeName && key == data.key;
};

/**
 * Aligns the virtual Element definition with the actual DOM, moving the
 * corresponding DOM node to the correct location or creating it if necessary.
 * @param {string} nodeName For an Element, this should be a valid tag string.
 *     For a Text, this should be #text.
 * @param {?string=} key The key used to identify this element.
 * @param {?Array<*>=} statics For an Element, this should be an array of
 *     name-value pairs.
 */
var alignWithDOM = function(nodeName, key, statics) {
  if (currentNode && matches(nodeName, key)) {
    return;
  }

  var node;

  // Check to see if the node has moved within the parent.
  if (key) {
    node = getChild(currentParent, key);
    if (node && process.env.NODE_ENV !== 'production') {
      assertKeyedTagMatches(getData(node).nodeName, nodeName, key);
    }
  }

  // Create the node if it doesn't exist.
  if (!node) {
    if (nodeName === '#text') {
      node = createText(doc);
    } else {
      node = createElement(doc, currentParent, nodeName, key, statics);
    }

    if (key) {
      registerChild(currentParent, key, node);
    }

    context.markCreated(node);
  }

  // If the node has a key, remove it from the DOM to prevent a large number
  // of re-orders in the case that it moved far or was completely removed.
  // Since we hold on to a reference through the keyMap, we can always add it
  // back.
  if (currentNode && getData(currentNode).key) {
    currentParent.replaceChild(node, currentNode);
    getData(currentParent).keyMapValid = false;
  } else {
    currentParent.insertBefore(node, currentNode);
  }

  currentNode = node;
};

/**
 * Clears out any unvisited Nodes, as the corresponding virtual element
 * functions were never called for them.
 */
var clearUnvisitedDOM = function() {
  var node = currentParent;
  var data = getData(node);
  var keyMap = data.keyMap;
  var keyMapValid = data.keyMapValid;
  var child = node.lastChild;
  var key;

  if (child === previousNode && keyMapValid) {
    return;
  }

  if (data.attrs[symbols.placeholder] && node !== root) {
    return;
  }

  while (child !== previousNode) {
    node.removeChild(child);
    context.markDeleted(/** @type {!Node}*/(child));

    key = getData(child).key;
    if (key) {
      delete keyMap[key];
    }
    child = node.lastChild;
  }

  // Clean the keyMap, removing any unusued keys.
  if (!keyMapValid) {
    for (key in keyMap) {
      child = keyMap[key];
      if (child.parentNode !== node) {
        context.markDeleted(child);
        delete keyMap[key];
      }
    }

    data.keyMapValid = true;
  }
};

/**
 * Changes to the first child of the current node.
 */
var enterNode = function() {
  currentParent = currentNode;
  currentNode = currentNode.firstChild;
  previousNode = null;
};

/**
 * Changes to the next sibling of the current node.
 */
var nextNode = function() {
  previousNode = currentNode;
  currentNode = currentNode.nextSibling;
};

/**
 * Changes to the parent of the current node, removing any unvisited children.
 */
var exitNode = function() {
  clearUnvisitedDOM();

  previousNode = currentParent;
  currentNode = currentParent.nextSibling;
  currentParent = currentParent.parentNode;
};

/**
 * Makes sure that the current node is an Element with a matching tagName and
 * key.
 *
 * @param {string} tag The element's tag.
 * @param {?string=} key The key used to identify this element. This can be an
 *     empty string, but performance may be better if a unique value is used
 *     when iterating over an array of items.
 * @param {?Array<*>=} statics An array of attribute name/value pairs of the
 *     static attributes for the Element. These will only be set once when the
 *     Element is created.
 * @return {!Element} The corresponding Element.
 */
var elementOpen = function(tag, key, statics) {
  alignWithDOM(tag, key, statics);
  enterNode();
  return /** @type {!Element} */(currentParent);
};

/**
 * Closes the currently open Element, removing any unvisited children if
 * necessary.
 *
 * @return {!Element} The corresponding Element.
 */
var elementClose = function() {
  if (process.env.NODE_ENV !== 'production') {
    setInSkip(false);
  }

  exitNode();
  return /** @type {!Element} */(previousNode);
};

/**
 * Makes sure the current node is a Text node and creates a Text node if it is
 * not.
 *
 * @return {!Text} The corresponding Text Node.
 */
var text = function() {
  alignWithDOM('#text', null, null);
  nextNode();
  return /** @type {!Text} */(previousNode);
};

/**
 * Gets the current Element being patched.
 * @return {!Element}
 */
var currentElement = function() {
  if (process.env.NODE_ENV !== 'production') {
    assertInPatch(context);
    assertNotInAttributes('currentElement');
  }
  return /** @type {!Element} */(currentParent);
};

/**
 * Skips the children in a subtree, allowing an Element to be closed without
 * clearing out the children.
 */
var skip = function() {
  if (process.env.NODE_ENV !== 'production') {
    assertNoChildrenDeclaredYet('skip', previousNode);
    setInSkip(true);
  }
  previousNode = currentParent.lastChild;
};

/** */
export {
  elementOpen,
  elementClose,
  text,
  patchInner,
  patchOuter,
  currentElement,
  skip
};
<commit_msg>Get rid of previousNode<commit_after>/**
 * Copyright 2015 The Incremental DOM 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.
 */

import {
  createElement,
  createText,
  getChild,
  registerChild
} from './nodes';
import { getData } from './node_data';
import { Context } from './context';
import { symbols } from './symbols';
import {
  assertInPatch,
  assertKeyedTagMatches,
  assertNoUnclosedTags,
  assertNotInAttributes,
  assertVirtualAttributesClosed,
  assertNoChildrenDeclaredYet,
  assertPatchElementNotEmpty,
  assertPatchElementNoExtras,
  setInAttributes,
  setInSkip
} from './assertions';
import { notifications } from './notifications';

/** @type {?Context} */
var context = null;

/** @type {?Node} */
var currentNode;

/** @type {?Node} */
var currentParent;

/** @type {?Element|?DocumentFragment} */
var root;

/** @type {?Document} */
var doc;

/**
 * Sets up and restores a patch context, running the patch function with the
 * provided data.
 * @param {!Element|!DocumentFragment} node The Element or Document
 *     where the patch should start.
 * @param {!function(T)} fn The patching function.
 * @param {T=} data An argument passed to fn.
 * @template T
 */
var runPatch = function(node, fn, data) {
  var prevContext = context;
  var prevRoot = root;
  var prevDoc = doc;
  var prevCurrentNode = currentNode;
  var prevCurrentParent = currentParent;
  var previousInAttributes = false;
  var previousInSkip = false;

  context = new Context();
  root = node;
  doc = node.ownerDocument;

  if (process.env.NODE_ENV !== 'production') {
    previousInAttributes = setInAttributes(false);
    previousInSkip = setInSkip(false);
  }

  fn(data);

  if (process.env.NODE_ENV !== 'production') {
    assertVirtualAttributesClosed();
    setInAttributes(previousInAttributes);
    setInSkip(previousInSkip);
  }

  context.notifyChanges();

  context = prevContext;
  root = prevRoot;
  doc = prevDoc;
  currentNode = prevCurrentNode;
  currentParent = prevCurrentParent;
};

/**
 * Patches the document starting at node with the provided function. This
 * function may be called during an existing patch operation.
 * @param {!Element|!DocumentFragment} node The Element or Document
 *     to patch.
 * @param {!function(T)} fn A function containing elementOpen/elementClose/etc.
 *     calls that describe the DOM.
 * @param {T=} data An argument passed to fn to represent DOM state.
 * @template T
 */
var patchInner = function(node, fn, data) {
  runPatch(node, function(data) {
    currentNode = node;
    currentParent = node.parentNode;

    enterNode();
    fn(data);
    exitNode();

    if (process.env.NODE_ENV !== 'production') {
      assertNoUnclosedTags(currentNode, node);
    }
  }, data);
};

/**
 * Patches an Element with the the provided function. Exactly one top level
 * element call should be made corresponding to `node`.
 * @param {!Element} node The Element where the patch should start.
 * @param {!function(T)} fn A function containing elementOpen/elementClose/etc.
 *     calls that describe the DOM. This should have at most one top level
 *     element call.
 * @param {T=} data An argument passed to fn to represent DOM state.
 * @template T
 */
var patchOuter = function(node, fn, data) {
  runPatch(node, function(data) {
    currentNode = /** @type {!Element} */({ nextSibling: node });
    currentParent = node.parentNode;

    fn(data);

    if (process.env.NODE_ENV !== 'production') {
      assertPatchElementNotEmpty(node, currentNode.nextSibling);
      assertPatchElementNoExtras(node, currentNode);
    }
  }, data);
};

/**
 * Checks whether or not the current node matches the specified nodeName and
 * key.
 *
 * @param {?string} nodeName The nodeName for this node.
 * @param {?string=} key An optional key that identifies a node.
 * @return {boolean} True if the node matches, false otherwise.
 */
var matches = function(nodeName, key) {
  var data = getData(currentNode);

  // Key check is done using double equals as we want to treat a null key the
  // same as undefined. This should be okay as the only values allowed are
  // strings, null and undefined so the == semantics are not too weird.
  return nodeName === data.nodeName && key == data.key;
};

/**
 * Aligns the virtual Element definition with the actual DOM, moving the
 * corresponding DOM node to the correct location or creating it if necessary.
 * @param {string} nodeName For an Element, this should be a valid tag string.
 *     For a Text, this should be #text.
 * @param {?string=} key The key used to identify this element.
 * @param {?Array<*>=} statics For an Element, this should be an array of
 *     name-value pairs.
 */
var alignWithDOM = function(nodeName, key, statics) {
  if (currentNode && matches(nodeName, key)) {
    return;
  }

  var node;

  // Check to see if the node has moved within the parent.
  if (key) {
    node = getChild(currentParent, key);
    if (node && process.env.NODE_ENV !== 'production') {
      assertKeyedTagMatches(getData(node).nodeName, nodeName, key);
    }
  }

  // Create the node if it doesn't exist.
  if (!node) {
    if (nodeName === '#text') {
      node = createText(doc);
    } else {
      node = createElement(doc, currentParent, nodeName, key, statics);
    }

    if (key) {
      registerChild(currentParent, key, node);
    }

    context.markCreated(node);
  }

  // If the node has a key, remove it from the DOM to prevent a large number
  // of re-orders in the case that it moved far or was completely removed.
  // Since we hold on to a reference through the keyMap, we can always add it
  // back.
  if (currentNode && getData(currentNode).key) {
    currentParent.replaceChild(node, currentNode);
    getData(currentParent).keyMapValid = false;
  } else {
    currentParent.insertBefore(node, currentNode);
  }

  currentNode = node;
};

/**
 * Clears out any unvisited Nodes, as the corresponding virtual element
 * functions were never called for them.
 */
var clearUnvisitedDOM = function() {
  var node = currentParent;
  var data = getData(node);
  var keyMap = data.keyMap;
  var keyMapValid = data.keyMapValid;
  var child = node.lastChild;
  var key;

  if (child === currentNode && keyMapValid) {
    return;
  }

  if (data.attrs[symbols.placeholder] && node !== root) {
    return;
  }

  while (child !== currentNode) {
    node.removeChild(child);
    context.markDeleted(/** @type {!Node}*/(child));

    key = getData(child).key;
    if (key) {
      delete keyMap[key];
    }
    child = node.lastChild;
  }

  // Clean the keyMap, removing any unusued keys.
  if (!keyMapValid) {
    for (key in keyMap) {
      child = keyMap[key];
      if (child.parentNode !== node) {
        context.markDeleted(child);
        delete keyMap[key];
      }
    }

    data.keyMapValid = true;
  }
};

/**
 * Changes to the first child of the current node.
 */
var enterNode = function() {
  currentParent = currentNode;
  currentNode = null;
};

/**
 * Changes to the next sibling of the current node.
 */
var nextNode = function() {
  if (currentNode) {
    currentNode = currentNode.nextSibling;
  } else {
    currentNode = currentParent.firstChild;
  }
};

/**
 * Changes to the parent of the current node, removing any unvisited children.
 */
var exitNode = function() {
  clearUnvisitedDOM();

  currentNode = currentParent;
  currentParent = currentParent.parentNode;
};

/**
 * Makes sure that the current node is an Element with a matching tagName and
 * key.
 *
 * @param {string} tag The element's tag.
 * @param {?string=} key The key used to identify this element. This can be an
 *     empty string, but performance may be better if a unique value is used
 *     when iterating over an array of items.
 * @param {?Array<*>=} statics An array of attribute name/value pairs of the
 *     static attributes for the Element. These will only be set once when the
 *     Element is created.
 * @return {!Element} The corresponding Element.
 */
var elementOpen = function(tag, key, statics) {
  nextNode();
  alignWithDOM(tag, key, statics);
  enterNode();
  return /** @type {!Element} */(currentParent);
};

/**
 * Closes the currently open Element, removing any unvisited children if
 * necessary.
 *
 * @return {!Element} The corresponding Element.
 */
var elementClose = function() {
  if (process.env.NODE_ENV !== 'production') {
    setInSkip(false);
  }

  exitNode();
  return /** @type {!Element} */(currentNode);
};

/**
 * Makes sure the current node is a Text node and creates a Text node if it is
 * not.
 *
 * @return {!Text} The corresponding Text Node.
 */
var text = function() {
  nextNode();
  alignWithDOM('#text', null, null);
  return /** @type {!Text} */(currentNode);
};

/**
 * Gets the current Element being patched.
 * @return {!Element}
 */
var currentElement = function() {
  if (process.env.NODE_ENV !== 'production') {
    assertInPatch(context);
    assertNotInAttributes('currentElement');
  }
  return /** @type {!Element} */(currentParent);
};

/**
 * Skips the children in a subtree, allowing an Element to be closed without
 * clearing out the children.
 */
var skip = function() {
  if (process.env.NODE_ENV !== 'production') {
    assertNoChildrenDeclaredYet('skip', currentNode);
    setInSkip(true);
  }
  currentNode = currentParent.lastChild;
};

/** */
export {
  elementOpen,
  elementClose,
  text,
  patchInner,
  patchOuter,
  currentElement,
  skip
};
<|endoftext|>


<commit_before>/**
 * @flow
 */

import joi from 'joi';
import os from 'os';
import path from 'path';
import url from 'url';
import promisify from 'util.promisify';

import ip from './ip';
import Config from './Config';
import ErrorCode from './ErrorCode';
import * as Exp from './Exp';
import * as ProjectSettings from './ProjectSettings';
import * as ProjectUtils from './project/ProjectUtils';
import * as Versions from './Versions';
import XDLError from './XDLError';

const joiValidateAsync = promisify(joi.validate);

export async function constructBundleUrlAsync(
  projectRoot: string,
  opts: any,
  requestHostname?: string
) {
  return constructUrlAsync(projectRoot, opts, true, requestHostname);
}

export async function constructManifestUrlAsync(
  projectRoot: string,
  opts: any,
  requestHostname?: string
) {
  return constructUrlAsync(projectRoot, opts, false, requestHostname);
}

export async function constructLogUrlAsync(projectRoot: string, requestHostname?: string) {
  let baseUrl = await constructUrlAsync(projectRoot, { urlType: 'http' }, false, requestHostname);
  return `${baseUrl}/logs`;
}

export async function constructUrlWithExtensionAsync(
  projectRoot: string,
  entryPoint: string,
  ext: string,
  requestHostname?: string
) {
  let bundleUrl = await constructBundleUrlAsync(
    projectRoot,
    {
      hostType: 'localhost',
      urlType: 'http',
    },
    requestHostname
  );

  let mainModulePath = guessMainModulePath(entryPoint);
  bundleUrl += `/${mainModulePath}.${ext}`;

  let queryParams = await constructBundleQueryParamsAsync(
    projectRoot,
    {
      dev: false,
      minify: true,
    },
    requestHostname
  );
  return `${bundleUrl}?${queryParams}`;
}

export async function constructPublishUrlAsync(
  projectRoot: string,
  entryPoint: string,
  requestHostname?: string
) {
  return await constructUrlWithExtensionAsync(projectRoot, entryPoint, 'bundle', requestHostname);
}

export async function constructSourceMapUrlAsync(
  projectRoot: string,
  entryPoint: string,
  requestHostname?: string
) {
  return await constructUrlWithExtensionAsync(projectRoot, entryPoint, 'map', requestHostname);
}

export async function constructAssetsUrlAsync(
  projectRoot: string,
  entryPoint: string,
  requestHostname?: string
) {
  return await constructUrlWithExtensionAsync(projectRoot, entryPoint, 'assets', requestHostname);
}

export async function constructDebuggerHostAsync(projectRoot: string, requestHostname?: string) {
  return constructUrlAsync(
    projectRoot,
    {
      urlType: 'no-protocol',
    },
    true,
    requestHostname
  );
}

export async function constructBundleQueryParamsAsync(projectRoot: string, opts: any) {
  let queryParams = `dev=${encodeURIComponent(!!opts.dev)}`;

  if (opts.hasOwnProperty('strict')) {
    queryParams += `&strict=${encodeURIComponent(!!opts.strict)}`;
  }

  if (opts.hasOwnProperty('minify')) {
    queryParams += `&minify=${encodeURIComponent(!!opts.minify)}`;
  }

  queryParams += '&hot=false';

  let { exp, pkg } = await ProjectUtils.readConfigJsonAsync(projectRoot);

  // Be backwards compatible for users who haven't migrated from `exponent`
  // to the `expo` sdk package.
  let sdkPkg = pkg.dependencies['expo'] ? 'expo' : 'exponent';
  // Use an absolute path here so that we can not worry about symlinks/relative requires
  let nodeModulesPath = exp.nodeModulesPath
    ? path.join(path.resolve(projectRoot, exp.nodeModulesPath), 'node_modules')
    : path.join(projectRoot, 'node_modules');
  let pluginModule = path.join(nodeModulesPath, sdkPkg, 'tools', 'hashAssetFiles');
  queryParams += `&assetPlugin=${pluginModule}`;

  // Only sdk-10.1.0+ supports the assetPlugin parameter. We use only the
  // major version in the sdkVersion field, so check for 11.0.0 to be sure.
  let supportsAssetPlugins = Versions.gteSdkVersion(exp, '11.0.0');
  if (!supportsAssetPlugins) {
    queryParams += '&includeAssetFileHashes=true';
  }

  return queryParams;
}

export async function constructUrlAsync(
  projectRoot: string,
  opts: any,
  isPackager: boolean,
  requestHostname?: string
) {
  if (opts) {
    // the randomness is only important if we're online and can build a tunnel
    let urlRandomnessSchema;
    if (Config.offline) {
      urlRandomnessSchema = joi
        .string()
        .optional()
        .allow(null);
    } else {
      urlRandomnessSchema = joi.string();
    }

    let schema = joi.object().keys({
      urlType: joi.any().valid('exp', 'http', 'redirect', 'no-protocol'),
      lanType: joi.any().valid('ip', 'hostname'),
      hostType: joi.any().valid('localhost', 'lan', 'tunnel'),
      dev: joi.boolean(),
      strict: joi.boolean(),
      minify: joi.boolean(),
      urlRandomness: urlRandomnessSchema,
    });

    try {
      await joiValidateAsync(opts, schema);
    } catch (e) {
      throw new XDLError(ErrorCode.INVALID_OPTIONS, e.toString());
    }
  }

  let defaultOpts = await ProjectSettings.getPackagerOptsAsync(projectRoot);
  if (!opts) {
    opts = defaultOpts;
  } else {
    opts = Object.assign({}, defaultOpts, opts);
  }

  let packagerInfo = await ProjectSettings.readPackagerInfoAsync(projectRoot);

  let protocol;
  if (opts.urlType === 'http') {
    protocol = 'http';
  } else if (opts.urlType === 'no-protocol') {
    protocol = null;
  } else {
    protocol = 'exp';

    let { exp } = await ProjectUtils.readConfigJsonAsync(projectRoot);
    if (exp.detach && exp.detach.scheme) {
      protocol = exp.detach.scheme;
    }
  }

  let hostname;
  let port;

  const proxyURL = isPackager
    ? process.env.EXPO_PACKAGER_PROXY_URL
    : process.env.EXPO_MANIFEST_PROXY_URL;
  if (proxyURL) {
    const parsedProxyURL = url.parse(proxyURL);
    hostname = parsedProxyURL.hostname;
    port = parsedProxyURL.port;
    if (parsedProxyURL.protocol === 'https:') {
      if (protocol === 'http') {
        protocol = 'https';
      }
      if (!port) {
        port = '443';
      }
    }
  } else if (opts.hostType === 'localhost' || requestHostname === 'localhost') {
    hostname = 'localhost';
    port = isPackager ? packagerInfo.packagerPort : packagerInfo.expoServerPort;
  } else if (opts.hostType === 'lan' || Config.offline) {
    if (process.env.EXPO_PACKAGER_HOSTNAME) {
      hostname = process.env.EXPO_PACKAGER_HOSTNAME;
    } else if (process.env.REACT_NATIVE_PACKAGER_HOSTNAME) {
      hostname = process.env.REACT_NATIVE_PACKAGER_HOSTNAME;
    } else if (opts.lanType === 'ip') {
      if (requestHostname) {
        hostname = requestHostname;
      } else {
        hostname = ip.address();
      }
    } else {
      // Some old versions of OSX work with hostname but not local ip address.
      hostname = os.hostname();
    }
    port = isPackager ? packagerInfo.packagerPort : packagerInfo.expoServerPort;
  } else {
    let ngrokUrl = isPackager ? packagerInfo.packagerNgrokUrl : packagerInfo.expoServerNgrokUrl;
    if (!ngrokUrl) {
      // use localhost
      hostname = 'localhost';
      port = isPackager ? packagerInfo.packagerPort : packagerInfo.expoServerPort;

      // TODO report a warning when this is for a currently served project, suppress for status checks
    } else {
      let pnu = url.parse(ngrokUrl);
      hostname = pnu.hostname;
      port = pnu.port;
    }
  }

  let url_ = '';
  if (protocol) {
    url_ += `${protocol}://`;
  }

  if (!hostname) {
    throw new Error('Hostname cannot be inferred.');
  }

  url_ += hostname;

  if (port) {
    url_ += `:${port}`;
  } else {
    // Android HMR breaks without this :|
    url_ += ':80';
  }

  if (opts.urlType === 'redirect') {
    return `https://exp.host/--/to-exp/${encodeURIComponent(url_)}`;
  }

  return url_;
}

export function guessMainModulePath(entryPoint: string) {
  return entryPoint.replace(/\.js$/, '');
}

export function randomIdentifier(length: number = 6) {
  let alphabet = '23456789qwertyuipasdfghjkzxcvbnm';
  let result = '';
  for (let i = 0; i < length; i++) {
    let j = Math.floor(Math.random() * alphabet.length);
    let c = alphabet.substr(j, 1);
    result += c;
  }
  return result;
}

export function sevenDigitIdentifier() {
  return `${randomIdentifier(3)}-${randomIdentifier(4)}`;
}

export function randomIdentifierForUser(username: string) {
  return `${username}-${randomIdentifier(3)}-${randomIdentifier(2)}`;
}

export function someRandomness() {
  return [randomIdentifier(2), randomIdentifier(3)].join('-');
}

export function domainify(s: string) {
  return s
    .toLowerCase()
    .replace(/[^a-z0-9-]/g, '-')
    .replace(/^-+/, '')
    .replace(/-+$/, '');
}

export function getPlatformSpecificBundleUrl(url: string, platform: string) {
  if (url.includes(Exp.ENTRY_POINT_PLATFORM_TEMPLATE_STRING)) {
    return url.replace(Exp.ENTRY_POINT_PLATFORM_TEMPLATE_STRING, platform);
  } else {
    return url;
  }
}
<commit_msg>Trim hostname if we get it from the env variable<commit_after>/**
 * @flow
 */

import joi from 'joi';
import os from 'os';
import path from 'path';
import url from 'url';
import promisify from 'util.promisify';

import ip from './ip';
import Config from './Config';
import ErrorCode from './ErrorCode';
import * as Exp from './Exp';
import * as ProjectSettings from './ProjectSettings';
import * as ProjectUtils from './project/ProjectUtils';
import * as Versions from './Versions';
import XDLError from './XDLError';

const joiValidateAsync = promisify(joi.validate);

export async function constructBundleUrlAsync(
  projectRoot: string,
  opts: any,
  requestHostname?: string
) {
  return constructUrlAsync(projectRoot, opts, true, requestHostname);
}

export async function constructManifestUrlAsync(
  projectRoot: string,
  opts: any,
  requestHostname?: string
) {
  return constructUrlAsync(projectRoot, opts, false, requestHostname);
}

export async function constructLogUrlAsync(projectRoot: string, requestHostname?: string) {
  let baseUrl = await constructUrlAsync(projectRoot, { urlType: 'http' }, false, requestHostname);
  return `${baseUrl}/logs`;
}

export async function constructUrlWithExtensionAsync(
  projectRoot: string,
  entryPoint: string,
  ext: string,
  requestHostname?: string
) {
  let bundleUrl = await constructBundleUrlAsync(
    projectRoot,
    {
      hostType: 'localhost',
      urlType: 'http',
    },
    requestHostname
  );

  let mainModulePath = guessMainModulePath(entryPoint);
  bundleUrl += `/${mainModulePath}.${ext}`;

  let queryParams = await constructBundleQueryParamsAsync(
    projectRoot,
    {
      dev: false,
      minify: true,
    },
    requestHostname
  );
  return `${bundleUrl}?${queryParams}`;
}

export async function constructPublishUrlAsync(
  projectRoot: string,
  entryPoint: string,
  requestHostname?: string
) {
  return await constructUrlWithExtensionAsync(projectRoot, entryPoint, 'bundle', requestHostname);
}

export async function constructSourceMapUrlAsync(
  projectRoot: string,
  entryPoint: string,
  requestHostname?: string
) {
  return await constructUrlWithExtensionAsync(projectRoot, entryPoint, 'map', requestHostname);
}

export async function constructAssetsUrlAsync(
  projectRoot: string,
  entryPoint: string,
  requestHostname?: string
) {
  return await constructUrlWithExtensionAsync(projectRoot, entryPoint, 'assets', requestHostname);
}

export async function constructDebuggerHostAsync(projectRoot: string, requestHostname?: string) {
  return constructUrlAsync(
    projectRoot,
    {
      urlType: 'no-protocol',
    },
    true,
    requestHostname
  );
}

export async function constructBundleQueryParamsAsync(projectRoot: string, opts: any) {
  let queryParams = `dev=${encodeURIComponent(!!opts.dev)}`;

  if (opts.hasOwnProperty('strict')) {
    queryParams += `&strict=${encodeURIComponent(!!opts.strict)}`;
  }

  if (opts.hasOwnProperty('minify')) {
    queryParams += `&minify=${encodeURIComponent(!!opts.minify)}`;
  }

  queryParams += '&hot=false';

  let { exp, pkg } = await ProjectUtils.readConfigJsonAsync(projectRoot);

  // Be backwards compatible for users who haven't migrated from `exponent`
  // to the `expo` sdk package.
  let sdkPkg = pkg.dependencies['expo'] ? 'expo' : 'exponent';
  // Use an absolute path here so that we can not worry about symlinks/relative requires
  let nodeModulesPath = exp.nodeModulesPath
    ? path.join(path.resolve(projectRoot, exp.nodeModulesPath), 'node_modules')
    : path.join(projectRoot, 'node_modules');
  let pluginModule = path.join(nodeModulesPath, sdkPkg, 'tools', 'hashAssetFiles');
  queryParams += `&assetPlugin=${pluginModule}`;

  // Only sdk-10.1.0+ supports the assetPlugin parameter. We use only the
  // major version in the sdkVersion field, so check for 11.0.0 to be sure.
  let supportsAssetPlugins = Versions.gteSdkVersion(exp, '11.0.0');
  if (!supportsAssetPlugins) {
    queryParams += '&includeAssetFileHashes=true';
  }

  return queryParams;
}

export async function constructUrlAsync(
  projectRoot: string,
  opts: any,
  isPackager: boolean,
  requestHostname?: string
) {
  if (opts) {
    // the randomness is only important if we're online and can build a tunnel
    let urlRandomnessSchema;
    if (Config.offline) {
      urlRandomnessSchema = joi
        .string()
        .optional()
        .allow(null);
    } else {
      urlRandomnessSchema = joi.string();
    }

    let schema = joi.object().keys({
      urlType: joi.any().valid('exp', 'http', 'redirect', 'no-protocol'),
      lanType: joi.any().valid('ip', 'hostname'),
      hostType: joi.any().valid('localhost', 'lan', 'tunnel'),
      dev: joi.boolean(),
      strict: joi.boolean(),
      minify: joi.boolean(),
      urlRandomness: urlRandomnessSchema,
    });

    try {
      await joiValidateAsync(opts, schema);
    } catch (e) {
      throw new XDLError(ErrorCode.INVALID_OPTIONS, e.toString());
    }
  }

  let defaultOpts = await ProjectSettings.getPackagerOptsAsync(projectRoot);
  if (!opts) {
    opts = defaultOpts;
  } else {
    opts = Object.assign({}, defaultOpts, opts);
  }

  let packagerInfo = await ProjectSettings.readPackagerInfoAsync(projectRoot);

  let protocol;
  if (opts.urlType === 'http') {
    protocol = 'http';
  } else if (opts.urlType === 'no-protocol') {
    protocol = null;
  } else {
    protocol = 'exp';

    let { exp } = await ProjectUtils.readConfigJsonAsync(projectRoot);
    if (exp.detach && exp.detach.scheme) {
      protocol = exp.detach.scheme;
    }
  }

  let hostname;
  let port;

  const proxyURL = isPackager
    ? process.env.EXPO_PACKAGER_PROXY_URL
    : process.env.EXPO_MANIFEST_PROXY_URL;
  if (proxyURL) {
    const parsedProxyURL = url.parse(proxyURL);
    hostname = parsedProxyURL.hostname;
    port = parsedProxyURL.port;
    if (parsedProxyURL.protocol === 'https:') {
      if (protocol === 'http') {
        protocol = 'https';
      }
      if (!port) {
        port = '443';
      }
    }
  } else if (opts.hostType === 'localhost' || requestHostname === 'localhost') {
    hostname = 'localhost';
    port = isPackager ? packagerInfo.packagerPort : packagerInfo.expoServerPort;
  } else if (opts.hostType === 'lan' || Config.offline) {
    if (process.env.EXPO_PACKAGER_HOSTNAME) {
      hostname = process.env.EXPO_PACKAGER_HOSTNAME.trim();
    } else if (process.env.REACT_NATIVE_PACKAGER_HOSTNAME) {
      hostname = process.env.REACT_NATIVE_PACKAGER_HOSTNAME.trim();
    } else if (opts.lanType === 'ip') {
      if (requestHostname) {
        hostname = requestHostname;
      } else {
        hostname = ip.address();
      }
    } else {
      // Some old versions of OSX work with hostname but not local ip address.
      hostname = os.hostname();
    }
    port = isPackager ? packagerInfo.packagerPort : packagerInfo.expoServerPort;
  } else {
    let ngrokUrl = isPackager ? packagerInfo.packagerNgrokUrl : packagerInfo.expoServerNgrokUrl;
    if (!ngrokUrl) {
      // use localhost
      hostname = 'localhost';
      port = isPackager ? packagerInfo.packagerPort : packagerInfo.expoServerPort;

      // TODO report a warning when this is for a currently served project, suppress for status checks
    } else {
      let pnu = url.parse(ngrokUrl);
      hostname = pnu.hostname;
      port = pnu.port;
    }
  }

  let url_ = '';
  if (protocol) {
    url_ += `${protocol}://`;
  }

  if (!hostname) {
    throw new Error('Hostname cannot be inferred.');
  }

  url_ += hostname;

  if (port) {
    url_ += `:${port}`;
  } else {
    // Android HMR breaks without this :|
    url_ += ':80';
  }

  if (opts.urlType === 'redirect') {
    return `https://exp.host/--/to-exp/${encodeURIComponent(url_)}`;
  }

  return url_;
}

export function guessMainModulePath(entryPoint: string) {
  return entryPoint.replace(/\.js$/, '');
}

export function randomIdentifier(length: number = 6) {
  let alphabet = '23456789qwertyuipasdfghjkzxcvbnm';
  let result = '';
  for (let i = 0; i < length; i++) {
    let j = Math.floor(Math.random() * alphabet.length);
    let c = alphabet.substr(j, 1);
    result += c;
  }
  return result;
}

export function sevenDigitIdentifier() {
  return `${randomIdentifier(3)}-${randomIdentifier(4)}`;
}

export function randomIdentifierForUser(username: string) {
  return `${username}-${randomIdentifier(3)}-${randomIdentifier(2)}`;
}

export function someRandomness() {
  return [randomIdentifier(2), randomIdentifier(3)].join('-');
}

export function domainify(s: string) {
  return s
    .toLowerCase()
    .replace(/[^a-z0-9-]/g, '-')
    .replace(/^-+/, '')
    .replace(/-+$/, '');
}

export function getPlatformSpecificBundleUrl(url: string, platform: string) {
  if (url.includes(Exp.ENTRY_POINT_PLATFORM_TEMPLATE_STRING)) {
    return url.replace(Exp.ENTRY_POINT_PLATFORM_TEMPLATE_STRING, platform);
  } else {
    return url;
  }
}
<|endoftext|>


<commit_before>'use strict';

//require('newrelic');

var express         = require('express');

var env             = process.env.NODE_ENV = process.env.NODE_ENV || 'development';

var app             = express();

var config          = require('./server/config/config')[env];

var user = process.env.DB_ID;
var pass = process.env.DB_KEY;

require('./server/config/express')(app, config);

require('./server/config/mongoose')(config, user, pass, env);

require('./server/config/passport')();

require('./server/config/routes')(app);

app.listen(config.port);

console.log('Listening on port ' + config.port + '...');
<commit_msg>enabled new relic<commit_after>'use strict';

require('newrelic');

var express         = require('express');

var env             = process.env.NODE_ENV = process.env.NODE_ENV || 'development';

var app             = express();

var config          = require('./server/config/config')[env];

var user = process.env.DB_ID;
var pass = process.env.DB_KEY;

require('./server/config/express')(app, config);

require('./server/config/mongoose')(config, user, pass, env);

require('./server/config/passport')();

require('./server/config/routes')(app);

app.listen(config.port);

console.log('Listening on port ' + config.port + '...');
<|endoftext|>


<commit_before>import { window, document, setTimeout } from "./globals";

import equiv from "./equiv";
import dump from "./dump";
import module from "./module";
import Assert from "./assert";
import Logger from "./logger";
import Test, { test, pushFailure } from "./test";
import exportQUnit from "./export";

import config from "./core/config";
import { extend, objectType, is, now } from "./core/utilities";
import { registerLoggingCallbacks, runLoggingCallbacks } from "./core/logging";
import { sourceFromStacktrace } from "./core/stacktrace";
import ProcessingQueue from "./core/processing-queue";

import SuiteReport from "./reports/suite";

import { on, emit } from "./events";
import onError from "./core/onerror";
import onUnhandledRejection from "./core/on-unhandled-rejection";

const QUnit = {};
export const globalSuite = new SuiteReport();

// The initial "currentModule" represents the global (or top-level) module that
// is not explicitly defined by the user, therefore we add the "globalSuite" to
// it since each module has a suiteReport associated with it.
config.currentModule.suiteReport = globalSuite;

var globalStartCalled = false;
var runStarted = false;

// Figure out if we're running the tests from a server or not
QUnit.isLocal = ( window && window.location && window.location.protocol === "file:" );

// Expose the current QUnit version
QUnit.version = "@VERSION";

extend( QUnit, {
	on,

	module,

	test: test,

	// alias other test flavors for easy access
	todo: test.todo,
	skip: test.skip,
	only: test.only,

	start: function( count ) {
		var globalStartAlreadyCalled = globalStartCalled;

		if ( !config.current ) {
			globalStartCalled = true;

			if ( runStarted ) {
				throw new Error( "Called start() while test already started running" );
			} else if ( globalStartAlreadyCalled || count > 1 ) {
				throw new Error( "Called start() outside of a test context too many times" );
			} else if ( config.autostart ) {
				throw new Error( "Called start() outside of a test context when " +
					"QUnit.config.autostart was true" );
			} else if ( !config.pageLoaded ) {

				// The page isn't completely loaded yet, so we set autostart and then
				// load if we're in Node or wait for the browser's load event.
				config.autostart = true;

				// Starts from Node even if .load was not previously called. We still return
				// early otherwise we'll wind up "beginning" twice.
				if ( !document ) {
					QUnit.load();
				}

				return;
			}
		} else {
			throw new Error( "QUnit.start cannot be called inside a test context." );
		}

		scheduleBegin();
	},

	config: config,

	is: is,

	objectType: objectType,

	extend: function( ...args ) {
		Logger.warn( "QUnit.extend is deprecated and will be removed in QUnit 3.0." +
			" Please use Object.assign instead." );

		// delegate to utility implementation, which does not warn and can be used elsewhere internally
		return extend.apply( this, args );
	},

	load: function() {
		config.pageLoaded = true;

		// Initialize the configuration options
		extend( config, {
			stats: { all: 0, bad: 0, testCount: 0 },
			started: 0,
			updateRate: 1000,
			autostart: true,
			filter: ""
		}, true );

		if ( !runStarted ) {
			config.blocking = false;

			if ( config.autostart ) {
				scheduleBegin();
			}
		}
	},

	stack: function( offset ) {
		offset = ( offset || 0 ) + 2;
		return sourceFromStacktrace( offset );
	},

	onError,

	onUnhandledRejection
} );

QUnit.pushFailure = pushFailure;
QUnit.assert = Assert.prototype;
QUnit.equiv = equiv;
QUnit.dump = dump;

registerLoggingCallbacks( QUnit );

function scheduleBegin() {

	runStarted = true;

	// Add a slight delay to allow definition of more modules and tests.
	if ( setTimeout ) {
		setTimeout( function() {
			begin();
		} );
	} else {
		begin();
	}
}

function unblockAndAdvanceQueue() {
	config.blocking = false;
	ProcessingQueue.advance();
}

export function begin() {
	var i, l,
		modulesLog = [];

	// If the test run hasn't officially begun yet
	if ( !config.started ) {

		// Record the time of the test run's beginning
		config.started = now();

		// Delete the loose unnamed module if unused.
		if ( config.modules[ 0 ].name === "" && config.modules[ 0 ].tests.length === 0 ) {
			config.modules.shift();
		}

		// Avoid unnecessary information by not logging modules' test environments
		for ( i = 0, l = config.modules.length; i < l; i++ ) {
			modulesLog.push( {
				name: config.modules[ i ].name,
				tests: config.modules[ i ].tests
			} );
		}

		// The test run is officially beginning now
		emit( "runStart", globalSuite.start( true ) );
		runLoggingCallbacks( "begin", {
			totalTests: Test.count,
			modules: modulesLog
		} ).then( unblockAndAdvanceQueue );
	} else {
		unblockAndAdvanceQueue();
	}
}

exportQUnit( QUnit );

export default QUnit;
<commit_msg>Core: Clean up internals to flatten logic and push down variables<commit_after>import { window, document, setTimeout } from "./globals";

import equiv from "./equiv";
import dump from "./dump";
import module from "./module";
import Assert from "./assert";
import Logger from "./logger";
import Test, { test, pushFailure } from "./test";
import exportQUnit from "./export";

import config from "./core/config";
import { extend, objectType, is, now } from "./core/utilities";
import { registerLoggingCallbacks, runLoggingCallbacks } from "./core/logging";
import { sourceFromStacktrace } from "./core/stacktrace";
import ProcessingQueue from "./core/processing-queue";

import SuiteReport from "./reports/suite";

import { on, emit } from "./events";
import onError from "./core/onerror";
import onUnhandledRejection from "./core/on-unhandled-rejection";

const QUnit = {};
export const globalSuite = new SuiteReport();

// The initial "currentModule" represents the global (or top-level) module that
// is not explicitly defined by the user, therefore we add the "globalSuite" to
// it since each module has a suiteReport associated with it.
config.currentModule.suiteReport = globalSuite;

var globalStartCalled = false;
var runStarted = false;

// Figure out if we're running the tests from a server or not
QUnit.isLocal = ( window && window.location && window.location.protocol === "file:" );

// Expose the current QUnit version
QUnit.version = "@VERSION";

extend( QUnit, {
	config,

	dump,
	equiv,
	is,
	objectType,
	on,
	onError,
	onUnhandledRejection,
	pushFailure,

	assert: Assert.prototype,
	module,
	test,

	// alias other test flavors for easy access
	todo: test.todo,
	skip: test.skip,
	only: test.only,

	start: function( count ) {

		if ( config.current ) {
			throw new Error( "QUnit.start cannot be called inside a test context." );
		}

		var globalStartAlreadyCalled = globalStartCalled;
		globalStartCalled = true;

		if ( runStarted ) {
			throw new Error( "Called start() while test already started running" );
		}
		if ( globalStartAlreadyCalled || count > 1 ) {
			throw new Error( "Called start() outside of a test context too many times" );
		}
		if ( config.autostart ) {
			throw new Error( "Called start() outside of a test context when " +
				"QUnit.config.autostart was true" );
		}

		if ( !config.pageLoaded ) {

			// The page isn't completely loaded yet, so we set autostart and then
			// load if we're in Node or wait for the browser's load event.
			config.autostart = true;

			// Starts from Node even if .load was not previously called. We still return
			// early otherwise we'll wind up "beginning" twice.
			if ( !document ) {
				QUnit.load();
			}

			return;
		}

		scheduleBegin();

	},

	extend: function( ...args ) {
		Logger.warn( "QUnit.extend is deprecated and will be removed in QUnit 3.0." +
			" Please use Object.assign instead." );

		// delegate to utility implementation, which does not warn and can be used elsewhere internally
		return extend.apply( this, args );
	},

	load: function() {
		config.pageLoaded = true;

		// Initialize the configuration options
		extend( config, {
			stats: { all: 0, bad: 0, testCount: 0 },
			started: 0,
			updateRate: 1000,
			autostart: true,
			filter: ""
		}, true );

		if ( !runStarted ) {
			config.blocking = false;

			if ( config.autostart ) {
				scheduleBegin();
			}
		}
	},

	stack: function( offset ) {
		offset = ( offset || 0 ) + 2;
		return sourceFromStacktrace( offset );
	}
} );

registerLoggingCallbacks( QUnit );

function scheduleBegin() {

	runStarted = true;

	// Add a slight delay to allow definition of more modules and tests.
	if ( setTimeout ) {
		setTimeout( function() {
			begin();
		} );
	} else {
		begin();
	}
}

function unblockAndAdvanceQueue() {
	config.blocking = false;
	ProcessingQueue.advance();
}

export function begin() {

	if ( config.started ) {
		unblockAndAdvanceQueue();
		return;
	}

	// The test run hasn't officially begun yet
	// Record the time of the test run's beginning
	config.started = now();

	// Delete the loose unnamed module if unused.
	if ( config.modules[ 0 ].name === "" && config.modules[ 0 ].tests.length === 0 ) {
		config.modules.shift();
	}

	// Avoid unnecessary information by not logging modules' test environments
	const l = config.modules.length;
	const modulesLog = [];
	for ( let i = 0; i < l; i++ ) {
		modulesLog.push( {
			name: config.modules[ i ].name,
			tests: config.modules[ i ].tests
		} );
	}

	// The test run is officially beginning now
	emit( "runStart", globalSuite.start( true ) );
	runLoggingCallbacks( "begin", {
		totalTests: Test.count,
		modules: modulesLog
	} ).then( unblockAndAdvanceQueue );
}

exportQUnit( QUnit );

export default QUnit;
<|endoftext|>


<commit_before>import React, { Component } from 'react';
import PropTypes from 'prop-types';

export const UserShape = {
  background: PropTypes.string,
  image: PropTypes.string,
  name: PropTypes.string,
  email: PropTypes.string
};

export class UserView extends Component {
  render() {
    const { background, image, name, email } = this.props;
    return (

        {background && (

        )}
        {image && (

        )}
        {name && (

            {name}

        )}
        {email && (

            {email}

        )}

    );
  }
}

UserView.propTypes = UserShape;

export default UserView;
<commit_msg>UserView: convert to stateless<commit_after>import React from 'react';
import PropTypes from 'prop-types';

export const UserShape = {
  background: PropTypes.string,
  image: PropTypes.string,
  name: PropTypes.string,
  email: PropTypes.string
};

const UserView = ({ background, image, name, email }) => (

    {background && (

    )}
    {image && (

    )}
    {name && (

        {name}

    )}
    {email && (

        {email}

    )}

);

UserView.propTypes = UserShape;

export default UserView;
<|endoftext|>


<commit_before>var express = require('express');
var app = express();
var path = require('path');
var bodyParser = require('body-parser')
var spawn = require('child_process').spawn;
var fs = require('fs');

var mjAPI = require("./node_modules/mathjax-node/lib/mj-single.js");
mjAPI.start();

app.use(bodyParser.json());
app.use(express.static('public'));

app.get('/', function (req, res) {
  res.sendFile(path.join(__dirname + '/index.html'));
});

app.post('/zip', function(req, res) {
  rm = spawn('rm', ['-f', './public/myfiles.zip']);
  for (var i=0; i<req.body.svg_names.length; i++) {
    zip = spawn('zip', ['-j', './public/myfiles.zip',
      './public/svg/' + req.body.svg_names[i] + '.svg']);
    zip.stdout.on('data', function (data) {
      //console.log('stdout: ' + data);
    });
    zip.stderr.on('data', function (data) {
      console.log('stderr: ' + data);
    });
    zip.on('close', function (code) {
      //console.log('child process exited with code ' + code);
    });
  }
  res.end();
});

app.post('/submit', function(req, res) {
  var fname = Math.random().toString(36).substring(7);
  mjAPI.typeset({
    math: req.body.math,
    format: "TeX",
    svg: true,
  }, function (data) {
    if (!data.errors) {
      fs.writeFile("./public/svg/" + fname + ".svg", data.svg, function(err) {
        if(err) { return console.log(err); }
        res.json(
          {"idx": req.body.idx,
          "fname": fname}
        );
      });
    }
  });
});

var server = app.listen(3000, function () {
  var host = server.address().address;
  var port = server.address().port;

  console.log('App listening at http://%s:%s', host, port);
});

<commit_msg>saves with normal file names<commit_after>var express = require('express');
var app = express();
var path = require('path');
var bodyParser = require('body-parser')
var spawn = require('child_process').spawn;
var fs = require('fs');

var mjAPI = require("./node_modules/mathjax-node/lib/mj-single.js");
mjAPI.start();

app.use(bodyParser.json());
app.use(express.static('public'));

app.get('/', function (req, res) {
  res.sendFile(path.join(__dirname + '/index.html'));
});

app.post('/zip', function(req, res) {
	rm = spawn('rm', ['-f', './public/myfiles.zip']);
	for (var i=0; i<req.body.svg_names.length; i++) {
		old_file = './public/svg/' + req.body.svg_names[i] + '.svg'
		new_file = './public/svg/eqn' + pad(i,3) + '.svg'
		archive = './public/myfiles.zip';
		copyAndZip(old_file, new_file, archive);
	}
	res.end();
});

app.post('/submit', function(req, res) {
  var fname = Math.random().toString(36).substring(7);
  mjAPI.typeset({
    math: req.body.math,
    format: "TeX",
    svg: true,
  }, function (data) {
    if (!data.errors) {
      fs.writeFile("./public/svg/" + fname + ".svg", data.svg, function(err) {
        if(err) { return console.log(err); }
        res.json(
          {"idx": req.body.idx,
          "fname": fname}
        );
      });
    }
  });
});

var server = app.listen(3000, function () {
  var host = server.address().address;
  var port = server.address().port;

  console.log('App listening at http://%s:%s', host, port);
});

function pad(num, size) {
    var s = "000000000" + num;
    return s.substr(s.length-size);
}

function zipme(new_file, archive) {
	zip = spawn('zip', ['-j', archive, new_file]);
	zip.stdout.on('data', function (data) {
		console.log('stdout [zip]: ' + data);
	});
	zip.stderr.on('data', function (data) {
		console.log('stderr [zip]: ' + data);
	});
	zip.on('close', function (code) {
		console.log('child process [zip] exited with code ' + code);
	});
}

function copyAndZip(old_file, new_file, archive) {
	cp = spawn('cp', [old_file, new_file]);
	cp.stdout.on('data', function(data) {
		console.log('stdout [cp]: ' + data);
	});
	cp.stderr.on('data', function(data) {
		console.log('stderr [cp]: ' + data);
	});
	cp.on('close', function(code) {
		console.log('child process [cp] exited with code ' + code);
		zipme(new_file, archive);
	});
}
<|endoftext|>


<commit_before>"use strict";
(function(window) {
    function Moon(opts) {
        var _el = opts.el;
        var _data = opts.data;
        var _methods = opts.methods;
        var directives = {};
        this.$el = document.getElementById(_el);
        this.components = opts.components;
        this.dom = {type: this.$el.nodeName, children: [], node: this.$el};

        // Change state when $data is changed
        Object.defineProperty(this, '$data', {
            get: function() {
                return _data;
            },
            set: function(value) {
                _data = value;
                this.build(this.dom.children);
            }
        });

        // Utility: Raw Attributes -> Key Value Pairs
        var extractAttrs = function(node) {
          var attrs = {};
          if(!node.attributes) return attrs;
          var rawAttrs = node.attributes;
          for(var i = 0; i < rawAttrs.length; i++) {
            attrs[rawAttrs[i].name] = rawAttrs[i].value
          }

          return attrs;
        }

        // Utility: Compile a template and return html
        var compileTemplate = function(template, data) {
          var code = template,
              re = /{{([.]?\w+)}}/gi;
          code.replace(re, function(match, p) {
            code = code.replace(match, "` + data." + p + " + `");
          });
          var compile = new Function("data", "var out = `" + code + "`; return out");
          return compile(data);
        }

        // Utility: Create Elements Recursively For all Children
        this.recursiveChildren = function(children) {
          var recursiveChildrenArr = [];
          for(var i = 0; i < children.length; i++) {
            var child = children[i];
            recursiveChildrenArr.push(this.createElement(child.nodeName, this.recursiveChildren(child.childNodes), child.textContent, extractAttrs(child), child));
          }
          return recursiveChildrenArr;
        }

        // Utility: Create Virtual DOM Instance
        this.createVirtualDOM = function(node) {
          var vdom = this.createElement(node.nodeName, this.recursiveChildren(node.childNodes), node.textContent, extractAttrs(node), node);
          this.dom = vdom;
        }

        // Build the DOM with $data
        this.build = function(children) {
          for(var i = 0; i < children.length; i++) {
            var el = children[i];

            if(el.type === "#text") {
              el.node.textContent = compileTemplate(el.val, this.$data);
            } else if(el.props) {
              for(var prop in el.props) {
                var propVal = el.props[prop];
                var compiledProperty = compileTemplate(propVal, this.$data);
                var directive = directives[prop];
                if(directive) {
                  directive(el.node, compiledProperty);
                }

                el.node.setAttribute(prop, compiledProperty);
              }
            }

            if(this.components[el.type.toLowerCase()]) {
              var component = this.components[el.type.toLowerCase()];
            }

            this.build(el.children);
          }
        }

        // Create Virtual DOM Object from Params
        this.createElement = function(type, children, val, props, node) {
          return {type: type, children: children, val: val, props: props, node: node};
        }

        // Set any value in $data
        this.set = function(key, val) {
          this.$data[key] = val;
          this.build(this.dom.children);
        }

        // Get any value in $data
        this.get = function(key) {
          return this.$data[key];
        }

        // Make AJAX GET/POST requests
        this.ajax = function(method, url, params, cb) {
          var xmlHttp = new XMLHttpRequest();
          method = method.toUpperCase();
          if(typeof params === "function") {
            cb = params;
          }
          var urlParams = "?";
          if(method === "POST") {
            http.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
            for(var param in params) {
              urlParams += param + "=" + params[param] + "&";
            }
          }
          xmlHttp.onreadystatechange = function() {
          if (xmlHttp.readyState == 4 && xmlHttp.status == 200)
            cb(JSON.parse(xmlHttp.responseText));
          }
          xmlHttp.open(method, url, true);
          xmlHttp.send(method === "POST" ? urlParams : null);
        }

        // Call a method defined in _methods
        this.method = function(method) {
          _methods[method]();
        }

        // Directive Initialization
        this.directive = function(name, action) {
          directives["m-" + name] = action;
          this.build(this.dom.children);
        }

        // Default Directives
        var dirSelf = this;
        directives["m-if"] = function(el, val) {
          var evaluated = new Function("return " + val);
          if(!evaluated) {
            el.textContent = "";
          }
        }

        // Initialize
        this.createVirtualDOM(this.$el);
        this.build(this.dom.children);
    }

    window.Moon = Moon;
    window.$ = function(el) {
      el = document.querySelectorAll(el);
      return el.length === 1 ? el[0] : el;
    }

})(window);
<commit_msg>remove from build<commit_after>"use strict";
(function(window) {
    function Moon(opts) {
        var _el = opts.el;
        var _data = opts.data;
        var _methods = opts.methods;
        var directives = {};
        this.$el = document.getElementById(_el);
        this.components = opts.components;
        this.dom = {type: this.$el.nodeName, children: [], node: this.$el};

        // Change state when $data is changed
        Object.defineProperty(this, '$data', {
            get: function() {
                return _data;
            },
            set: function(value) {
                _data = value;
                this.build(this.dom.children);
            }
        });

        // Utility: Raw Attributes -> Key Value Pairs
        var extractAttrs = function(node) {
          var attrs = {};
          if(!node.attributes) return attrs;
          var rawAttrs = node.attributes;
          for(var i = 0; i < rawAttrs.length; i++) {
            attrs[rawAttrs[i].name] = rawAttrs[i].value
          }

          return attrs;
        }

        // Utility: Compile a template and return html
        var compileTemplate = function(template, data) {
          var code = template,
              re = /{{([.]?\w+)}}/gi;
          code.replace(re, function(match, p) {
            code = code.replace(match, "` + data." + p + " + `");
          });
          var compile = new Function("data", "var out = `" + code + "`; return out");
          return compile(data);
        }

        // Utility: Create Elements Recursively For all Children
        this.recursiveChildren = function(children) {
          var recursiveChildrenArr = [];
          for(var i = 0; i < children.length; i++) {
            var child = children[i];
            if()
            recursiveChildrenArr.push(this.createElement(child.nodeName, this.recursiveChildren(child.childNodes), child.textContent, extractAttrs(child), child));
          }
          return recursiveChildrenArr;
        }

        // Utility: Create Virtual DOM Instance
        this.createVirtualDOM = function(node) {
          var vdom = this.createElement(node.nodeName, this.recursiveChildren(node.childNodes), node.textContent, extractAttrs(node), node);
          this.dom = vdom;
        }

        // Build the DOM with $data
        this.build = function(children) {
          for(var i = 0; i < children.length; i++) {
            var el = children[i];

            if(el.type === "#text") {
              el.node.textContent = compileTemplate(el.val, this.$data);
            } else if(el.props) {
              for(var prop in el.props) {
                var propVal = el.props[prop];
                var compiledProperty = compileTemplate(propVal, this.$data);
                var directive = directives[prop];
                if(directive) {
                  directive(el.node, compiledProperty);
                }

                el.node.setAttribute(prop, compiledProperty);
              }
            }

            this.build(el.children);
          }
        }

        // Create Virtual DOM Object from Params
        this.createElement = function(type, children, val, props, node) {
          return {type: type, children: children, val: val, props: props, node: node};
        }

        // Set any value in $data
        this.set = function(key, val) {
          this.$data[key] = val;
          this.build(this.dom.children);
        }

        // Get any value in $data
        this.get = function(key) {
          return this.$data[key];
        }

        // Make AJAX GET/POST requests
        this.ajax = function(method, url, params, cb) {
          var xmlHttp = new XMLHttpRequest();
          method = method.toUpperCase();
          if(typeof params === "function") {
            cb = params;
          }
          var urlParams = "?";
          if(method === "POST") {
            http.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
            for(var param in params) {
              urlParams += param + "=" + params[param] + "&";
            }
          }
          xmlHttp.onreadystatechange = function() {
          if (xmlHttp.readyState == 4 && xmlHttp.status == 200)
            cb(JSON.parse(xmlHttp.responseText));
          }
          xmlHttp.open(method, url, true);
          xmlHttp.send(method === "POST" ? urlParams : null);
        }

        // Call a method defined in _methods
        this.method = function(method) {
          _methods[method]();
        }

        // Directive Initialization
        this.directive = function(name, action) {
          directives["m-" + name] = action;
          this.build(this.dom.children);
        }

        // Default Directives
        var dirSelf = this;
        directives["m-if"] = function(el, val) {
          var evaluated = new Function("return " + val);
          if(!evaluated) {
            el.textContent = "";
          }
        }

        // Initialize
        this.createVirtualDOM(this.$el);
        this.build(this.dom.children);
    }

    window.Moon = Moon;
    window.$ = function(el) {
      el = document.querySelectorAll(el);
      return el.length === 1 ? el[0] : el;
    }

})(window);
<|endoftext|>