Skip to content
This repository has been archived by the owner on Dec 11, 2019. It is now read-only.

Commit

Permalink
notify the renderer when the main process has finished dispatching an…
Browse files Browse the repository at this point in the history
… action message

fix #4220
  • Loading branch information
bridiver committed Sep 23, 2016
1 parent f51e1a3 commit 35de6cf
Show file tree
Hide file tree
Showing 4 changed files with 122 additions and 37 deletions.
14 changes: 14 additions & 0 deletions docs/appActions.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,20 @@

* * *

### onDispatchComplete(cb)

Calls the supplied callback function when dispatching is complete for all stores. This
method does not wait for UI or other updates that are triggered as a result of the
state changes triggered by the action

Usage: appActions.onDispatchComplete(fn).newWindow(...)

**Parameters**

**cb**: `function`, Callback



### setState(appState)

Dispatches an event to the main process to replace the app state
Expand Down
21 changes: 21 additions & 0 deletions js/actions/appActions.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,27 @@ const AppDispatcher = require('../dispatcher/appDispatcher')
const AppConstants = require('../constants/appConstants')

const appActions = {
/**
* Calls the supplied callback function when dispatching is complete for all stores. This
* method does not wait for UI or other updates that are triggered as a result of the
* state changes triggered by the action
*
* Usage: appActions.onDispatchComplete(fn).newWindow(...)
*
* @param {function} cb - Callback
*/
onDispatchComplete: function (cb) {
let handler = {
get (target, propKey, receiver) {
const origMethod = target[propKey]
return function (...args) {
return AppDispatcher.notifyOnDispatchComplete(origMethod.bind(this, ...args), cb)
}
}
}
return new Proxy(this, handler)
},

/**
* Dispatches an event to the main process to replace the app state
* This is called from the main process on startup before anything else
Expand Down
5 changes: 3 additions & 2 deletions js/components/braveryPanel.js
Original file line number Diff line number Diff line change
Expand Up @@ -144,8 +144,9 @@ class BraveryPanel extends ImmutableComponent {
if (setting !== 'noScript' && (parsedUrl.protocol === 'https:' || parsedUrl.protocol === 'http:')) {
ruleKey = `https?://${parsedUrl.host}`
}
appActions.changeSiteSetting(ruleKey, setting, e.target.value)
this.onReload()
appActions.onDispatchComplete(() => {
this.onReload()
}).changeSiteSetting(ruleKey, setting, e.target.value)
}
get displayHost () {
const parsedUrl = urlParse(this.props.activeRequestedLocation)
Expand Down
119 changes: 84 additions & 35 deletions js/dispatcher/appDispatcher.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,23 @@
const Serializer = require('./serializer')
const messages = require('../constants/messages')
const electron = process.type === 'renderer' ? global.require('electron') : require('electron')
const uuid = require('uuid').v4

'use strict'

const serializePayload = (payload, dispatchId = null) => {
if (dispatchId && !payload.dispatchId) {
payload.dispatchId = dispatchId
}
return Serializer.serialize(payload)
}

class AppDispatcher {

constructor () {
this.callbacks = []
this.promises = []
this.callbacks = {}
this.promises = {}
this.notifyOnDispatchCompleteFn = null
}

/**
Expand All @@ -22,20 +32,21 @@ class AppDispatcher {
* @param {function} callback The callback to be registered.
* @return {number} The index of the callback within the _callbacks array.
*/
register (callback) {
register (callback, token = uuid()) {
if (process.type === 'renderer') {
const ipc = electron.ipcRenderer
ipc.send('app-dispatcher-register')
ipc.send('app-dispatcher-register', token)
}
this.callbacks.push(callback)
return this.callbacks.length - 1 // index
this.callbacks[token] = callback
return token
}

unregister (callback) {
const index = this.callbacks.indexOf(callback)
if (index !== -1) {
this.callbacks.splice(index, 1)
unregister (token) {
if (process.type === 'renderer') {
const ipc = electron.ipcRenderer
ipc.send('app-dispatcher-unregister', token)
}
delete this.callbacks[token]
}

/**
Expand All @@ -51,31 +62,61 @@ class AppDispatcher {
if (payload.actionType === undefined) {
throw new Error('Dispatcher: Undefined action for payload', payload)
}
// First create array of promises for callbacks to reference.
const resolves = []
const rejects = []
this.promises = this.callbacks.map(function (_, i) {
return new Promise(function (resolve, reject) {
resolves[i] = resolve
rejects[i] = reject

// First create map of promises for callbacks to reference.
const resolves = {}
const rejects = {}
for (var token in this.callbacks) {
this.promises[token] = new Promise(function (resolve, reject) {
resolves[token] = resolve
rejects[token] = reject
})
})
}

let dispatchId = uuid()

if (this.notifyOnDispatchCompleteFn && process.type === 'renderer') {
let cb = this.notifyOnDispatchCompleteFn
const ipc = electron.ipcRenderer
// wait for all the local handlers
let dispatchedCallback = (evt, dispatchedActionId) => {
// in the renderer process we only have to wait
// for the main process
if (dispatchId === dispatchedActionId) {
this.waitFor(Object.keys(this.promises), cb)
ipc.removeListener('app-dispatcher-action-dispatched', dispatchedCallback)
}
}
ipc.on('app-dispatcher-action-dispatched', dispatchedCallback)
}

// Dispatch to callbacks and resolve/reject promises.
this.callbacks.forEach(function (callback, i) {
for (token in this.callbacks) {
let callback = this.callbacks[token]
// Callback can return an obj, to resolve, or a promise, to chain.
// See waitFor() for why this might be useful.
Promise.resolve(callback(payload)).then(function () {
resolves[i](payload)
resolves[token](payload)
}, function () {
rejects[i](new Error('Dispatcher callback unsuccessful'))
rejects[token](new Error('Dispatcher callback unsuccessful'))
})
})
this.promises = []
}

if (process.type === 'renderer') {
const ipc = electron.ipcRenderer
ipc.send(messages.DISPATCH_ACTION, Serializer.serialize(payload))
ipc.send(messages.DISPATCH_ACTION, serializePayload(payload, dispatchId), !!this.notifyOnDispatchCompleteFn)
}

this.promises = {}

return dispatchId
}

notifyOnDispatchComplete (fn, cb) {
this.notifyOnDispatchCompleteFn = cb
const returnVal = fn()
this.notifyOnDispatchCompleteFn = null
return returnVal
}

waitFor (promiseIndexes, callback) {
Expand All @@ -89,50 +130,58 @@ const appDispatcher = new AppDispatcher()
if (process.type === 'browser') {
const electron = require('electron')
const ipcMain = electron.ipcMain
ipcMain.on('app-dispatcher-register', (event) => {
ipcMain.on('app-dispatcher-unregister', (event, token) => {
appDispatcher.unregister(token)
})
ipcMain.on('app-dispatcher-register', (event, token) => {
let registrant = event.sender
const callback = function (payload) {
try {
if (registrant.isDestroyed()) {
appDispatcher.unregister(callback)
appDispatcher.unregister(token)
} else {
registrant.send(messages.DISPATCH_ACTION, Serializer.serialize(payload))
registrant.send(messages.DISPATCH_ACTION, serializePayload(payload))
}
} catch (e) {
console.error('unregistering callback', e)
appDispatcher.unregister(callback)
appDispatcher.unregister(token)
}
}
event.sender.on('crashed', () => {
appDispatcher.unregister(callback)
appDispatcher.unregister(token)
})
event.sender.on('destroyed', () => {
appDispatcher.unregister(callback)
appDispatcher.unregister(token)
})
appDispatcher.register(callback)
appDispatcher.register(callback, token)
})

ipcMain.on(messages.DISPATCH_ACTION, (event, payload) => {
ipcMain.on(messages.DISPATCH_ACTION, (event, payload, notifyOnDispatchComplete) => {
payload = Serializer.deserialize(payload)

let queryInfo = payload.queryInfo || payload.frameProps || (payload.queryInfo = {})
queryInfo = queryInfo.toJS ? queryInfo.toJS() : queryInfo
let sender = event.sender
if (event.sender.hostWebContents) {
sender = event.sender.hostWebContents
// received from an extension
// only extension messages will have a hostWebContents
let win = require('electron').BrowserWindow.fromWebContents(event.sender.hostWebContents)
let win = require('electron').BrowserWindow.fromWebContents(sender)
// default to the windowId of the hostWebContents
queryInfo.windowId = queryInfo.windowId || win.id
// add queryInfo if we only had frameProps before
payload.queryInfo = queryInfo

appDispatcher.dispatch(payload, event.sender.hostWebContents)
appDispatcher.dispatch(payload, sender)
} else {
// received from a browser window
if (event.sender.id !== queryInfo.windowId) {
appDispatcher.dispatch(payload, event.sender)
appDispatcher.dispatch(payload, sender)
}
}
if (notifyOnDispatchComplete && payload.dispatchId) {
sender.send('app-dispatcher-action-dispatched', payload.dispatchId)
}
})
}

Expand Down

0 comments on commit 35de6cf

Please sign in to comment.