diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/background/Application.ts | 32 | ||||
-rw-r--r-- | src/background/di.ts | 6 | ||||
-rw-r--r-- | src/background/infrastructures/FindPortListener.ts | 23 | ||||
-rw-r--r-- | src/background/operators/impls/FindNextOperator.ts | 93 | ||||
-rw-r--r-- | src/background/operators/impls/FindOperatorFactoryChain.ts | 10 | ||||
-rw-r--r-- | src/background/operators/impls/FindPrevOperator.ts | 94 | ||||
-rw-r--r-- | src/background/presenters/FramePresenter.ts | 12 | ||||
-rw-r--r-- | src/background/repositories/FindRepository.ts | 3 | ||||
-rw-r--r-- | src/background/repositories/ReadyFrameRepository.ts | 70 | ||||
-rw-r--r-- | src/background/usecases/StartFindUseCase.ts | 23 | ||||
-rw-r--r-- | src/content/Application.ts | 16 |
11 files changed, 251 insertions, 131 deletions
diff --git a/src/background/Application.ts b/src/background/Application.ts index 2006965..b439d19 100644 --- a/src/background/Application.ts +++ b/src/background/Application.ts @@ -1,9 +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 { @@ -14,9 +16,16 @@ export default class Application { @inject("SyncSettingRepository") private syncSettingRepository: SettingRepository, @inject("FindRepository") - private readonly findRepository: FindRepositoryImpl + 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(); @@ -36,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/di.ts b/src/background/di.ts index e97c4a8..495de7c 100644 --- a/src/background/di.ts +++ b/src/background/di.ts @@ -18,10 +18,10 @@ import { BrowserSettingRepositoryImpl } from "./repositories/BrowserSettingRepos import { RepeatRepositoryImpl } from "./repositories/RepeatRepository"; import { ZoomPresenterImpl } from "./presenters/ZoomPresenter"; import { WindowPresenterImpl } from "./presenters/WindowPresenter"; -import { FramePresenterImpl } from "./presenters/FramePresenter"; 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, @@ -43,10 +43,12 @@ container.register("TabRepository", { useClass: TabRepositoryImpl }); container.register("ZoomPresenter", { useClass: ZoomPresenterImpl }); container.register("TabPresenter", { useClass: TabPresenterImpl }); container.register("WindowPresenter", { useClass: WindowPresenterImpl }); -container.register("FramePresenter", { useClass: FramePresenterImpl }); 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/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 index 241f71d..99f1759 100644 --- a/src/background/operators/impls/FindNextOperator.ts +++ b/src/background/operators/impls/FindNextOperator.ts @@ -3,7 +3,7 @@ import TabPresenter from "../../presenters/TabPresenter"; import FindRepository from "../../repositories/FindRepository"; import FindClient from "../../clients/FindClient"; import ConsoleClient from "../../infrastructures/ConsoleClient"; -import FramePresenter from "../../presenters/FramePresenter"; +import ReadyFrameRepository from "../../repositories/ReadyFrameRepository"; export default class FindNextOperator implements Operator { constructor( @@ -11,7 +11,7 @@ export default class FindNextOperator implements Operator { private readonly findRepository: FindRepository, private readonly findClient: FindClient, private readonly consoleClient: ConsoleClient, - private readonly framePresenter: FramePresenter + private readonly frameRepository: ReadyFrameRepository ) {} async run(): Promise<void> { @@ -21,67 +21,64 @@ export default class FindNextOperator implements Operator { 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) { - // 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 = state.frameIds - .slice(state.framePos) - .concat( - state.frameIds.slice(0, state.framePos), - state.frameIds[state.framePos] - ); + 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 (let i = 0; i < targetFrameIds.length; ++i) { - const found = await this.findClient.findNext( + 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, - targetFrameIds[i], - state.keyword + "Pattern not found: " + state.keyword ); - if (found) { - this.findRepository.setLocalState(tabId, { - ...state, - framePos: (i + state.framePos) % state.frameIds.length, // save current frame position or first - }); - return; - } - this.findClient.clearSelection(tabId, targetFrameIds[i]); + return; } - - // The keyword is gone. - this.consoleClient.showError( - tabId, - "Pattern not found: " + state.keyword - ); - return; } const keyword = await this.findRepository.getGlobalKeyword(); if (keyword) { - const frameIds = await this.framePresenter.getAllFrameIds(tabId); for (const frameId of frameIds) { await this.findClient.clearSelection(tabId, frameId); } - for (let framePos = 0; framePos < frameIds.length; ++framePos) { - const found = await this.findClient.findNext( - tabId, - frameIds[framePos], - keyword - ); + for (const frameId of frameIds) { + const found = await this.findClient.findNext(tabId, frameId, keyword); if (found) { - await this.findRepository.setLocalState(tabId, { - frameIds, - framePos, - keyword, - }); + await this.findRepository.setLocalState(tabId, { frameId, keyword }); await this.consoleClient.showInfo(tabId, "Pattern found: " + keyword); return; } diff --git a/src/background/operators/impls/FindOperatorFactoryChain.ts b/src/background/operators/impls/FindOperatorFactoryChain.ts index b71f032..cc169dd 100644 --- a/src/background/operators/impls/FindOperatorFactoryChain.ts +++ b/src/background/operators/impls/FindOperatorFactoryChain.ts @@ -7,8 +7,8 @@ import FindNextOperator from "./FindNextOperator"; import FindPrevOperator from "./FindPrevOperator"; import FindRepository from "../../repositories/FindRepository"; import FindClient from "../../clients/FindClient"; -import FramePresenter from "../../presenters/FramePresenter"; import ConsoleClient from "../../infrastructures/ConsoleClient"; +import ReadyFrameRepository from "../../repositories/ReadyFrameRepository"; @injectable() export default class FindOperatorFactoryChain implements OperatorFactoryChain { @@ -21,8 +21,8 @@ export default class FindOperatorFactoryChain implements OperatorFactoryChain { private readonly findClient: FindClient, @inject("ConsoleClient") private readonly consoleClient: ConsoleClient, - @inject("FramePresenter") - private readonly framePresenter: FramePresenter + @inject("ReadyFrameRepository") + private readonly frameRepository: ReadyFrameRepository ) {} create(op: operations.Operation): Operator | null { @@ -33,7 +33,7 @@ export default class FindOperatorFactoryChain implements OperatorFactoryChain { this.findRepository, this.findClient, this.consoleClient, - this.framePresenter + this.frameRepository ); case operations.FIND_PREV: return new FindPrevOperator( @@ -41,7 +41,7 @@ export default class FindOperatorFactoryChain implements OperatorFactoryChain { this.findRepository, this.findClient, this.consoleClient, - this.framePresenter + this.frameRepository ); } return null; diff --git a/src/background/operators/impls/FindPrevOperator.ts b/src/background/operators/impls/FindPrevOperator.ts index 822c386..f8506b9 100644 --- a/src/background/operators/impls/FindPrevOperator.ts +++ b/src/background/operators/impls/FindPrevOperator.ts @@ -3,7 +3,7 @@ import TabPresenter from "../../presenters/TabPresenter"; import FindRepository from "../../repositories/FindRepository"; import FindClient from "../../clients/FindClient"; import ConsoleClient from "../../infrastructures/ConsoleClient"; -import FramePresenter from "../../presenters/FramePresenter"; +import ReadyFrameRepository from "../../repositories/ReadyFrameRepository"; export default class FindPrevOperator implements Operator { constructor( @@ -11,7 +11,7 @@ export default class FindPrevOperator implements Operator { private readonly findRepository: FindRepository, private readonly findClient: FindClient, private readonly consoleClient: ConsoleClient, - private readonly framePresenter: FramePresenter + private readonly frameRepository: ReadyFrameRepository ) {} async run(): Promise<void> { @@ -21,67 +21,65 @@ export default class FindPrevOperator implements Operator { 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) { - // 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 = state.frameIds - .slice(state.framePos) - .concat( - state.frameIds.slice(0, state.framePos), - state.frameIds[state.framePos] - ); + 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 (let i = targetFrameIds.length - 1; i >= 0; --i) { - const found = await this.findClient.findPrev( + 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, - targetFrameIds[i], - state.keyword + "Pattern not found: " + state.keyword ); - if (found) { - this.findRepository.setLocalState(tabId, { - ...state, - framePos: (i + state.framePos) % state.frameIds.length, // save current frame position or first - }); - return; - } - this.findClient.clearSelection(tabId, targetFrameIds[i]); + return; } - - // The keyword is gone. - this.consoleClient.showError( - tabId, - "Pattern not found: " + state.keyword - ); - return; } const keyword = await this.findRepository.getGlobalKeyword(); if (keyword) { - const frameIds = await this.framePresenter.getAllFrameIds(tabId); for (const frameId of frameIds) { await this.findClient.clearSelection(tabId, frameId); } - for (let framePos = frameIds.length - 1; framePos >= 0; --framePos) { - const found = await this.findClient.findPrev( - tabId, - frameIds[framePos], - keyword - ); + for (const frameId of frameIds) { + const found = await this.findClient.findPrev(tabId, frameId, keyword); if (found) { - await this.findRepository.setLocalState(tabId, { - frameIds, - framePos, - keyword, - }); + await this.findRepository.setLocalState(tabId, { frameId, keyword }); await this.consoleClient.showInfo(tabId, "Pattern found: " + keyword); return; } diff --git a/src/background/presenters/FramePresenter.ts b/src/background/presenters/FramePresenter.ts deleted file mode 100644 index c94f8dd..0000000 --- a/src/background/presenters/FramePresenter.ts +++ /dev/null @@ -1,12 +0,0 @@ -export default interface FramePresenter { - getAllFrameIds(tabId: number): Promise<Array<number>>; -} - -export class FramePresenterImpl implements FramePresenter { - async getAllFrameIds(tabId: number): Promise<Array<number>> { - const frames = await browser.webNavigation.getAllFrames({ tabId: tabId }); - return frames - .filter((f) => !f.url.startsWith("moz-extension://")) - .map((f) => f.frameId); - } -} diff --git a/src/background/repositories/FindRepository.ts b/src/background/repositories/FindRepository.ts index 46ee390..3492759 100644 --- a/src/background/repositories/FindRepository.ts +++ b/src/background/repositories/FindRepository.ts @@ -6,8 +6,7 @@ const FIND_LOCAL_KEYWORD_KEY = "find-local-keyword"; export type FindState = { keyword: string; - framePos: number; - frameIds: number[]; + frameId: number; }; export default interface FindRepository { 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/StartFindUseCase.ts b/src/background/usecases/StartFindUseCase.ts index 066d930..6aad962 100644 --- a/src/background/usecases/StartFindUseCase.ts +++ b/src/background/usecases/StartFindUseCase.ts @@ -2,7 +2,7 @@ import { inject, injectable } from "tsyringe"; import ConsoleClient from "../infrastructures/ConsoleClient"; import FindRepositoryImpl from "../repositories/FindRepository"; import FindClient from "../clients/FindClient"; -import FramePresenter from "../presenters/FramePresenter"; +import ReadyFrameRepository from "../repositories/ReadyFrameRepository"; @injectable() export default class StartFindUseCase { @@ -13,8 +13,8 @@ export default class StartFindUseCase { private readonly findRepository: FindRepositoryImpl, @inject("ConsoleClient") private readonly consoleClient: ConsoleClient, - @inject("FramePresenter") - private readonly framePresenter: FramePresenter + @inject("ReadyFrameRepository") + private readonly frameRepository: ReadyFrameRepository ) {} async startFind(tabId: number, keyword?: string): Promise<void> { @@ -31,21 +31,20 @@ export default class StartFindUseCase { this.findRepository.setGlobalKeyword(keyword); - const frameIds = await this.framePresenter.getAllFrameIds(tabId); + 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 (let framePos = 0; framePos < frameIds.length; ++framePos) { - const found = await this.findClient.findNext( - tabId, - frameIds[framePos], - keyword - ); + for (const frameId of frameIds) { + const found = await this.findClient.findNext(tabId, frameId, keyword); if (found) { await this.findRepository.setLocalState(tabId, { - frameIds, - framePos, + frameId, keyword, }); await this.consoleClient.showInfo(tabId, "Pattern found: " + keyword); diff --git a/src/content/Application.ts b/src/content/Application.ts index a12c3c6..6b881fe 100644 --- a/src/content/Application.ts +++ b/src/content/Application.ts @@ -41,7 +41,21 @@ export default class Application { if (window.self === window.top) { this.routeMasterComponents(); } - return this.routeCommonComponents(); + this.routeCommonComponents(); + // Make sure the background script sends a message to the content script by + // establishing a connection. If the background script tries to send a + // message to a frame on which cannot run the content script, it fails with + // a message "Could not establish connection." + // + // The port is never used, and the messages are delivered via + // `browser.tabs.sendMessage` API because sending a message via port cannot + // receive returned value. + // + // /* on background script */ + // port.sendMessage({ type: "do something" }); <- returns void + // + browser.runtime.connect(undefined, { name: "vimvixen-find" }); + return Promise.resolve(); } private routeMasterComponents() { |