text: 'use strict'
  
  const {ipcMain, webContents} = require('electron')
  const parseFeaturesString = require('../common/parse-features-string')
  
  // Doesn't exist in early initialization.
  let webViewManager = null
  
  const supportedWebViewEvents = [
    'load-commit',
    'did-attach',
    'did-finish-load',
    'did-fail-load',
    'did-frame-finish-load',
    'did-start-loading',
    'did-stop-loading',
    '-did-get-response-details',
    '-did-get-redirect-request',
    'dom-ready',
    'console-message',
    'context-menu',
    'devtools-opened',
    'devtools-closed',
    'devtools-focused',
    'new-window',
    'will-navigate',
    'did-start-navigation',
    'did-navigate',
    'did-frame-navigate',
    'did-navigate-in-page',
    'close',
    'crashed',
    'gpu-crashed',
    'plugin-crashed',
    'destroyed',
    'page-title-updated',
    'page-favicon-updated',
    'enter-html-full-screen',
    'leave-html-full-screen',
    'media-started-playing',
    'media-paused',
    'found-in-page',
    'did-change-theme-color',
    'update-target-url'
  ]
  
  let nextGuestInstanceId = 0
  const guestInstances = {}
  const embedderElementsMap = {}
  
  // Moves the last element of array to the first one.
  const moveLastToFirst = function (list) {
    list.unshift(list.pop())
  }
  
  // Generate guestInstanceId.
  const getNextGuestInstanceId = function () {
    return ++nextGuestInstanceId
  }
  
  // Create a new guest instance.
  const createGuest = function (embedder, params) {
    if (webViewManager == null) {
      webViewManager = process.atomBinding('web_view_manager')
    }
  
    const guestInstanceId = getNextGuestInstanceId(embedder)
    const guest = webContents.create({
      isGuest: true,
      partition: params.partition,
      embedder: embedder
    })
    guestInstances[guestInstanceId] = {
      guest: guest,
      embedder: embedder
    }
  
    watchEmbedder(embedder)
  
    // Init guest web view after attached.
    guest.on('did-attach', function (event) {
      params = this.attachParams
      delete this.attachParams
  
      const previouslyAttached = this.viewInstanceId != null
      this.viewInstanceId = params.instanceId
  
      // Only load URL and set size on first attach
      if (previouslyAttached) {
        return
      }
  
      this.setSize({
        normal: {
          width: params.elementWidth,
          height: params.elementHeight
        },
        enableAutoSize: params.autosize,
        min: {
          width: params.minwidth,
          height: params.minheight
        },
        max: {
          width: params.maxwidth,
          height: params.maxheight
        }
      })
      if (params.src) {
        const opts = {}
        if (params.httpreferrer) {
          opts.httpReferrer = params.httpreferrer
        }
        if (params.useragent) {
          opts.userAgent = params.useragent
        }
        this.loadURL(params.src, opts)
      }
      guest.allowPopups = params.allowpopups
      embedder.emit('did-attach-webview', event, guest)
    })
  
    const sendToEmbedder = (channel, ...args) => {
      const embedder = getEmbedder(guestInstanceId)
      if (embedder != null) {
        embedder.send(`${channel}-${guest.viewInstanceId}`, ...args)
      }
    }
  
    // Dispatch events to embedder.
    const fn = function (event) {
      guest.on(event, function (_, ...args) {
        sendToEmbedder('ELECTRON_GUEST_VIEW_INTERNAL_DISPATCH_EVENT', event, ...args)
      })
    }
    for (const event of supportedWebViewEvents) {
      fn(event)
    }
  
    // Dispatch guest's IPC messages to embedder.
    guest.on('ipc-message-host', function (_, [channel, ...args]) {
      sendToEmbedder('ELECTRON_GUEST_VIEW_INTERNAL_IPC_MESSAGE', channel, ...args)
    })
  
    // Autosize.
    guest.on('size-changed', function (_, ...args) {
      sendToEmbedder('ELECTRON_GUEST_VIEW_INTERNAL_SIZE_CHANGED', ...args)
    })
  
    // Notify guest of embedder window visibility when it is ready
    // FIXME Remove once https://github.com/electron/electron/issues/6828 is fixed
    guest.on('dom-ready', function () {
      const guestInstance = guestInstances[guestInstanceId]
      if (guestInstance != null && guestInstance.visibilityState != null) {
        guest.send('ELECTRON_GUEST_INSTANCE_VISIBILITY_CHANGE', guestInstance.visibilityState)
      }
    })
  
    // Forward internal web contents event to embedder to handle
    // native window.open setup
    guest.on('-add-new-contents', (...args) => {
      if (guest.getLastWebPreferences().nativeWindowOpen === true) {
        const embedder = getEmbedder(guestInstanceId)
        if (embedder != null) {
          embedder.emit('-add-new-contents', ...args)
        }
      }
    })
    guest.on('-web-contents-created', (...args) => {
      if (guest.getLastWebPreferences().nativeWindowOpen === true) {
        const embedder = getEmbedder(guestInstanceId)
        if (embedder != null) {
          embedder.emit('-web-contents-created', ...args)
        }
      }
    })
  
    return guestInstanceId
  }
  
  // Attach the guest to an element of embedder.
  const attachGuest = function (event, elementInstanceId, guestInstanceId, params) {
    const embedder = event.sender
    // Destroy the old guest when attaching.
    const key = `${embedder.getId()}-${elementInstanceId}`
    const oldGuestInstanceId = embedderElementsMap[key]
    if (oldGuestInstanceId != null) {
      // Reattachment to the same guest is just a no-op.
      if (oldGuestInstanceId === guestInstanceId) {
        return
      }
  
      destroyGuest(embedder, oldGuestInstanceId)
    }
  
    const guestInstance = guestInstances[guestInstanceId]
    // If this isn't a valid guest instance then do nothing.
    if (!guestInstance) {
      return
    }
    const {guest} = guestInstance
  
    // If this guest is already attached to an element then remove it
    if (guestInstance.elementInstanceId) {
      const oldKey = `${guestInstance.embedder.getId()}-${guestInstance.elementInstanceId}`
      delete embedderElementsMap[oldKey]
  
      // Remove guest from embedder if moving across web views
      if (guest.viewInstanceId !== params.instanceId) {
        webViewManager.removeGuest(guestInstance.embedder, guestInstanceId)
        guestInstance.embedder.send(`ELECTRON_GUEST_VIEW_INTERNAL_DESTROY_GUEST-${guest.viewInstanceId}`)
      }
    }
  
    const webPreferences = {
      guestInstanceId: guestInstanceId,
      nodeIntegration: params.nodeintegration != null ? params.nodeintegration : false,
      plugins: params.plugins,
      zoomFactor: embedder._getZoomFactor(),
      webSecurity: !params.disablewebsecurity,
      blinkFeatures: params.blinkfeatures,
      disableBlinkFeatures: params.disableblinkfeatures
    }
  
    // parse the 'webpreferences' attribute string, if set
    // this uses the same parsing rules as window.open uses for its features
    if (typeof params.webpreferences === 'string') {
      parseFeaturesString(params.webpreferences, function (key, value) {
        if (value === undefined) {
          // no value was specified, default it to true
          value = true
        }
        webPreferences[key] = value
      })
    }
  
    if (params.preload) {
      webPreferences.preloadURL = params.preload
    }
  
    // Return null from native window.open if allowpopups is unset
    if (webPreferences.nativeWindowOpen === true && !params.allowpopups) {
      webPreferences.disablePopups = true
    }
  
    embedder.emit('will-attach-webview', event, webPreferences, params)
    if (event.defaultPrevented) {
      if (guest.viewInstanceId == null) guest.viewInstanceId = params.instanceId
      destroyGuest(embedder, guestInstanceId)
      return
    }
  
    webViewManager.addGuest(guestInstanceId, elementInstanceId, embedder, guest, webPreferences)
    guest.attachParams = params
    embedderElementsMap[key] = guestInstanceId
  
    guest.setEmbedder(embedder)
    guestInstance.embedder = embedder
    guestInstance.elementInstanceId = elementInstanceId
  
    watchEmbedder(embedder)
  }
  
  // Destroy an existing guest instance.
  const destroyGuest = function (embedder, guestInstanceId) {
    if (!(guestInstanceId in guestInstances)) {
      return
    }
  
    const guestInstance = guestInstances[guestInstanceId]
    if (embedder !== guestInstance.embedder) {
      return
    }
  
    webViewManager.removeGuest(embedder, guestInstanceId)
    guestInstance.guest.destroy()
    delete guestInstances[guestInstanceId]
  
    const key = `${embedder.getId()}-${guestInstance.elementInstanceId}`
    delete embedderElementsMap[key]
  }
  
  // Once an embedder has had a guest attached we watch it for destruction to
  // destroy any remaining guests.
  const watchedEmbedders = new Set()
  const watchEmbedder = function (embedder) {
    if (watchedEmbedders.has(embedder)) {
      return
    }
    watchedEmbedders.add(embedder)
  
    // Forward embedder window visiblity change events to guest
    const onVisibilityChange = function (visibilityState) {
      for (const guestInstanceId of Object.keys(guestInstances)) {
        const guestInstance = guestInstances[guestInstanceId]
        guestInstance.visibilityState = visibilityState
        if (guestInstance.embedder === embedder) {
          guestInstance.guest.send('ELECTRON_GUEST_INSTANCE_VISIBILITY_CHANGE', visibilityState)
        }
      }
    }
    embedder.on('-window-visibility-change', onVisibilityChange)
  
    const destroyEvents = ['will-destroy', 'crashed', 'did-navigate']
    const destroy = function () {
      for (const guestInstanceId of Object.keys(guestInstances)) {
        if (guestInstances[guestInstanceId].embedder === embedder) {
          destroyGuest(embedder, parseInt(guestInstanceId))
        }
      }
  
      for (const event of destroyEvents) {
        embedder.removeListener(event, destroy)
      }
      embedder.removeListener('-window-visibility-change', onVisibilityChange)
  
      watchedEmbedders.delete(embedder)
    }
  
    for (const event of destroyEvents) {
      embedder.once(event, destroy)
  
      // Users might also listen to the crashed event, so we must ensure the guest
      // is destroyed before users' listener gets called. It is done by moving our
      // listener to the first one in queue.
      const listeners = embedder._events[event]
      if (Array.isArray(listeners)) {
        moveLastToFirst(listeners)
      }
    }
  }
  
  ipcMain.on('ELECTRON_GUEST_VIEW_MANAGER_CREATE_GUEST', function (event, params, requestId) {
    event.sender.send(`ELECTRON_RESPONSE_${requestId}`, createGuest(event.sender, params))
  })
  
  ipcMain.on('ELECTRON_GUEST_VIEW_MANAGER_ATTACH_GUEST', function (event, elementInstanceId, guestInstanceId, params) {
    attachGuest(event, elementInstanceId, guestInstanceId, params)
  })
  
  ipcMain.on('ELECTRON_GUEST_VIEW_MANAGER_DESTROY_GUEST', function (event, guestInstanceId) {
    destroyGuest(event.sender, guestInstanceId)
  })
  
  ipcMain.on('ELECTRON_GUEST_VIEW_MANAGER_SET_SIZE', function (event, guestInstanceId, params) {
    const guest = getGuest(guestInstanceId)
    if (guest != null) guest.setSize(params)
  })
  
  // Returns WebContents from its guest id.
  const getGuest = function (guestInstanceId) {
    const guestInstance = guestInstances[guestInstanceId]
    if (guestInstance != null) return guestInstance.guest
  }
  
  // Returns the embedder of the guest.
  const getEmbedder = function (guestInstanceId) {
    const guestInstance = guestInstances[guestInstanceId]
    if (guestInstance != null) return guestInstance.embedder
  }
  
  exports.getGuest = getGuest
  exports.getEmbedder = getEmbedder
  