diff --git a/app/content/webviewPreload.js b/app/content/webviewPreload.js index b21a17d0812..177900a65aa 100644 --- a/app/content/webviewPreload.js +++ b/app/content/webviewPreload.js @@ -27,3 +27,126 @@ ipc.on('zoom-reset', function () { browserZoomLevel = 0 webFrame.setZoomLevel(browserZoomLevel) }) + +/** + * Ensures a node replacement div is visible and has a proper zIndex + */ +function ensureNodeVisible (node) { + if (document.defaultView.getComputedStyle(node).display === 'none') { + node.style.display = '' + } + if (document.defaultView.getComputedStyle(node).zIndex === '-1') { + node.style.zIndex = '' + } +} + +/** + * Determines the ad size which should be shown + * It will first check the node's size and try to infer that way. + * If that is not possible it will rely on the iframeData + * + * @param node The node that is being replaced + * @param iframeData The known preprocessed iframeData for that node + */ +function getAdSize (node, iframeData) { + var acceptableAdSizes = [ + [728, 90], + [300, 250], + [160, 600], + [320, 50] + ] + for (var i = 0; i < acceptableAdSizes.length; i++) { + var adSize = acceptableAdSizes[i] + if (node.offsetWidth === adSize[0] && node.offsetHeight >= adSize[1] || + node.offsetWidth >= adSize[0] && node.offsetHeight === adSize[1]) { + return adSize + } + } + + if (iframeData) { + return [iframeData.width, iframeData.height] + } + + return null +} + +/** + * Processes a single node which is an ad + * + * @param node The node of the ad to process + * @param iframeData The iframe data of the node to process from the slimerJS bot + * @param placeholderUrl The vault URL with encoded user ID and session ID to use + */ +function processAdNode (node, iframeData, placeholderUrl) { + if (!node) { + return + } + + var adSize = getAdSize(node, iframeData) + // Could not determine the ad size, so just skip this replacement + if (!adSize) { + return + } + var srcUrl = placeholderUrl + '&width=' + encodeURIComponent(adSize[0]) + '&height=' + encodeURIComponent(adSize[1]) + if (node.tagName === 'IFRAME') { + node.src = srcUrl + } else { + while (node.firstChild) { + node.removeChild(node.firstChild) + } + var iframe = document.createElement('iframe') + iframe.style.padding = 0 + iframe.style.border = 0 + iframe.style.margin = 0 + iframe.style.width = adSize[0] + 'px' + iframe.style.height = adSize[1] + 'px' + iframe.src = srcUrl + node.appendChild(iframe) + ensureNodeVisible(node) + if (node.parentNode) { + ensureNodeVisible(node.parentNode) + if (node.parentNode) { + ensureNodeVisible(node.parentNode.parentNode) + } + } + } +} + +// Fires when the browser has ad replacement information to give +ipc.on('set-ad-div-candidates', function (e, adDivCandidates, placeholderUrl) { + // Keep a lookup for skipped common elements + var fallbackNodeDataForCommon = {} + + // Process all of the specific ad information for this page + adDivCandidates.forEach(function (iframeData) { + var selector = '[id="' + iframeData.replaceId + '"]' + var node = document.querySelector(selector) + if (!node) { + return + } + + // Skip over known common elements + if (iframeData.replaceId.startsWith('google_ads_iframe_') || + iframeData.replaceId.endsWith('__container__')) { + fallbackNodeDataForCommon[node.id] = iframeData + return + } + + // Find the node and process it + processAdNode(document.querySelector(selector), iframeData, placeholderUrl) + }) + + // Common selectors which could be on every page + var commonSelectors = [ + '[id^="google_ads_iframe_"][id$="__container__"]' + ] + commonSelectors.forEach(commonSelector => { + var nodes = document.querySelectorAll(commonSelector) + if (!nodes) { + return + } + Array.from(nodes).forEach(node => { + processAdNode(node, fallbackNodeDataForCommon[node.id], placeholderUrl) + }) + }) +}) diff --git a/js/components/frame.js b/js/components/frame.js index d80000632d6..f01a435e0bb 100644 --- a/js/components/frame.js +++ b/js/components/frame.js @@ -7,6 +7,10 @@ const ReactDOM = require('react-dom') const AppActions = require('../actions/appActions') const ImmutableComponent = require('./immutableComponent') const cx = require('../lib/classSet.js') +const uuid = require('node-uuid') + +import adInfo from '../data/adInfo.js' +import Config from '../constants/config.js' class Frame extends ImmutableComponent { constructor () { @@ -17,6 +21,10 @@ class Frame extends ImmutableComponent { return ReactDOM.findDOMNode(this.refs.webview) } + componentDidMount () { + this.addEventListeners() + } + componentDidUpdate () { const activeShortcut = this.props.frame.get('activeShortcut') switch (activeShortcut) { @@ -48,55 +56,44 @@ class Frame extends ImmutableComponent { } } - componentDidMount () { + addEventListeners () { this.webview.addEventListener('new-window', (e) => { - console.log('new window: ' + e.url) AppActions.newFrame({ location: e.url }) }) this.webview.addEventListener('close', () => { - console.log('close window') }) this.webview.addEventListener('enter-html-full-screen', () => { - console.log('enter html full screen') }) this.webview.addEventListener('leave-html-full-screen', () => { - console.log('leave html full screen') }) this.webview.addEventListener('page-favicon-updated', () => { - console.log('favicon updated') }) this.webview.addEventListener('page-title-set', ({title}) => { - console.log('title set', title) AppActions.setFrameTitle(this.props.frame, title) }) - this.webview.addEventListener('dom-ready', () => { - console.log('dom is ready') + this.webview.addEventListener('dom-ready', (event) => { + this.insertAds(event.target.src) }) this.webview.addEventListener('load-commit', (event) => { if (event.isMainFrame) { let key = this.props.frame.get('key') - console.log('load committed', event.url, key) AppActions.setLocation(event.url, key) } }) this.webview.addEventListener('did-start-loading', () => { - console.log('spinner start loading') AppActions.onWebviewLoadStart( this.props.frame) }) this.webview.addEventListener('did-stop-loading', () => { - console.log('did stop loading') AppActions.onWebviewLoadEnd( this.props.frame, this.webview.getURL()) }) this.webview.addEventListener('did-fail-load', () => { - console.log('did fail load') }) this.webview.addEventListener('did-finish-load', () => { - console.log('did finish load') AppActions.updateBackForwardState( this.props.frame, this.webview.canGoBack(), @@ -104,6 +101,22 @@ class Frame extends ImmutableComponent { }) } + insertAds (currentLocation) { + let host = new window.URL(currentLocation).hostname.replace('www.', '') + let adDivCandidates = adInfo[host] + if (adDivCandidates) { + // TODO: Use a real user ID and sessionID + const userId = uuid.v4() + const sessionId = uuid.v4() + + const placeholderUrl = Config.vault.replacementUrl(userId) + '?' + [ + `sessionId=${sessionId}`, + `tagName=IFRAME` + ].join('&') + this.webview.send('set-ad-div-candidates', adDivCandidates, placeholderUrl) + } + } + goBack () { this.webview.goBack() } diff --git a/js/constants/config.js b/js/constants/config.js index 45ce1a13207..47b1bdeab9e 100644 --- a/js/constants/config.js +++ b/js/constants/config.js @@ -2,7 +2,11 @@ * 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/. */ -var vaultHost = process.env.VAULT_HOST || 'http://localhost:3000' +// VAULT_HOST can be set to: +// https://vault.brave.com for production +// https://vault-staging.brave.com for a dev build +// http://localhost:3000 for production +var vaultHost = process.env.VAULT_HOST || 'https://vault-staging.brave.com' export default { zoom: { diff --git a/package.json b/package.json index 1919cbc64fe..853b6baaa8e 100644 --- a/package.json +++ b/package.json @@ -60,6 +60,7 @@ "less-loader": "^2.2.1", "mocha": "^2.3.4", "node-libs-browser": "^0.5.3", + "node-uuid": "^1.4.7", "pre-commit": "^1.1.2", "spectron": "^0.35.3", "standard": "^5.4.1", @@ -73,6 +74,7 @@ "Brave-darwin-x64/**", "less/**", "res/**", + "js/data/**", "dist/**", "doc/**", "public/**",