diff --git a/app/browser/reducers/windowsReducer.js b/app/browser/reducers/windowsReducer.js
index d93fbc4fa45..1facd173926 100644
--- a/app/browser/reducers/windowsReducer.js
+++ b/app/browser/reducers/windowsReducer.js
@@ -23,14 +23,10 @@ const electron = require('electron')
const BrowserWindow = electron.BrowserWindow
const firstDefinedValue = require('../../../js/lib/functional').firstDefinedValue
const appConfig = require('../../../js/constants/appConfig')
-const messages = require('../../../js/constants/messages')
-const appUrlUtil = require('../../../js/lib/appUrlUtil')
const settings = require('../../../js/constants/settings')
const getSetting = require('../../../js/settings').getSetting
-const {zoomLevel} = require('../../common/constants/toolbarUserInterfaceScale')
+
const platformUtil = require('../../common/lib/platformUtil')
-const {initWindowCacheState} = require('../../sessionStoreShutdown')
-const appDispatcher = require('../../../js/dispatcher/appDispatcher')
const isDarwin = platformUtil.isDarwin()
const isWindows = platformUtil.isWindows()
@@ -43,17 +39,43 @@ function isModal (browserOpts) {
const navbarHeight = () => {
// TODO there has to be a better way to get this or at least add a test
+ // TODO try creating a window and measuring the difference between window and content area
+ // and updating this number with that value once the first window is created
return 75
}
+function clearFramesFromWindowState (windowState) {
+ return windowState
+ .set('frames', Immutable.List())
+ .set('tabs', Immutable.List())
+}
+
+/**
+ * Determine the frame(s) to be loaded in a new window
+ * based on user preferences
+ */
+function getFramesForNewWindow () {
+ const startupSetting = getSetting(settings.STARTUP_MODE)
+ const homepageSetting = getSetting(settings.HOMEPAGE)
+ if (startupSetting === 'homePage' && homepageSetting) {
+ return homepageSetting
+ .split('|')
+ .map((homepage) => ({
+ location: homepage
+ }))
+ }
+ return [ { } ]
+}
+
/**
* Determine window dimensions (width / height)
*/
const setWindowDimensions = (browserOpts, defaults, immutableWindowState) => {
assert(isImmutable(immutableWindowState))
- if (immutableWindowState.getIn(['windowInfo'])) {
- browserOpts.width = firstDefinedValue(browserOpts.width, immutableWindowState.getIn(['windowInfo', 'width']))
- browserOpts.height = firstDefinedValue(browserOpts.height, immutableWindowState.getIn(['windowInfo', 'height']))
+ const windowInfoState = immutableWindowState.get('windowInfo')
+ if (windowInfoState) {
+ browserOpts.width = firstDefinedValue(browserOpts.width, windowInfoState.get('width'))
+ browserOpts.height = firstDefinedValue(browserOpts.height, windowInfoState.get('windowInfo'))
} else {
browserOpts.width = firstDefinedValue(browserOpts.width, browserOpts.innerWidth, defaults.width)
// height and innerHeight are the frame webview size
@@ -108,7 +130,6 @@ const setMaximized = (state, browserOpts, immutableWindowState) => {
function windowDefaults (state) {
return {
- show: false,
width: state.getIn(['defaultWindowParams', 'width']) || state.get('defaultWindowWidth'),
height: state.getIn(['defaultWindowParams', 'height']) || state.get('defaultWindowHeight'),
x: state.getIn(['defaultWindowParams', 'x']) || undefined,
@@ -146,10 +167,10 @@ function setDefaultWindowSize (state) {
return state
}
-const createWindow = (state, action) => {
+const handleCreateWindowAction = (state, action) => {
const frameOpts = (action.get('frameOpts') || Immutable.Map()).toJS()
let browserOpts = (action.get('browserOpts') || Immutable.Map()).toJS()
- const immutableWindowState = action.get('restoredState') || Immutable.Map()
+ let immutableWindowState = action.get('restoredState') || Immutable.Map()
state = setDefaultWindowSize(state)
const defaults = windowDefaults(state)
const isMaximized = setMaximized(state, browserOpts, immutableWindowState)
@@ -160,21 +181,30 @@ const createWindow = (state, action) => {
delete browserOpts.left
delete browserOpts.top
+ // decide which bounds to restrict new window to
const screen = electron.screen
+ // use primary display by default
let primaryDisplay = screen.getPrimaryDisplay()
- const parentWindowKey = browserOpts.parentWindowKey
- if (browserOpts.x != null && browserOpts.y != null) {
- const matchingDisplay = screen.getDisplayMatching(browserOpts)
+ // can override with provided x, y coords
+ if (browserOpts.x != null && browserOpts.y != null && browserOpts.width != null && browserOpts.height != null) {
+ const matchingDisplay = screen.getDisplayMatching({
+ x: browserOpts.x,
+ y: browserOpts.y,
+ width: browserOpts.width,
+ height: browserOpts.height
+ })
if (matchingDisplay != null) {
primaryDisplay = matchingDisplay
}
}
-
+ // always override with parent window if present
+ const parentWindowKey = browserOpts.parentWindowKey
const parentWindow = parentWindowKey
? BrowserWindow.fromId(parentWindowKey)
: BrowserWindow.getFocusedWindow()
const bounds = parentWindow ? parentWindow.getBounds() : primaryDisplay.bounds
+ // decide which screen to position on
// position on screen should be relative to focused window
// or the primary display if there is no focused window
const display = screen.getDisplayNearestPoint(bounds)
@@ -229,7 +259,8 @@ const createWindow = (state, action) => {
autoHideMenuBar: autoHideMenuBarSetting,
title: appConfig.name,
webPreferences: defaults.webPreferences,
- frame: !isWindows
+ frame: !isWindows,
+ disposition: frameOpts.disposition
}
if (process.platform === 'linux') {
@@ -239,82 +270,35 @@ const createWindow = (state, action) => {
if (immutableWindowState.getIn(['windowInfo', 'state']) === 'fullscreen') {
windowProps.fullscreen = true
}
-
- const homepageSetting = getSetting(settings.HOMEPAGE)
- const startupSetting = getSetting(settings.STARTUP_MODE)
- const toolbarUserInterfaceScale = getSetting(settings.TOOLBAR_UI_SCALE)
-
+ // continue with window creation process outside of store action handler
setImmediate(() => {
- const win = new BrowserWindow(Object.assign(windowProps, browserOpts, {disposition: frameOpts.disposition}))
- let restoredImmutableWindowState = action.get('restoredState')
- initWindowCacheState(win.id, restoredImmutableWindowState)
-
- // initialize frames state
- let frames = Immutable.List()
- if (restoredImmutableWindowState && restoredImmutableWindowState.get('frames', Immutable.List()).size > 0) {
- frames = restoredImmutableWindowState.get('frames')
- restoredImmutableWindowState = restoredImmutableWindowState.set('frames', Immutable.List())
- restoredImmutableWindowState = restoredImmutableWindowState.set('tabs', Immutable.List())
+ // decide which frames to load in the window
+ let frames
+ // handle frames from restored state
+ const immutableFrames = immutableWindowState.get('frames')
+ if (Immutable.List.isList(immutableFrames) && immutableFrames.count()) {
+ frames = immutableFrames.toJS()
} else {
- if (frameOpts && Object.keys(frameOpts).length > 0) {
- if (frameOpts.forEach) {
- frames = Immutable.fromJS(frameOpts)
+ // handle frames from action
+ // can be single object or multiple in array
+ if (frameOpts && Object.keys(frameOpts).length) {
+ if (Array.isArray(frameOpts)) {
+ frames = frameOpts
} else {
- frames = frames.push(Immutable.fromJS(frameOpts))
+ frames = [ frameOpts ]
}
- } else if (startupSetting === 'homePage' && homepageSetting) {
- frames = Immutable.fromJS(homepageSetting.split('|').map((homepage) => {
- return {
- location: homepage
- }
- }))
+ } else {
+ // handle nothing provided, so follow 'new tab' preferences
+ frames = getFramesForNewWindow()
}
}
-
- if (frames.size === 0) {
- frames = Immutable.fromJS([{}])
- }
-
- if (isMaximized) {
- win.maximize()
- }
-
- appDispatcher.registerWindow(win, win.webContents)
- win.webContents.on('did-finish-load', (e) => {
- const appStore = require('../../../js/stores/appStore')
- win.webContents.setZoomLevel(zoomLevel[toolbarUserInterfaceScale] || 0.0)
-
- const position = win.getPosition()
- const size = win.getSize()
-
- const mem = muon.shared_memory.create({
- windowValue: {
- disposition: frameOpts.disposition,
- id: win.id,
- focused: win.isFocused(),
- left: position[0],
- top: position[1],
- height: size[1],
- width: size[0]
- },
- appState: appStore.getLastEmittedState().toJS(),
- frames: frames.toJS(),
- windowState: (restoredImmutableWindowState && restoredImmutableWindowState.toJS()) || undefined
- })
-
- e.sender.sendShared(messages.INITIALIZE_WINDOW, mem)
- if (action.cb) {
- action.cb()
- }
- })
-
- win.on('ready-to-show', () => {
- win.show()
- })
-
- win.loadURL(appUrlUtil.getBraveExtIndexHTML())
+ // window does not need to receive frames as part of initial state
+ immutableWindowState = clearFramesFromWindowState(immutableWindowState)
+ // allow override of defaults with incoming action argument
+ const windowOptions = Object.assign(windowProps, browserOpts)
+ // instruct muon to create window
+ windows.createWindow(windowOptions, parentWindow, isMaximized, frames, immutableWindowState, true, action.cb)
})
-
return state
}
@@ -325,11 +309,14 @@ const windowsReducer = (state, action, immutableAction) => {
state = windows.init(state, action)
break
case appConstants.APP_NEW_WINDOW:
- state = createWindow(state, action)
+ state = handleCreateWindowAction(state, action)
break
case appConstants.APP_WINDOW_READY:
windows.windowReady(action.get('windowId'))
break
+ case appConstants.APP_WINDOW_RENDERED:
+ windows.windowRendered(action.get('windowId'))
+ break
case appConstants.APP_TAB_UPDATED:
if (immutableAction.getIn(['changeInfo', 'pinned']) != null) {
setImmediate(() => {
diff --git a/app/browser/windows.js b/app/browser/windows.js
index 5971c659c4b..0d0d731f11c 100644
--- a/app/browser/windows.js
+++ b/app/browser/windows.js
@@ -2,7 +2,8 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
-const {app, BrowserWindow, ipcMain} = require('electron')
+const electron = require('electron')
+const Immutable = require('immutable')
const appActions = require('../../js/actions/appActions')
const appUrlUtil = require('../../js/lib/appUrlUtil')
const {getLocationIfPDF} = require('../../js/lib/urlutil')
@@ -10,12 +11,20 @@ const debounce = require('../../js/lib/debounce')
const {getSetting} = require('../../js/settings')
const locale = require('../locale')
const LocalShortcuts = require('../localShortcuts')
+const {initWindowCacheState} = require('../sessionStoreShutdown')
const {makeImmutable} = require('../common/state/immutableUtil')
const {getPinnedTabsByWindowId} = require('../common/state/tabState')
const messages = require('../../js/constants/messages')
const settings = require('../../js/constants/settings')
+const config = require('../../js/constants/config')
+const appDispatcher = require('../../js/dispatcher/appDispatcher')
+const platformUtil = require('../common/lib/platformUtil')
const windowState = require('../common/state/windowState')
const pinnedSitesState = require('../common/state/pinnedSitesState')
+const {zoomLevel} = require('../common/constants/toolbarUserInterfaceScale')
+
+const isDarwin = platformUtil.isDarwin()
+const {app, BrowserWindow, ipcMain} = electron
// TODO(bridiver) - set window uuid
let currentWindows = {}
@@ -111,6 +120,26 @@ const updatePinnedTabs = (win) => {
}
}
+function showDeferredShowWindow (win) {
+ win.show()
+ if (win.__shouldFullscreen) {
+ // this timeout helps with an issue that
+ // when a user is loading from state, and
+ // has many full screen windows and non fullscreen windows
+ // the non fullscreen windows can get opened on top of the fullscreen
+ // spaces because macOS has switched away from the desktop space
+ setTimeout(() => {
+ win.setFullScreen(true)
+ }, 100)
+ } else if (win.__shouldMaximize) {
+ win.maximize()
+ }
+ // reset temporary properties on win object
+ win.__showWhenRendered = undefined
+ win.__shouldFullscreen = undefined
+ win.__shouldMaximize = undefined
+}
+
const api = {
init: (state, action) => {
app.on('browser-window-created', function (event, win) {
@@ -317,6 +346,19 @@ const api = {
})
},
+ windowRendered: (windowIdOrWin) => {
+ setImmediate(() => {
+ const win = windowIdOrWin instanceof electron.BrowserWindow
+ ? windowIdOrWin
+ : currentWindows[windowIdOrWin]
+ if (win && win.__showWhenRendered && !win.isDestroyed() && !win.isVisible()) {
+ // window is hidden by default until we receive 'ready' message,
+ // so show it now
+ showDeferredShowWindow(win)
+ }
+ })
+ },
+
closeWindow: (windowId) => {
let win = api.getWindow(windowId)
try {
@@ -330,6 +372,108 @@ const api = {
}
},
+ createWindow: function (windowOptionsIn, parentWindow, maximized, frames, immutableState = Immutable.Map(), hideUntilRendered = true, cb = null) {
+ const defaultOptions = {
+ // hide the window until the window reports that it is rendered
+ show: true,
+ fullscreenable: true
+ }
+ const windowOptions = Object.assign(
+ defaultOptions,
+ windowOptionsIn
+ )
+ // will only hide until rendered if the options specify to show window
+ // so that a caller can control showing the window themselves with the option { show: false }
+ const showWhenRendered = hideUntilRendered && windowOptions.show
+ if (showWhenRendered) {
+ // prevent browserwindow from opening window immediately
+ windowOptions.show = false
+ }
+ // normally macOS will open immediately-created windows from fullscreen
+ // parent windows as fullscreen
+ // but if we are showing the window async, we will set the window
+ // fullscreen once it is ready to be shown
+ // (windowOptionsIn.fullscreen may already be set when loading from saved state,
+ // so this just sets it for other scenarios)
+ if (showWhenRendered && isDarwin && parentWindow && parentWindow.isFullScreen()) {
+ windowOptions.fullscreen = true
+ }
+ // if delaying window show, remember if the window should be opened fullscreen
+ // and remove the fullscreen property for now
+ // (otherwise the window will be shown immediately by macOS / muon)
+ let fullscreenWhenRendered = false
+ if (showWhenRendered && windowOptions.fullscreen) {
+ windowOptions.fullscreen = false
+ fullscreenWhenRendered = true
+ }
+ // create window with Url to renderer
+ const win = new electron.BrowserWindow(windowOptions)
+ win.loadURL(appUrlUtil.getBraveExtIndexHTML())
+ // TODO: pass UUID
+ initWindowCacheState(win.id, immutableState)
+ // let the windowReady handler know to show the window
+ win.__showWhenRendered = showWhenRendered
+ if (win.__showWhenRendered) {
+ // let the windowReady handler know to set the window state
+ win.__shouldFullscreen = fullscreenWhenRendered
+ win.__shouldMaximize = maximized
+ // the window is hidden until render, but we'll check to see
+ // if it is shown in a timeout as, if the window errors, it won't send
+ // the message to ask to be shown
+ // in those cases, we want to still show it, so that the user can find the error message
+ setTimeout(() => {
+ if (win && !win.isDestroyed() && !win.isVisible()) {
+ showDeferredShowWindow(win)
+ }
+ }, config.windows.timeoutToShowWindowMs)
+ } else {
+ // window should be shown already
+ // manual maximize
+ if (maximized) {
+ win.maximize()
+ }
+ // NOTE: we don't need to fullscreen manually since it's specified in options
+ // passed to BrowserWindow constructor
+ }
+ // let store know there's a new window
+ // so it can subscrive to state updates
+ appDispatcher.registerWindow(win, win.webContents)
+ // when window has finished loading, assume it has communications
+ // handler setup, and then send state
+ win.webContents.on('did-finish-load', (e) => {
+ const appStore = require('../../js/stores/appStore')
+ const toolbarUserInterfaceScale = getSetting(settings.TOOLBAR_UI_SCALE)
+ win.webContents.setZoomLevel(zoomLevel[toolbarUserInterfaceScale] || 0.0)
+
+ const position = win.getPosition()
+ const size = win.getSize()
+ const windowState = (immutableState && immutableState.toJS()) || undefined
+ const mem = muon.shared_memory.create({
+ windowValue: {
+ disposition: windowOptions.disposition,
+ id: win.id,
+ focused: win.isFocused(),
+ left: position[0],
+ top: position[1],
+ height: size[1],
+ width: size[0]
+ },
+ appState: appStore.getLastEmittedState().toJS(),
+ windowState,
+ // TODO: dispatch frame create action on appStore, as this is what the window does anyway
+ // ...and do it after the window has rendered
+ frames
+ })
+
+ e.sender.sendShared(messages.INITIALIZE_WINDOW, mem)
+ // TODO: remove callback, use store action, returning a new window UUID from this function
+ if (cb) {
+ cb()
+ }
+ })
+ return win
+ },
+
getWindow: (windowId) => {
return currentWindows[windowId]
},
diff --git a/js/actions/appActions.js b/js/actions/appActions.js
index b81a533cc6c..f30e6b54244 100644
--- a/js/actions/appActions.js
+++ b/js/actions/appActions.js
@@ -48,6 +48,16 @@ const appActions = {
})
},
+ windowRendered: function (windowId) {
+ dispatch({
+ actionType: appConstants.APP_WINDOW_RENDERED,
+ windowId,
+ queryInfo: {
+ windowId
+ }
+ })
+ },
+
closeWindow: function (windowId) {
dispatch({
actionType: appConstants.APP_CLOSE_WINDOW,
diff --git a/js/constants/appConstants.js b/js/constants/appConstants.js
index 4a4a2e4a189..6fec46fee43 100644
--- a/js/constants/appConstants.js
+++ b/js/constants/appConstants.js
@@ -7,6 +7,7 @@ const _ = null
const appConstants = {
APP_NEW_WINDOW: _,
APP_WINDOW_READY: _,
+ APP_WINDOW_RENDERED: _,
APP_CLOSE_WINDOW: _,
APP_WINDOW_CLOSED: _,
APP_WINDOW_CREATED: _,
diff --git a/js/constants/config.js b/js/constants/config.js
index 11cfac53e5e..41a7ff5e4d0 100644
--- a/js/constants/config.js
+++ b/js/constants/config.js
@@ -97,5 +97,8 @@ module.exports = {
tabs: {
maxAllowedNewSessions: 9
},
+ windows: {
+ timeoutToShowWindowMs: 5000
+ },
iconSize: 16
}
diff --git a/js/entry.js b/js/entry.js
index e0e8c4dac61..a31926b4088 100644
--- a/js/entry.js
+++ b/js/entry.js
@@ -85,9 +85,13 @@ ipc.on(messages.INITIALIZE_WINDOW, (e, mem) => {
windowStore.state = newState
generateTabs(newState, message.frames, windowValue.id)
appActions.windowReady(windowValue.id, windowValue)
- ReactDOM.render(, document.getElementById('appContainer'))
+ ReactDOM.render(, document.getElementById('appContainer'), fireOnReactRender.bind(null, windowValue))
})
+const fireOnReactRender = (windowValue) => {
+ appActions.windowRendered(windowValue.id)
+}
+
const generateTabs = (windowState, frames, windowId) => {
const activeFrameKey = windowState.get('activeFrameKey')
diff --git a/test/unit/app/browser/reducers/downloadsReducerTest.js b/test/unit/app/browser/reducers/downloadsReducerTest.js
index d2bd0a0a1a5..0baffce3ad9 100644
--- a/test/unit/app/browser/reducers/downloadsReducerTest.js
+++ b/test/unit/app/browser/reducers/downloadsReducerTest.js
@@ -148,20 +148,26 @@ describe('downloadsReducer', function () {
})
describe('APP_DOWNLOAD_REDOWNLOADED', function () {
- it('should redownload the same URL', function (cb) {
- const win = {
- webContents: {
- downloadURL: function () {
- }
+ const win = {
+ webContents: {
+ downloadURL: function () {
}
}
+ }
+ let spy
+ before(() => {
+ spy = sinon.stub(fakeElectron.BrowserWindow, 'getFocusedWindow', (path) => {
+ return win
+ })
+ })
+ after(() => {
+ spy.restore()
+ })
+ it('should redownload the same URL', function (cb) {
sinon.stub(win.webContents, 'downloadURL', (redownloadUrl) => {
assert.equal(redownloadUrl, downloadUrl)
cb()
})
- sinon.stub(fakeElectron.BrowserWindow, 'getFocusedWindow', (path) => {
- return win
- })
const oldState = oneDownloadWithState(CANCELLED)
downloadsReducer(oldState, {actionType: appConstants.APP_DOWNLOAD_REDOWNLOADED, downloadId: downloadId(oldState)})
})
diff --git a/test/unit/app/browser/reducers/windowsReducerTest.js b/test/unit/app/browser/reducers/windowsReducerTest.js
index 1809eb50925..a343056f272 100644
--- a/test/unit/app/browser/reducers/windowsReducerTest.js
+++ b/test/unit/app/browser/reducers/windowsReducerTest.js
@@ -6,8 +6,9 @@
const mockery = require('mockery')
const sinon = require('sinon')
const Immutable = require('immutable')
-const assert = require('assert')
+const { assert } = require('chai')
const fakeAdBlock = require('../../../lib/fakeAdBlock')
+const FakeElectronDisplay = require('../../../lib/fakeElectronDisplay')
const appConstants = require('../../../../../js/constants/appConstants')
require('../../../braveUnit')
@@ -20,36 +21,192 @@ describe('windowsReducer unit test', function () {
maybeCreateWindow: (state, action) => state
}
+ const fakeWindowApi = {
+ createWindow: () => {}
+ }
+
+ const fakePlatformUtil = {
+ isDarwin: () => true,
+ isWindows: () => false
+ }
+
const state = Immutable.fromJS({
windows: [],
defaultWindowParams: {}
})
-
+ let fakeTimers
before(function () {
mockery.enable({
warnOnReplace: false,
warnOnUnregistered: false,
useCleanCache: true
})
+ fakeTimers = sinon.useFakeTimers()
mockery.registerMock('electron', fakeElectron)
mockery.registerMock('ad-block', fakeAdBlock)
mockery.registerMock('../../common/state/windowState', fakeWindowState)
+ mockery.registerMock('../windows', fakeWindowApi)
+ mockery.registerMock('../../common/lib/platformUtil', fakePlatformUtil)
windowsReducer = require('../../../../../app/browser/reducers/windowsReducer')
})
after(function () {
mockery.disable()
+ fakeTimers.restore()
})
- describe('APP_WINDOW_UPDATED', function () {
+ describe('APP_NEW_WINDOW', function () {
let spy
+ before(function () {
+ spy = sinon.spy(fakeWindowApi, 'createWindow')
+ })
+ afterEach(function () {
+ spy.reset()
+ })
+ after(function () {
+ spy.restore()
+ })
+ const sampleFrame1 = {
+ location: 'http://mysite.com'
+ }
+ const sampleFrame2 = {
+ location: 'http://mysite2.com'
+ }
+
+ it('creates a window, with a single specified frame', function () {
+ const action = {
+ actionType: appConstants.APP_NEW_WINDOW,
+ frameOpts: sampleFrame1
+ }
+ windowsReducer(state, action)
+ fakeTimers.tick(0)
+ // ensure the window api was asked to create the frame
+ const actualCreateWindowFrameArg = spy.args[0][3]
+ assert.deepEqual(actualCreateWindowFrameArg, [ sampleFrame1 ])
+ })
+
+ it('creates a window, with multiple specified frames', function () {
+ const action = {
+ actionType: appConstants.APP_NEW_WINDOW,
+ frameOpts: [ sampleFrame1, sampleFrame2 ]
+ }
+ windowsReducer(state, action)
+ fakeTimers.tick(0)
+ // ensure the window api was asked to create the frame
+ const actualCreateWindowFrameArg = spy.args[0][3]
+ assert.deepEqual(actualCreateWindowFrameArg, [ sampleFrame1, sampleFrame2 ])
+ })
+
+ it('creates a window taking up the entire screen workarea by default', function () {
+ const fakeDisplay = new FakeElectronDisplay()
+ const action = {
+ actionType: appConstants.APP_NEW_WINDOW,
+ frameOpts: [ sampleFrame1, sampleFrame2 ]
+ }
+ windowsReducer(state, action)
+ fakeTimers.tick(0)
+ // ensure the window api was asked to create the frame
+ const { width, height } = spy.args[0][0]
+ assert.deepEqual(fakeDisplay.workAreaSize, { width, height })
+ })
+
+ it('allows a window size to be exactly specified', function () {
+ const expectedDimensions = { width: 600, outerHeight: 700 }
+ const action = {
+ actionType: appConstants.APP_NEW_WINDOW,
+ browserOpts: Object.assign({}, expectedDimensions)
+ }
+ windowsReducer(state, action)
+ fakeTimers.tick(0)
+ const windowOptions = spy.args[0][0]
+ assert.propertyVal(windowOptions, 'width', expectedDimensions.width)
+ assert.propertyVal(windowOptions, 'height', expectedDimensions.outerHeight)
+ })
+
+ it('allows a window size to be specified, ignoring navBar height', function () {
+ const expectedDimensions = { width: 600, height: 700 }
+ const action = {
+ actionType: appConstants.APP_NEW_WINDOW,
+ browserOpts: Object.assign({}, expectedDimensions)
+ }
+ windowsReducer(state, action)
+ fakeTimers.tick(0)
+ const windowOptions = spy.args[0][0]
+ // width should be exact
+ assert.propertyVal(windowOptions, 'width', expectedDimensions.width)
+ // height should have 'navBar' added on to it
+ assert.isAbove(windowOptions.height, expectedDimensions.height)
+ // but should not be larger than screen height
+ assert.isBelow(windowOptions.height, new FakeElectronDisplay().workAreaSize.height)
+ })
+
+ it('positions the window by the mouse cursor when asked', function () {
+ const expectedPosition = fakeElectron.screen.getCursorScreenPoint()
+ const action = {
+ actionType: appConstants.APP_NEW_WINDOW,
+ browserOpts: { positionByMouseCursor: true }
+ }
+ windowsReducer(state, action)
+ fakeTimers.tick(0)
+ const windowOptions = spy.args[0][0]
+ assert.propertyVal(windowOptions, 'x', expectedPosition.x)
+ assert.propertyVal(windowOptions, 'y', expectedPosition.y)
+ })
+
+ it('positions the window to an exact point when asked', function () {
+ const expectedPosition = { x: 500, y: 600 }
+ const action = {
+ actionType: appConstants.APP_NEW_WINDOW,
+ browserOpts: {
+ x: expectedPosition.x,
+ y: expectedPosition.y
+ }
+ }
+ windowsReducer(state, action)
+ fakeTimers.tick(0)
+ const windowOptions = spy.args[0][0]
+ assert.propertyVal(windowOptions, 'x', expectedPosition.x)
+ assert.propertyVal(windowOptions, 'y', expectedPosition.y)
+ })
+
+ it('restores a maximized window', function () {
+ const action = {
+ actionType: appConstants.APP_NEW_WINDOW,
+ restoredState: {
+ windowInfo: { state: 'maximized' }
+ }
+ }
+ windowsReducer(state, action)
+ fakeTimers.tick(0)
+ const actualIsMaximized = spy.args[0][2]
+ assert.isTrue(actualIsMaximized)
+ })
+
+ it('does not maximize a window by default', function () {
+ const action = {
+ actionType: appConstants.APP_NEW_WINDOW
+ }
+ windowsReducer(state, action)
+ fakeTimers.tick(0)
+ const actualIsMaximized = spy.args[0][2]
+ assert.isFalse(actualIsMaximized)
+ })
+ })
+
+ describe('APP_WINDOW_UPDATED', function () {
+ let spy
+ before(function () {
+ spy = sinon.spy(fakeWindowState, 'maybeCreateWindow')
+ })
afterEach(function () {
+ spy.reset()
+ })
+ after(function () {
spy.restore()
})
it('null case', function () {
- spy = sinon.spy(fakeWindowState, 'maybeCreateWindow')
const newState = windowsReducer(state, {
actionType: appConstants.APP_WINDOW_UPDATED
})
@@ -58,7 +215,6 @@ describe('windowsReducer unit test', function () {
})
it('updateDefault is false (we shouldnt update it)', function () {
- spy = sinon.spy(fakeWindowState, 'maybeCreateWindow')
const newState = windowsReducer(state, {
actionType: appConstants.APP_WINDOW_UPDATED,
updateDefault: false
@@ -68,7 +224,6 @@ describe('windowsReducer unit test', function () {
})
it('updateDefault is true', function () {
- spy = sinon.spy(fakeWindowState, 'maybeCreateWindow')
const newState = windowsReducer(state, {
actionType: appConstants.APP_WINDOW_UPDATED,
updateDefault: true,
diff --git a/test/unit/app/browser/windowsTest.js b/test/unit/app/browser/windowsTest.js
index bec84f1cd82..a49b8b4b724 100644
--- a/test/unit/app/browser/windowsTest.js
+++ b/test/unit/app/browser/windowsTest.js
@@ -2,16 +2,33 @@
const mockery = require('mockery')
const sinon = require('sinon')
const Immutable = require('immutable')
-const assert = require('assert')
+const { assert } = require('chai')
const fakeElectron = require('../../lib/fakeElectron')
+const FakeWindow = require('../../lib/fakeWindow')
const fakeAdBlock = require('../../lib/fakeAdBlock')
+const fakePlatformUtil = {
+ isDarwin: () => true,
+ isWindows: () => false
+}
+
+const fakeAppDispatcher = {
+ registerWindow: () => {
+ }
+}
+
require('../../braveUnit')
describe('window API unit tests', function () {
let windows, appActions
let appStore
let defaultState, createTabState, tabCloseState
+ const windowCreateTimeout = 5000
+ let browserWindowSpy
+ let browserShowSpy
+ let fakeTimers
+ let setFullscreenSpy
+ let maximizeSpy
before(function () {
mockery.enable({
@@ -66,7 +83,8 @@ describe('window API unit tests', function () {
mockery.registerMock('electron', fakeElectron)
mockery.registerMock('ad-block', fakeAdBlock)
mockery.registerMock('../../js/stores/appStore', appStore)
-
+ mockery.registerMock('../../common/lib/platformUtil', fakePlatformUtil)
+ mockery.registerMock('../../js/dispatcher/appDispatcher', fakeAppDispatcher)
windows = require('../../../../app/browser/windows')
appActions = require('../../../../js/actions/appActions')
})
@@ -75,6 +93,33 @@ describe('window API unit tests', function () {
mockery.disable()
})
+ // BrowserWindow related hooks
+ before(function () {
+ browserShowSpy = sinon.spy(FakeWindow.prototype, 'show')
+ browserWindowSpy = sinon.spy(fakeElectron, 'BrowserWindow')
+ setFullscreenSpy = sinon.spy(FakeWindow.prototype, 'setFullScreen')
+ maximizeSpy = sinon.spy(FakeWindow.prototype, 'maximize')
+ })
+
+ beforeEach(function () {
+ fakeTimers = sinon.useFakeTimers()
+ })
+
+ after(function () {
+ browserWindowSpy.restore()
+ browserShowSpy.restore()
+ setFullscreenSpy.restore()
+ maximizeSpy.restore()
+ })
+
+ afterEach(function () {
+ fakeTimers.restore()
+ browserWindowSpy.reset()
+ browserShowSpy.reset()
+ setFullscreenSpy.reset()
+ maximizeSpy.reset()
+ })
+
describe('privateMethods', function () {
let updatePinnedTabs
let createTabRequestedSpy, tabCloseRequestedSpy
@@ -121,5 +166,119 @@ describe('window API unit tests', function () {
assert.equal(tabCloseRequestedSpy.calledOnce, true)
})
})
+
+ describe('createWindow', function () {
+ describe('show window immediately', function () {
+ it('creates a window immediately visible, when asked not to hide until render', function () {
+ windows.createWindow({ }, null, false, null, Immutable.Map(), false)
+ const windowOptions = browserWindowSpy.args[0][0]
+ assert.equal(browserWindowSpy.callCount, 1)
+ // BrowserWindow ctor options.show is true by default
+ // so make sure we're not passing in false
+ assert.isNotFalse(windowOptions.show)
+ })
+ it('maximizes the window, when specified', function () {
+ windows.createWindow({ }, null, true, null, Immutable.Map(), false)
+ // check if window is made fullscreen
+ assert.propertyVal(maximizeSpy, 'callCount', 1)
+ })
+ })
+
+ describe('hide window until render', function () {
+ it('creates a window hidden at first', function () {
+ windows.createWindow({ }, null, false, null, Immutable.Map(), true)
+ fakeTimers.tick(windowCreateTimeout)
+ assert.equal(browserWindowSpy.callCount, 1)
+ const windowOptions = browserWindowSpy.args[0][0]
+ assert.isFalse(windowOptions.show)
+ })
+
+ it('shows the window after a timeout', function () {
+ windows.createWindow({ }, null, false, null, Immutable.Map(), true)
+ assert.equal(browserWindowSpy.callCount, 1)
+ assert.equal(browserShowSpy.callCount, 0)
+ fakeTimers.tick(windowCreateTimeout)
+ assert.equal(browserShowSpy.callCount, 1)
+ })
+
+ it('replicates macOS functionality by creating a fullscreen window from a parent fullscreen window', function () {
+ const parentWindow = new FakeWindow()
+ parentWindow.isFullScreen = () => true
+ windows.createWindow({ }, parentWindow, false, null, Immutable.Map(), true)
+ // allow the window to be created after timeout
+ const windowOptions = browserWindowSpy.args[0][0]
+ // should not ask OS to go fullscreen when window isn't shown yet
+ assert.isNotTrue(windowOptions.fullscreen)
+ assert.equal(browserWindowSpy.callCount, 1)
+ // should store that the window should go fullscreen when rendered
+ assert.isObject(browserWindowSpy.returnValues[0])
+ assert.propertyVal(browserWindowSpy.returnValues[0], '__shouldFullscreen', true)
+ })
+ })
+ })
+
+ describe('windowRendered', function () {
+ it('shows the window if it is not visible', function () {
+ // create a window that is set to show on render
+ const win = windows.createWindow({ }, null, false, null, Immutable.Map(), true)
+ assert.equal(browserWindowSpy.callCount, 1)
+ // make sure window has not been shown
+ const windowOptions = browserWindowSpy.args[0][0]
+ assert.isFalse(windowOptions.show)
+ assert.equal(browserShowSpy.callCount, 0)
+ // a little time elapsed, but not enough to timeout window showing
+ fakeTimers.tick(Math.floor(windowCreateTimeout / 2))
+ // make sure window has not yet been shown
+ assert.equal(browserShowSpy.callCount, 0)
+ // notify rendered
+ windows.windowRendered(win)
+ // windowRendered schedules on setImmediate
+ fakeTimers.tick(0)
+ // check if window is shown
+ assert.propertyVal(browserShowSpy, 'callCount', 1)
+ })
+
+ it('makes the window fullscreen if specified', function () {
+ const parentWindow = new FakeWindow()
+ parentWindow.isFullScreen = () => true
+ // create a window that is set to show on render
+ const win = windows.createWindow({ }, parentWindow, false, null, Immutable.Map(), true)
+ assert.equal(browserWindowSpy.callCount, 1)
+ // make sure window has not been shown
+ const windowOptions = browserWindowSpy.args[0][0]
+ assert.isFalse(windowOptions.show)
+ assert.equal(browserShowSpy.callCount, 0)
+ // a little time elapsed, but not enough to timeout window showing
+ fakeTimers.tick(Math.floor(windowCreateTimeout / 2))
+ assert.propertyVal(setFullscreenSpy, 'callCount', 0)
+ // notify rendered
+ windows.windowRendered(win)
+ // windowRendered schedules on setImmediate
+ // setfullscreen performs action after 100ms timeout
+ fakeTimers.tick(100)
+ // check if window is made fullscreen
+ assert.propertyVal(setFullscreenSpy, 'callCount', 1)
+ })
+
+ it('makes the window maximized if specified', function () {
+ // create a window that is set to show on render
+ const win = windows.createWindow({ }, null, true, null, Immutable.Map(), true)
+ assert.equal(browserWindowSpy.callCount, 1)
+ // make sure window has not been shown
+ const windowOptions = browserWindowSpy.args[0][0]
+ assert.isFalse(windowOptions.show)
+ assert.equal(browserShowSpy.callCount, 0)
+ // a little time elapsed, but not enough to timeout window showing
+ fakeTimers.tick(Math.floor(windowCreateTimeout / 2))
+ assert.propertyVal(maximizeSpy, 'callCount', 0)
+ // notify rendered
+ windows.windowRendered(win)
+ // windowRendered schedules on setImmediate
+ fakeTimers.tick(0)
+ // windowRendered schedules on setImmediate
+ // check if window is made fullscreen
+ assert.propertyVal(maximizeSpy, 'callCount', 1)
+ })
+ })
})
})
diff --git a/test/unit/lib/fakeElectron.js b/test/unit/lib/fakeElectron.js
index f33b52f7dcc..82faf8d533b 100644
--- a/test/unit/lib/fakeElectron.js
+++ b/test/unit/lib/fakeElectron.js
@@ -1,4 +1,6 @@
const {EventEmitter} = require('events')
+const FakeElectronDisplay = require('./fakeElectronDisplay')
+const FakeElectronWindow = require('./fakeWindow')
const ipcMain = new EventEmitter()
ipcMain.send = ipcMain.emit
const fakeElectron = {
@@ -7,21 +9,7 @@ const fakeElectron = {
fakeElectron.remote.app.removeAllListeners()
fakeElectron.autoUpdater.removeAllListeners()
},
- BrowserWindow: {
- getFocusedWindow: function () {
- return {
- id: 1
- }
- },
- getActiveWindow: function () {
- return {
- id: 1
- }
- },
- getAllWindows: function () {
- return [{id: 1}]
- }
- },
+ BrowserWindow: FakeElectronWindow,
MenuItem: class {
constructor (template) {
this.template = template
@@ -100,7 +88,16 @@ const fakeElectron = {
autoUpdater: new EventEmitter(),
importer: {
on: () => {}
+ },
+ screen: {
+ getDisplayMatching: () => new FakeElectronDisplay(),
+ getPrimaryDisplay: () => new FakeElectronDisplay(),
+ getDisplayNearestPoint: () => new FakeElectronDisplay(),
+ getAllDisplays: () => [new FakeElectronDisplay()],
+ getCursorScreenPoint: () => ({ x: 200, y: 200 })
}
}
+// function getFakeDisplay
+
module.exports = fakeElectron
diff --git a/test/unit/lib/fakeElectronDisplay.js b/test/unit/lib/fakeElectronDisplay.js
new file mode 100644
index 00000000000..a774dc2b762
--- /dev/null
+++ b/test/unit/lib/fakeElectronDisplay.js
@@ -0,0 +1,25 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+class FakeElectronDisplay {
+ constructor () {
+ this.id = 1
+ this.bounds = {
+ x: 0,
+ y: 0,
+ width: 1280,
+ height: 1100
+ }
+ this.size = {
+ width: 1280,
+ height: 1100
+ }
+ this.workAreaSize = {
+ width: 1100,
+ height: 1000
+ }
+ }
+}
+
+module.exports = FakeElectronDisplay
diff --git a/test/unit/lib/fakeWindow.js b/test/unit/lib/fakeWindow.js
index be694a57749..ed08fc3245f 100644
--- a/test/unit/lib/fakeWindow.js
+++ b/test/unit/lib/fakeWindow.js
@@ -3,17 +3,65 @@
* You can obtain one at http://mozilla.org/MPL/2.0/. */
const EventEmitter = require('events')
+const util = require('util')
-class FakeWindow extends EventEmitter {
- constructor (id) {
- super()
- this.id = id
- this.webContents = Object.assign(new EventEmitter())
- this.webContents.send = this.webContents.emit
- }
- getId () {
- return this.id
+// cannot be a class since sinon has
+// trouble stubbing the constructtor for a class
+function FakeWindow (id) {
+ this.id = id
+ this.webContents = Object.assign(new EventEmitter())
+ this.webContents.send = this.webContents.emit
+ this._isVisible = false
+}
+
+util.inherits(FakeWindow, EventEmitter)
+
+//
+// instance functions
+//
+
+FakeWindow.prototype.getId = function () {
+ return this.id
+}
+FakeWindow.prototype.getBounds = function () {
+ return {
+ x: 10,
+ y: 10,
+ width: 800,
+ height: 600
}
}
+FakeWindow.prototype.isDestroyed = function () {
+ return false
+}
+FakeWindow.prototype.loadURL = function (url) { }
+FakeWindow.prototype.show = function () {
+ this._isVisible = true
+}
+FakeWindow.prototype.hide = function () {
+ this._isVisible = false
+}
+FakeWindow.prototype.setFullScreen = function () { }
+FakeWindow.prototype.maximize = function () { }
+FakeWindow.prototype.isVisible = function () {
+ return this._isVisible
+}
+
+//
+// static functions
+//
+
+FakeWindow.getFocusedWindow = function () {
+ return new FakeWindow(1)
+}
+FakeWindow.getActiveWindow = function () {
+ return new FakeWindow(1)
+}
+FakeWindow.getAllWindows = function () {
+ return [new FakeWindow(1)]
+}
+FakeWindow.fromWebContents = function () {
+ return new FakeWindow(1)
+}
module.exports = FakeWindow