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

Commit

Permalink
Merge pull request #145 from brave/window-opener-fix
Browse files Browse the repository at this point in the history
better handling of frames on window start
  • Loading branch information
bbondy committed Jan 13, 2016
2 parents 2c810ec + 3066492 commit f446ef4
Show file tree
Hide file tree
Showing 15 changed files with 714 additions and 118 deletions.
1 change: 0 additions & 1 deletion docs/state.md
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,6 @@ WindowStore
activeMixedContent: boolean, // has active mixed content
passiveMixedContent: boolean, // has passive mixed content
},
parentWindowKey: number, // the key of the window this frame was opened from
parentFrameKey: number, // the key of the frame this frame was opened from
contextMenuDetail: {...},
modalPromptDetail: {...},
Expand Down
53 changes: 31 additions & 22 deletions js/components/frame.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,19 +27,22 @@ class Frame extends ImmutableComponent {
}

createWebview () {
while (this.webviewContainer.firstChild) {
this.webviewContainer.removeChild(this.webviewContainer.firstChild)
}
// Create the webview dynamically because React doesn't whitelist all
// of the attributes we need.
this.webview = document.createElement('webview')
this.webview = this.webview || document.createElement('webview')
this.webview.setAttribute('preload', 'content/webviewPreload.js')
if (this.props.frame.get('isPrivate')) {
this.webview.setAttribute('partition', 'private-1')
}
if (this.props.frame.get('guestInstanceId')) {
this.webview.setAttribute('data-guest-instance-id', this.props.frame.get('guestInstanceId'))
}
this.webview.setAttribute('src', this.props.frame.get('src'))
this.webviewContainer.appendChild(this.webview)
this.addEventListeners()

if (!this.webviewContainer.firstChild) {
this.webviewContainer.appendChild(this.webview)
this.addEventListeners()
}
}

componentDidMount () {
Expand All @@ -51,8 +54,7 @@ class Frame extends ImmutableComponent {
if (didSrcChange) {
this.createWebview()
}
if ((this.props.isActive && !prevProps.isActive && !this.props.frame.getIn(['navbar', 'urlbar', 'focused'])) ||
(this.props.isActive && didSrcChange)) {
if (this.props.isActive && !prevProps.isActive) {
this.webview.focus()
}
const activeShortcut = this.props.frame.get('activeShortcut')
Expand Down Expand Up @@ -104,33 +106,35 @@ class Frame extends ImmutableComponent {
}

addEventListeners () {
// @see <a href="https://github.com/atom/electron/blob/master/docs/api/web-view-tag.md#event-new-window">new-window event</a>
this.webview.addEventListener('focus', this.onFocus.bind(this))
this.webview.addEventListener('new-window', (e) => {
// TODO handle _top, _parent and named frames
// also popup blocking and security restrictions!!
// @see <a href="https://github.com/atom/electron/blob/master/docs/api/web-view-tag.md#event-new-window">new-window event</a>
this.webview.addEventListener('new-window', (e, url, frameName, disposition, options) => {
e.preventDefault()

let guestInstanceId = e.options.webPreferences && e.options.webPreferences.guestInstanceId
let windowOptions = e.options.windowOptions || {}
windowOptions.parentWindowKey = remote.getCurrentWindow().id
windowOptions.disposition = e.disposition

// @see <a href="http://www.w3.org/TR/html5/browsers.html#dom-open">dom open</a>
// @see <a href="http://www.w3.org/TR/html-markup/datatypes.html#common.data.browsing-context-name-or-keyword">browsing context name or keyword</a>
if (e.frameName.toLowerCase() === '_self') {
WindowActions.loadUrl(this.props.frame, e.url)
} else if (e.disposition === 'new-window' || e.frameName.toLowerCase() === '_blank') {
if (e.disposition === 'new-window' || e.disposition === 'new-popup') {
AppActions.newWindow({
location: e.url,
parentFrameKey: this.props.frame.get('key'),
parentWindowKey: remote.getCurrentWindow().id
}, e.options)
guestInstanceId
}, windowOptions)
} else {
WindowActions.newFrame({
location: e.url,
parentFrameKey: this.props.frame.get('key'),
parentWindowKey: remote.getCurrentWindow().id,
openInForeground: e.disposition !== 'background-tab'
openInForeground: e.disposition !== 'background-tab',
guestInstanceId
})
}
})
this.webview.addEventListener('destroyed', (e) => {
WindowActions.closeFrame(this.props.frames, this.props.frame)
})
this.webview.addEventListener('close', () => {
// security restrictions here?
AppActions.closeWindow(remote.getCurrentWindow().id)
})
this.webview.addEventListener('enter-html-full-screen', () => {
Expand Down Expand Up @@ -162,6 +166,11 @@ class Frame extends ImmutableComponent {
this.webview.canGoBack(),
this.webview.canGoForward())
})
this.webview.addEventListener('did-navigate', (e) => {
if (this.props.isActive && this.webview.getURL() !== Config.defaultUrl) {
this.webview.focus()
}
})
this.webview.addEventListener('did-start-loading', () => {
WindowActions.onWebviewLoadStart(
this.props.frame)
Expand Down
7 changes: 1 addition & 6 deletions js/components/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,12 +30,6 @@ const FrameStateUtil = require('../state/frameStateUtil')

class Main extends ImmutableComponent {
componentDidMount () {
if (this.props.windowState.get('frames').isEmpty()) {
WindowActions.newFrame({
location: Config.defaultUrl
})
}

ipc.on(messages.STOP_LOAD, () => {
electron.remote.getCurrentWebContents().send(messages.SHORTCUT_ACTIVE_FRAME_STOP)
})
Expand Down Expand Up @@ -173,6 +167,7 @@ class Main extends ImmutableComponent {
sortedFrames.map(frame =>
<Frame
ref={`frame${frame.get('key')}`}
frames={this.props.windowState.get('frames')}
frame={frame}
key={frame.get('key')}
isPreview={frame.get('key') === this.props.windowState.get('previewFrameKey')}
Expand Down
15 changes: 12 additions & 3 deletions js/components/window.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ const Main = require('./main')
const ipc = global.require('electron').ipcRenderer
const messages = require('../constants/messages')
const SiteTags = require('../constants/siteTags')
const Config = require('../constants/config')

class Window extends React.Component {
constructor (props) {
Expand Down Expand Up @@ -46,9 +47,17 @@ class Window extends React.Component {
}

componentWillMount () {
this.props.frames.forEach((frame) => {
WindowActions.newFrame(frame)
})
if (this.props.frames.length === 0) {
WindowActions.newFrame({
location: Config.defaultUrl
})
} else {
WindowStore.suspend()
this.props.frames.forEach(frame => {
WindowActions.newFrame(frame)
})
WindowStore.resume()
}
}

render () {
Expand Down
2 changes: 1 addition & 1 deletion js/state/frameStateUtil.js
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,7 @@ export function addFrame (frames, frameOpts, newKey, activeFrameKey) {
isPinned: frameOpts.isPinned,
key: newKey,
parentFrameKey: frameOpts.parentFrameKey,
parentWindowKey: frameOpts.parentWindowKey,
guestInstanceId: frameOpts.guestInstanceId,
navbar: {
searchSuggestions: true,
focused: true,
Expand Down
16 changes: 9 additions & 7 deletions js/stores/appStore.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,10 +31,8 @@ function navbarHeight () {
return 75
}

const createWindow = (browserOpts, defaults, parentWindowKey) => {
browserOpts = browserOpts || {}
// clean up properties
delete browserOpts.webPreferences
const createWindow = (browserOpts, defaults) => {
let parentWindowKey = browserOpts.parentWindowKey

browserOpts.width = firstDefinedValue(browserOpts.width, browserOpts.innerWidth, defaults.width)
// height and innerHeight are the frame webview size
Expand Down Expand Up @@ -156,6 +154,7 @@ function windowDefaults () {
setDefaultWindowSize()

return {
show: false,
width: appState.get('defaultWindowWidth'),
height: appState.get('defaultWindowHeight'),
minWidth: 500,
Expand Down Expand Up @@ -191,9 +190,10 @@ const handleAppAction = (action) => {
appStore.emitChange()
break
case AppConstants.APP_NEW_WINDOW:
const frameOpts = action.frameOpts && action.frameOpts.toJS() || undefined
const browserOpts = action.browserOpts && action.browserOpts.toJS() || undefined
let mainWindow = createWindow(browserOpts, windowDefaults(), frameOpts && frameOpts.parentWindowKey)
const frameOpts = action.frameOpts && action.frameOpts.toJS()
const browserOpts = (action.browserOpts && action.browserOpts.toJS()) || {}

let mainWindow = createWindow(browserOpts, windowDefaults())
if (action.restoredState) {
mainWindow.webContents.once('dom-ready', () => {
mainWindow.webContents.send('restore-state', action.restoredState)
Expand Down Expand Up @@ -226,6 +226,8 @@ const handleAppAction = (action) => {
mainWindow.loadURL('file://' + __dirname + '/../../app/index.html?' + queryString)
}
appStore.emitChange()

mainWindow.show()
break
case AppConstants.APP_CLOSE_WINDOW:
let appWindow = BrowserWindow.fromId(action.appWindowId)
Expand Down
27 changes: 26 additions & 1 deletion js/stores/windowStore.js
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ let currentKey = 0
const incrementNextKey = () => ++currentKey

class WindowStore extends EventEmitter {

getState () {
return windowState
}
Expand All @@ -67,7 +68,11 @@ class WindowStore extends EventEmitter {
}

emitChange () {
this.emit(CHANGE_EVENT)
if (!this.suspended) {
this.emit(CHANGE_EVENT)
} else {
this.emitOnResume = true
}
}

addChangeListener (callback) {
Expand All @@ -77,6 +82,26 @@ class WindowStore extends EventEmitter {
removeChangeListener (callback) {
this.removeListener(CHANGE_EVENT, callback)
}

/**
* Temporarily suspend the emitting of change events for this store
* You can use this when you have multiple actions that can/should be accomplished in a single render
*/
suspend () {
this.suspended = true
}

/**
* Resume the emitting of change events
* A change event will be emitted if any updates were made while the store was suspended
*/
resume () {
this.suspended = false
if (this.emitOnResume) {
this.emitOnResume = false
this.emitChange()
}
}
}

const windowStore = new WindowStore()
Expand Down
10 changes: 7 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@
"description": "Brave laptop and desktop browser",
"main": "./app/index.js",
"scripts": {
"start": "node ./tools/start.js --debug=5858",
"start-brk": "node ./tools/start.js --debug-brk=5858",
"start": "node ./tools/start.js --debug=5858 -enable-logging --v=0 --enable-dcheck",
"start-brk": "node ./tools/start.js --debug-brk=5858 -enable-logging --v=0 --enable-dcheck",
"watch-all": "npm run watch & npm run watch-test",
"watch-test": "NODE_ENV=test webpack --watch",
"watch": "webpack-dev-server",
Expand Down Expand Up @@ -38,6 +38,10 @@
{
"name": "Aubrey Keus",
"email": "[email protected]"
},
{
"name": "Brian Johnson",
"email": "[email protected]"
}
],
"license": "MPL-2.0",
Expand Down Expand Up @@ -89,7 +93,7 @@
"node-static": "^0.7.7",
"node-uuid": "^1.4.7",
"pre-commit": "^1.1.2",
"spectron": "^0.35.3",
"spectron": "^0.36.0",
"standard": "^5.4.1",
"style-loader": "^0.13.0",
"webdriverio": "^3.3.0",
Expand Down
13 changes: 13 additions & 0 deletions test/fixtures/click_with_target.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<html>
<head>
<meta charset="UTF-8">
<title>click.with.target</title>
</head>
<body>
<a href="page1.html" id="none">none</a>
<a href="page1.html" id="name" target="name">name</a>
<a href="page2.html" id="name2" target="name">name2</a>
<a href="page1.html" id="_self" target="_self">self</a>
<iframe id="parent" src="iframe_target_parent.html"/>
</body>
</html>
10 changes: 10 additions & 0 deletions test/fixtures/iframe_target_parent.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<html>
<head>
<meta charset="UTF-8">
<title>iframe.target.parent</title>
</head>
<body>
<a href="page1.html" id="_parent" target="_parent">parent</a>
<iframe id="top" src="iframe_target_top.html"/>
</body>
</html>
9 changes: 9 additions & 0 deletions test/fixtures/iframe_target_top.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<html>
<head>
<meta charset="UTF-8">
<title>iframe.target.top</title>
</head>
<body>
<a href="page1.html" id="_top" target="_top">top</a>
</body>
</html>
45 changes: 45 additions & 0 deletions test/lib/brave.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,19 @@ chai.use(chaiAsPromised)

const Server = require('./server')

var promiseMapSeries = function (array, iterator) {
var length = array.length
var current = Promise.resolve()
var results = new Array(length)

for (var i = 0; i < length; ++i) {
current = results[i] = current.then(function (i) {
return iterator(array[i])
}.bind(undefined, i))
}
return Promise.all(results)
}

var exports = {

keys: {
Expand Down Expand Up @@ -127,6 +140,38 @@ var exports = {
return require('electron').remote.getCurrentWindow().setSize(width, height)
}, width, height).then((response) => response.value)
})

this.app.client.addCommand('windowParentByUrl', function (url, childSelector = 'webview') {
var context = this
return this.windowHandles().then(response => response.value).then(function (handles) {
return promiseMapSeries(handles, function (handle) {
return context.window(handle).getAttribute(childSelector, 'src').catch(() => '')
})
}).then(function (response) {
let index = response.indexOf(url)
if (index !== -1) {
return context.windowByIndex(index)
} else {
return undefined
}
})
})

this.app.client.addCommand('windowByUrl', function (url) {
var context = this
return this.windowHandles().then(response => response.value).then(function (handles) {
return promiseMapSeries(handles, function (handle) {
return context.window(handle).getUrl()
})
}).then(function (response) {
let index = response.indexOf(url)
if (index !== -1) {
return context.windowByIndex(index)
} else {
return undefined
}
})
})
},

startApp: function () {
Expand Down
8 changes: 8 additions & 0 deletions test/lib/server.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,14 @@ Server.prototype = {
return 'http://localhost:' + this.port + '/' + path
},

urlWithIpAddress: function (path) {
return 'http://127.0.0.1:' + this.port + '/' + path
},

urlOrigin: function () {
return 'http://localhost:' + this.port
},

/**
* Sends signal to stop child process and stop server.
*/
Expand Down
Loading

0 comments on commit f446ef4

Please sign in to comment.