diff options
Diffstat (limited to 'src/background')
-rw-r--r-- | src/background/Application.ts | 40 | ||||
-rw-r--r-- | src/background/clients/FindClient.ts | 45 | ||||
-rw-r--r-- | src/background/controllers/FindController.ts | 12 | ||||
-rw-r--r-- | src/background/di.ts | 8 | ||||
-rw-r--r-- | src/background/infrastructures/ContentMessageListener.ts | 22 | ||||
-rw-r--r-- | src/background/infrastructures/FindPortListener.ts | 23 | ||||
-rw-r--r-- | src/background/operators/impls/FindNextOperator.ts | 91 | ||||
-rw-r--r-- | src/background/operators/impls/FindOperatorFactoryChain.ts | 49 | ||||
-rw-r--r-- | src/background/operators/impls/FindPrevOperator.ts | 92 | ||||
-rw-r--r-- | src/background/operators/impls/OperatorFactoryImpl.ts | 3 | ||||
-rw-r--r-- | src/background/repositories/FindRepository.ts | 58 | ||||
-rw-r--r-- | src/background/repositories/ReadyFrameRepository.ts | 70 | ||||
-rw-r--r-- | src/background/usecases/FindUseCase.ts | 15 | ||||
-rw-r--r-- | src/background/usecases/StartFindUseCase.ts | 56 |
14 files changed, 539 insertions, 45 deletions
diff --git a/src/background/Application.ts b/src/background/Application.ts index 69fe4a4..b439d19 100644 --- a/src/background/Application.ts +++ b/src/background/Application.ts @@ -1,8 +1,11 @@ import { injectable, inject } from "tsyringe"; import ContentMessageListener from "./infrastructures/ContentMessageListener"; +import FindPortListener from "./infrastructures/FindPortListener"; import SettingController from "./controllers/SettingController"; import VersionController from "./controllers/VersionController"; import SettingRepository from "./repositories/SettingRepository"; +import FindRepositoryImpl from "./repositories/FindRepository"; +import ReadyFrameRepository from "./repositories/ReadyFrameRepository"; @injectable() export default class Application { @@ -11,12 +14,26 @@ export default class Application { private settingController: SettingController, private versionController: VersionController, @inject("SyncSettingRepository") - private syncSettingRepository: SettingRepository + private syncSettingRepository: SettingRepository, + @inject("FindRepository") + private readonly findRepository: FindRepositoryImpl, + @inject("ReadyFrameRepository") + private readonly frameRepository: ReadyFrameRepository ) {} + private readonly findPortListener = new FindPortListener( + this.onFindPortConnect.bind(this), + this.onFindPortDisconnect.bind(this) + ); + run() { this.settingController.reload(); + browser.tabs.onUpdated.addListener((tabId: number, info) => { + if (info.status == "loading") { + this.findRepository.deleteLocalState(tabId); + } + }); browser.runtime.onInstalled.addListener((details) => { if (details.reason !== "install" && details.reason !== "update") { return; @@ -28,5 +45,26 @@ export default class Application { this.syncSettingRepository.onChange(() => { this.settingController.reload(); }); + this.findPortListener.run(); + } + + private onFindPortConnect(port: browser.runtime.Port) { + const tabId = port.sender?.tab?.id; + const frameId = port.sender?.frameId; + if (typeof tabId === "undefined" || typeof frameId === "undefined") { + return; + } + + this.frameRepository.addFrameId(tabId, frameId); + } + + private onFindPortDisconnect(port: browser.runtime.Port) { + const tabId = port.sender?.tab?.id; + const frameId = port.sender?.frameId; + if (typeof tabId === "undefined" || typeof frameId === "undefined") { + return; + } + + this.frameRepository.removeFrameId(tabId, frameId); } } diff --git a/src/background/clients/FindClient.ts b/src/background/clients/FindClient.ts new file mode 100644 index 0000000..b46b964 --- /dev/null +++ b/src/background/clients/FindClient.ts @@ -0,0 +1,45 @@ +import * as messages from "../../shared/messages"; + +export default interface FindClient { + findNext(tabId: number, frameId: number, keyword: string): Promise<boolean>; + + findPrev(tabId: number, frameId: number, keyword: string): Promise<boolean>; + + clearSelection(tabId: number, frameId: number): Promise<void>; +} + +export class FindClientImpl implements FindClient { + async findNext( + tabId: number, + frameId: number, + keyword: string + ): Promise<boolean> { + const found = (await browser.tabs.sendMessage( + tabId, + { type: messages.FIND_NEXT, keyword }, + { frameId } + )) as boolean; + return found; + } + + async findPrev( + tabId: number, + frameId: number, + keyword: string + ): Promise<boolean> { + const found = (await browser.tabs.sendMessage( + tabId, + { type: messages.FIND_PREV, keyword }, + { frameId } + )) as boolean; + return found; + } + + clearSelection(tabId: number, frameId: number): Promise<void> { + return browser.tabs.sendMessage( + tabId, + { type: messages.FIND_CLEAR_SELECTION }, + { frameId } + ); + } +} diff --git a/src/background/controllers/FindController.ts b/src/background/controllers/FindController.ts index f3ea93f..0772866 100644 --- a/src/background/controllers/FindController.ts +++ b/src/background/controllers/FindController.ts @@ -1,15 +1,11 @@ import { injectable } from "tsyringe"; -import FindUseCase from "../usecases/FindUseCase"; +import StartFindUseCase from "../usecases/StartFindUseCase"; @injectable() export default class FindController { - constructor(private findUseCase: FindUseCase) {} + constructor(private startFindUseCase: StartFindUseCase) {} - getKeyword(): Promise<string> { - return this.findUseCase.getKeyword(); - } - - setKeyword(keyword: string): Promise<void> { - return this.findUseCase.setKeyword(keyword); + startFind(tabId: number, keyword?: string): Promise<void> { + return this.startFindUseCase.startFind(tabId, keyword); } } diff --git a/src/background/di.ts b/src/background/di.ts index a571c7d..495de7c 100644 --- a/src/background/di.ts +++ b/src/background/di.ts @@ -18,7 +18,10 @@ import { BrowserSettingRepositoryImpl } from "./repositories/BrowserSettingRepos import { RepeatRepositoryImpl } from "./repositories/RepeatRepository"; import { ZoomPresenterImpl } from "./presenters/ZoomPresenter"; import { WindowPresenterImpl } from "./presenters/WindowPresenter"; +import { FindClientImpl } from "./clients/FindClient"; import { ConsoleFrameClientImpl } from "./clients/ConsoleFrameClient"; +import { FindRepositoryImpl } from "./repositories/FindRepository"; +import { ReadyFrameRepositoryImpl } from "./repositories/ReadyFrameRepository"; container.register("LocalSettingRepository", { useClass: LocalSettingRepository, @@ -40,7 +43,12 @@ container.register("TabRepository", { useClass: TabRepositoryImpl }); container.register("ZoomPresenter", { useClass: ZoomPresenterImpl }); container.register("TabPresenter", { useClass: TabPresenterImpl }); container.register("WindowPresenter", { useClass: WindowPresenterImpl }); +container.register("FindRepository", { useClass: FindRepositoryImpl }); +container.register("FindClient", { useClass: FindClientImpl }); container.register("NavigateClient", { useClass: NavigateClientImpl }); container.register("ConsoleClient", { useClass: ConsoleClientImpl }); container.register("ConsoleFrameClient", { useClass: ConsoleFrameClientImpl }); container.register("OperatorFactory", { useClass: OperatorFactoryImpl }); +container.register("ReadyFrameRepository", { + useClass: ReadyFrameRepositoryImpl, +}); diff --git a/src/background/infrastructures/ContentMessageListener.ts b/src/background/infrastructures/ContentMessageListener.ts index 6023047..ce7ff09 100644 --- a/src/background/infrastructures/ContentMessageListener.ts +++ b/src/background/infrastructures/ContentMessageListener.ts @@ -3,13 +3,13 @@ import * as messages from "../../shared/messages"; import * as operations from "../../shared/operations"; import CommandController from "../controllers/CommandController"; import SettingController from "../controllers/SettingController"; -import FindController from "../controllers/FindController"; import AddonEnabledController from "../controllers/AddonEnabledController"; import LinkController from "../controllers/LinkController"; import OperationController from "../controllers/OperationController"; import MarkController from "../controllers/MarkController"; import CompletionController from "../controllers/CompletionController"; import ConsoleController from "../controllers/ConsoleController"; +import FindController from "../controllers/FindController"; @injectable() export default class ContentMessageListener { @@ -19,12 +19,12 @@ export default class ContentMessageListener { private readonly settingController: SettingController, private readonly commandController: CommandController, private readonly completionController: CompletionController, - private readonly findController: FindController, private readonly addonEnabledController: AddonEnabledController, private readonly linkController: LinkController, private readonly operationController: OperationController, private readonly markController: MarkController, - private readonly consoleController: ConsoleController + private readonly consoleController: ConsoleController, + private readonly findController: FindController ) {} run(): void { @@ -36,6 +36,7 @@ export default class ContentMessageListener { return {}; } return ret.catch((e) => { + console.error(e); if (!sender.tab || !sender.tab.id) { return; } @@ -45,6 +46,7 @@ export default class ContentMessageListener { }); }); } catch (e) { + console.error(e); if (!sender.tab || !sender.tab.id) { return; } @@ -80,6 +82,8 @@ export default class ContentMessageListener { return this.completionController.getProperties(); case messages.CONSOLE_ENTER_COMMAND: return this.onConsoleEnterCommand(message.text); + case messages.CONSOLE_ENTER_FIND: + return this.findController.startFind(senderTab.id!, message.keyword); case messages.CONSOLE_RESIZE: return this.onConsoleResize( senderTab.id!, @@ -88,10 +92,6 @@ export default class ContentMessageListener { ); case messages.SETTINGS_QUERY: return this.onSettingsQuery(); - case messages.FIND_GET_KEYWORD: - return this.onFindGetKeyword(); - case messages.FIND_SET_KEYWORD: - return this.onFindSetKeyword(message.keyword); case messages.ADDON_ENABLED_RESPONSE: return this.onAddonEnabledResponse(message.enabled); case messages.OPEN_URL: @@ -132,14 +132,6 @@ export default class ContentMessageListener { return (await this.settingController.getSetting()).toJSON(); } - onFindGetKeyword(): Promise<string> { - return this.findController.getKeyword(); - } - - onFindSetKeyword(keyword: string): Promise<void> { - return this.findController.setKeyword(keyword); - } - onAddonEnabledResponse(enabled: boolean): Promise<void> { return this.addonEnabledController.indicate(enabled); } diff --git a/src/background/infrastructures/FindPortListener.ts b/src/background/infrastructures/FindPortListener.ts new file mode 100644 index 0000000..ca82439 --- /dev/null +++ b/src/background/infrastructures/FindPortListener.ts @@ -0,0 +1,23 @@ +import { injectable } from "tsyringe"; + +type OnConnectFunc = (port: browser.runtime.Port) => void; +type OnDisconnectFunc = (port: browser.runtime.Port) => void; + +@injectable() +export default class FindPortListener { + constructor( + private readonly onConnect: OnConnectFunc, + private readonly onDisconnect: OnDisconnectFunc + ) {} + + run(): void { + browser.runtime.onConnect.addListener((port) => { + if (port.name !== "vimvixen-find") { + return; + } + + port.onDisconnect.addListener(this.onDisconnect); + this.onConnect(port); + }); + } +} diff --git a/src/background/operators/impls/FindNextOperator.ts b/src/background/operators/impls/FindNextOperator.ts new file mode 100644 index 0000000..99f1759 --- /dev/null +++ b/src/background/operators/impls/FindNextOperator.ts @@ -0,0 +1,91 @@ +import Operator from "../Operator"; +import TabPresenter from "../../presenters/TabPresenter"; +import FindRepository from "../../repositories/FindRepository"; +import FindClient from "../../clients/FindClient"; +import ConsoleClient from "../../infrastructures/ConsoleClient"; +import ReadyFrameRepository from "../../repositories/ReadyFrameRepository"; + +export default class FindNextOperator implements Operator { + constructor( + private readonly tabPresenter: TabPresenter, + private readonly findRepository: FindRepository, + private readonly findClient: FindClient, + private readonly consoleClient: ConsoleClient, + private readonly frameRepository: ReadyFrameRepository + ) {} + + async run(): Promise<void> { + const tab = await this.tabPresenter.getCurrent(); + const tabId = tab?.id; + if (tabId == null) { + return; + } + + const frameIds = await this.frameRepository.getFrameIds(tabId); + if (typeof frameIds === "undefined") { + // No frames are ready + return; + } + + const state = await this.findRepository.getLocalState(tabId); + if (state) { + const framePos = frameIds.indexOf(state.frameId); + if (framePos !== -1) { + // Start to find the keyword from the current frame which last found on, + // and concat it to end of frame ids to perform a wrap-search + // + // ,- keyword should be in this frame + // | + // [100, 101, 0, 100] + // | + // `- continue from frame id 100 + // + const targetFrameIds = frameIds + .slice(framePos) + .concat(frameIds.slice(0, framePos), frameIds[framePos]); + + for (const frameId of targetFrameIds) { + const found = await this.findClient.findNext( + tabId, + frameId, + state.keyword + ); + if (found) { + this.findRepository.setLocalState(tabId, { + keyword: state.keyword, + frameId, + }); + return; + } + this.findClient.clearSelection(tabId, frameId); + } + + // The keyword is gone. + this.consoleClient.showError( + tabId, + "Pattern not found: " + state.keyword + ); + return; + } + } + + const keyword = await this.findRepository.getGlobalKeyword(); + if (keyword) { + for (const frameId of frameIds) { + await this.findClient.clearSelection(tabId, frameId); + } + + for (const frameId of frameIds) { + const found = await this.findClient.findNext(tabId, frameId, keyword); + if (found) { + await this.findRepository.setLocalState(tabId, { frameId, keyword }); + await this.consoleClient.showInfo(tabId, "Pattern found: " + keyword); + return; + } + } + this.consoleClient.showError(tabId, "Pattern not found: " + keyword); + return; + } + await this.consoleClient.showError(tabId, "No previous search keywords"); + } +} diff --git a/src/background/operators/impls/FindOperatorFactoryChain.ts b/src/background/operators/impls/FindOperatorFactoryChain.ts new file mode 100644 index 0000000..cc169dd --- /dev/null +++ b/src/background/operators/impls/FindOperatorFactoryChain.ts @@ -0,0 +1,49 @@ +import { inject, injectable } from "tsyringe"; +import Operator from "../Operator"; +import OperatorFactoryChain from "../OperatorFactoryChain"; +import TabPresenter from "../../presenters/TabPresenter"; +import * as operations from "../../../shared/operations"; +import FindNextOperator from "./FindNextOperator"; +import FindPrevOperator from "./FindPrevOperator"; +import FindRepository from "../../repositories/FindRepository"; +import FindClient from "../../clients/FindClient"; +import ConsoleClient from "../../infrastructures/ConsoleClient"; +import ReadyFrameRepository from "../../repositories/ReadyFrameRepository"; + +@injectable() +export default class FindOperatorFactoryChain implements OperatorFactoryChain { + constructor( + @inject("TabPresenter") + private readonly tabPresenter: TabPresenter, + @inject("FindRepository") + private readonly findRepository: FindRepository, + @inject("FindClient") + private readonly findClient: FindClient, + @inject("ConsoleClient") + private readonly consoleClient: ConsoleClient, + @inject("ReadyFrameRepository") + private readonly frameRepository: ReadyFrameRepository + ) {} + + create(op: operations.Operation): Operator | null { + switch (op.type) { + case operations.FIND_NEXT: + return new FindNextOperator( + this.tabPresenter, + this.findRepository, + this.findClient, + this.consoleClient, + this.frameRepository + ); + case operations.FIND_PREV: + return new FindPrevOperator( + this.tabPresenter, + this.findRepository, + this.findClient, + this.consoleClient, + this.frameRepository + ); + } + return null; + } +} diff --git a/src/background/operators/impls/FindPrevOperator.ts b/src/background/operators/impls/FindPrevOperator.ts new file mode 100644 index 0000000..f8506b9 --- /dev/null +++ b/src/background/operators/impls/FindPrevOperator.ts @@ -0,0 +1,92 @@ +import Operator from "../Operator"; +import TabPresenter from "../../presenters/TabPresenter"; +import FindRepository from "../../repositories/FindRepository"; +import FindClient from "../../clients/FindClient"; +import ConsoleClient from "../../infrastructures/ConsoleClient"; +import ReadyFrameRepository from "../../repositories/ReadyFrameRepository"; + +export default class FindPrevOperator implements Operator { + constructor( + private readonly tabPresenter: TabPresenter, + private readonly findRepository: FindRepository, + private readonly findClient: FindClient, + private readonly consoleClient: ConsoleClient, + private readonly frameRepository: ReadyFrameRepository + ) {} + + async run(): Promise<void> { + const tab = await this.tabPresenter.getCurrent(); + const tabId = tab?.id; + if (tabId == null) { + return; + } + + let frameIds = await this.frameRepository.getFrameIds(tabId); + if (typeof frameIds === "undefined") { + // No frames are ready + return; + } + frameIds = frameIds.slice(0).reverse(); + + const state = await this.findRepository.getLocalState(tabId); + if (state) { + const framePos = frameIds.indexOf(state.frameId); + if (framePos !== -1) { + // Start to find the keyword from the current frame which last found on, + // and concat it to end of frame ids to perform a wrap-search + // + // ,- keyword should be in this frame + // | + // [100, 101, 0, 100] + // | + // `- continue from frame id 100 + // + const targetFrameIds = frameIds + .slice(framePos) + .concat(frameIds.slice(0, framePos), frameIds[framePos]); + + for (const frameId of targetFrameIds) { + const found = await this.findClient.findPrev( + tabId, + frameId, + state.keyword + ); + if (found) { + this.findRepository.setLocalState(tabId, { + keyword: state.keyword, + frameId, + }); + return; + } + this.findClient.clearSelection(tabId, frameId); + } + + // The keyword is gone. + this.consoleClient.showError( + tabId, + "Pattern not found: " + state.keyword + ); + return; + } + } + + const keyword = await this.findRepository.getGlobalKeyword(); + if (keyword) { + for (const frameId of frameIds) { + await this.findClient.clearSelection(tabId, frameId); + } + + for (const frameId of frameIds) { + const found = await this.findClient.findPrev(tabId, frameId, keyword); + if (found) { + await this.findRepository.setLocalState(tabId, { frameId, keyword }); + await this.consoleClient.showInfo(tabId, "Pattern found: " + keyword); + return; + } + } + this.consoleClient.showError(tabId, "Pattern not found: " + keyword); + return; + } + await this.consoleClient.showError(tabId, "No previous search keywords"); + } +} diff --git a/src/background/operators/impls/OperatorFactoryImpl.ts b/src/background/operators/impls/OperatorFactoryImpl.ts index 34e7bb5..ce87491 100644 --- a/src/background/operators/impls/OperatorFactoryImpl.ts +++ b/src/background/operators/impls/OperatorFactoryImpl.ts @@ -8,6 +8,7 @@ import NavigateOperatorFactoryChain from "./NavigateOperatorFactoryChain"; import RepeatOperatorFactoryChain from "./RepeatOperatorFactoryChain"; import TabOperatorFactoryChain from "./TabOperatorFactoryChain"; import ZoomOperatorFactoryChain from "./ZoomOperatorFactoryChain"; +import FindOperatorFactoryChain from "./FindOperatorFactoryChain"; import * as operations from "../../../shared/operations"; @injectable() @@ -20,6 +21,7 @@ export class OperatorFactoryImpl implements OperatorFactory { navigateOperatorFactoryChain: NavigateOperatorFactoryChain, tabOperatorFactoryChain: TabOperatorFactoryChain, zoomOperatorFactoryChain: ZoomOperatorFactoryChain, + findOperatorFactoryChain: FindOperatorFactoryChain, @inject(delay(() => RepeatOperatorFactoryChain)) repeatOperatorFactoryChain: RepeatOperatorFactoryChain ) { @@ -30,6 +32,7 @@ export class OperatorFactoryImpl implements OperatorFactory { repeatOperatorFactoryChain, tabOperatorFactoryChain, zoomOperatorFactoryChain, + findOperatorFactoryChain, ]; } diff --git a/src/background/repositories/FindRepository.ts b/src/background/repositories/FindRepository.ts index 813e065..3492759 100644 --- a/src/background/repositories/FindRepository.ts +++ b/src/background/repositories/FindRepository.ts @@ -1,22 +1,68 @@ import { injectable } from "tsyringe"; import MemoryStorage from "../infrastructures/MemoryStorage"; -const FIND_KEYWORD_KEY = "find-keyword"; +const FIND_GLOBAL_KEYWORD_KEY = "find-global-keyword"; +const FIND_LOCAL_KEYWORD_KEY = "find-local-keyword"; + +export type FindState = { + keyword: string; + frameId: number; +}; + +export default interface FindRepository { + getGlobalKeyword(): Promise<string | undefined>; + + setGlobalKeyword(keyword: string): Promise<void>; + + getLocalState(tabId: number): Promise<undefined | FindState>; + + setLocalState(tabId: number, state: FindState): Promise<void>; + + deleteLocalState(tabId: number): Promise<void>; +} @injectable() -export default class FindRepository { +export class FindRepositoryImpl implements FindRepository { private cache: MemoryStorage; constructor() { this.cache = new MemoryStorage(); } - getKeyword(): Promise<string> { - return Promise.resolve(this.cache.get(FIND_KEYWORD_KEY)); + getGlobalKeyword(): Promise<string | undefined> { + return Promise.resolve(this.cache.get(FIND_GLOBAL_KEYWORD_KEY)); + } + + setGlobalKeyword(keyword: string): Promise<void> { + this.cache.set(FIND_GLOBAL_KEYWORD_KEY, keyword); + return Promise.resolve(); + } + + getLocalState(tabId: number): Promise<FindState | undefined> { + let states = this.cache.get(FIND_LOCAL_KEYWORD_KEY); + if (typeof states === "undefined") { + states = {}; + } + return Promise.resolve(states[tabId]); + } + + setLocalState(tabId: number, state: FindState): Promise<void> { + let states = this.cache.get(FIND_LOCAL_KEYWORD_KEY); + if (typeof states === "undefined") { + states = {}; + } + states[tabId] = state; + this.cache.set(FIND_LOCAL_KEYWORD_KEY, states); + return Promise.resolve(); } - setKeyword(keyword: string): Promise<void> { - this.cache.set(FIND_KEYWORD_KEY, keyword); + deleteLocalState(tabId: number): Promise<void> { + const states = this.cache.get(FIND_LOCAL_KEYWORD_KEY); + if (typeof states === "undefined") { + return Promise.resolve(); + } + delete states[tabId]; + this.cache.set(FIND_LOCAL_KEYWORD_KEY, states); return Promise.resolve(); } } diff --git a/src/background/repositories/ReadyFrameRepository.ts b/src/background/repositories/ReadyFrameRepository.ts new file mode 100644 index 0000000..c993858 --- /dev/null +++ b/src/background/repositories/ReadyFrameRepository.ts @@ -0,0 +1,70 @@ +import MemoryStorage from "../infrastructures/MemoryStorage"; + +const REPOSITORY_KEY = "readyFrameRepository"; + +type State = { [tabId: number]: { [frameId: number]: number } }; + +export default interface ReadyFrameRepository { + addFrameId(tabId: number, frameId: number): Promise<void>; + + removeFrameId(tabId: number, frameId: number): Promise<void>; + + getFrameIds(tabId: number): Promise<number[] | undefined>; +} + +export class ReadyFrameRepositoryImpl implements ReadyFrameRepository { + private cache: MemoryStorage; + + constructor() { + this.cache = new MemoryStorage(); + } + + addFrameId(tabId: number, frameId: number): Promise<void> { + let state: State | undefined = this.cache.get(REPOSITORY_KEY); + if (typeof state === "undefined") { + state = {}; + } + const tab = state[tabId] || {}; + tab[frameId] = (tab[frameId] || 0) + 1; + state[tabId] = tab; + this.cache.set(REPOSITORY_KEY, state); + return Promise.resolve(); + } + + removeFrameId(tabId: number, frameId: number): Promise<void> { + const state: State | undefined = this.cache.get(REPOSITORY_KEY); + if (typeof state === "undefined") { + return Promise.resolve(); + } + const ids = state[tabId]; + if (typeof ids === "undefined") { + return Promise.resolve(); + } + const tab = state[tabId] || {}; + tab[frameId] = (tab[frameId] || 0) - 1; + if (tab[frameId] == 0) { + delete tab[frameId]; + } + if (Object.keys(tab).length === 0) { + delete state[tabId]; + } + + this.cache.set(REPOSITORY_KEY, state); + return Promise.resolve(); + } + + getFrameIds(tabId: number): Promise<number[] | undefined> { + const state: State | undefined = this.cache.get(REPOSITORY_KEY); + if (typeof state === "undefined") { + return Promise.resolve(undefined); + } + const tab = state[tabId]; + if (typeof tab === "undefined") { + return Promise.resolve(undefined); + } + const frameIds = Object.keys(tab) + .map((v) => Number(v)) + .sort(); + return Promise.resolve(frameIds); + } +} diff --git a/src/background/usecases/FindUseCase.ts b/src/background/usecases/FindUseCase.ts deleted file mode 100644 index cc111f2..0000000 --- a/src/background/usecases/FindUseCase.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { injectable } from "tsyringe"; -import FindRepository from "../repositories/FindRepository"; - -@injectable() -export default class FindUseCase { - constructor(private readonly findRepository: FindRepository) {} - - getKeyword(): Promise<string> { - return this.findRepository.getKeyword(); - } - - setKeyword(keyword: string): Promise<void> { - return this.findRepository.setKeyword(keyword); - } -} diff --git a/src/background/usecases/StartFindUseCase.ts b/src/background/usecases/StartFindUseCase.ts new file mode 100644 index 0000000..6aad962 --- /dev/null +++ b/src/background/usecases/StartFindUseCase.ts @@ -0,0 +1,56 @@ +import { inject, injectable } from "tsyringe"; +import ConsoleClient from "../infrastructures/ConsoleClient"; +import FindRepositoryImpl from "../repositories/FindRepository"; +import FindClient from "../clients/FindClient"; +import ReadyFrameRepository from "../repositories/ReadyFrameRepository"; + +@injectable() +export default class StartFindUseCase { + constructor( + @inject("FindClient") + private readonly findClient: FindClient, + @inject("FindRepository") + private readonly findRepository: FindRepositoryImpl, + @inject("ConsoleClient") + private readonly consoleClient: ConsoleClient, + @inject("ReadyFrameRepository") + private readonly frameRepository: ReadyFrameRepository + ) {} + + async startFind(tabId: number, keyword?: string): Promise<void> { + if (typeof keyword === "undefined") { + keyword = (await this.findRepository.getLocalState(tabId))?.keyword; + } + if (typeof keyword === "undefined") { + keyword = await this.findRepository.getGlobalKeyword(); + } + if (typeof keyword === "undefined") { + await this.consoleClient.showError(tabId, "No previous search keywords"); + return; + } + + this.findRepository.setGlobalKeyword(keyword); + + const frameIds = await this.frameRepository.getFrameIds(tabId); + if (typeof frameIds === "undefined") { + // No frames are ready + return; + } + for (const frameId of frameIds) { + await this.findClient.clearSelection(tabId, frameId); + } + + for (const frameId of frameIds) { + const found = await this.findClient.findNext(tabId, frameId, keyword); + if (found) { + await this.findRepository.setLocalState(tabId, { + frameId, + keyword, + }); + await this.consoleClient.showInfo(tabId, "Pattern found: " + keyword); + return; + } + } + this.consoleClient.showError(tabId, "Pattern not found: " + keyword); + } +} |