From 6c882944bac5bc370abaf16f797e4ec6f96f2ee6 Mon Sep 17 00:00:00 2001 From: yan Date: Wed, 15 Feb 2017 02:13:28 +0000 Subject: [PATCH] add noScriptInfo checkboxes to allow scripts by origin fix #6431. adjusts the noScriptInfo dialog so that it can be used to allow scripts by origin, instead of all scripts on the page, when NoScript is globally enabled. removes the option to allow scripts persistently (since this can be done via the Bravery panel anyway) todo: noScriptInfo tests --- .../brave/locales/en-US/app.properties | 7 +- app/sessionStore.js | 2 + docs/appActions.md | 12 +++ docs/state.md | 1 + js/actions/appActions.js | 13 +++ js/components/frame.js | 4 + js/components/noScriptInfo.js | 79 +++++++++++++++---- js/constants/appConstants.js | 3 +- js/state/contentSettings.js | 21 +++++ js/stores/appStore.js | 12 +++ less/forms.less | 14 +++- 11 files changed, 145 insertions(+), 23 deletions(-) diff --git a/app/extensions/brave/locales/en-US/app.properties b/app/extensions/brave/locales/en-US/app.properties index d4294bc67f8..58d28d35e24 100644 --- a/app/extensions/brave/locales/en-US/app.properties +++ b/app/extensions/brave/locales/en-US/app.properties @@ -117,11 +117,10 @@ updateHide=Hide sessionInfoCommon=This tab uses session {{partitionNumber}} sessionInfo={{sessionInfoCommon}} sessionInfoTab.title={{sessionInfoCommon}} -allowScripts=Allow on this site always -allowScriptsTemp=Allow on this site until restart +allowScripts=Allow always +allowScriptsTemp=Allow until restart allowScriptsOnce=Allow this time -scriptsBlocked={{numberBlocked}} scripts blocked on {{site}} -scriptBlocked={{numberBlocked}} script blocked on {{site}} +scriptsBlocked=Do you want to allow scripts on {{site}} from these sources? findResults={{activeMatchOrdinal}} of {{numberOfMatches}} findResultMatches={[plural(numberOfMatches)]} findResultMatches[one]={{numberOfMatches}} match diff --git a/app/sessionStore.js b/app/sessionStore.js index 98f7ba33f1d..8e32f89bdf7 100644 --- a/app/sessionStore.js +++ b/app/sessionStore.js @@ -293,6 +293,8 @@ module.exports.cleanAppData = (data, isShutdown) => { if (typeof noScript === 'number') { delete data.siteSettings[host].noScript } + // Don't persist any noScript exceptions + delete data.siteSettings[host].noScriptExceptions // Don't write runInsecureContent to session delete data.siteSettings[host].runInsecureContent // If the site setting is empty, delete it for privacy diff --git a/docs/appActions.md b/docs/appActions.md index 5fb497b29b4..1b3c3e06fd0 100644 --- a/docs/appActions.md +++ b/docs/appActions.md @@ -652,6 +652,18 @@ Dispatches a message when a tab is being cloned +### setNoScriptExceptions(hostPattern, origins) + +Dispatches a message when noscript exceptions are added for an origin + +**Parameters** + +**hostPattern**: `string`, Dispatches a message when noscript exceptions are added for an origin + +**origins**: `Object.<string, (boolean|number)>`, Dispatches a message when noscript exceptions are added for an origin + + + * * * diff --git a/docs/state.md b/docs/state.md index 384121fe7ce..78d2951c396 100644 --- a/docs/state.md +++ b/docs/state.md @@ -245,6 +245,7 @@ AppStore midiSysexPermission: boolean, notificationsPermission: boolean, noScript: (number|boolean), // true = block scripts, false = allow, 0 = allow once, 1 = allow until restart + noScriptExceptions: {[hostPattern]: (number|boolean)}, // hosts where scripts are allowed once (0) or until restart (1). false = block openExternalPermission: boolean, pointerLockPermission: boolean, protocolRegistrationPermission: boolean, diff --git a/js/actions/appActions.js b/js/actions/appActions.js index 157dffcb6e6..5d462401cc8 100644 --- a/js/actions/appActions.js +++ b/js/actions/appActions.js @@ -800,6 +800,19 @@ const appActions = { tabId, options }) + }, + + /** + * Dispatches a message when noscript exceptions are added for an origin + * @param {string} hostPattern + * @param {Object.} origins + */ + setNoScriptExceptions: function (hostPattern, origins) { + AppDispatcher.dispatch({ + actionType: appConstants.APP_SET_NOSCRIPT_EXCEPTIONS, + hostPattern, + origins + }) } } diff --git a/js/components/frame.js b/js/components/frame.js index acbb0ee0e39..f4db338c857 100644 --- a/js/components/frame.js +++ b/js/components/frame.js @@ -280,6 +280,10 @@ class Frame extends ImmutableComponent { if (activeSiteSettings.get('noScript') === 0) { appActions.removeSiteSetting(origin, 'noScript', this.props.isPrivate) } + const noScriptExceptions = activeSiteSettings.get('noScriptExceptions') + if (noScriptExceptions) { + appActions.setNoScriptExceptions(origin, noScriptExceptions.filter((value, host) => value !== 0)) + } } componentWillUnmount () { diff --git a/js/components/noScriptInfo.js b/js/components/noScriptInfo.js index 213ca82ea63..8838761f973 100644 --- a/js/components/noScriptInfo.js +++ b/js/components/noScriptInfo.js @@ -3,6 +3,7 @@ * You can obtain one at http://mozilla.org/MPL/2.0/. */ const React = require('react') +const Immutable = require('immutable') const ImmutableComponent = require('./immutableComponent') const Dialog = require('./dialog') const Button = require('./button') @@ -10,11 +11,37 @@ const appActions = require('../actions/appActions') const siteUtil = require('../state/siteUtil') const ipc = require('electron').ipcRenderer const messages = require('../constants/messages') +const urlParse = require('url').parse + +class NoScriptCheckbox extends ImmutableComponent { + toggleCheckbox (e) { + this.checkbox.checked = !this.checkbox.checked + e.stopPropagation() + } + + get id () { + return `checkbox-for-${this.props.origin}` + } + + render () { + return
+ { e.stopPropagation() }} + ref={(node) => { this.checkbox = node }} defaultChecked + origin={this.props.origin} /> + +
+ } +} class NoScriptInfo extends ImmutableComponent { - get numberBlocked () { + get blockedOrigins () { const blocked = this.props.frameProps.getIn(['noScript', 'blocked']) - return blocked ? blocked.size : 0 + if (blocked && blocked.size) { + return new Immutable.Set(blocked.map(siteUtil.getOrigin)) + } else { + return new Immutable.Set() + } } get origin () { @@ -29,18 +56,32 @@ class NoScriptInfo extends ImmutableComponent { ipc.emit(messages.SHORTCUT_ACTIVE_FRAME_CLEAN_RELOAD) } - onAllow (setting) { + onAllow (setting, e) { if (!this.origin) { return } - appActions.changeSiteSetting(this.origin, 'noScript', setting) - this.reload() + if (setting === false) { + appActions.changeSiteSetting(this.origin, 'noScript', setting) + this.reload() + } else { + let checkedOrigins = new Immutable.Map() + this.checkboxes.querySelectorAll('input').forEach((box) => { + const origin = box.getAttribute('origin') + if (origin) { + checkedOrigins = checkedOrigins.set(origin, box.checked ? setting : false) + } + }) + if (checkedOrigins.size) { + appActions.setNoScriptExceptions(this.origin, checkedOrigins) + this.reload() + } + } } get buttons () { if (!this.props.noScriptGlobalEnabled) { // NoScript is not turned on globally - return
} else { return
@@ -48,26 +89,32 @@ class NoScriptInfo extends ImmutableComponent { onClick={this.onAllow.bind(this, 0)} /> {this.isPrivate ? null - :
-
-
-
} + :
} } render () { + if (!this.origin) { + return null + } const l10nArgs = { - numberBlocked: this.numberBlocked, - site: this.props.frameProps.get('location') || 'this page' + site: urlParse(this.props.frameProps.get('location')).host } return
- {this.buttons} + data-l10n-id={'scriptsBlocked'} /> + {this.blockedOrigins.size + ?
+
{ this.checkboxes = node }}> + {this.blockedOrigins.map((origin) => )} +
+ {this.buttons} +
+ : null}
} diff --git a/js/constants/appConstants.js b/js/constants/appConstants.js index 61fd876eb20..eaf09dbca28 100644 --- a/js/constants/appConstants.js +++ b/js/constants/appConstants.js @@ -84,7 +84,8 @@ const appConstants = { APP_SHUTTING_DOWN: _, APP_CLIPBOARD_TEXT_UPDATED: _, APP_TAB_TOGGLE_DEV_TOOLS: _, - APP_TAB_CLONED: _ + APP_TAB_CLONED: _, + APP_SET_NOSCRIPT_EXCEPTIONS: _ } module.exports = mapValuesByKeys(appConstants) diff --git a/js/state/contentSettings.js b/js/state/contentSettings.js index 7e01933a5da..de8f5eda8f0 100644 --- a/js/state/contentSettings.js +++ b/js/state/contentSettings.js @@ -200,6 +200,26 @@ const siteSettingsToContentSettings = (currentSiteSettings, defaultContentSettin if (['number', 'boolean'].includes(typeof siteSetting.get('noScript'))) { contentSettings = addContentSettings(contentSettings, 'javascript', primaryPattern, '*', siteSetting.get('noScript') === true ? 'block' : 'allow') } + const noScriptExceptions = siteSetting.get('noScriptExceptions') + if (noScriptExceptions && noScriptExceptions.has(hostPattern)) { + // Allow all is needed for inline scripts to run. XXX: this seems like + // a muon bug. + contentSettings = addContentSettings(contentSettings, 'javascript', primaryPattern, '*', 'allow') + // Re-block the origins that aren't excluded + noScriptExceptions.forEach((value, origin) => { + if (value === false) { + contentSettings = addContentSettings(contentSettings, 'javascript', + primaryPattern, origin, 'block') + } + }) + } else if (noScriptExceptions && noScriptExceptions.size) { + noScriptExceptions.forEach((value, origin) => { + if (typeof value === 'number') { + contentSettings = addContentSettings(contentSettings, 'javascript', + primaryPattern, origin === hostPattern ? '*' : origin, 'allow') + } + }) + } if (typeof siteSetting.get('runInsecureContent') === 'boolean') { contentSettings = addContentSettings(contentSettings, 'runInsecureContent', primaryPattern, '*', siteSetting.get('runInsecureContent') ? 'allow' : 'block') @@ -279,6 +299,7 @@ const doAction = (action) => { switch (action.actionType) { case appConstants.APP_REMOVE_SITE_SETTING: case appConstants.APP_CHANGE_SITE_SETTING: + case appConstants.APP_SET_NOSCRIPT_EXCEPTIONS: AppDispatcher.waitFor([AppStore.dispatchToken], () => { userPrefsUpdateTrigger(action.temporary) contentSettingsUpdateTrigger(action.temporary) diff --git a/js/stores/appStore.js b/js/stores/appStore.js index fcf65cfd487..41eb6386a12 100644 --- a/js/stores/appStore.js +++ b/js/stores/appStore.js @@ -620,6 +620,18 @@ const handleAppAction = (action) => { appState = appState.set(propertyName, newSiteSettings) break } + case appConstants.APP_SET_NOSCRIPT_EXCEPTIONS: + // Note that this is always cleared on restart or reload, so should not + // be synced or persisted. + let key = 'noScriptExceptions' + if (!action.origins || !action.origins.size) { + // Clear the exceptions + appState = appState.setIn(['siteSettings', action.hostPattern, key], new Immutable.Map()) + } else { + const currentExceptions = appState.getIn(['siteSettings', action.hostPattern, key]) || new Immutable.Map() + appState = appState.setIn(['siteSettings', action.hostPattern, key], currentExceptions.merge(action.origins)) + } + break case appConstants.APP_UPDATE_LEDGER_INFO: appState = appState.set('ledgerInfo', Immutable.fromJS(action.ledgerInfo)) break diff --git a/less/forms.less b/less/forms.less index a0b881e3f1f..1e98282a778 100644 --- a/less/forms.less +++ b/less/forms.less @@ -504,14 +504,24 @@ select { .flyoutDialog; right: 20px; width: auto; - max-width: 350px; - text-align: center; + max-width: 400px; font-size: 15px; cursor: default; + text-align: center; .truncate { margin-bottom: 5px; } + .noScriptCheckbox { + text-align: left; + } + button { + margin: 2px; + margin-top: 10px; + } + label { + margin-left: 2px; + } } }