Merge pull request #90 from ueokande/11-url-blacklist

URL blacklist
jh-changes
Shin'ya Ueoka 7 years ago committed by GitHub
commit ccc81312a1
  1. 7
      src/content/actions/index.js
  2. 9
      src/content/actions/input.js
  3. 10
      src/content/actions/setting.js
  4. 4
      src/content/components/common/index.js
  5. 15
      src/content/components/common/keymapper.js
  6. 26
      src/content/components/top-content/index.js
  7. 3
      src/content/reducers/index.js
  8. 7
      src/content/reducers/input.js
  9. 13
      src/content/reducers/setting.js
  10. 6
      src/shared/utils/re.js
  11. 2
      src/shared/validators/setting.js
  12. 13
      test/content/actions/setting.test.js
  13. 18
      test/content/reducers/setting.test.js
  14. 20
      test/shared/util/re.test.js

@ -1,12 +1,15 @@
export default { export default {
// User input // Enable/disable
ADDON_ENABLE: 'addon.enable', ADDON_ENABLE: 'addon.enable',
ADDON_DISABLE: 'addon.disable', ADDON_DISABLE: 'addon.disable',
ADDON_TOGGLE_ENABLED: 'addon.toggle.enabled', ADDON_TOGGLE_ENABLED: 'addon.toggle.enabled',
// Settings
SETTING_SET: 'setting.set',
// User input
INPUT_KEY_PRESS: 'input.key,press', INPUT_KEY_PRESS: 'input.key,press',
INPUT_CLEAR_KEYS: 'input.clear.keys', INPUT_CLEAR_KEYS: 'input.clear.keys',
INPUT_SET_KEYMAPS: 'input.set.keymaps',
// Completion // Completion
COMPLETION_SET_ITEMS: 'completion.set.items', COMPLETION_SET_ITEMS: 'completion.set.items',

@ -13,11 +13,4 @@ const clearKeys = () => {
}; };
}; };
const setKeymaps = (keymaps) => { export { keyPress, clearKeys };
return {
type: actions.INPUT_SET_KEYMAPS,
keymaps,
};
};
export { keyPress, clearKeys, setKeymaps };

@ -0,0 +1,10 @@
import actions from 'content/actions';
const set = (value) => {
return {
type: actions.SETTING_SET,
value,
};
};
export { set };

@ -1,7 +1,7 @@
import InputComponent from './input'; import InputComponent from './input';
import KeymapperComponent from './keymapper'; import KeymapperComponent from './keymapper';
import FollowComponent from './follow'; import FollowComponent from './follow';
import * as inputActions from 'content/actions/input'; import * as settingActions from 'content/actions/setting';
import messages from 'shared/messages'; import messages from 'shared/messages';
export default class Common { export default class Common {
@ -40,7 +40,7 @@ export default class Common {
browser.runtime.sendMessage({ browser.runtime.sendMessage({
type: messages.SETTINGS_QUERY, type: messages.SETTINGS_QUERY,
}).then((settings) => { }).then((settings) => {
this.store.dispatch(inputActions.setKeymaps(settings.keymaps)); this.store.dispatch(settingActions.set(settings));
}); });
} }
} }

@ -11,19 +11,20 @@ export default class KeymapperComponent {
} }
key(key) { key(key) {
let enabled = this.store.getState().addon.enabled;
this.store.dispatch(inputActions.keyPress(key)); this.store.dispatch(inputActions.keyPress(key));
let input = this.store.getState().input; let state = this.store.getState();
let matched = Object.keys(input.keymaps).filter((keyStr) => { let input = state.input;
let keymaps = state.setting.keymaps;
let matched = Object.keys(keymaps).filter((keyStr) => {
return keyStr.startsWith(input.keys); return keyStr.startsWith(input.keys);
}); });
if (!enabled) { if (!state.addon.enabled) {
// available keymaps are only ADDON_ENABLE and ADDON_TOGGLE_ENABLED if // available keymaps are only ADDON_ENABLE and ADDON_TOGGLE_ENABLED if
// the addon disabled // the addon disabled
matched = matched.filter((keys) => { matched = matched.filter((keys) => {
let type = input.keymaps[keys].type; let type = keymaps[keys].type;
return type === operations.ADDON_ENABLE || return type === operations.ADDON_ENABLE ||
type === operations.ADDON_TOGGLE_ENABLED; type === operations.ADDON_TOGGLE_ENABLED;
}); });
@ -35,7 +36,7 @@ export default class KeymapperComponent {
matched.length === 1 && input.keys !== matched[0]) { matched.length === 1 && input.keys !== matched[0]) {
return true; return true;
} }
let operation = input.keymaps[matched]; let operation = keymaps[matched];
this.store.dispatch(operationActions.exec(operation)); this.store.dispatch(operationActions.exec(operation));
this.store.dispatch(inputActions.clearKeys()); this.store.dispatch(inputActions.clearKeys());
return true; return true;

@ -1,7 +1,9 @@
import CommonComponent from '../common'; import CommonComponent from '../common';
import FollowController from './follow-controller'; import FollowController from './follow-controller';
import * as consoleFrames from '../../console-frames'; import * as consoleFrames from '../../console-frames';
import * as addonActions from '../../actions/addon';
import messages from 'shared/messages'; import messages from 'shared/messages';
import * as re from 'shared/utils/re';
export default class TopContent { export default class TopContent {
@ -11,17 +13,39 @@ export default class TopContent {
new CommonComponent(win, store), new CommonComponent(win, store),
new FollowController(win, store), new FollowController(win, store),
]; ];
this.store = store;
this.prevBlacklist = undefined;
// TODO make component // TODO make component
consoleFrames.initialize(window.document); consoleFrames.initialize(this.win.document);
messages.onMessage(this.onMessage.bind(this)); messages.onMessage(this.onMessage.bind(this));
} }
update() { update() {
let blacklist = this.store.getState().setting.blacklist;
if (JSON.stringify(this.prevBlacklist) !== JSON.stringify(blacklist)) {
this.disableIfBlack(blacklist);
this.prevBlacklist = blacklist;
}
this.children.forEach(c => c.update()); this.children.forEach(c => c.update());
} }
disableIfBlack(blacklist) {
let loc = this.win.location;
let partial = loc.host + loc.pathname;
let matched = blacklist
.map((item) => {
let pattern = item.includes('/') ? item : item + '/*';
return re.fromWildcard(pattern);
})
.some(regex => regex.test(partial));
if (matched) {
this.store.dispatch(addonActions.disable());
}
}
onMessage(message) { onMessage(message) {
switch (message.type) { switch (message.type) {
case messages.CONSOLE_HIDE_COMMAND: case messages.CONSOLE_HIDE_COMMAND:

@ -1,10 +1,12 @@
import addonReducer from './addon'; import addonReducer from './addon';
import settingReducer from './setting';
import inputReducer from './input'; import inputReducer from './input';
import followReducer from './follow'; import followReducer from './follow';
// Make setting reducer instead of re-use // Make setting reducer instead of re-use
const defaultState = { const defaultState = {
addon: addonReducer(undefined, {}), addon: addonReducer(undefined, {}),
setting: settingReducer(undefined, {}),
input: inputReducer(undefined, {}), input: inputReducer(undefined, {}),
follow: followReducer(undefined, {}), follow: followReducer(undefined, {}),
}; };
@ -12,6 +14,7 @@ const defaultState = {
export default function reducer(state = defaultState, action = {}) { export default function reducer(state = defaultState, action = {}) {
return Object.assign({}, state, { return Object.assign({}, state, {
addon: addonReducer(state.addon, action), addon: addonReducer(state.addon, action),
setting: settingReducer(state.setting, action),
input: inputReducer(state.input, action), input: inputReducer(state.input, action),
follow: followReducer(state.follow, action), follow: followReducer(state.follow, action),
}); });

@ -1,8 +1,7 @@
import actions from 'content/actions'; import actions from 'content/actions';
const defaultState = { const defaultState = {
keys: '', keys: ''
keymaps: {},
}; };
export default function reducer(state = defaultState, action = {}) { export default function reducer(state = defaultState, action = {}) {
@ -15,10 +14,6 @@ export default function reducer(state = defaultState, action = {}) {
return Object.assign({}, state, { return Object.assign({}, state, {
keys: '', keys: '',
}); });
case actions.INPUT_SET_KEYMAPS:
return Object.assign({}, state, {
keymaps: action.keymaps,
});
default: default:
return state; return state;
} }

@ -0,0 +1,13 @@
import actions from 'content/actions';
const defaultState = {};
export default function reducer(state = defaultState, action = {}) {
switch (action.type) {
case actions.SETTING_SET:
return Object.assign({}, action.value);
default:
return state;
}
}

@ -0,0 +1,6 @@
const fromWildcard = (pattern) => {
let regexStr = '^' + pattern.replace(/\*/g, '.*') + '$';
return new RegExp(regexStr);
};
export { fromWildcard };

@ -1,6 +1,6 @@
import operations from 'shared/operations'; import operations from 'shared/operations';
const VALID_TOP_KEYS = ['keymaps', 'search']; const VALID_TOP_KEYS = ['keymaps', 'search', 'blacklist'];
const VALID_OPERATION_VALUES = Object.keys(operations).map((key) => { const VALID_OPERATION_VALUES = Object.keys(operations).map((key) => {
return operations[key]; return operations[key];
}); });

@ -0,0 +1,13 @@
import { expect } from "chai";
import actions from 'content/actions';
import * as settingActions from 'content/actions/setting';
describe("setting actions", () => {
describe("set", () => {
it('create SETTING_SET action', () => {
let action = settingActions.set({ red: 'apple', yellow: 'banana' });
expect(action.type).to.equal(actions.SETTING_SET);
expect(action.value).to.deep.equal({ red: 'apple', yellow: 'banana' });
});
});
});

@ -0,0 +1,18 @@
import { expect } from "chai";
import actions from 'content/actions';
import settingReducer from 'content/reducers/setting';
describe("content setting reducer", () => {
it('return the initial state', () => {
let state = settingReducer(undefined, {});
expect(state).to.deep.equal({});
});
it('return next state for SETTING_SET', () => {
let newSettings = { red: 'apple', yellow: 'banana' };
let action = { type: actions.SETTING_SET, value: newSettings };
let state = settingReducer(undefined, action);
expect(state).to.deep.equal(newSettings);
expect(state).not.to.equal(newSettings); // assert deep copy
});
});

@ -0,0 +1,20 @@
import { expect } from 'chai';
import * as re from 'shared/utils/re';
describe("re util", () => {
it('matches by pattern', () => {
let regex = re.fromWildcard('*.example.com/*');
expect('foo.example.com/bar').to.match(regex);
expect('foo.example.com').not.to.match(regex);
expect('example.com/bar').not.to.match(regex);
regex = re.fromWildcard('example.com/*')
expect('example.com/foo').to.match(regex);
expect('example.com/').to.match(regex);
regex = re.fromWildcard('example.com/*bar')
expect('example.com/foobar').to.match(regex);
expect('example.com/bar').to.match(regex);
expect('example.com/foobarfoo').not.to.match(regex);
})
});