From 66c23423f931bb66c59cd29cf9279a5de5d56535 Mon Sep 17 00:00:00 2001 From: Shin'ya Ueoka Date: Sat, 28 Jul 2018 10:42:32 +0900 Subject: [PATCH] Background operation as Clean Architecture --- src/background/components/operation.js | 3 - src/background/controllers/operation.js | 65 ++++++ .../content-message-listener.js | 8 + src/background/presenters/bookmark.js | 0 src/background/presenters/console.js | 20 ++ src/background/presenters/tab.js | 35 ++++ src/background/usecases/operation.js | 190 ++++++++++++++++++ 7 files changed, 318 insertions(+), 3 deletions(-) create mode 100644 src/background/controllers/operation.js delete mode 100644 src/background/presenters/bookmark.js create mode 100644 src/background/usecases/operation.js diff --git a/src/background/components/operation.js b/src/background/components/operation.js index ce93270..57b2497 100644 --- a/src/background/components/operation.js +++ b/src/background/components/operation.js @@ -22,9 +22,6 @@ export default class BackgroundComponent { onMessage(message, sender) { switch (message.type) { - case messages.BACKGROUND_OPERATION: - return this.store.dispatch( - this.exec(message.operation, sender.tab)); } } diff --git a/src/background/controllers/operation.js b/src/background/controllers/operation.js new file mode 100644 index 0000000..1339006 --- /dev/null +++ b/src/background/controllers/operation.js @@ -0,0 +1,65 @@ +import operations from '../../shared/operations'; +import OperationInteractor from '../usecases/operation'; + +export default class OperationController { + constructor() { + this.operationInteractor = new OperationInteractor(); + } + + // eslint-disable-next-line complexity, max-lines-per-function + exec(operation) { + switch (operation.type) { + case operations.TAB_CLOSE: + return this.operationInteractor.close(false); + case operations.TAB_CLOSE_FORCE: + return this.operationInteractor.close(true); + case operations.TAB_REOPEN: + return this.operationInteractor.reopen(); + case operations.TAB_PREV: + return this.operationInteractor.selectPrev(1); + case operations.TAB_NEXT: + return this.operationInteractor.selectNext(1); + case operations.TAB_FIRST: + return this.operationInteractor.selectFirst(); + case operations.TAB_LAST: + return this.operationInteractor.selectLast(); + case operations.TAB_PREV_SEL: + return this.operationInteractor.selectPrevSelected(); + case operations.TAB_RELOAD: + return this.operationInteractor.reload(operation.cache); + case operations.TAB_PIN: + return this.operationInteractor.setPinned(true); + case operations.TAB_UNPIN: + return this.operationInteractor.setPinned(false); + case operations.TAB_TOGGLE_PINNED: + return this.operationInteractor.togglePinned(); + case operations.TAB_DUPLICATE: + return this.operationInteractor.duplicate(); + case operations.PAGE_SOURCE: + return this.operationInteractor.openPageSource(); + case operations.ZOOM_IN: + return this.operationInteractor.zoomIn(); + case operations.ZOOM_OUT: + return this.operationInteractor.zoomOut(); + case operations.ZOOM_NEUTRAL: + return this.operationInteractor.zoomNutoral(); + case operations.COMMAND_SHOW: + return this.operationInteractor.showCommand(); + case operations.COMMAND_SHOW_OPEN: + return this.operationInteractor.showOpenCommand(operation.alter); + case operations.COMMAND_SHOW_TABOPEN: + return this.operationInteractor.showTabopenCommand(operation.alter); + case operations.COMMAND_SHOW_WINOPEN: + return this.operationInteractor.showWinopenCommand(operation.alter); + case operations.COMMAND_SHOW_BUFFER: + return this.operationInteractor.showBufferCommand(); + case operations.COMMAND_SHOW_ADDBOOKMARK: + return this.operationInteractor.showAddbookmarkCommand(operation.alter); + case operations.FIND_START: + return this.operationInteractor.findStart(); + case operations.CANCEL: + return this.operationInteractor.hideConsole(); + } + } +} + diff --git a/src/background/infrastructures/content-message-listener.js b/src/background/infrastructures/content-message-listener.js index 2e84fcc..277d108 100644 --- a/src/background/infrastructures/content-message-listener.js +++ b/src/background/infrastructures/content-message-listener.js @@ -4,6 +4,7 @@ import SettingController from '../controllers/setting'; import FindController from '../controllers/find'; import AddonEnabledController from '../controllers/addon-enabled'; import LinkController from '../controllers/link'; +import OperationController from '../controllers/operation'; export default class ContentMessageListener { constructor() { @@ -12,6 +13,7 @@ export default class ContentMessageListener { this.findController = new FindController(); this.addonEnabledController = new AddonEnabledController(); this.linkController = new LinkController(); + this.backgroundOperationController = new OperationController(); } run() { @@ -46,6 +48,8 @@ export default class ContentMessageListener { case messages.OPEN_URL: return this.onOpenUrl( message.newTab, message.url, sender.tab.id, message.background); + case messages.BACKGROUND_OPERATION: + return this.onBackgroundOperation(message.operation); } } @@ -85,4 +89,8 @@ export default class ContentMessageListener { } return this.linkController.openToTab(url, openerId); } + + onBackgroundOperation(operation) { + return this.backgroundOperationController.exec(operation); + } } diff --git a/src/background/presenters/bookmark.js b/src/background/presenters/bookmark.js deleted file mode 100644 index e69de29..0000000 diff --git a/src/background/presenters/console.js b/src/background/presenters/console.js index f7d3777..8259238 100644 --- a/src/background/presenters/console.js +++ b/src/background/presenters/console.js @@ -1,16 +1,36 @@ import messages from '../../shared/messages'; export default class ConsolePresenter { + showCommand(tabId, command) { + return browser.tabs.sendMessage(tabId, { + type: messages.CONSOLE_SHOW_COMMAND, + command, + }); + } + + showFind(tabId) { + return browser.tabs.sendMessage(tabId, { + type: messages.CONSOLE_SHOW_FIND + }); + } + showInfo(tabId, message) { return browser.tabs.sendMessage(tabId, { type: messages.CONSOLE_SHOW_INFO, text: message, }); } + showError(tabId, message) { return browser.tabs.sendMessage(tabId, { type: messages.CONSOLE_SHOW_ERROR, text: message, }); } + + hide(tabId) { + return browser.tabs.sendMessage(tabId, { + type: messages.CONSOLE_HIDE, + }); + } } diff --git a/src/background/presenters/tab.js b/src/background/presenters/tab.js index be6955a..2a06a5a 100644 --- a/src/background/presenters/tab.js +++ b/src/background/presenters/tab.js @@ -48,6 +48,41 @@ export default class TabPresenter { return browser.tabs.remove(ids); } + async reopen() { + let window = await browser.windows.getCurrent(); + let sessions = await browser.sessions.getRecentlyClosed(); + let session = sessions.find((s) => { + return s.tab && s.tab.windowId === window.id; + }); + if (!session) { + return; + } + if (session.tab) { + return browser.sessions.restore(session.tab.sessionId); + } + return browser.sessions.restore(session.window.sessionId); + } + + reload(tabId, cache) { + return browser.tabs.reload(tabId, { bypassCache: cache }); + } + + setPinned(tabId, pinned) { + return browser.tabs.update(tabId, { pinned }); + } + + duplicate(id) { + return browser.tabs.duplicate(id); + } + + getZoom(tabId) { + return browser.tabs.getZoom(tabId); + } + + setZoom(tabId, factor) { + return browser.tabs.setZoom(tabId, factor); + } + async createAdjacent(url, { openerTabId, active }) { let tabs = await browser.tabs.query({ active: true, currentWindow: true diff --git a/src/background/usecases/operation.js b/src/background/usecases/operation.js new file mode 100644 index 0000000..f19c632 --- /dev/null +++ b/src/background/usecases/operation.js @@ -0,0 +1,190 @@ +import MemoryStorage from '../infrastructures/memory-storage'; +import TabPresenter from '../presenters/tab'; +import ConsolePresenter from '../presenters/console'; + +const CURRENT_SELECTED_KEY = 'tabs.current.selected'; +const LAST_SELECTED_KEY = 'tabs.last.selected'; + +const ZOOM_SETTINGS = [ + 0.33, 0.50, 0.66, 0.75, 0.80, 0.90, 1.00, + 1.10, 1.25, 1.50, 1.75, 2.00, 2.50, 3.00 +]; + +export default class OperationInteractor { + constructor() { + this.tabPresenter = new TabPresenter(); + this.tabPresenter.onSelected(info => this.onTabSelected(info.tabId)); + + this.consolePresenter = new ConsolePresenter(); + + this.cache = new MemoryStorage(); + } + + async close(force) { + let tab = await this.tabPresenter.getCurrent(); + if (!force && tab.pinned) { + return; + } + return this.tabPresenter.remove([tab.id]); + } + + reopen() { + return this.tabPresenter.reopen(); + } + + async selectPrev(count) { + let tabs = await this.tabPresenter.getAll(); + if (tabs.length < 2) { + return; + } + let tab = tabs.find(t => t.active); + if (!tab) { + return; + } + let select = (tab.index - count + tabs.length) % tabs.length; + return this.tabPresenter.select(tabs[select].id); + } + + async selectNext(count) { + let tabs = await this.tabPresenter.getAll(); + if (tabs.length < 2) { + return; + } + let tab = tabs.find(t => t.active); + if (!tab) { + return; + } + let select = (tab.index + count) % tabs.length; + return this.tabPresenter.select(tabs[select].id); + } + + async selectFirst() { + let tabs = await this.tabPresenter.getAll(); + return this.tabPresenter.select(tabs[0].id); + } + + async selectLast() { + let tabs = await this.tabPresenter.getAll(); + return this.tabPresenter.select(tabs[tabs.length - 1].id); + } + + async selectPrevSelected() { + let tabId = await this.cache.get(LAST_SELECTED_KEY); + if (tabId === null || typeof tabId === 'undefined') { + return; + } + this.tabPresenter.select(tabId); + } + + async reload(cache) { + let tab = await this.tabPresenter.getCurrent(); + return this.tabPresenter.reload(tab.id, cache); + } + + async setPinned(pinned) { + let tab = await this.tabPresenter.getCurrent(); + return this.tabPresenter.setPinned(tab.id, pinned); + } + + async togglePinned() { + let tab = await this.tabPresenter.getCurrent(); + return this.tabPresenter.setPinned(tab.id, !tab.pinned); + } + + async duplicate() { + let tab = await this.tabPresenter.getCurrent(); + return this.tabPresenter.duplicate(tab.id); + } + + async openPageSource() { + let tab = await this.tabPresenter.getCurrent(); + let url = 'view-source:' + tab.url; + return this.tabPresenter.create(url); + } + + async zoomIn(tabId) { + let tab = await this.tabPresenter.getCurrent(); + let current = await this.tabPresenter.getZoom(tab.id); + let factor = ZOOM_SETTINGS.find(f => f > current); + if (factor) { + return this.tabPresenter.setZoom(tabId, factor); + } + } + + async zoomOut(tabId) { + let tab = await this.tabPresenter.getCurrent(); + let current = await this.tabPresenter.getZoom(tab.id); + let factor = [].concat(ZOOM_SETTINGS).reverse().find(f => f < current); + if (factor) { + return this.tabPresenter.setZoom(tabId, factor); + } + } + + zoomNutoral(tabId) { + return this.tabPresenter.setZoom(tabId, 1); + } + + async showCommand() { + let tab = await this.tabPresenter.getCurrent(); + this.consolePresenter.showCommand(tab.id, ''); + } + + async showOpenCommand(alter) { + let tab = await this.tabPresenter.getCurrent(); + let command = 'open '; + if (alter) { + command += tab.url; + } + return this.consolePresenter.showCommand(tab.id, command); + } + + async showTabopenCommand(alter) { + let tab = await this.tabPresenter.getCurrent(); + let command = 'tabopen '; + if (alter) { + command += tab.url; + } + return this.consolePresenter.showCommand(tab.id, command); + } + + async showWinopenCommand(alter) { + let tab = await this.tabPresenter.getCurrent(); + let command = 'winopen '; + if (alter) { + command += tab.url; + } + return this.consolePresenter.showCommand(tab.id, command); + } + + async showBufferCommand() { + let tab = await this.tabPresenter.getCurrent(); + let command = 'buffer '; + return this.consolePresenter.showCommand(tab.id, command); + } + + async showAddbookmarkCommand(alter) { + let tab = await this.tabPresenter.getCurrent(); + let command = 'addbookmark '; + if (alter) { + command += tab.title; + } + return this.consolePresenter.showCommand(tab.id, command); + } + + async findStart() { + let tab = await this.tabPresenter.getCurrent(); + this.consolePresenter.showFind(tab.id); + } + + async hideConsole() { + let tab = await this.tabPresenter.getCurrent(); + this.consolePresenter.hide(tab.id); + } + + onTabSelected(tabId) { + let lastId = this.cache.get(CURRENT_SELECTED_KEY); + this.cache.set(LAST_SELECTED_KEY, lastId); + this.cache.set(CURRENT_SELECTED_KEY, tabId); + } +} +