<commit_before>'use strict'

const {BrowserWindow, ipcMain, webContents} = require('electron')
const {isSameOrigin} = process.atomBinding('v8_util')

const hasProp = {}.hasOwnProperty
const frameToGuest = {}

// Copy attribute of |parent| to |child| if it is not defined in |child|.
const mergeOptions = function (child, parent) {
  let key, value
  for (key in parent) {
    if (!hasProp.call(parent, key)) continue
    value = parent[key]
    if (!(key in child)) {
      if (typeof value === 'object') {
        child[key] = mergeOptions({}, value)
      } else {
        child[key] = value
      }
    }
  }
  return child
}

// Merge |options| with the |embedder|'s window's options.
const mergeBrowserWindowOptions = function (embedder, options) {
  if (options.webPreferences == null) {
    options.webPreferences = {}
  }
  if (embedder.browserWindowOptions != null) {
    // Inherit the original options if it is a BrowserWindow.
    mergeOptions(options, embedder.browserWindowOptions)
  } else {
    // Or only inherit web-preferences if it is a webview.
    mergeOptions(options.webPreferences, embedder.getWebPreferences())
  }

  // Disable node integration on child window if disabled on parent window
  if (embedder.getWebPreferences().nodeIntegration === false) {
    options.webPreferences.nodeIntegration = false
  }

  // Sets correct openerId here to give correct options to 'new-window' event handler
  options.webPreferences.openerId = embedder.id

  return options
}

// Setup a new guest with |embedder|
const setupGuest = function (embedder, frameName, guest, options) {
  // When |embedder| is destroyed we should also destroy attached guest, and if
  // guest is closed by user then we should prevent |embedder| from double
  // closing guest.
  const guestId = guest.webContents.id
  const closedByEmbedder = function () {
    guest.removeListener('closed', closedByUser)
    guest.destroy()
  }
  const closedByUser = function () {
    embedder.send('ELECTRON_GUEST_WINDOW_MANAGER_WINDOW_CLOSED_' + guestId)
    embedder.removeListener('render-view-deleted', closedByEmbedder)
  }
  if (!options.webPreferences.sandbox) {
    // These events should only be handled when the guest window is opened by a
    // non-sandboxed renderer for two reasons:
    //
    // - `render-view-deleted` is emitted when the popup is closed by the user,
    //   and that will eventually result in NativeWindow::NotifyWindowClosed
    //   using a dangling pointer since `destroy()` would have been called by
    //   `closeByEmbedded`
    // - No need to emit `ELECTRON_GUEST_WINDOW_MANAGER_WINDOW_CLOSED_` since
    //   there's no renderer code listening to it.,
    embedder.once('render-view-deleted', closedByEmbedder)
    guest.once('closed', closedByUser)
  }
  if (frameName) {
    frameToGuest[frameName] = guest
    guest.frameName = frameName
    guest.once('closed', function () {
      delete frameToGuest[frameName]
    })
  }
  return guestId
}

// Create a new guest created by |embedder| with |options|.
const createGuest = function (embedder, url, frameName, options, postData) {
  let guest = frameToGuest[frameName]
  if (frameName && (guest != null)) {
    guest.loadURL(url)
    return guest.webContents.id
  }

  // Remember the embedder window's id.
  if (options.webPreferences == null) {
    options.webPreferences = {}
  }

  guest = new BrowserWindow(options)
  if (!options.webContents || url !== 'about:blank') {
    // We should not call `loadURL` if the window was constructed from an
    // existing webContents(window.open in a sandboxed renderer) and if the url
    // is not 'about:blank'.
    //
    // Navigating to the url when creating the window from an existing
    // webContents would not be necessary(it will navigate there anyway), but
    // apparently there's a bug that allows the child window to be scripted by
    // the opener, even when the child window is from another origin.
    //
    // That's why the second condition(url !== "about:blank") is required: to
    // force `OverrideSiteInstanceForNavigation` to be called and consequently
    // spawn a new renderer if the new window is targeting a different origin.
    //
    // If the URL is "about:blank", then it is very likely that the opener just
    // wants to synchronously script the popup, for example:
    //
    //     let popup = window.open()
    //     popup.document.body.write('
hello
')
    //
    // The above code would not work if a navigation to "about:blank" is done
    // here, since the window would be cleared of all changes in the next tick.
    const loadOptions = {}
    if (postData != null) {
      loadOptions.postData = postData
      loadOptions.extraHeaders = 'content-type: application/x-www-form-urlencoded'
      if (postData.length > 0) {
        const postDataFront = postData[0].bytes.toString()
        const boundary = /^--.*[^-\r\n]/.exec(postDataFront)
        if (boundary != null) {
          loadOptions.extraHeaders = `content-type: multipart/form-data; boundary=${boundary[0].substr(2)}`
        }
      }
    }
    guest.loadURL(url, loadOptions)
  }

  return setupGuest(embedder, frameName, guest, options)
}

const getGuestWindow = function (guestId) {
  const guestContents = webContents.fromId(guestId)
  if (guestContents == null) return

  let guestWindow = BrowserWindow.fromWebContents(guestContents)
  if (guestWindow == null) {
    const hostContents = guestContents.hostWebContents
    if (hostContents != null) {
      guestWindow = BrowserWindow.fromWebContents(hostContents)
    }
  }
  return guestWindow
}

// Checks whether |sender| can access the |target|:
// 1. Check whether |sender| is the parent of |target|.
// 2. Check whether |sender| has node integration, if so it is allowed to
//    do anything it wants.
// 3. Check whether the origins match.
//
// However it allows a child window without node integration but with same
// origin to do anything it wants, when its opener window has node integration.
// The W3C does not have anything on this, but from my understanding of the
// security model of |window.opener|, this should be fine.
const canAccessWindow = function (sender, target) {
  return (target.getWebPreferences().openerId === sender.webContents.id) ||
         (sender.getWebPreferences().nodeIntegration === true) ||
         isSameOrigin(sender.getURL(), target.getURL())
}

// Routed window.open messages.
ipcMain.on('ELECTRON_GUEST_WINDOW_MANAGER_WINDOW_OPEN', function (event, url, frameName,
                                                                  disposition, options,
                                                                  additionalFeatures, postData) {
  options = mergeBrowserWindowOptions(event.sender, options)
  event.sender.emit('new-window', event, url, frameName, disposition, options, additionalFeatures)
  const newGuest = event.newGuest
  if ((event.sender.isGuest() && !event.sender.allowPopups) || event.defaultPrevented) {
    if (newGuest !== undefined && newGuest !== null) {
      event.returnValue = setupGuest(event.sender, frameName, newGuest, options)
    } else {
      event.returnValue = null
    }
  } else {
    event.returnValue = createGuest(event.sender, url, frameName, options, postData)
  }
})

ipcMain.on('ELECTRON_GUEST_WINDOW_MANAGER_WINDOW_CLOSE', function (event, guestId) {
  const guestWindow = getGuestWindow(guestId)
  if (guestWindow != null && canAccessWindow(event.sender, guestWindow.webContents)) {
    guestWindow.destroy()
  } else {
    console.error(`Blocked ${event.sender.getURL()} from closing its opener.`)
  }
})

ipcMain.on('ELECTRON_GUEST_WINDOW_MANAGER_WINDOW_METHOD', function (event, guestId, method, ...args) {
  const guestWindow = getGuestWindow(guestId)
  if (guestWindow != null && canAccessWindow(event.sender, guestWindow.webContents)) {
    event.returnValue = guestWindow[method].apply(guestWindow, args)
  } else {
    console.error(`Blocked ${event.sender.getURL()} from calling ${method} of its opener.`)
    event.returnValue = null
  }
})

ipcMain.on('ELECTRON_GUEST_WINDOW_MANAGER_WINDOW_POSTMESSAGE', function (event, guestId, message, targetOrigin, sourceOrigin) {
  const guestContents = webContents.fromId(guestId)
  if (guestContents == null) return

  // The W3C does not seem to have word on how postMessage should work when the
  // origins do not match, so we do not do |canAccessWindow| check here since
  // postMessage across origins is usefull and not harmful.
  if (guestContents.getURL().indexOf(targetOrigin) === 0 || targetOrigin === '*') {
    const sourceId = event.sender.id
    guestContents.send('ELECTRON_GUEST_WINDOW_POSTMESSAGE', sourceId, message, sourceOrigin)
  }
})

ipcMain.on('ELECTRON_GUEST_WINDOW_MANAGER_WEB_CONTENTS_METHOD', function (event, guestId, method, ...args) {
  const guestContents = webContents.fromId(guestId)
  if (guestContents != null && canAccessWindow(event.sender, guestContents)) {
    guestContents[method].apply(guestContents, args)
  } else {
    console.error(`Blocked ${event.sender.getURL()} from calling ${method} of its opener.`)
  }
})
<commit_msg>Don't log blocked messages when guestWindow is null<commit_after>'use strict'

const {BrowserWindow, ipcMain, webContents} = require('electron')
const {isSameOrigin} = process.atomBinding('v8_util')

const hasProp = {}.hasOwnProperty
const frameToGuest = {}

// Copy attribute of |parent| to |child| if it is not defined in |child|.
const mergeOptions = function (child, parent) {
  let key, value
  for (key in parent) {
    if (!hasProp.call(parent, key)) continue
    value = parent[key]
    if (!(key in child)) {
      if (typeof value === 'object') {
        child[key] = mergeOptions({}, value)
      } else {
        child[key] = value
      }
    }
  }
  return child
}

// Merge |options| with the |embedder|'s window's options.
const mergeBrowserWindowOptions = function (embedder, options) {
  if (options.webPreferences == null) {
    options.webPreferences = {}
  }
  if (embedder.browserWindowOptions != null) {
    // Inherit the original options if it is a BrowserWindow.
    mergeOptions(options, embedder.browserWindowOptions)
  } else {
    // Or only inherit web-preferences if it is a webview.
    mergeOptions(options.webPreferences, embedder.getWebPreferences())
  }

  // Disable node integration on child window if disabled on parent window
  if (embedder.getWebPreferences().nodeIntegration === false) {
    options.webPreferences.nodeIntegration = false
  }

  // Sets correct openerId here to give correct options to 'new-window' event handler
  options.webPreferences.openerId = embedder.id

  return options
}

// Setup a new guest with |embedder|
const setupGuest = function (embedder, frameName, guest, options) {
  // When |embedder| is destroyed we should also destroy attached guest, and if
  // guest is closed by user then we should prevent |embedder| from double
  // closing guest.
  const guestId = guest.webContents.id
  const closedByEmbedder = function () {
    guest.removeListener('closed', closedByUser)
    guest.destroy()
  }
  const closedByUser = function () {
    embedder.send('ELECTRON_GUEST_WINDOW_MANAGER_WINDOW_CLOSED_' + guestId)
    embedder.removeListener('render-view-deleted', closedByEmbedder)
  }
  if (!options.webPreferences.sandbox) {
    // These events should only be handled when the guest window is opened by a
    // non-sandboxed renderer for two reasons:
    //
    // - `render-view-deleted` is emitted when the popup is closed by the user,
    //   and that will eventually result in NativeWindow::NotifyWindowClosed
    //   using a dangling pointer since `destroy()` would have been called by
    //   `closeByEmbedded`
    // - No need to emit `ELECTRON_GUEST_WINDOW_MANAGER_WINDOW_CLOSED_` since
    //   there's no renderer code listening to it.,
    embedder.once('render-view-deleted', closedByEmbedder)
    guest.once('closed', closedByUser)
  }
  if (frameName) {
    frameToGuest[frameName] = guest
    guest.frameName = frameName
    guest.once('closed', function () {
      delete frameToGuest[frameName]
    })
  }
  return guestId
}

// Create a new guest created by |embedder| with |options|.
const createGuest = function (embedder, url, frameName, options, postData) {
  let guest = frameToGuest[frameName]
  if (frameName && (guest != null)) {
    guest.loadURL(url)
    return guest.webContents.id
  }

  // Remember the embedder window's id.
  if (options.webPreferences == null) {
    options.webPreferences = {}
  }

  guest = new BrowserWindow(options)
  if (!options.webContents || url !== 'about:blank') {
    // We should not call `loadURL` if the window was constructed from an
    // existing webContents(window.open in a sandboxed renderer) and if the url
    // is not 'about:blank'.
    //
    // Navigating to the url when creating the window from an existing
    // webContents would not be necessary(it will navigate there anyway), but
    // apparently there's a bug that allows the child window to be scripted by
    // the opener, even when the child window is from another origin.
    //
    // That's why the second condition(url !== "about:blank") is required: to
    // force `OverrideSiteInstanceForNavigation` to be called and consequently
    // spawn a new renderer if the new window is targeting a different origin.
    //
    // If the URL is "about:blank", then it is very likely that the opener just
    // wants to synchronously script the popup, for example:
    //
    //     let popup = window.open()
    //     popup.document.body.write('
hello
')
    //
    // The above code would not work if a navigation to "about:blank" is done
    // here, since the window would be cleared of all changes in the next tick.
    const loadOptions = {}
    if (postData != null) {
      loadOptions.postData = postData
      loadOptions.extraHeaders = 'content-type: application/x-www-form-urlencoded'
      if (postData.length > 0) {
        const postDataFront = postData[0].bytes.toString()
        const boundary = /^--.*[^-\r\n]/.exec(postDataFront)
        if (boundary != null) {
          loadOptions.extraHeaders = `content-type: multipart/form-data; boundary=${boundary[0].substr(2)}`
        }
      }
    }
    guest.loadURL(url, loadOptions)
  }

  return setupGuest(embedder, frameName, guest, options)
}

const getGuestWindow = function (guestId) {
  const guestContents = webContents.fromId(guestId)
  if (guestContents == null) return

  let guestWindow = BrowserWindow.fromWebContents(guestContents)
  if (guestWindow == null) {
    const hostContents = guestContents.hostWebContents
    if (hostContents != null) {
      guestWindow = BrowserWindow.fromWebContents(hostContents)
    }
  }
  return guestWindow
}

// Checks whether |sender| can access the |target|:
// 1. Check whether |sender| is the parent of |target|.
// 2. Check whether |sender| has node integration, if so it is allowed to
//    do anything it wants.
// 3. Check whether the origins match.
//
// However it allows a child window without node integration but with same
// origin to do anything it wants, when its opener window has node integration.
// The W3C does not have anything on this, but from my understanding of the
// security model of |window.opener|, this should be fine.
const canAccessWindow = function (sender, target) {
  return (target.getWebPreferences().openerId === sender.webContents.id) ||
         (sender.getWebPreferences().nodeIntegration === true) ||
         isSameOrigin(sender.getURL(), target.getURL())
}

// Routed window.open messages.
ipcMain.on('ELECTRON_GUEST_WINDOW_MANAGER_WINDOW_OPEN', function (event, url, frameName,
                                                                  disposition, options,
                                                                  additionalFeatures, postData) {
  options = mergeBrowserWindowOptions(event.sender, options)
  event.sender.emit('new-window', event, url, frameName, disposition, options, additionalFeatures)
  const newGuest = event.newGuest
  if ((event.sender.isGuest() && !event.sender.allowPopups) || event.defaultPrevented) {
    if (newGuest !== undefined && newGuest !== null) {
      event.returnValue = setupGuest(event.sender, frameName, newGuest, options)
    } else {
      event.returnValue = null
    }
  } else {
    event.returnValue = createGuest(event.sender, url, frameName, options, postData)
  }
})

ipcMain.on('ELECTRON_GUEST_WINDOW_MANAGER_WINDOW_CLOSE', function (event, guestId) {
  const guestWindow = getGuestWindow(guestId)
  if (guestWindow == null) return

  if (canAccessWindow(event.sender, guestWindow.webContents)) {
    guestWindow.destroy()
  } else {
    console.error(`Blocked ${event.sender.getURL()} from closing its opener.`)
  }
})

ipcMain.on('ELECTRON_GUEST_WINDOW_MANAGER_WINDOW_METHOD', function (event, guestId, method, ...args) {
  const guestWindow = getGuestWindow(guestId)
  if (guestWindow == null) {
    event.returnValue = null
    return
  }

  if (canAccessWindow(event.sender, guestWindow.webContents)) {
    event.returnValue = guestWindow[method].apply(guestWindow, args)
  } else {
    console.error(`Blocked ${event.sender.getURL()} from calling ${method} on its opener.`)
    event.returnValue = null
  }
})

ipcMain.on('ELECTRON_GUEST_WINDOW_MANAGER_WINDOW_POSTMESSAGE', function (event, guestId, message, targetOrigin, sourceOrigin) {
  const guestContents = webContents.fromId(guestId)
  if (guestContents == null) return

  // The W3C does not seem to have word on how postMessage should work when the
  // origins do not match, so we do not do |canAccessWindow| check here since
  // postMessage across origins is useful and not harmful.
  if (guestContents.getURL().indexOf(targetOrigin) === 0 || targetOrigin === '*') {
    const sourceId = event.sender.id
    guestContents.send('ELECTRON_GUEST_WINDOW_POSTMESSAGE', sourceId, message, sourceOrigin)
  }
})

ipcMain.on('ELECTRON_GUEST_WINDOW_MANAGER_WEB_CONTENTS_METHOD', function (event, guestId, method, ...args) {
  const guestContents = webContents.fromId(guestId)
  if (guestContents == null) return

  if (canAccessWindow(event.sender, guestContents)) {
    guestContents[method].apply(guestContents, args)
  } else {
    console.error(`Blocked ${event.sender.getURL()} from calling ${method} on its opener.`)
  }
})
<|endoftext|>


<commit_before>'use strict'

const {BrowserWindow, ipcMain, webContents} = require('electron')
const {isSameOrigin} = process.atomBinding('v8_util')
const parseFeaturesString = require('../common/parse-features-string')

const hasProp = {}.hasOwnProperty
const frameToGuest = {}

// Copy attribute of |parent| to |child| if it is not defined in |child|.
const mergeOptions = function (child, parent, visited) {
  // Check for circular reference.
  if (visited == null) visited = new Set()
  if (visited.has(parent)) return

  visited.add(parent)
  for (const key in parent) {
    if (!hasProp.call(parent, key)) continue
    if (key in child) continue

    const value = parent[key]
    if (typeof value === 'object') {
      child[key] = mergeOptions({}, value, visited)
    } else {
      child[key] = value
    }
  }
  visited.delete(parent)

  return child
}

// Merge |options| with the |embedder|'s window's options.
const mergeBrowserWindowOptions = function (embedder, options) {
  if (options.webPreferences == null) {
    options.webPreferences = {}
  }
  if (embedder.browserWindowOptions != null) {
    // Inherit the original options if it is a BrowserWindow.
    mergeOptions(options, embedder.browserWindowOptions)
  } else {
    // Or only inherit web-preferences if it is a webview.
    mergeOptions(options.webPreferences, embedder.getWebPreferences())
  }

  // Disable node integration on child window if disabled on parent window
  if (embedder.getWebPreferences().nodeIntegration === false) {
    options.webPreferences.nodeIntegration = false
  }

  // Enable context isolation on child window if enable on parent window
  if (embedder.getWebPreferences().contextIsolation === true) {
    options.webPreferences.contextIsolation = true
  }

  // Sets correct openerId here to give correct options to 'new-window' event handler
  options.webPreferences.openerId = embedder.id

  return options
}

// Setup a new guest with |embedder|
const setupGuest = function (embedder, frameName, guest, options) {
  // When |embedder| is destroyed we should also destroy attached guest, and if
  // guest is closed by user then we should prevent |embedder| from double
  // closing guest.
  const guestId = guest.webContents.id
  const closedByEmbedder = function () {
    guest.removeListener('closed', closedByUser)
    guest.destroy()
  }
  const closedByUser = function () {
    embedder.send('ELECTRON_GUEST_WINDOW_MANAGER_WINDOW_CLOSED_' + guestId)
    embedder.removeListener('render-view-deleted', closedByEmbedder)
  }
  if (!options.webPreferences.sandbox) {
    // These events should only be handled when the guest window is opened by a
    // non-sandboxed renderer for two reasons:
    //
    // - `render-view-deleted` is emitted when the popup is closed by the user,
    //   and that will eventually result in NativeWindow::NotifyWindowClosed
    //   using a dangling pointer since `destroy()` would have been called by
    //   `closeByEmbedded`
    // - No need to emit `ELECTRON_GUEST_WINDOW_MANAGER_WINDOW_CLOSED_` since
    //   there's no renderer code listening to it.,
    embedder.once('render-view-deleted', closedByEmbedder)
    guest.once('closed', closedByUser)
  }
  if (frameName) {
    frameToGuest[frameName] = guest
    guest.frameName = frameName
    guest.once('closed', function () {
      delete frameToGuest[frameName]
    })
  }
  return guestId
}

// Create a new guest created by |embedder| with |options|.
const createGuest = function (embedder, url, frameName, options, postData) {
  let guest = frameToGuest[frameName]
  if (frameName && (guest != null)) {
    guest.loadURL(url)
    return guest.webContents.id
  }

  // Remember the embedder window's id.
  if (options.webPreferences == null) {
    options.webPreferences = {}
  }

  guest = new BrowserWindow(options)
  if (!options.webContents || url !== 'about:blank') {
    // We should not call `loadURL` if the window was constructed from an
    // existing webContents(window.open in a sandboxed renderer) and if the url
    // is not 'about:blank'.
    //
    // Navigating to the url when creating the window from an existing
    // webContents would not be necessary(it will navigate there anyway), but
    // apparently there's a bug that allows the child window to be scripted by
    // the opener, even when the child window is from another origin.
    //
    // That's why the second condition(url !== "about:blank") is required: to
    // force `OverrideSiteInstanceForNavigation` to be called and consequently
    // spawn a new renderer if the new window is targeting a different origin.
    //
    // If the URL is "about:blank", then it is very likely that the opener just
    // wants to synchronously script the popup, for example:
    //
    //     let popup = window.open()
    //     popup.document.body.write('
hello
')
    //
    // The above code would not work if a navigation to "about:blank" is done
    // here, since the window would be cleared of all changes in the next tick.
    const loadOptions = {}
    if (postData != null) {
      loadOptions.postData = postData
      loadOptions.extraHeaders = 'content-type: application/x-www-form-urlencoded'
      if (postData.length > 0) {
        const postDataFront = postData[0].bytes.toString()
        const boundary = /^--.*[^-\r\n]/.exec(postDataFront)
        if (boundary != null) {
          loadOptions.extraHeaders = `content-type: multipart/form-data; boundary=${boundary[0].substr(2)}`
        }
      }
    }
    guest.loadURL(url, loadOptions)
  }

  return setupGuest(embedder, frameName, guest, options)
}

const getGuestWindow = function (guestContents) {
  let guestWindow = BrowserWindow.fromWebContents(guestContents)
  if (guestWindow == null) {
    const hostContents = guestContents.hostWebContents
    if (hostContents != null) {
      guestWindow = BrowserWindow.fromWebContents(hostContents)
    }
  }
  return guestWindow
}

// Checks whether |sender| can access the |target|:
// 1. Check whether |sender| is the parent of |target|.
// 2. Check whether |sender| has node integration, if so it is allowed to
//    do anything it wants.
// 3. Check whether the origins match.
//
// However it allows a child window without node integration but with same
// origin to do anything it wants, when its opener window has node integration.
// The W3C does not have anything on this, but from my understanding of the
// security model of |window.opener|, this should be fine.
const canAccessWindow = function (sender, target) {
  return (target.getWebPreferences().openerId === sender.id) ||
         (sender.getWebPreferences().nodeIntegration === true) ||
         isSameOrigin(sender.getURL(), target.getURL())
}

// Routed window.open messages with raw options
ipcMain.on('ELECTRON_GUEST_WINDOW_MANAGER_WINDOW_OPEN', (event, url, frameName, features) => {
  if (url == null || url === '') url = 'about:blank'
  if (frameName == null) frameName = ''
  if (features == null) features = ''

  const options = {}

  const ints = ['x', 'y', 'width', 'height', 'minWidth', 'maxWidth', 'minHeight', 'maxHeight', 'zoomFactor']
  const webPreferences = ['zoomFactor', 'nodeIntegration', 'preload']
  const disposition = 'new-window'

  // Used to store additional features
  const additionalFeatures = []

  // Parse the features
  parseFeaturesString(features, function (key, value) {
    if (value === undefined) {
      additionalFeatures.push(key)
    } else {
      if (webPreferences.includes(key)) {
        if (options.webPreferences == null) {
          options.webPreferences = {}
        }
        options.webPreferences[key] = value
      } else {
        options[key] = value
      }
    }
  })
  if (options.left) {
    if (options.x == null) {
      options.x = options.left
    }
  }
  if (options.top) {
    if (options.y == null) {
      options.y = options.top
    }
  }
  if (options.title == null) {
    options.title = frameName
  }
  if (options.width == null) {
    options.width = 800
  }
  if (options.height == null) {
    options.height = 600
  }

  for (const name of ints) {
    if (options[name] != null) {
      options[name] = parseInt(options[name], 10)
    }
  }

  ipcMain.emit('ELECTRON_GUEST_WINDOW_MANAGER_INTERNAL_WINDOW_OPEN', event,
               url, frameName, disposition, options, additionalFeatures)
})

// Routed window.open messages with fully parsed options
ipcMain.on('ELECTRON_GUEST_WINDOW_MANAGER_INTERNAL_WINDOW_OPEN', function (event, url, frameName,
                                                                  disposition, options,
                                                                  additionalFeatures, postData) {
  options = mergeBrowserWindowOptions(event.sender, options)
  event.sender.emit('new-window', event, url, frameName, disposition, options, additionalFeatures)
  const newGuest = event.newGuest
  if ((event.sender.isGuest() && !event.sender.allowPopups) || event.defaultPrevented) {
    if (newGuest !== undefined && newGuest !== null) {
      if (options.webContents === newGuest.webContents) {
        event.defaultPrevented = false
      }
      event.returnValue = setupGuest(event.sender, frameName, newGuest, options)
    } else {
      event.returnValue = null
    }
  } else {
    event.returnValue = createGuest(event.sender, url, frameName, options, postData)
  }
})

ipcMain.on('ELECTRON_GUEST_WINDOW_MANAGER_WINDOW_CLOSE', function (event, guestId) {
  const guestContents = webContents.fromId(guestId)
  if (guestContents == null) return

  if (!canAccessWindow(event.sender, guestContents)) {
    console.error(`Blocked ${event.sender.getURL()} from closing its opener.`)
    return
  }

  const guestWindow = getGuestWindow(guestContents)
  if (guestWindow != null) guestWindow.destroy()
})

ipcMain.on('ELECTRON_GUEST_WINDOW_MANAGER_WINDOW_METHOD', function (event, guestId, method, ...args) {
  const guestContents = webContents.fromId(guestId)
  if (guestContents == null) {
    event.returnValue = null
    return
  }

  if (!canAccessWindow(event.sender, guestContents)) {
    console.error(`Blocked ${event.sender.getURL()} from calling ${method} on its opener.`)
    event.returnValue = null
    return
  }

  const guestWindow = getGuestWindow(guestContents)
  if (guestWindow != null) {
    event.returnValue = guestWindow[method](...args)
  } else {
    event.returnValue = null
  }
})

ipcMain.on('ELECTRON_GUEST_WINDOW_MANAGER_WINDOW_POSTMESSAGE', function (event, guestId, message, targetOrigin, sourceOrigin) {
  if (targetOrigin == null) {
    targetOrigin = '*'
  }

  const guestContents = webContents.fromId(guestId)
  if (guestContents == null) return

  // The W3C does not seem to have word on how postMessage should work when the
  // origins do not match, so we do not do |canAccessWindow| check here since
  // postMessage across origins is useful and not harmful.
  if (guestContents.getURL().indexOf(targetOrigin) === 0 || targetOrigin === '*') {
    const sourceId = event.sender.id
    guestContents.send('ELECTRON_GUEST_WINDOW_POSTMESSAGE', sourceId, message, sourceOrigin)
  }
})

ipcMain.on('ELECTRON_GUEST_WINDOW_MANAGER_WEB_CONTENTS_METHOD', function (event, guestId, method, ...args) {
  const guestContents = webContents.fromId(guestId)
  if (guestContents == null) return

  if (canAccessWindow(event.sender, guestContents)) {
    guestContents[method](...args)
  } else {
    console.error(`Blocked ${event.sender.getURL()} from calling ${method} on its opener.`)
  }
})

ipcMain.on('ELECTRON_GUEST_WINDOW_MANAGER_WEB_CONTENTS_METHOD_SYNC', function (event, guestId, method, ...args) {
  const guestContents = webContents.fromId(guestId)
  if (guestContents == null) {
    event.returnValue = null
    return
  }

  if (canAccessWindow(event.sender, guestContents)) {
    event.returnValue = guestContents[method](...args)
  } else {
    console.error(`Blocked ${event.sender.getURL()} from calling ${method} on its opener.`)
    event.returnValue = null
  }
})
<commit_msg>Add comment why defaultPrevented = false<commit_after>'use strict'

const {BrowserWindow, ipcMain, webContents} = require('electron')
const {isSameOrigin} = process.atomBinding('v8_util')
const parseFeaturesString = require('../common/parse-features-string')

const hasProp = {}.hasOwnProperty
const frameToGuest = {}

// Copy attribute of |parent| to |child| if it is not defined in |child|.
const mergeOptions = function (child, parent, visited) {
  // Check for circular reference.
  if (visited == null) visited = new Set()
  if (visited.has(parent)) return

  visited.add(parent)
  for (const key in parent) {
    if (!hasProp.call(parent, key)) continue
    if (key in child) continue

    const value = parent[key]
    if (typeof value === 'object') {
      child[key] = mergeOptions({}, value, visited)
    } else {
      child[key] = value
    }
  }
  visited.delete(parent)

  return child
}

// Merge |options| with the |embedder|'s window's options.
const mergeBrowserWindowOptions = function (embedder, options) {
  if (options.webPreferences == null) {
    options.webPreferences = {}
  }
  if (embedder.browserWindowOptions != null) {
    // Inherit the original options if it is a BrowserWindow.
    mergeOptions(options, embedder.browserWindowOptions)
  } else {
    // Or only inherit web-preferences if it is a webview.
    mergeOptions(options.webPreferences, embedder.getWebPreferences())
  }

  // Disable node integration on child window if disabled on parent window
  if (embedder.getWebPreferences().nodeIntegration === false) {
    options.webPreferences.nodeIntegration = false
  }

  // Enable context isolation on child window if enable on parent window
  if (embedder.getWebPreferences().contextIsolation === true) {
    options.webPreferences.contextIsolation = true
  }

  // Sets correct openerId here to give correct options to 'new-window' event handler
  options.webPreferences.openerId = embedder.id

  return options
}

// Setup a new guest with |embedder|
const setupGuest = function (embedder, frameName, guest, options) {
  // When |embedder| is destroyed we should also destroy attached guest, and if
  // guest is closed by user then we should prevent |embedder| from double
  // closing guest.
  const guestId = guest.webContents.id
  const closedByEmbedder = function () {
    guest.removeListener('closed', closedByUser)
    guest.destroy()
  }
  const closedByUser = function () {
    embedder.send('ELECTRON_GUEST_WINDOW_MANAGER_WINDOW_CLOSED_' + guestId)
    embedder.removeListener('render-view-deleted', closedByEmbedder)
  }
  if (!options.webPreferences.sandbox) {
    // These events should only be handled when the guest window is opened by a
    // non-sandboxed renderer for two reasons:
    //
    // - `render-view-deleted` is emitted when the popup is closed by the user,
    //   and that will eventually result in NativeWindow::NotifyWindowClosed
    //   using a dangling pointer since `destroy()` would have been called by
    //   `closeByEmbedded`
    // - No need to emit `ELECTRON_GUEST_WINDOW_MANAGER_WINDOW_CLOSED_` since
    //   there's no renderer code listening to it.,
    embedder.once('render-view-deleted', closedByEmbedder)
    guest.once('closed', closedByUser)
  }
  if (frameName) {
    frameToGuest[frameName] = guest
    guest.frameName = frameName
    guest.once('closed', function () {
      delete frameToGuest[frameName]
    })
  }
  return guestId
}

// Create a new guest created by |embedder| with |options|.
const createGuest = function (embedder, url, frameName, options, postData) {
  let guest = frameToGuest[frameName]
  if (frameName && (guest != null)) {
    guest.loadURL(url)
    return guest.webContents.id
  }

  // Remember the embedder window's id.
  if (options.webPreferences == null) {
    options.webPreferences = {}
  }

  guest = new BrowserWindow(options)
  if (!options.webContents || url !== 'about:blank') {
    // We should not call `loadURL` if the window was constructed from an
    // existing webContents(window.open in a sandboxed renderer) and if the url
    // is not 'about:blank'.
    //
    // Navigating to the url when creating the window from an existing
    // webContents would not be necessary(it will navigate there anyway), but
    // apparently there's a bug that allows the child window to be scripted by
    // the opener, even when the child window is from another origin.
    //
    // That's why the second condition(url !== "about:blank") is required: to
    // force `OverrideSiteInstanceForNavigation` to be called and consequently
    // spawn a new renderer if the new window is targeting a different origin.
    //
    // If the URL is "about:blank", then it is very likely that the opener just
    // wants to synchronously script the popup, for example:
    //
    //     let popup = window.open()
    //     popup.document.body.write('
hello
')
    //
    // The above code would not work if a navigation to "about:blank" is done
    // here, since the window would be cleared of all changes in the next tick.
    const loadOptions = {}
    if (postData != null) {
      loadOptions.postData = postData
      loadOptions.extraHeaders = 'content-type: application/x-www-form-urlencoded'
      if (postData.length > 0) {
        const postDataFront = postData[0].bytes.toString()
        const boundary = /^--.*[^-\r\n]/.exec(postDataFront)
        if (boundary != null) {
          loadOptions.extraHeaders = `content-type: multipart/form-data; boundary=${boundary[0].substr(2)}`
        }
      }
    }
    guest.loadURL(url, loadOptions)
  }

  return setupGuest(embedder, frameName, guest, options)
}

const getGuestWindow = function (guestContents) {
  let guestWindow = BrowserWindow.fromWebContents(guestContents)
  if (guestWindow == null) {
    const hostContents = guestContents.hostWebContents
    if (hostContents != null) {
      guestWindow = BrowserWindow.fromWebContents(hostContents)
    }
  }
  return guestWindow
}

// Checks whether |sender| can access the |target|:
// 1. Check whether |sender| is the parent of |target|.
// 2. Check whether |sender| has node integration, if so it is allowed to
//    do anything it wants.
// 3. Check whether the origins match.
//
// However it allows a child window without node integration but with same
// origin to do anything it wants, when its opener window has node integration.
// The W3C does not have anything on this, but from my understanding of the
// security model of |window.opener|, this should be fine.
const canAccessWindow = function (sender, target) {
  return (target.getWebPreferences().openerId === sender.id) ||
         (sender.getWebPreferences().nodeIntegration === true) ||
         isSameOrigin(sender.getURL(), target.getURL())
}

// Routed window.open messages with raw options
ipcMain.on('ELECTRON_GUEST_WINDOW_MANAGER_WINDOW_OPEN', (event, url, frameName, features) => {
  if (url == null || url === '') url = 'about:blank'
  if (frameName == null) frameName = ''
  if (features == null) features = ''

  const options = {}

  const ints = ['x', 'y', 'width', 'height', 'minWidth', 'maxWidth', 'minHeight', 'maxHeight', 'zoomFactor']
  const webPreferences = ['zoomFactor', 'nodeIntegration', 'preload']
  const disposition = 'new-window'

  // Used to store additional features
  const additionalFeatures = []

  // Parse the features
  parseFeaturesString(features, function (key, value) {
    if (value === undefined) {
      additionalFeatures.push(key)
    } else {
      if (webPreferences.includes(key)) {
        if (options.webPreferences == null) {
          options.webPreferences = {}
        }
        options.webPreferences[key] = value
      } else {
        options[key] = value
      }
    }
  })
  if (options.left) {
    if (options.x == null) {
      options.x = options.left
    }
  }
  if (options.top) {
    if (options.y == null) {
      options.y = options.top
    }
  }
  if (options.title == null) {
    options.title = frameName
  }
  if (options.width == null) {
    options.width = 800
  }
  if (options.height == null) {
    options.height = 600
  }

  for (const name of ints) {
    if (options[name] != null) {
      options[name] = parseInt(options[name], 10)
    }
  }

  ipcMain.emit('ELECTRON_GUEST_WINDOW_MANAGER_INTERNAL_WINDOW_OPEN', event,
               url, frameName, disposition, options, additionalFeatures)
})

// Routed window.open messages with fully parsed options
ipcMain.on('ELECTRON_GUEST_WINDOW_MANAGER_INTERNAL_WINDOW_OPEN', function (event, url, frameName,
                                                                  disposition, options,
                                                                  additionalFeatures, postData) {
  options = mergeBrowserWindowOptions(event.sender, options)
  event.sender.emit('new-window', event, url, frameName, disposition, options, additionalFeatures)
  const newGuest = event.newGuest
  if ((event.sender.isGuest() && !event.sender.allowPopups) || event.defaultPrevented) {
    if (newGuest !== undefined && newGuest !== null) {
      if (options.webContents === newGuest.webContents) {
        // the webContents is not changed, so set defaultPrevented to false to
        // stop the callers of this event from destroying the webContents.
        event.defaultPrevented = false
      }
      event.returnValue = setupGuest(event.sender, frameName, newGuest, options)
    } else {
      event.returnValue = null
    }
  } else {
    event.returnValue = createGuest(event.sender, url, frameName, options, postData)
  }
})

ipcMain.on('ELECTRON_GUEST_WINDOW_MANAGER_WINDOW_CLOSE', function (event, guestId) {
  const guestContents = webContents.fromId(guestId)
  if (guestContents == null) return

  if (!canAccessWindow(event.sender, guestContents)) {
    console.error(`Blocked ${event.sender.getURL()} from closing its opener.`)
    return
  }

  const guestWindow = getGuestWindow(guestContents)
  if (guestWindow != null) guestWindow.destroy()
})

ipcMain.on('ELECTRON_GUEST_WINDOW_MANAGER_WINDOW_METHOD', function (event, guestId, method, ...args) {
  const guestContents = webContents.fromId(guestId)
  if (guestContents == null) {
    event.returnValue = null
    return
  }

  if (!canAccessWindow(event.sender, guestContents)) {
    console.error(`Blocked ${event.sender.getURL()} from calling ${method} on its opener.`)
    event.returnValue = null
    return
  }

  const guestWindow = getGuestWindow(guestContents)
  if (guestWindow != null) {
    event.returnValue = guestWindow[method](...args)
  } else {
    event.returnValue = null
  }
})

ipcMain.on('ELECTRON_GUEST_WINDOW_MANAGER_WINDOW_POSTMESSAGE', function (event, guestId, message, targetOrigin, sourceOrigin) {
  if (targetOrigin == null) {
    targetOrigin = '*'
  }

  const guestContents = webContents.fromId(guestId)
  if (guestContents == null) return

  // The W3C does not seem to have word on how postMessage should work when the
  // origins do not match, so we do not do |canAccessWindow| check here since
  // postMessage across origins is useful and not harmful.
  if (guestContents.getURL().indexOf(targetOrigin) === 0 || targetOrigin === '*') {
    const sourceId = event.sender.id
    guestContents.send('ELECTRON_GUEST_WINDOW_POSTMESSAGE', sourceId, message, sourceOrigin)
  }
})

ipcMain.on('ELECTRON_GUEST_WINDOW_MANAGER_WEB_CONTENTS_METHOD', function (event, guestId, method, ...args) {
  const guestContents = webContents.fromId(guestId)
  if (guestContents == null) return

  if (canAccessWindow(event.sender, guestContents)) {
    guestContents[method](...args)
  } else {
    console.error(`Blocked ${event.sender.getURL()} from calling ${method} on its opener.`)
  }
})

ipcMain.on('ELECTRON_GUEST_WINDOW_MANAGER_WEB_CONTENTS_METHOD_SYNC', function (event, guestId, method, ...args) {
  const guestContents = webContents.fromId(guestId)
  if (guestContents == null) {
    event.returnValue = null
    return
  }

  if (canAccessWindow(event.sender, guestContents)) {
    event.returnValue = guestContents[method](...args)
  } else {
    console.error(`Blocked ${event.sender.getURL()} from calling ${method} on its opener.`)
    event.returnValue = null
  }
})
<|endoftext|>


<commit_before>/**
 * @module ol/Observable
 */
import EventTarget from './events/Target.js';
import EventType from './events/EventType.js';
import {listen, listenOnce, unlistenByKey} from './events.js';

/***
 * @template {string} Type
 * @template {Event|import("./events/Event.js").default} EventClass
 * @typedef {(type: Type|Type[], listener: (event: EventClass) => (void|boolean)) => import("./events.js").EventsKey|Array<import("./events.js").EventsKey>} OnSignature
 */

/**
 * @typedef {'change'|'error'} EventTypes
 */

/**
 * @classdesc
 * Abstract base class; normally only used for creating subclasses and not
 * instantiated in apps.
 * An event target providing convenient methods for listener registration
 * and unregistration. A generic `change` event is always available through
 * {@link module:ol/Observable~Observable#changed}.
 *
 * @fires import("./events/Event.js").default
 * @api
 */
class Observable extends EventTarget {
  constructor() {
    super();

    /**
     * @private
     * @type {number}
     */
    this.revision_ = 0;
  }

  /**
   * Increases the revision counter and dispatches a 'change' event.
   * @api
   */
  changed() {
    ++this.revision_;
    this.dispatchEvent(EventType.CHANGE);
  }

  /**
   * Get the version number for this object.  Each time the object is modified,
   * its version number will be incremented.
   * @return {number} Revision.
   * @api
   */
  getRevision() {
    return this.revision_;
  }

  /**
   * Listen for a certain type of event.
   * @param {string|Array<string>} type The event type or array of event types.
   * @param {function(?): ?} listener The listener function.
   * @return {import("./events.js").EventsKey|Array<import("./events.js").EventsKey>} Unique key for the listener. If
   *     called with an array of event types as the first argument, the return
   *     will be an array of keys.
   * @api
   */
  on(type, listener) {
    if (Array.isArray(type)) {
      const len = type.length;
      const keys = new Array(len);
      for (let i = 0; i < len; ++i) {
        keys[i] = listen(this, type[i], listener);
      }
      return keys;
    } else {
      return listen(this, /** @type {string} */ (type), listener);
    }
  }

  /**
   * Listen once for a certain type of event.
   * @param {string|Array<string>} type The event type or array of event types.
   * @param {function(?): ?} listener The listener function.
   * @return {import("./events.js").EventsKey|Array<import("./events.js").EventsKey>} Unique key for the listener. If
   *     called with an array of event types as the first argument, the return
   *     will be an array of keys.
   * @api
   */
  once(type, listener) {
    let key;
    if (Array.isArray(type)) {
      const len = type.length;
      key = new Array(len);
      for (let i = 0; i < len; ++i) {
        key[i] = listenOnce(this, type[i], listener);
      }
    } else {
      key = listenOnce(this, /** @type {string} */ (type), listener);
    }
    /** @type {Object} */ (listener).ol_key = key;
    return key;
  }

  /**
   * Unlisten for a certain type of event.
   * @param {string|Array<string>} type The event type or array of event types.
   * @param {function(?): ?} listener The listener function.
   * @api
   */
  un(type, listener) {
    const key = /** @type {Object} */ (listener).ol_key;
    if (key) {
      unByKey(key);
    } else if (Array.isArray(type)) {
      for (let i = 0, ii = type.length; i < ii; ++i) {
        this.removeEventListener(type[i], listener);
      }
    } else {
      this.removeEventListener(type, listener);
    }
  }
}

/**
 * Removes an event listener using the key returned by `on()` or `once()`.
 * @param {import("./events.js").EventsKey|Array<import("./events.js").EventsKey>} key The key returned by `on()`
 *     or `once()` (or an array of keys).
 * @api
 */
export function unByKey(key) {
  if (Array.isArray(key)) {
    for (let i = 0, ii = key.length; i < ii; ++i) {
      unlistenByKey(key[i]);
    }
  } else {
    unlistenByKey(/** @type {import("./events.js").EventsKey} */ (key));
  }
}

export default Observable;
<commit_msg>Observable workaround for on.<commit_after>/**
 * @module ol/Observable
 */
import EventTarget from './events/Target.js';
import EventType from './events/EventType.js';
import {listen, listenOnce, unlistenByKey} from './events.js';

/***
 * @template {string} Type
 * @template {import("./events/Event.js").default} EventClass
 * @typedef {(type: Type|Type[], listener: (event: EventClass) => (void|boolean)) => import("./events").EventsKey|Array<import("./events").EventsKey>} OnSignature
 */

/***
 * @template {string} Type
 * @typedef {(type: Type[], listener: (event: import("./events/Event").default) => (void|boolean)) => import("./events").EventsKey|Array<import("./events").EventsKey>} CombinedOnSignature
 */

/**
 * @typedef {'change'|'error'} EventTypes
 */

/**
 * @classdesc
 * Abstract base class; normally only used for creating subclasses and not
 * instantiated in apps.
 * An event target providing convenient methods for listener registration
 * and unregistration. A generic `change` event is always available through
 * {@link module:ol/Observable~Observable#changed}.
 *
 * @fires import("./events/Event.js").default
 * @api
 */
class Observable extends EventTarget {
  constructor() {
    super();

    /***
     * @type {OnSignature<EventTypes, import("./events/Event.js").default>}
     */
    this.on = this.onInternal;

    /**
     * @private
     * @type {number}
     */
    this.revision_ = 0;
  }

  /**
   * Increases the revision counter and dispatches a 'change' event.
   * @api
   */
  changed() {
    ++this.revision_;
    this.dispatchEvent(EventType.CHANGE);
  }

  /**
   * Get the version number for this object.  Each time the object is modified,
   * its version number will be incremented.
   * @return {number} Revision.
   * @api
   */
  getRevision() {
    return this.revision_;
  }

  /**
   * @param {string|Array<string>} type Type.
   * @param {function(import("./events/Event").default): (void|boolean)} listener Listener.
   * @return {import("./events.js").EventsKey|Array<import("./events.js").EventsKey>} Event key.
   * @protected
   */
  // the jsdoc api annotation follows below the class declaration.
  onInternal(type, listener) {
    if (Array.isArray(type)) {
      const len = type.length;
      const keys = new Array(len);
      for (let i = 0; i < len; ++i) {
        keys[i] = listen(this, type[i], listener);
      }
      return keys;
    } else {
      return listen(this, /** @type {string} */ (type), listener);
    }
  }

  /**
   * Listen once for a certain type of event.
   * @param {string|Array<string>} type The event type or array of event types.
   * @param {function(?): ?} listener The listener function.
   * @return {import("./events.js").EventsKey|Array<import("./events.js").EventsKey>} Unique key for the listener. If
   *     called with an array of event types as the first argument, the return
   *     will be an array of keys.
   * @api
   */
  once(type, listener) {
    let key;
    if (Array.isArray(type)) {
      const len = type.length;
      key = new Array(len);
      for (let i = 0; i < len; ++i) {
        key[i] = listenOnce(this, type[i], listener);
      }
    } else {
      key = listenOnce(this, /** @type {string} */ (type), listener);
    }
    /** @type {Object} */ (listener).ol_key = key;
    return key;
  }

  /**
   * Unlisten for a certain type of event.
   * @param {string|Array<string>} type The event type or array of event types.
   * @param {function(?): ?} listener The listener function.
   * @api
   */
  un(type, listener) {
    const key = /** @type {Object} */ (listener).ol_key;
    if (key) {
      unByKey(key);
    } else if (Array.isArray(type)) {
      for (let i = 0, ii = type.length; i < ii; ++i) {
        this.removeEventListener(type[i], listener);
      }
    } else {
      this.removeEventListener(type, listener);
    }
  }
}

/**
 * Listen for a certain type of event.
 * @function
 * @param {string|Array<string>} type The event type or array of event types.
 * @param {function(?): ?} listener The listener function.
 * @return {import("./events.js").EventsKey|Array<import("./events.js").EventsKey>} Unique key for the listener. If
 *     called with an array of event types as the first argument, the return
 *     will be an array of keys.
 * @api
 */
Observable.prototype.on;

/**
 * Removes an event listener using the key returned by `on()` or `once()`.
 * @param {import("./events.js").EventsKey|Array<import("./events.js").EventsKey>} key The key returned by `on()`
 *     or `once()` (or an array of keys).
 * @api
 */
export function unByKey(key) {
  if (Array.isArray(key)) {
    for (let i = 0, ii = key.length; i < ii; ++i) {
      unlistenByKey(key[i]);
    }
  } else {
    unlistenByKey(/** @type {import("./events.js").EventsKey} */ (key));
  }
}

export default Observable;
<|endoftext|>


<commit_before>define(function(require, exports, module) {
  var expr = require('./expr');

  return function parseSignals(model, spec) {
    // process each signal definition
    (spec || []).forEach(function(s) {
      var signal = model.graph.signal(s.name, s.init),
          node, exp;

      if(s.expr) {
        exp = expr(model, s.expr);
        node = new model.Node(function(input) {
          var value = expr.eval(model, exp.fn, null, null, null, null, exp.signals);
          if(spec.scale) value = model.scene().scale(spec, value);
          signal.value(value);
          input.signals[s.name] = 1;
          return input;
        });
        node._deps.signals = exp.signals;
        exp.signals.forEach(function(dep) { model.signal(dep).addListener(node); });
        signal.node(node);
      }
    });

    return spec;
  };
});<commit_msg>Expr signals can now just override the evaluate method, rather than the entire Node. Nice!<commit_after>define(function(require, exports, module) {
  var expr = require('./expr'),
      C = require('../util/constants');

  return function parseSignals(model, spec) {
    // process each signal definition
    (spec || []).forEach(function(s) {
      var signal = model.graph.signal(s.name, s.init),
          node, exp;

      if(s.expr) {
        exp = expr(model, s.expr);
        signal.evaluate = function(input) {
          var value = expr.eval(model, exp.fn, null, null, null, null, exp.signals);
          if(spec.scale) value = model.scene().scale(spec, value);
          signal.value(value);
          input.signals[s.name] = 1;
          return input;
        };
        signal.dependency(C.SIGNALS, exp.signals);
        exp.signals.forEach(function(dep) { model.signal(dep).addListener(node); });
      }
    });

    return spec;
  };
});<|endoftext|>


<commit_before>(function (root,$) {
    root.SCS = {
        version: '1.0.0'
    };
    //default settings
    SCS.defaults = {
        navArr: [//province，city，district
            { title: "省", id: "scs_items_prov" },
            { title: "市", id: "scs_items_city" },
            { title: "区", id: "scs_items_dist" }
        ],
        skinCss: '',//customize skin by adding an extra class
        onOk: function (selectedKey, selectedValue) { },//trigger when click on ok, the selectedDey and selectedValue can be used directly
        onCancel: function () { }//trigger when click on cancel
    };
    SCS.init = function (options) {
        var options = $.extend({}, SCS.defaults, options);
        var html = '',
            navArr = options.navArr;
        html += '

';
        navArr.forEach(function (nav) {
            html += '

' + nav.title + '

';
        });
        html += '

确定

取消

';
        $("#cascade_scroll_selects").remove();//make sure only one SCS is running
        $("body").append(html);
        var This = $("#cascade_scroll_selects");
        This.find(".scs_dialog").slideDown(300);
        //bind click events
        This.find(".scs_cover").click(function () {
            This.find(".scs_dialog").slideUp(200, function () {
                This.remove();
            });
        });
        This.find(".scs_btn_ok").click(function () {
            var selectedKey = '',
                selectedValue = '';
            $("#cascade_scroll_selects").find(".scs_selected").each(function (n) {
                var $obj = $(this);
                if (n != 0) {
                    selectedKey += ("-" + $obj.attr("data-val"));
                    selectedValue += (" " + $obj.html());
                } else {
                    selectedKey = $obj.attr("data-val");
                    selectedValue = $obj.html();
                }
            });
            options.onOk(selectedKey, selectedValue);
            This.find(".scs_dialog").slideUp(200, function () {
                This.remove();
            });
        });
        This.find(".scs_btn_cancel").click(function () {
            options.onCancel();
            This.find(".scs_dialog").slideUp(200, function () {
                This.remove();
            });
        });
    }
    SCS.destroy = function (callback) {//destroy SCS
        if (typeof callback === 'function')
            callback();
        var This = $("#cascade_scroll_selects");
        This.find(".scs_dialog").slideUp(200, function () {
            This.remove();
        });
    }
    //a scroller class
    SCS.scrollCascadeSelect = function (options) {
        var defaults = {
            el: undefined,
            addrArr: [],
            startIndex: 0,
            onInit: function (selectedItem, selectedIndex) { },//exposing the selectedItem and selectIndex parameters for outter using
            onChange: function (selectedItem, selectedIndex) { }//exposing the selectedItem and selectIndex parameters for outter using
        };
        var options = $.extend({}, defaults, options);
        var This = $(options.el),
            el = This.get(0),
            startPos = { x: 0, y: 0 },//a relative start position
            STARTPOS = { x: 0, y: 0 },//a precise start position
            currentPos = { x: 0, y: 0 },
            addrArr = options.addrArr,
            startIndex = options.startIndex,
            _self = this;//use '_self' to store 'this' in case of some misunderstanding.
        //exposing the selected item and index for outter use
        _self.selectedIndex = startIndex;
        _self.getSelectedItem = function () {
            return addrArr[this.selectedIndex];
        };
        //bind all the touch event
        el.addEventListener('touchstart', function (event) {
            startPos = { x: event.changedTouches[0].pageX, y: event.changedTouches[0].pageY };
            STARTPOS = { x: event.changedTouches[0].pageX, y: event.changedTouches[0].pageY };
        }, false);
        el.addEventListener('touchmove', function (event) {
            event.preventDefault();//prevent default scrolling event when touch moving
            currentPos = { x: event.changedTouches[0].pageX, y: event.changedTouches[0].pageY };
            var movedDistance = currentPos.y - startPos.y,
                currentTranslatedY = parseInt(This.css("webkitTransform").split(",").pop().replace(" ", "").replace(")", "")),
                newTranslatedY = movedDistance + currentTranslatedY;
            if (newTranslatedY > 80) {
                newTranslatedY = 80;
            } else if (newTranslatedY < (This.attr("data-height") - 120) * (-1)) {
                newTranslatedY = (This.attr("data-height") - 120) * (-1);
            }
            This.css('-webkit-transform', 'translate3d(0, ' + newTranslatedY + 'px, 0)');
            This.css('transform', 'translate3d(0, ' + newTranslatedY + 'px, 0)');
            startPos = { x: currentPos.x, y: currentPos.y };
        }, false);
        el.addEventListener('touchend', function (event) {
            currentPos = { x: event.changedTouches[0].pageX, y: event.changedTouches[0].pageY };
            if (Math.abs(currentPos.y - STARTPOS.y) > 5) {//exclude the 'click' event
                var currentTranslatedY = parseInt(This.css("webkitTransform").split(",").pop().replace(" ", "").replace(")", "")),
                    residue = currentTranslatedY % 40;
                if (Math.abs(residue) >= 20) {
                    if (residue < 0)
                        currentTranslatedY += ((40 + residue) * -1);
                    else {
                        currentTranslatedY += (40 - residue);
                    }
                } else {
                    currentTranslatedY -= residue;
                }
                This.css('-webkit-transform', 'translate3d(0, ' + currentTranslatedY + 'px, 0)');
                This.css('transform', 'translate3d(0, ' + currentTranslatedY + 'px, 0)');
                _self.selectedIndex = Math.abs((currentTranslatedY - 80) / (-40));
                This.find(".scs_item").removeClass("scs_selected").eq(_self.selectedIndex).addClass("scs_selected");
                startPos = { x: 0, y: 0 },
                currentPos = { x: 0, y: 0 };
                options.onChange(_self.getSelectedItem(), _self.selectedIndex);//trigger onChange event
            }
        }, false);
        //bind a click event
        This.on("click", ".scs_item", function (e) {
            var That = $(this),
            itemPositionY = That.position().top,
            currentTranslatedY = 80 - itemPositionY;
            This.css('-webkit-transform', 'translate3d(0, ' + currentTranslatedY + 'px, 0)');
            This.css('transform', 'translate3d(0, ' + currentTranslatedY + 'px, 0)');
            This.find(".scs_item").removeClass("scs_selected");
            That.addClass("scs_selected");
            _self.selectedIndex = Math.abs((currentTranslatedY - 80) / (-40));
            options.onChange(_self.getSelectedItem(), _self.selectedIndex);//trigger onChange event
        });
        //a public out-exposing function, render / re-render the scroller, based on the parameters 'newAddrArr' and 'newStartIndex'
        _self.render = function (newAddrArr, newStartIndex, callback) {
            var html = "",
                sIndex = newStartIndex || 0,
                currentTranslatedY = 80 - sIndex * 40;
            if (newAddrArr != undefined)
                newAddrArr.forEach(function (obj, index) {
                    if (sIndex != index)
                        html += '
' + obj.val + '
';
                    else {
                        html += '
' + obj.val + '
';
                    }
                });
            else {
                This.html(html);
                return;
            }
            This.html(html);
            This.attr("data-height", newAddrArr.length * 40);
            This.css('-webkit-transform', 'translate3d(0, ' + currentTranslatedY + 'px, 0)');
            This.css('transform', 'translate3d(0, ' + currentTranslatedY + 'px, 0)');
            _self.selectedIndex = sIndex;
            addrArr = newAddrArr;
            if (typeof callback === 'function')
                callback();

        };
        //private funtion，initialization
        var init = function () {
            _self.render(addrArr, startIndex);
            options.onInit(_self.getSelectedItem(), _self.selectedIndex);//trigger the onInit callback function
        };
        init();//bootstrap a scroller
    }
})(this,jQuery);
<commit_msg>Update jquery.scs.js<commit_after>(function (root,$) {
    root.SCS = {
        version: '1.0.0'
    };
    //default settings
    SCS.defaults = {
        navArr: [//province，city，district
            { title: "省", id: "scs_items_prov" },
            { title: "市", id: "scs_items_city" },
            { title: "区", id: "scs_items_dist" }
        ],
        skinCss: '',//customize skin by adding an extra class
        onOk: function (selectedKey, selectedValue) { },//trigger when click on ok, the selectedDey and selectedValue can be used directly
        onCancel: function () { }//trigger when click on cancel
    };
    SCS.init = function (options) {
        var options = $.extend({}, SCS.defaults, options);
        var html = '',
            navArr = options.navArr;
        html += '

';
        navArr.forEach(function (nav) {
            html += '

' + nav.title + '

';
        });
        html += '

确定

取消

';
        $("#cascade_scroll_selects").remove();//make sure only one SCS is running
        $("body").append(html);
        var This = $("#cascade_scroll_selects");
        This.find(".scs_dialog").slideDown(300);
        //bind click events
        This.find(".scs_cover").click(function () {
            This.find(".scs_dialog").slideUp(200, function () {
                This.remove();
            });
        });
        This.find(".scs_btn_ok").click(function () {
            var selectedKey = '',
                selectedValue = '';
            $("#cascade_scroll_selects").find(".scs_selected").each(function (n) {
                var $obj = $(this);
                if (n != 0) {
                    selectedKey += ("-" + $obj.attr("data-val"));
                    selectedValue += (" " + $obj.html());
                } else {
                    selectedKey = $obj.attr("data-val");
                    selectedValue = $obj.html();
                }
            });
            options.onOk(selectedKey, selectedValue);
            This.find(".scs_dialog").slideUp(200, function () {
                This.remove();
            });
        });
        This.find(".scs_btn_cancel").click(function () {
            options.onCancel();
            This.find(".scs_dialog").slideUp(200, function () {
                This.remove();
            });
        });
    }
    SCS.destroy = function (callback) {//destroy SCS
        if (typeof callback === 'function')
            callback();
        var This = $("#cascade_scroll_selects");
        This.find(".scs_dialog").slideUp(200, function () {
            This.remove();
        });
    }
    //a scroller class
    SCS.scrollCascadeSelect = function (options) {
        var defaults = {
            el: undefined,
            dataArr: [],
            startIndex: 0,
            onInit: function (selectedItem, selectedIndex) { },//exposing the selectedItem and selectIndex parameters for outter using
            onChange: function (selectedItem, selectedIndex) { }//exposing the selectedItem and selectIndex parameters for outter using
        };
        var options = $.extend({}, defaults, options);
        var This = $(options.el),
            el = This.get(0),
            startPos = { x: 0, y: 0 },//a relative start position
            STARTPOS = { x: 0, y: 0 },//a precise start position
            currentPos = { x: 0, y: 0 },
            dataArr = options.dataArr,
            startIndex = options.startIndex,
            _self = this;//use '_self' to store 'this' in case of some misunderstanding.
        //exposing the selected item and index for outter use
        _self.selectedIndex = startIndex;
        _self.getSelectedItem = function () {
            return dataArr[this.selectedIndex];
        };
        //bind all the touch event
        el.addEventListener('touchstart', function (event) {
            startPos = { x: event.changedTouches[0].pageX, y: event.changedTouches[0].pageY };
            STARTPOS = { x: event.changedTouches[0].pageX, y: event.changedTouches[0].pageY };
        }, false);
        el.addEventListener('touchmove', function (event) {
            event.preventDefault();//prevent default scrolling event when touch moving
            currentPos = { x: event.changedTouches[0].pageX, y: event.changedTouches[0].pageY };
            var movedDistance = currentPos.y - startPos.y,
                currentTranslatedY = parseInt(This.css("webkitTransform").split(",").pop().replace(" ", "").replace(")", "")),
                newTranslatedY = movedDistance + currentTranslatedY;
            if (newTranslatedY > 80) {
                newTranslatedY = 80;
            } else if (newTranslatedY < (This.attr("data-height") - 120) * (-1)) {
                newTranslatedY = (This.attr("data-height") - 120) * (-1);
            }
            This.css('-webkit-transform', 'translate3d(0, ' + newTranslatedY + 'px, 0)');
            This.css('transform', 'translate3d(0, ' + newTranslatedY + 'px, 0)');
            startPos = { x: currentPos.x, y: currentPos.y };
        }, false);
        el.addEventListener('touchend', function (event) {
            currentPos = { x: event.changedTouches[0].pageX, y: event.changedTouches[0].pageY };
            if (Math.abs(currentPos.y - STARTPOS.y) > 5) {//exclude the 'click' event
                var currentTranslatedY = parseInt(This.css("webkitTransform").split(",").pop().replace(" ", "").replace(")", "")),
                    residue = currentTranslatedY % 40;
                if (Math.abs(residue) >= 20) {
                    if (residue < 0)
                        currentTranslatedY += ((40 + residue) * -1);
                    else {
                        currentTranslatedY += (40 - residue);
                    }
                } else {
                    currentTranslatedY -= residue;
                }
                This.css('-webkit-transform', 'translate3d(0, ' + currentTranslatedY + 'px, 0)');
                This.css('transform', 'translate3d(0, ' + currentTranslatedY + 'px, 0)');
                _self.selectedIndex = Math.abs((currentTranslatedY - 80) / (-40));
                This.find(".scs_item").removeClass("scs_selected").eq(_self.selectedIndex).addClass("scs_selected");
                startPos = { x: 0, y: 0 },
                currentPos = { x: 0, y: 0 };
                options.onChange(_self.getSelectedItem(), _self.selectedIndex);//trigger onChange event
            }
        }, false);
        //bind a click event
        This.on("click", ".scs_item", function (e) {
            var That = $(this),
            itemPositionY = That.position().top,
            currentTranslatedY = 80 - itemPositionY;
            This.css('-webkit-transform', 'translate3d(0, ' + currentTranslatedY + 'px, 0)');
            This.css('transform', 'translate3d(0, ' + currentTranslatedY + 'px, 0)');
            This.find(".scs_item").removeClass("scs_selected");
            That.addClass("scs_selected");
            _self.selectedIndex = Math.abs((currentTranslatedY - 80) / (-40));
            options.onChange(_self.getSelectedItem(), _self.selectedIndex);//trigger onChange event
        });
        //a public out-exposing function, render / re-render the scroller, based on the parameters 'newDataArr' and 'newStartIndex'
        _self.render = function (newDataArr, newStartIndex, callback) {
            var html = "",
                sIndex = newStartIndex || 0,
                currentTranslatedY = 80 - sIndex * 40;
            if (newDataArr != undefined)
                newDataArr.forEach(function (obj, index) {
                    if (sIndex != index)
                        html += '
' + obj.val + '
';
                    else {
                        html += '
' + obj.val + '
';
                    }
                });
            else {
                This.html(html);
                return;
            }
            This.html(html);
            This.attr("data-height", newDataArr.length * 40);
            This.css('-webkit-transform', 'translate3d(0, ' + currentTranslatedY + 'px, 0)');
            This.css('transform', 'translate3d(0, ' + currentTranslatedY + 'px, 0)');
            _self.selectedIndex = sIndex;
            dataArr = newDataArr;
            if (typeof callback === 'function')
                callback();

        };
        //private funtion，initialization
        var init = function () {
            _self.render(dataArr, startIndex);
            options.onInit(_self.getSelectedItem(), _self.selectedIndex);//trigger the onInit callback function
        };
        init();//bootstrap a scroller
    }
})(this,jQuery);
<|endoftext|>


<commit_before>// @arg o == configuration
ui.positioner = function( o ) {
/*   References
     points: x, y
         x values: center, left, right
         y values: middle, top, bottom

     examples:
         "cm" = center middle
         "tl" = top left
         "tr" = top right
         "bl" = bottom left
         "br" = bottom right

    example configuration:
    {
        element: $element
        [context]: $element | viewport
        [offset]: "x y"
        [points]: "cm cm" // default
        [fixed]: false // default
        [draggable]: false // default

    } */

    // Initial configuration
	var element = $(o.element);
	var context;
	var viewport;

	// Default parameters
	if(!o.points) o.points = "cm cm";
    if(!o.offset) o.offset = "0 0";

    // Class names
    var classReferences = {
		"lt lb": "down",
		"lb lt": "top",
		"rt rb": "down",
		"rb rt": "top",
		"lt rt": "right",
		"cm cm": "center"
	};

	// Offset parameter
    var splittedOffset = o.offset.split(" ");
   	var offset_left = parseInt(splittedOffset[0]);
	var offset_top = parseInt(splittedOffset[1]);

    // Get viewport with your configuration - Crossbrowser
    //Conditional Advance Loading method
	var getViewport = (typeof window.innerWidth != "undefined") ?
		// the more standards compliant browsers (mozilla/netscape/opera/IE7) use window.innerWidth and window.innerHeight
		function getViewport() {
			var viewport, width, height, left, top, pageX, pageY;

			viewport = window;
			width = viewport.innerWidth;
			height = viewport.innerHeight;
			pageX = viewport.pageXOffset;
			pageY = viewport.pageYOffset;

			left = 0 + offset_left + pageX;
			top = 0 + offset_top + pageY;
			bottom = height + pageY;
			right = width + pageX;

			// Return viewport object
			return {
				element: viewport,
				left: 0 + offset_left + pageX,
				top: 0 + offset_top + pageY,
				bottom: height + pageY,
				right: width + pageX,
				width: width,
				height: height
			}
		}:
		// IE6 in standards compliant mode (i.e. with a valid doctype as the first line in the document)
		// older versions of IE - viewport = document.getElementsByTagName('body')[0];
		function getViewport(){
			var viewport, width, height, left, top, pageX, pageY;

			viewport = document.documentElement;
			width = viewport.clientWidth;
			height = viewport.clientHeight;
			pageX = viewport.scrollLeft;
			pageY = viewport.scrollTop;

			left = 0 + offset_left + pageX;
			top = 0 + offset_top + pageY;
			bottom = height + pageY;
			right = width + pageX;

			// Return viewport object
			return {
				element: viewport,
				left: 0 + offset_left + pageX,
				top: 0 + offset_top + pageY,
				bottom: height + pageY,
				right: width + pageX,
				width: width,
				height: height
			}
		};

	// Calculate css left and top to element on context
	var getPosition = function(unitPoints) {
		// my_x and at_x values together
		// cache properties
		var contextLeft = context.left;
		var contextTop = context.top;
		var contextWidth = context.width;
		var contextHeight = context.height;
		var elementWidth = element.outerWidth();
		var elementHeight = element.outerHeight();

		var xReferences = {
			ll: contextLeft,
			lr: contextLeft + contextWidth,
			rr: contextLeft + contextWidth - elementWidth,
			cc: contextLeft + contextWidth/2 - elementWidth/2
			// TODO: lc, rl, rc, cl, cr
		}

		// my_y and at_y values together
		var yReferences = {
			tt: contextTop,
			tb: contextTop + contextHeight,
			bt: contextTop - elementHeight,
			mm: contextTop + contextHeight/2 - elementHeight/2
			// TODO: tm, bb, bm, mt, mb
		}

		var axis = {
			left: xReferences[unitPoints.my_x + unitPoints.at_x],
			top: yReferences[unitPoints.my_y + unitPoints.at_y]
		}

		return axis;
	};

	// Evaluate viewport spaces and set points
	var calculatePoints = function(points, unitPoints){

		// Default styles
        var styles = getPosition(unitPoints);
        	styles.direction = classReferences[points];
        // Check viewport limits
		//var viewport = getViewport();

		// Down to top
		if ( (points == "lt lb") && ((styles.top + element.outerHeight()) > viewport.bottom) ) { // Element bottom > Viewport bottom
			unitPoints.my_y = "b";
			unitPoints.at_y = "t";

			// New styles
			styles = getPosition(unitPoints);
			styles.direction = "top";
			styles.top -= context.height; // TODO: Al recalcular toma al top del context como si fuese el bottom. (Solo en componentes. En los tests anda ok)
		};

		// Left to right
		if ( (styles.left + element.outerWidth()) > viewport.right ) { // Element right > Viewport right
			unitPoints.my_x = "r";
			unitPoints.at_x = "r";

			// New styles
			var current = styles.direction;
			styles = getPosition(unitPoints);
			styles.direction = current + "-right";
			if(current == "top") styles.top -= context.height; // TODO: Al recalcular toma al top del context como si fuese el bottom. (Solo en componentes. En los tests anda ok)
		};

		return styles;
	};

	// Set position to element on context
	var setPosition = function() {
		// Separate points config
        var splitted = o.points.split(" ");

        var unitPoints = {
        	my_x: splitted[0].slice(0,1),
        	my_y: splitted[0].slice(1,2),
        	at_x: splitted[1].slice(0,1),
        	at_y: splitted[1].slice(1,2)
        }

		var styles = calculatePoints(o.points, unitPoints);

		element
			.css({
				left: styles.left,
				top: styles.top
			})
			.removeClass( "ch-top ch-left ch-down ch-right ch-down-right ch-top-right" )
			.addClass( "ch-" + styles.direction );

	};

	// Get context
	//Conditional Advance Loading method
	var getContext = (o.context) ?
		function getContext(){
			var contextOffset = o.context.offset();
		    context = {
		    	element: o.context,
				top: contextOffset.top + offset_top,
				left: contextOffset.left + offset_left,
				width: o.context.outerWidth(),
				height: o.context.outerHeight()
		    };

		    return context;
		}:
		function getContext(){
			return viewport;
		};

	// Set element position on resize
    var initPosition = function(){
	    viewport = getViewport();
	    context = getContext();
	    setPosition();
    };

	// Init
	initPosition();

	// Scroll and resize events
	//tested on IE = Magic, no lag!!
	var scrolled = false;
	ui.utils.window.bind("resize scroll", function() {
		scrolled = true;
	});
	setInterval(function() {
	    if( !scrolled ) return;
		scrolled = false;
		initPosition();

	}, 250);

	return $(element);
};
<commit_msg>UI-223 Si no hay lugar hacia abajo o hacia arriba del viewport los floats se muestran por default para abajo.<commit_after>// @arg o == configuration
ui.positioner = function( o ) {
/*   References
     points: x, y
         x values: center, left, right
         y values: middle, top, bottom

     examples:
         "cm" = center middle
         "tl" = top left
         "tr" = top right
         "bl" = bottom left
         "br" = bottom right

    example configuration:
    {
        element: $element
        [context]: $element | viewport
        [offset]: "x y"
        [points]: "cm cm" // default
        [fixed]: false // default
        [draggable]: false // default

    } */

    // Initial configuration
	var element = $(o.element);
	var context;
	var viewport;

	// Default parameters
	if(!o.points) o.points = "cm cm";
    if(!o.offset) o.offset = "0 0";

    // Class names
    var classReferences = {
		"lt lb": "down",
		"lb lt": "top",
		"rt rb": "down",
		"rb rt": "top",
		"lt rt": "right",
		"cm cm": "center"
	};

	// Offset parameter
    var splittedOffset = o.offset.split(" ");
   	var offset_left = parseInt(splittedOffset[0]);
	var offset_top = parseInt(splittedOffset[1]);

    // Get viewport with your configuration - Crossbrowser
    //Conditional Advance Loading method
	var getViewport = (typeof window.innerWidth != "undefined") ?
		// the more standards compliant browsers (mozilla/netscape/opera/IE7) use window.innerWidth and window.innerHeight
		function getViewport() {
			var viewport, width, height, left, top, pageX, pageY;

			viewport = window;
			width = viewport.innerWidth;
			height = viewport.innerHeight;
			pageX = viewport.pageXOffset;
			pageY = viewport.pageYOffset;

			left = 0 + offset_left + pageX;
			top = 0 + offset_top + pageY;
			bottom = height + pageY;
			right = width + pageX;

			// Return viewport object
			return {
				element: viewport,
				left: 0 + offset_left + pageX,
				top: 0 + offset_top + pageY,
				bottom: height + pageY,
				right: width + pageX,
				width: width,
				height: height
			}
		}:
		// IE6 in standards compliant mode (i.e. with a valid doctype as the first line in the document)
		// older versions of IE - viewport = document.getElementsByTagName('body')[0];
		function getViewport(){
			var viewport, width, height, left, top, pageX, pageY;

			viewport = document.documentElement;
			width = viewport.clientWidth;
			height = viewport.clientHeight;
			pageX = viewport.scrollLeft;
			pageY = viewport.scrollTop;

			left = 0 + offset_left + pageX;
			top = 0 + offset_top + pageY;
			bottom = height + pageY;
			right = width + pageX;

			// Return viewport object
			return {
				element: viewport,
				left: 0 + offset_left + pageX,
				top: 0 + offset_top + pageY,
				bottom: height + pageY,
				right: width + pageX,
				width: width,
				height: height
			}
		};

	// Calculate css left and top to element on context
	var getPosition = function(unitPoints) {
		// my_x and at_x values together
		// cache properties
		var contextLeft = context.left;
		var contextTop = context.top;
		var contextWidth = context.width;
		var contextHeight = context.height;
		var elementWidth = element.outerWidth();
		var elementHeight = element.outerHeight();

		var xReferences = {
			ll: contextLeft,
			lr: contextLeft + contextWidth,
			rr: contextLeft + contextWidth - elementWidth,
			cc: contextLeft + contextWidth/2 - elementWidth/2
			// TODO: lc, rl, rc, cl, cr
		}

		// my_y and at_y values together
		var yReferences = {
			tt: contextTop,
			tb: contextTop + contextHeight,
			bt: contextTop - elementHeight,
			mm: contextTop + contextHeight/2 - elementHeight/2
			// TODO: tm, bb, bm, mt, mb
		}

		var axis = {
			left: xReferences[unitPoints.my_x + unitPoints.at_x],
			top: yReferences[unitPoints.my_y + unitPoints.at_y]
		}

		return axis;
	};

	// Evaluate viewport spaces and set points
	var calculatePoints = function(points, unitPoints){

		// Default styles
        var styles = getPosition(unitPoints);
        	styles.direction = classReferences[points];

        // Check viewport limits
		// Down to top
		if ( (points == "lt lb") && ((styles.top + element.outerHeight()) > viewport.bottom) ) { // Element bottom > Viewport bottom
			unitPoints.my_y = "b";
			unitPoints.at_y = "t";

			//store old styles
			stylesDown = styles;

			// New styles
			styles = getPosition(unitPoints);
			styles.direction = "top";
			styles.top -= context.height; // TODO: Al recalcular toma al top del context como si fuese el bottom. (Solo en componentes. En los tests anda ok)

			// Top to Down - Default again
			if(styles.top < viewport.top){
				unitPoints.my_y = "t";
				unitPoints.at_y = "b";
				styles = stylesDown;
				styles.direction = "down";
			};
		};

		// Left to right
		if ( (styles.left + element.outerWidth()) > viewport.right ) { // Element right > Viewport right
			unitPoints.my_x = "r";
			unitPoints.at_x = "r";

			// New styles
			var current = styles.direction;
			styles = getPosition(unitPoints);
			styles.direction = current + "-right";
			if(current == "top") styles.top -= context.height; // TODO: Al recalcular toma al top del context como si fuese el bottom. (Solo en componentes. En los tests anda ok)
		};

		return styles;
	};

	// Set position to element on context
	var setPosition = function() {
		// Separate points config
        var splitted = o.points.split(" ");

        var unitPoints = {
        	my_x: splitted[0].slice(0,1),
        	my_y: splitted[0].slice(1,2),
        	at_x: splitted[1].slice(0,1),
        	at_y: splitted[1].slice(1,2)
        }

		var styles = calculatePoints(o.points, unitPoints);

		element
			.css({
				left: styles.left,
				top: styles.top
			})
			.removeClass( "ch-top ch-left ch-down ch-right ch-down-right ch-top-right" )
			.addClass( "ch-" + styles.direction );

	};

	// Get context
	//Conditional Advance Loading method
	var getContext = (o.context) ?
		function getContext(){
			var contextOffset = o.context.offset();
		    context = {
		    	element: o.context,
				top: contextOffset.top + offset_top,
				left: contextOffset.left + offset_left,
				width: o.context.outerWidth(),
				height: o.context.outerHeight()
		    };

		    return context;
		}:
		function getContext(){
			return viewport;
		};

	// Set element position on resize
    var initPosition = function(){
	    viewport = getViewport();
	    context = getContext();
	    setPosition();
    };

	// Init
	initPosition();

	// Scroll and resize events
	//tested on IE = Magic, no lag!!
	var scrolled = false;
	ui.utils.window.bind("resize scroll", function() {
		scrolled = true;
	});
	setInterval(function() {
	    if( !scrolled ) return;
		scrolled = false;
		initPosition();

	}, 250);

	return $(element);
};
<|endoftext|>


<commit_before>var xhr = new XMLHttpRequest();
xhr.open('GET', 'http://walkingdeadcomics.org/the-walking-dead-next-issue/', true);

xhr.onload = function () {
  var parser = new DOMParser();
  var doc = parser.parseFromString(xhr.responseText, 'text/html');

  var titleNode = doc.getElementById('p-title');
  var issuenumber = titleNode.innerText.substr(titleNode.innerText.indexOf('#')+1);
  var availabilityText = doc.querySelector('div.vision-contentbox-content.tt-content-style-black > span').innerText;
  var dateStart = availabilityText.substr(availabilityText.indexOf(':') + 1).trim();
  var array = dateStart.split(' ');
  var releasedatestring = array[0] + ' ' + array[1] + ' ' + array[2] + ' 09:00 GMT-0500';

  var releaseDate = new Date(releasedatestring);

  var msUntil = releaseDate - new Date();

  var days = msUntil/1000/60/60/24;
  var wholedays = Math.floor(days);
  var img = doc.querySelector('#jig1 a').href;

  setVariablesInTasker(img, issuenumber, wholedays, releasedatestring);

  //leave the Tasker javascriptlet
  exit();
};

xhr.send();

function setVariablesInTasker(img, issuenumber, wholedays, releasedatestring) {
  setLocalVariable('img',img);
  setLocalVariable('issueno',issuenumber);
  setLocalVariable('days',wholedays);
  setLocalVariable('date',releasedatestring);
}

function setLocalVariable(name, value, debug) {
    if (debug) {
    flash('setting ' + name + ' to ' + value);
	}
	setLocal(name,value);
};<commit_msg>Added function to get TWD issue cover img path<commit_after>var xhr = new XMLHttpRequest();
xhr.open('GET', 'http://walkingdeadcomics.org/the-walking-dead-next-issue/', true);

xhr.onload = function () {
  var parser = new DOMParser();
  var doc = parser.parseFromString(xhr.responseText, 'text/html');

  var titleNode = doc.getElementById('p-title');
  var issueNumber = titleNode.innerText.substr(titleNode.innerText.indexOf('#')+1);
  var availabilityText = doc.querySelector('div.vision-contentbox-content.tt-content-style-black > span').innerText;
  var dateStart = availabilityText.substr(availabilityText.indexOf(':') + 1).trim();
  var array = dateStart.split(' ');
  var releasedatestring = array[0] + ' ' + array[1] + ' ' + array[2] + ' 09:00 GMT-0500';

  var releaseDate = new Date(releasedatestring);

  var msUntil = releaseDate - new Date();

  var days = msUntil/1000/60/60/24;
  var wholedays = Math.floor(days);
  var img = getIssueImagePath(issueNumber);

  setVariablesInTasker(img, issueNumber, wholedays, releasedatestring);

  //leave the Tasker javascriptlet
  exit();
};

xhr.send();

function getIssueImagePath(issueNo) {
    if (issueNo.length === 1) {
	    issueNo = '0' + issueNo;
	}
	return ['http://i1.wp.com/walkingdeadcomics.org/wp-images/gallery/standard/', issueNo, '.jpg'].join()
}

function setVariablesInTasker(img, issueNumber, wholedays, releasedatestring) {
  setLocalVariable('img',img);
  setLocalVariable('issueno',issueNumber);
  setLocalVariable('days',wholedays);
  setLocalVariable('date',releasedatestring);
}

function setLocalVariable(name, value, debug) {
    if (debug) {
    flash('setting ' + name + ' to ' + value);
	}
	setLocal(name,value);
};<|endoftext|>


<commit_before>/**
 * Patterns checklist - Easily (un)check all checkboxes
 *
 * Copyright 2012-2013 Simplon B.V. - Wichert Akkerman
 * Copyright 2012-2013 Florian Friesdorf
 */
define([
    "jquery",
    "pat-parser",
    "pat-registry"
], function($, Parser, registry) {
    var parser = new Parser("checklist");
    parser.add_argument("select", ".functions .select-all");
    parser.add_argument("deselect", ".functions .deselect-all");

    var _ = {
        name: "checklist",
        trigger: ".pat-checklist",
        jquery_plugin: true,

        init: function($el, opts) {
            return $el.each(function() {
                var $trigger = $(this),
                    options = parser.parse($trigger, opts, false);

                $trigger.data("patternChecklist", options);
                $trigger.find(options.select)
                    .on("click.pat-checklist", {trigger: $trigger}, _.onSelectAll);
                $trigger.find(options.deselect)
                    .on("click.pat-checklist", {trigger: $trigger}, _.onDeselectAll);
                $trigger.on("change.pat-checklist", "input[type=checkbox]", {trigger: $trigger}, _.onChange);
            });
        },

        destroy: function($el) {
            return $el.each(function() {
                var $trigger = $(this),
                    options = $trigger.data("patternChecklist");
                $trigger.find(options.select).off(".pat-checklist");
                $trigger.find(options.deselect).off(".pat-checklist");
                $trigger.off(".pat-checklist", "input[type=checkbox]");
                $trigger.data("patternChecklist", null);
            });
        },

        onChange: function(event) {
            var $trigger = event.data.trigger,
                options = $trigger.data("patternChecklist"),
                deselect = $trigger.find(options.deselect),
                select = $trigger.find(options.select);

            if (($trigger.find("input[type=checkbox]:visible:checked").length===0) &&
                (!deselect.prop("disabled"))) {
                deselect.attr({disabled: "disabled"});
            } else if (deselect.prop("disabled")) {
                deselect.prop("disabled", false);
            }

            if (($trigger.find("input[type=checkbox]:visible:not(:checked)").length===0) &&
                (!select.prop("disabled"))) {
                select.attr({disabled: "disabled"});
            } else if (select.prop("disabled")) {
                select.prop("disabled", false);
            }
        },

        onSelectAll: function(event) {
            var $trigger = event.data.trigger,
                options = $trigger.data("patternChecklist");
            $trigger.find("input[type=checkbox]:not(:checked)").prop("checked", true).trigger("change");
            $trigger.find(options.deselect).prop("disabled", false);
            $trigger.find(options.select).attr({disabled: "disabled"});
            $trigger.change();
            event.preventDefault();
        },

        onDeselectAll: function(event) {
            var $trigger = event.data.trigger,
                options = $trigger.data("patternChecklist");
            $trigger.find("input[type=checkbox]:checked").prop("checked", false).trigger("change");
            $trigger.find(options.select).prop("disabled", false);
            $trigger.find(options.deselect).attr({disabled: "disabled"});
            $trigger.change();
            event.preventDefault();
        }
    };

    registry.register(_);
    return _;
});

// jshint indent: 4, browser: true, jquery: true, quotmark: double
// vim: sw=4 expandtab
<commit_msg>Set correct checklist button state on init.<commit_after>/**
 * Patterns checklist - Easily (un)check all checkboxes
 *
 * Copyright 2012-2013 Simplon B.V. - Wichert Akkerman
 * Copyright 2012-2013 Florian Friesdorf
 */
define([
    "jquery",
    "pat-parser",
    "pat-registry"
], function($, Parser, registry) {
    var parser = new Parser("checklist");
    parser.add_argument("select", ".functions .select-all");
    parser.add_argument("deselect", ".functions .deselect-all");

    var _ = {
        name: "checklist",
        trigger: ".pat-checklist",
        jquery_plugin: true,

        init: function($el, opts) {
            return $el.each(function() {
                var $trigger = $(this),
                    options = parser.parse($trigger, opts, false);

                $trigger.data("patternChecklist", options);
                $trigger.find(options.select)
                    .on("click.pat-checklist", {trigger: $trigger}, _.onSelectAll);
                $trigger.find(options.deselect)
                    .on("click.pat-checklist", {trigger: $trigger}, _.onDeselectAll);
                $trigger.on("change.pat-checklist", "input[type=checkbox]", {trigger: $trigger}, _.onChange);

                // update select/deselect button status
                _.onChange({data: {trigger: $trigger}});
            });
        },

        destroy: function($el) {
            return $el.each(function() {
                var $trigger = $(this),
                    options = $trigger.data("patternChecklist");
                $trigger.find(options.select).off(".pat-checklist");
                $trigger.find(options.deselect).off(".pat-checklist");
                $trigger.off(".pat-checklist", "input[type=checkbox]");
                $trigger.data("patternChecklist", null);
            });
        },

        onChange: function(event) {
            var $trigger = event.data.trigger,
                options = $trigger.data("patternChecklist"),
                deselect = $trigger.find(options.deselect),
                select = $trigger.find(options.select);

            if (($trigger.find("input[type=checkbox]:visible:checked").length===0) &&
                (!deselect.prop("disabled"))) {
                deselect.attr({disabled: "disabled"});
            } else if (deselect.prop("disabled")) {
                deselect.prop("disabled", false);
            }

            if (($trigger.find("input[type=checkbox]:visible:not(:checked)").length===0) &&
                (!select.prop("disabled"))) {
                select.attr({disabled: "disabled"});
            } else if (select.prop("disabled")) {
                select.prop("disabled", false);
            }
        },

        onSelectAll: function(event) {
            var $trigger = event.data.trigger,
                options = $trigger.data("patternChecklist");
            $trigger.find("input[type=checkbox]:not(:checked)").prop("checked", true).trigger("change");
            $trigger.find(options.deselect).prop("disabled", false);
            $trigger.find(options.select).attr({disabled: "disabled"});
            $trigger.change();
            event.preventDefault();
        },

        onDeselectAll: function(event) {
            var $trigger = event.data.trigger,
                options = $trigger.data("patternChecklist");
            $trigger.find("input[type=checkbox]:checked").prop("checked", false).trigger("change");
            $trigger.find(options.select).prop("disabled", false);
            $trigger.find(options.deselect).attr({disabled: "disabled"});
            $trigger.change();
            event.preventDefault();
        }
    };

    registry.register(_);
    return _;
});

// jshint indent: 4, browser: true, jquery: true, quotmark: double
// vim: sw=4 expandtab
<|endoftext|>


<commit_before>var wiredep = require('wiredep');

var js = [],
    test = ['./test/**/*.js'],
    bowerFiles = wiredep({
        devDependencies: true
    })['js'],
    karmaFiles = [].concat(bowerFiles, js, test),
    src = './src/',
    dist = './dist/';

module.exports = getConfig;

function getConfig() {
    var config = {
        index: src + 'index.html',
        js: js,
        css: src + 'css/**/*.css',
        images: src + 'images/**/*.***',
        fonts: src + 'fonts/*.***',
        dist: dist,
        src: src,
        test: test,
        bowerFiles: bowerFiles,
        karmaFiles: karmaFiles
    };
    return config;
}
<commit_msg>add js files to be loaded/injected<commit_after>var wiredep = require('wiredep');

var js = [
        './src/js/config.js',
        './src/js/resources.js',
        './src/js/Enemy.js',
        './src/js/Player.js',
        './src/js/engine.js',
        './src/js/*.js'
    ],
    test = ['./test/**/*.js'],
    bowerFiles = wiredep({
        devDependencies: true
    })['js'],
    karmaFiles = [].concat(bowerFiles, js, test),
    src = './src/',
    dist = './dist/';

module.exports = getConfig;

function getConfig() {
    var config = {
        index: src + 'index.html',
        js: js,
        css: src + 'css/**/*.css',
        images: src + 'images/**/*.***',
        fonts: src + 'fonts/*.***',
        dist: dist,
        src: src,
        test: test,
        bowerFiles: bowerFiles,
        karmaFiles: karmaFiles
    };
    return config;
}
<|endoftext|>


<commit_before>var gulp = require('gulp');

var $ = require('gulp-load-plugins')();

gulp.task('deploy', ['build'], function () {
    gulp.src("./dist/**/*")
        .pipe($.ghPages());
});
<commit_msg>clean before deploy<commit_after>var gulp = require('gulp');

var $ = require('gulp-load-plugins')();

gulp.task('deploy', ['clean', 'build'], function () {
    gulp.src("./dist/**/*")
        .pipe($.ghPages());
});
<|endoftext|>