diff --git a/docs/appActions.md b/docs/appActions.md index 7223d1336fe..3ab31508e62 100644 --- a/docs/appActions.md +++ b/docs/appActions.md @@ -217,6 +217,20 @@ Changes an application level setting +### changeSiteSetting(hostPattern, key, value) + +Change a hostPattern's config + +**Parameters** + +**hostPattern**: `string`, The host pattern to update the config for + +**key**: `string`, The config key to update + +**value**: `string | number`, The value to update to + + + * * * diff --git a/docs/state.md b/docs/state.md index 3bfff1bc3e9..01a6ede4482 100644 --- a/docs/state.md +++ b/docs/state.md @@ -30,7 +30,9 @@ AppStore } }], siteSettings: { - [origin]: Object // settings object + [hostPattern]: { + zoomLevel: number + } }, visits: [{ location: string, @@ -113,7 +115,6 @@ WindowStore activeFrameKey: number, previewFrameKey: number, frames: [{ - zoomLevel: number, // current frame zoom level audioMuted: boolean, // frame is muted audioPlaybackActive: boolean, // frame is playing audio canGoBack: boolean, diff --git a/docs/windowActions.md b/docs/windowActions.md index 4aecbc1f0b9..574241d2774 100644 --- a/docs/windowActions.md +++ b/docs/windowActions.md @@ -561,6 +561,18 @@ Similar to setBlockedBy but for httpse redirects +### inspectElement(x, y) + +Inspect the element for the active webview at the x, y content position + +**Parameters** + +**x**: `number`, horizontal position of the element to inspect + +**y**: `number`, vertical position of the element to inspect + + + * * * diff --git a/js/actions/appActions.js b/js/actions/appActions.js index a160a988cf7..11be262fa08 100644 --- a/js/actions/appActions.js +++ b/js/actions/appActions.js @@ -254,6 +254,21 @@ const appActions = { key, value }) + }, + + /** + * Change a hostPattern's config + * @param {string} hostPattern - The host pattern to update the config for + * @param {string} key - The config key to update + * @param {string|number} value - The value to update to + */ + changeSiteSetting: function (hostPattern, key, value) { + AppDispatcher.dispatch({ + actionType: AppConstants.APP_CHANGE_SITE_SETTING, + hostPattern, + key, + value + }) } } diff --git a/js/actions/windowActions.js b/js/actions/windowActions.js index 76cdcdba7a4..a19c04aae51 100644 --- a/js/actions/windowActions.js +++ b/js/actions/windowActions.js @@ -786,29 +786,11 @@ const windowActions = { }) }, - zoomIn: function (frameProps) { - windowActions.zoom(frameProps, 0.5) - }, - - zoomOut: function (frameProps) { - windowActions.zoom(frameProps, -0.5) - }, - - zoom: function (frameProps, stepSize) { - dispatch({ - frameProps, - stepSize, - actionType: WindowConstants.WINDOW_ZOOM - }) - }, - - zoomReset: function (frameProps) { - dispatch({ - frameProps, - actionType: WindowConstants.WINDOW_ZOOM_RESET - }) - }, - + /** + * Inspect the element for the active webview at the x, y content position + * @param {number} x - horizontal position of the element to inspect + * @param {number} y - vertical position of the element to inspect + */ inspectElement: function (x, y) { const webview = document.querySelector('.frameWrapper.isActive webview') if (webview) { diff --git a/js/components/frame.js b/js/components/frame.js index a19accdb14a..083cbb70533 100644 --- a/js/components/frame.js +++ b/js/components/frame.js @@ -10,6 +10,7 @@ const ImmutableComponent = require('./immutableComponent') const Immutable = require('immutable') const cx = require('../lib/classSet.js') const siteUtil = require('../state/siteUtil') +const siteSettings = require('../state/siteSettings') const UrlUtil = require('../lib/urlutil') const messages = require('../constants/messages.js') const remote = global.require('electron').remote @@ -85,6 +86,36 @@ class Frame extends ImmutableComponent { componentDidMount () { this.updateWebview() + if (this.zoomLevel !== config.zoom.defaultValue) { + // Timeout to work around setting zoom too early not working in Electron + setTimeout(() => { + this.webview.setZoomLevel(this.zoomLevel) + }, 1000) + } + } + + zoom (stepSize) { + let newZoomLevel = this.zoomLevel + if (stepSize !== undefined && + config.zoom.max >= this.zoomLevel + stepSize && + config.zoom.min <= this.zoomLevel + stepSize) { + newZoomLevel += stepSize + } else { + newZoomLevel = config.zoom.defaultValue + } + appActions.changeSiteSetting(this.origin, 'zoomLevel', newZoomLevel) + } + + zoomIn () { + this.zoom(config.zoom.step) + } + + zoomOut () { + this.zoom(config.zoom.step * -1) + } + + zoomReset () { + this.zoom() } componentDidUpdate (prevProps, prevState) { @@ -140,13 +171,13 @@ class Frame extends ImmutableComponent { this.webview.loadURL(this.props.frame.get('location')) break case 'zoom-in': - windowActions.zoomIn(this.props.frame) + this.zoomIn() break case 'zoom-out': - windowActions.zoomOut(this.props.frame) + this.zoomOut() break case 'zoom-reset': - windowActions.zoomReset(this.props.frame) + this.zoomReset() break case 'toggle-dev-tools': if (this.webview.isDevToolsOpened()) { @@ -435,6 +466,11 @@ class Frame extends ImmutableComponent { this.webview.goForward() } + get origin () { + const parsedUrl = urlParse(this.props.frame.get('location')) + return `${parsedUrl.protocol}//${parsedUrl.host}` + } + onFocus () { windowActions.setTabPageIndexByFrame(this.props.frame) windowActions.setUrlBarActive(false) @@ -449,9 +485,9 @@ class Frame extends ImmutableComponent { onUpdateWheelZoom () { if (this.wheelDeltaY > 0) { - windowActions.zoomIn(this.props.frame) + this.zoomIn() } else if (this.wheelDeltaY < 0) { - windowActions.zoomOut(this.props.frame) + this.zoomOut() } this.wheelDeltaY = 0 } @@ -491,10 +527,23 @@ class Frame extends ImmutableComponent { this.webview.setAudioMuted(false) } - let zoomLevel = nextProps.frame.get('zoomLevel') - if (zoomLevel !== this.props.frame.get('zoomLevel')) { - this.webview.setZoomLevel(zoomLevel) + const nextLocation = nextProps.frame.get('location') + const nextSiteSettings = siteSettings.getSiteSettingsForURL(nextProps.siteSettings, nextLocation) + if (nextSiteSettings) { + const nextZoom = nextSiteSettings.get('zoomLevel') + if (this.zoomLevel !== nextZoom) { + this.webview.setZoomLevel(nextZoom) + } + } + } + + get zoomLevel () { + const location = this.props.frame.get('location') + const settings = siteSettings.getSiteSettingsForURL(this.props.siteSettings, location) + if (!settings) { + return config.zoom.defaultValue } + return settings.get('zoomLevel') } render () { diff --git a/js/components/main.js b/js/components/main.js index a189ea96cea..128f185e6c6 100644 --- a/js/components/main.js +++ b/js/components/main.js @@ -533,6 +533,7 @@ class Main extends ImmutableComponent { .includes(siteTags.BOOKMARK_FOLDER)) || new Immutable.Map() : null} passwords={this.props.appState.get('passwords')} + siteSettings={this.props.appState.get('siteSettings')} enableAds={this.enableAds} isPreview={frame.get('key') === this.props.windowState.get('previewFrameKey')} isActive={FrameStateUtil.isFrameKeyActive(this.props.windowState, frame.get('key'))} diff --git a/js/constants/appConstants.js b/js/constants/appConstants.js index 103e2f7bffe..97b2097f5f7 100644 --- a/js/constants/appConstants.js +++ b/js/constants/appConstants.js @@ -24,7 +24,8 @@ const AppConstants = { APP_SET_RESOURCE_ENABLED: _, APP_UPDATE_LAST_CHECK: _, APP_SET_UPDATE_STATUS: _, - APP_CHANGE_SETTING: _ + APP_CHANGE_SETTING: _, + APP_CHANGE_SITE_SETTING: _ } module.exports = mapValuesByKeys(AppConstants) diff --git a/js/constants/config.js b/js/constants/config.js index 052d74bbc32..eb9ca0a923a 100644 --- a/js/constants/config.js +++ b/js/constants/config.js @@ -14,7 +14,7 @@ module.exports = { defaultValue: 0, min: -8, max: 9, - step: 1 + step: 0.5 }, maxClosedFrames: 100, thumbnail: { diff --git a/js/state/siteSettings.js b/js/state/siteSettings.js index 7a58a729af1..9047df15ad2 100644 --- a/js/state/siteSettings.js +++ b/js/state/siteSettings.js @@ -3,6 +3,7 @@ * You can obtain one at http://mozilla.org/MPL/2.0/. */ const urlParse = require('url').parse +const Immutable = require('immutable') /** * Obtains the site settings stored for a specific pattern. @@ -15,13 +16,16 @@ module.exports.getSiteSettingsForHostPattern = (siteSettings, hostPattern) => siteSettings.get(hostPattern) /** - * Set the settings object for the specified host pattern. + * Merges the settings for the specified host pattern. * @param {Object} siteSettings - The top level app state site settings indexed by hostPattern. * @param {string} hostPattern - The host pattern to merge into - * @param {Object} settingObj - An object of settings for the site + * @param {string} key - A setting key + * @param {string|number} value - A setting value */ -module.exports.setSiteSettings = (siteSettings, hostPattern, settingObj) => - siteSettings.set(hostPattern, settingObj) +module.exports.mergeSiteSetting = (siteSettings, hostPattern, key, value) => + (siteSettings || Immutable.Map()).mergeIn([hostPattern], { + [key]: value + }) /** * Remove all site settings for the specified hostPattern. @@ -38,6 +42,9 @@ module.exports.removeSiteSettings = (siteSettings, hostPattern) => * @return {Object} A merged settings object for the specified site setting or undefined */ module.exports.getSiteSettingsForURL = (siteSettings, location) => { + if (!location || !siteSettings) { + return undefined + } // Example: https://www.brianbondy.com:8080/projects // parsedUrl.host: www.brianbondy.com:8080 // parsedUrl.hostname: www.brianbondy.com @@ -77,10 +84,10 @@ module.exports.getSiteSettingsForURL = (siteSettings, location) => { // Merge all the settingObj with the more specific first rules taking precedence const settingObj = settingObjs.reduce((mergedSettingObj, settingObj) => - Object.assign(settingObj || {}, mergedSettingObj), {}) - if (Object.keys(settingObj).length === 0) { + (settingObj || Immutable.Map()).merge(mergedSettingObj), Immutable.Map()) + if (settingObj.size === 0) { return undefined } - return settingObj + return Immutable.fromJS(settingObj) } diff --git a/js/stores/appStore.js b/js/stores/appStore.js index 7de11e13054..e9381c75195 100644 --- a/js/stores/appStore.js +++ b/js/stores/appStore.js @@ -7,6 +7,7 @@ const AppConstants = require('../constants/appConstants') const appConfig = require('../constants/appConfig') const settings = require('../constants/settings') const siteUtil = require('../state/siteUtil') +const siteSettings = require('../state/siteSettings') const electron = require('electron') const app = electron.app const ipcMain = electron.ipcMain @@ -393,6 +394,10 @@ const handleAppAction = (action) => { case AppConstants.APP_CHANGE_SETTING: appState = appState.setIn(['settings', action.key], action.value) break + case AppConstants.APP_CHANGE_SITE_SETTING: + appState = appState.set('siteSettings', + siteSettings.mergeSiteSetting(appState.get('siteSettings'), action.hostPattern, action.key, action.value)) + break default: } diff --git a/js/stores/windowStore.js b/js/stores/windowStore.js index 59ff1bf852c..9e8d9e34a77 100644 --- a/js/stores/windowStore.js +++ b/js/stores/windowStore.js @@ -2,7 +2,6 @@ * 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 config = require('../constants/config') const WindowDispatcher = require('../dispatcher/windowDispatcher') const EventEmitter = require('events').EventEmitter const WindowConstants = require('../constants/windowConstants') @@ -472,22 +471,6 @@ const doAction = (action) => { let redirectedBy = windowState.getIn(redirectedByPath) || new Immutable.List() windowState = windowState.setIn(redirectedByPath, redirectedBy.push(action.location)) break - // Zoom state - case WindowConstants.WINDOW_ZOOM: - let zoomLevel = FrameStateUtil.getFramePropValue(windowState, action.frameProps, 'zoomLevel') - // for backwards compatibility with previous stored window state - if (zoomLevel === undefined) { - zoomLevel = 1 - } - if (config.zoom.max >= zoomLevel + action.stepSize && - config.zoom.min <= zoomLevel + action.stepSize) { - zoomLevel += action.stepSize - } - windowState = windowState.setIn(FrameStateUtil.getFramePropPath(windowState, action.frameProps, 'zoomLevel'), zoomLevel) - break - case WindowConstants.WINDOW_ZOOM_RESET: - windowState = windowState.setIn(FrameStateUtil.getFramePropPath(windowState, action.frameProps, 'zoomLevel'), config.zoom.defaultValue) - break default: } diff --git a/test/state/siteSettingsTest.js b/test/state/siteSettingsTest.js index 62403477fde..c5b4641c169 100644 --- a/test/state/siteSettingsTest.js +++ b/test/state/siteSettingsTest.js @@ -8,53 +8,47 @@ let siteSettingsMap = new Immutable.Map() describe('siteSettings', function () { describe('simple URL host pattern', function () { before(function () { - siteSettingsMap = siteSettings.setSiteSettings(siteSettingsMap, 'https://www.brave.com', { - prop1: 1 - }) + siteSettingsMap = siteSettings.mergeSiteSetting(siteSettingsMap, 'https://www.brave.com', 'prop1', 1) }) it('can obtain a site setting from a host pattern', function *() { const setting = siteSettings.getSiteSettingsForHostPattern(siteSettingsMap, 'https://www.brave.com') - assert.strictEqual(setting.prop1, 1) + assert.strictEqual(setting.get('prop1'), 1) }) it('can obtain a site setting from an exact URL', function *() { const setting = siteSettings.getSiteSettingsForURL(siteSettingsMap, 'https://www.brave.com') - assert.strictEqual(setting.prop1, 1) + assert.strictEqual(setting.get('prop1'), 1) }) it('can obtain a site setting from a URL at that host', function *() { const setting = siteSettings.getSiteSettingsForURL(siteSettingsMap, 'https://www.brave.com/projects#test') - assert.strictEqual(setting.prop1, 1) + assert.strictEqual(setting.get('prop1'), 1) }) }) describe('any port pattern', function () { before(function () { siteSettingsMap = new Immutable.Map() - siteSettingsMap = siteSettings.setSiteSettings(siteSettingsMap, 'https://www.brave.com:*', { - prop1: 2 - }) + siteSettingsMap = siteSettings.mergeSiteSetting(siteSettingsMap, 'https://www.brave.com:*', 'prop1', 2) }) it('can obtain without a port', function *() { const setting = siteSettings.getSiteSettingsForHostPattern(siteSettingsMap, 'https://www.brave.com:*') - assert.strictEqual(setting.prop1, 2) + assert.strictEqual(setting.get('prop1'), 2) }) it('can obtain without a port', function *() { const setting = siteSettings.getSiteSettingsForURL(siteSettingsMap, 'https://www.brave.com') - assert.strictEqual(setting.prop1, 2) + assert.strictEqual(setting.get('prop1'), 2) }) it('can obtain with a specific port', function *() { const setting = siteSettings.getSiteSettingsForURL(siteSettingsMap, 'https://www.brave.com:8080/projects#test') - assert.strictEqual(setting.prop1, 2) + assert.strictEqual(setting.get('prop1'), 2) }) }) describe('all https protocol pattern', function () { before(function () { siteSettingsMap = new Immutable.Map() - siteSettingsMap = siteSettings.setSiteSettings(siteSettingsMap, 'https://*', { - prop1: 3 - }) + siteSettingsMap = siteSettings.mergeSiteSetting(siteSettingsMap, 'https://*', 'prop1', 3) }) it('Can obtain from https url', function *() { const setting = siteSettings.getSiteSettingsForURL(siteSettingsMap, 'https://www.brave.com') - assert.strictEqual(setting.prop1, 3) + assert.strictEqual(setting.get('prop1'), 3) }) it('Cannot obtain from http url', function *() { const setting = siteSettings.getSiteSettingsForURL(siteSettingsMap, 'http://www.brave.com') @@ -64,82 +58,93 @@ describe('siteSettings', function () { describe('http or https specific URLs', function () { before(function () { siteSettingsMap = new Immutable.Map() - siteSettingsMap = siteSettings.setSiteSettings(siteSettingsMap, 'http?://www.brave.com', { - prop1: 4 - }) + siteSettingsMap = siteSettings.mergeSiteSetting(siteSettingsMap, 'http?://www.brave.com', 'prop1', 4) }) it('Can obtain from https url', function *() { const setting = siteSettings.getSiteSettingsForURL(siteSettingsMap, 'https://www.brave.com/projects') - assert.strictEqual(setting.prop1, 4) + assert.strictEqual(setting.get('prop1'), 4) }) it('Can obtain from http url', function *() { const setting = siteSettings.getSiteSettingsForURL(siteSettingsMap, 'http://www.brave.com/projects') - assert.strictEqual(setting.prop1, 4) + assert.strictEqual(setting.get('prop1'), 4) }) }) describe('subdomain wildcards', function () { before(function () { siteSettingsMap = new Immutable.Map() - siteSettingsMap = siteSettings.setSiteSettings(siteSettingsMap, 'http?://*.brave.com:*', { - prop1: 5 - }) + siteSettingsMap = siteSettings.mergeSiteSetting(siteSettingsMap, 'http?://*.brave.com:*', 'prop1', 5) }) it('Can obtain from no subdomain', function *() { const setting = siteSettings.getSiteSettingsForURL(siteSettingsMap, 'https://brave.com/projects') - assert.strictEqual(setting.prop1, 5) + assert.strictEqual(setting.get('prop1'), 5) }) it('Can obtain from a single subdomain', function *() { const setting = siteSettings.getSiteSettingsForURL(siteSettingsMap, 'https://www.brave.com/projects') - assert.strictEqual(setting.prop1, 5) + assert.strictEqual(setting.get('prop1'), 5) }) it('Can obtain from multiple subdomains', function *() { const setting = siteSettings.getSiteSettingsForURL(siteSettingsMap, 'http://a.b.brave.com/projects') - assert.strictEqual(setting.prop1, 5) + assert.strictEqual(setting.get('prop1'), 5) }) it('Can obtain from multiple subdomains and a different port', function *() { const setting = siteSettings.getSiteSettingsForURL(siteSettingsMap, 'http://a.b.brave.com:99/projects') - assert.strictEqual(setting.prop1, 5) + assert.strictEqual(setting.get('prop1'), 5) }) it('Cannot obtain from a diff domain', function *() { const setting = siteSettings.getSiteSettingsForURL(siteSettingsMap, 'https://brianbondy.com/projects') assert.strictEqual(setting, undefined) }) }) + describe('Overwriting a setting works', function () { + before(function () { + siteSettingsMap = new Immutable.Map() + siteSettingsMap = siteSettings.mergeSiteSetting(siteSettingsMap, 'https://www.brave.com', 'prop1', 1) + siteSettingsMap = siteSettings.mergeSiteSetting(siteSettingsMap, 'https://www.brave.com', 'prop1', 2) + }) + it('can overwrites what is needed but does not ', function *() { + let setting = siteSettings.getSiteSettingsForHostPattern(siteSettingsMap, 'https://www.brave.com') + assert.strictEqual(setting.get('prop1'), 2) + }) + it('merging settings does not lose other data', function *() { + siteSettingsMap = siteSettings.mergeSiteSetting(siteSettingsMap, 'https://www.brave.com', 'prop2', 3) + let setting = siteSettings.getSiteSettingsForHostPattern(siteSettingsMap, 'https://www.brave.com') + assert.strictEqual(setting.get('prop1'), 2) + assert.strictEqual(setting.get('prop2'), 3) + }) + }) describe('More specific rules override', function () { before(function () { - siteSettingsMap = siteSettings.setSiteSettings(siteSettingsMap, 'https://www.brave.com', { - prop1: 1 - }) - siteSettingsMap = siteSettings.setSiteSettings(siteSettingsMap, 'https://www.brave.com:*', { - prop1: 2, - prop2: 2 - }) - siteSettingsMap = siteSettings.setSiteSettings(siteSettingsMap, 'https://*.brave.com', { - prop1: 3, - prop2: 3, - prop3: 3 - }) + siteSettingsMap = new Immutable.Map() + + siteSettingsMap = siteSettings.mergeSiteSetting(siteSettingsMap, 'https://*.brave.com', 'prop1', 3) + siteSettingsMap = siteSettings.mergeSiteSetting(siteSettingsMap, 'https://*.brave.com', 'prop2', 3) + siteSettingsMap = siteSettings.mergeSiteSetting(siteSettingsMap, 'https://*.brave.com', 'prop3', 3) + + siteSettingsMap = siteSettings.mergeSiteSetting(siteSettingsMap, 'https://www.brave.com', 'prop1', 1) + + siteSettingsMap = siteSettings.mergeSiteSetting(siteSettingsMap, 'https://www.brave.com:*', 'prop1', 2) + siteSettingsMap = siteSettings.mergeSiteSetting(siteSettingsMap, 'https://www.brave.com:*', 'prop2', 2) }) it('can obtain a site setting from a host pattern', function *() { let setting = siteSettings.getSiteSettingsForHostPattern(siteSettingsMap, 'https://www.brave.com') - assert.strictEqual(setting.prop1, 1) - assert.strictEqual(setting.prop2, undefined) - assert.strictEqual(setting.prop3, undefined) + assert.strictEqual(setting.get('prop1'), 1) + assert.strictEqual(setting.get('prop2'), undefined) + assert.strictEqual(setting.get('prop3'), undefined) setting = siteSettings.getSiteSettingsForHostPattern(siteSettingsMap, 'https://www.brave.com:*') - assert.strictEqual(setting.prop1, 2) - assert.strictEqual(setting.prop2, 2) + assert.strictEqual(setting.get('prop1'), 2) + assert.strictEqual(setting.get('prop2'), 2) setting = siteSettings.getSiteSettingsForHostPattern(siteSettingsMap, 'https://*.brave.com') - assert.strictEqual(setting.prop1, 3) - assert.strictEqual(setting.prop2, 3) - assert.strictEqual(setting.prop3, 3) + assert.strictEqual(setting.get('prop1'), 3) + assert.strictEqual(setting.get('prop2'), 3) + assert.strictEqual(setting.get('prop3'), 3) }) it('can obtain properly combined settings', function *() { const setting = siteSettings.getSiteSettingsForURL(siteSettingsMap, 'https://www.brave.com') - assert.strictEqual(setting.prop1, 1) - assert.strictEqual(setting.prop2, 2) - assert.strictEqual(setting.prop3, 3) + assert.strictEqual(setting.get('prop1'), 1) + assert.strictEqual(setting.get('prop2'), 2) + assert.strictEqual(setting.get('prop3'), 3) }) }) })