diff options
-rw-r--r-- | src/background/Application.ts | 20 | ||||
-rw-r--r-- | src/background/di.ts | 6 | ||||
-rw-r--r-- | src/background/operators/impls/FindNextOperator.ts | 11 | ||||
-rw-r--r-- | src/background/operators/impls/FindOperatorFactoryChain.ts | 10 | ||||
-rw-r--r-- | src/background/operators/impls/FindPrevOperator.ts | 10 | ||||
-rw-r--r-- | src/background/presenters/FramePresenter.ts | 12 | ||||
-rw-r--r-- | src/background/repositories/ReadyFrameRepository.ts | 49 | ||||
-rw-r--r-- | src/background/usecases/ReadyFrameUseCase.ts | 18 | ||||
-rw-r--r-- | src/background/usecases/StartFindUseCase.ts | 12 | ||||
-rw-r--r-- | test/background/mock/MockReadyFrameRepository.ts | 15 | ||||
-rw-r--r-- | test/background/operators/impls/FindNextOperator.test.ts | 8 | ||||
-rw-r--r-- | test/background/operators/impls/FindPrevOperator.test.ts | 8 | ||||
-rw-r--r-- | test/background/repositories/ReadyFrameRepository.test.ts | 25 | ||||
-rw-r--r-- | test/background/usecases/StartFindUseCase.test.ts | 8 |
14 files changed, 170 insertions, 42 deletions
diff --git a/src/background/Application.ts b/src/background/Application.ts index 2006965..87865e6 100644 --- a/src/background/Application.ts +++ b/src/background/Application.ts @@ -4,6 +4,7 @@ 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,7 +15,9 @@ 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 ) {} run() { @@ -22,6 +25,7 @@ export default class Application { browser.tabs.onUpdated.addListener((tabId: number, info) => { if (info.status == "loading") { + this.frameRepository.clearFrameIds(tabId); this.findRepository.deleteLocalState(tabId); } }); @@ -31,6 +35,20 @@ export default class Application { } this.versionController.notify(); }); + browser.webNavigation.onCompleted.addListener((detail) => { + // The console iframe embedded by Vim-Vixen has url starting with + // 'moz-extensions://'. The add-on should ignore it from search targets. + // + // When a browser blocks to load an iframe by x-frame options or a + // content security policy, the URL begins with 'about:neterror', and + // a background script fails to send a message to iframe. + if ( + detail.url.startsWith("http://") || + detail.url.startsWith("https://") + ) { + this.frameRepository.addFrameId(detail.tabId, detail.frameId); + } + }); this.contentMessageListener.run(); this.syncSettingRepository.onChange(() => { 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/operators/impls/FindNextOperator.ts b/src/background/operators/impls/FindNextOperator.ts index 241f71d..2aed9fb 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> { @@ -65,7 +65,12 @@ export default class FindNextOperator implements Operator { const keyword = await this.findRepository.getGlobalKeyword(); if (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); } 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..3c4411d 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> { @@ -65,7 +65,11 @@ export default class FindPrevOperator implements Operator { const keyword = await this.findRepository.getGlobalKeyword(); if (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); } 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/ReadyFrameRepository.ts b/src/background/repositories/ReadyFrameRepository.ts new file mode 100644 index 0000000..725f604 --- /dev/null +++ b/src/background/repositories/ReadyFrameRepository.ts @@ -0,0 +1,49 @@ +import MemoryStorage from "../infrastructures/MemoryStorage"; + +const REPOSITORY_KEY = "readyFrameRepository"; + +type State = { [tabId: number]: number[] }; + +export default interface ReadyFrameRepository { + clearFrameIds(tabId: number): Promise<void>; + + addFrameId(tabId: number, fraemId: number): Promise<void>; + + getFrameIds(tabId: number): Promise<number[] | undefined>; +} + +export class ReadyFrameRepositoryImpl implements ReadyFrameRepository { + private cache: MemoryStorage; + + constructor() { + this.cache = new MemoryStorage(); + } + + clearFrameIds(tabId: number): Promise<void> { + let state: State | undefined = this.cache.get(REPOSITORY_KEY); + if (typeof state === "undefined") { + state = {}; + } + delete state[tabId]; + this.cache.set(REPOSITORY_KEY, state); + return Promise.resolve(); + } + + addFrameId(tabId: number, fraemId: number): Promise<void> { + let state: State | undefined = this.cache.get(REPOSITORY_KEY); + if (typeof state === "undefined") { + state = {}; + } + state[tabId] = (state[tabId] || []).concat(fraemId); + 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); + } + return Promise.resolve(state[tabId]); + } +} diff --git a/src/background/usecases/ReadyFrameUseCase.ts b/src/background/usecases/ReadyFrameUseCase.ts new file mode 100644 index 0000000..81bee0c --- /dev/null +++ b/src/background/usecases/ReadyFrameUseCase.ts @@ -0,0 +1,18 @@ +import { inject, injectable } from "tsyringe"; +import ReadyFrameRepository from "../repositories/ReadyFrameRepository"; + +@injectable() +export default class ReadyFrameUseCase { + constructor( + @inject("ReadyFrameRepository") + private readonly frameRepository: ReadyFrameRepository + ) {} + + async addReadyFrame(tabId: number, frameId: number): Promise<void> { + return this.frameRepository.addFrameId(tabId, frameId); + } + + async clearReadyFrame(tabId: number): Promise<void> { + return this.frameRepository.clearFrameIds(tabId); + } +} diff --git a/src/background/usecases/StartFindUseCase.ts b/src/background/usecases/StartFindUseCase.ts index 066d930..a62462f 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,7 +31,11 @@ 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); } diff --git a/test/background/mock/MockReadyFrameRepository.ts b/test/background/mock/MockReadyFrameRepository.ts new file mode 100644 index 0000000..63b7cf4 --- /dev/null +++ b/test/background/mock/MockReadyFrameRepository.ts @@ -0,0 +1,15 @@ +import ReadyFrameRepository from "../../../src/background/repositories/ReadyFrameRepository"; + +export default class MockReadyFrameRepository implements ReadyFrameRepository { + clearFrameIds(_tabId: number): Promise<void> { + throw new Error("not implemented"); + } + + addFrameId(_tabId: number, _fraemId: number): Promise<void> { + throw new Error("not implemented"); + } + + getFrameIds(_tabId: number): Promise<number[] | undefined> { + throw new Error("not implemented"); + } +} diff --git a/test/background/operators/impls/FindNextOperator.test.ts b/test/background/operators/impls/FindNextOperator.test.ts index 0bee3f5..b5088df 100644 --- a/test/background/operators/impls/FindNextOperator.test.ts +++ b/test/background/operators/impls/FindNextOperator.test.ts @@ -4,7 +4,7 @@ import FindNextOperator from "../../../../src/background/operators/impls/FindNex import MockFindRepository from "../../mock/MockFindRepository"; import MockFindClient from "../../mock/MockFindClient"; import MockConsoleClient from "../../mock/MockConsoleClient"; -import MockFramePresenter from "../../mock/MockFramePresenter"; +import MockReadyFrameRepository from "../../mock/MockReadyFrameRepository"; describe("FindNextOperator", () => { const keyword = "hello"; @@ -14,13 +14,13 @@ describe("FindNextOperator", () => { const findRepository = new MockFindRepository(); const findClient = new MockFindClient(); const consoleClient = new MockConsoleClient(); - const framePresenter = new MockFramePresenter(); + const frameRepository = new MockReadyFrameRepository(); const sut = new FindNextOperator( tabPresenter, findRepository, findClient, consoleClient, - framePresenter + frameRepository ); let currentTabId: number; @@ -152,7 +152,7 @@ describe("FindNextOperator", () => { .stub(findRepository, "getGlobalKeyword") .returns(Promise.resolve(keyword)); sinon - .stub(framePresenter, "getAllFrameIds") + .stub(frameRepository, "getFrameIds") .returns(Promise.resolve(frameIds)); sinon.stub(consoleClient, "showInfo").returns(Promise.resolve()); diff --git a/test/background/operators/impls/FindPrevOperator.test.ts b/test/background/operators/impls/FindPrevOperator.test.ts index ebac0dc..296f0dd 100644 --- a/test/background/operators/impls/FindPrevOperator.test.ts +++ b/test/background/operators/impls/FindPrevOperator.test.ts @@ -4,7 +4,7 @@ import FindPrevOperator from "../../../../src/background/operators/impls/FindPre import MockFindRepository from "../../mock/MockFindRepository"; import MockFindClient from "../../mock/MockFindClient"; import MockConsoleClient from "../../mock/MockConsoleClient"; -import MockFramePresenter from "../../mock/MockFramePresenter"; +import MockReadyFrameRepository from "../../mock/MockReadyFrameRepository"; describe("FindPrevOperator", () => { const keyword = "hello"; @@ -14,13 +14,13 @@ describe("FindPrevOperator", () => { const findRepository = new MockFindRepository(); const findClient = new MockFindClient(); const consoleClient = new MockConsoleClient(); - const framePresenter = new MockFramePresenter(); + const frameRepository = new MockReadyFrameRepository(); const sut = new FindPrevOperator( tabPresenter, findRepository, findClient, consoleClient, - framePresenter + frameRepository ); let currentTabId: number; @@ -152,7 +152,7 @@ describe("FindPrevOperator", () => { .stub(findRepository, "getGlobalKeyword") .returns(Promise.resolve(keyword)); sinon - .stub(framePresenter, "getAllFrameIds") + .stub(frameRepository, "getFrameIds") .returns(Promise.resolve(frameIds)); sinon.stub(consoleClient, "showInfo").returns(Promise.resolve()); diff --git a/test/background/repositories/ReadyFrameRepository.test.ts b/test/background/repositories/ReadyFrameRepository.test.ts new file mode 100644 index 0000000..d952a9b --- /dev/null +++ b/test/background/repositories/ReadyFrameRepository.test.ts @@ -0,0 +1,25 @@ +import { expect } from "chai"; +import { ReadyFrameRepositoryImpl } from "../../../src/background/repositories/ReadyFrameRepository"; + +describe("background/repositories/ReadyFrameRepositoryImpl", () => { + let sut: ReadyFrameRepositoryImpl; + + beforeEach(() => { + sut = new ReadyFrameRepositoryImpl(); + }); + + it("get and set a keyword", async () => { + expect(await sut.getFrameIds(1)).to.be.undefined; + + await sut.addFrameId(1, 10); + await sut.addFrameId(1, 11); + await sut.addFrameId(2, 20); + + expect(await sut.getFrameIds(1)).to.deep.equal([10, 11]); + expect(await sut.getFrameIds(2)).to.deep.equal([20]); + + await sut.clearFrameIds(1); + + expect(await sut.getFrameIds(1)).to.be.undefined; + }); +}); diff --git a/test/background/usecases/StartFindUseCase.test.ts b/test/background/usecases/StartFindUseCase.test.ts index 22ff9a5..99ab508 100644 --- a/test/background/usecases/StartFindUseCase.test.ts +++ b/test/background/usecases/StartFindUseCase.test.ts @@ -2,7 +2,7 @@ import * as sinon from "sinon"; import MockFindClient from "../mock/MockFindClient"; import MockFindRepository from "../mock/MockFindRepository"; import MockConsoleClient from "../mock/MockConsoleClient"; -import MockFramePresenter from "../mock/MockFramePresenter"; +import MockReadyFrameRepository from "../mock/MockReadyFrameRepository"; import StartFindUseCase from "../../../src/background/usecases/StartFindUseCase"; describe("StartFindUseCase", () => { @@ -13,19 +13,19 @@ describe("StartFindUseCase", () => { const findClient = new MockFindClient(); const findRepository = new MockFindRepository(); const consoleClient = new MockConsoleClient(); - const framePresenter = new MockFramePresenter(); + const frameRepository = new MockReadyFrameRepository(); const sut = new StartFindUseCase( findClient, findRepository, consoleClient, - framePresenter + frameRepository ); beforeEach(async () => { sinon.restore(); sinon - .stub(framePresenter, "getAllFrameIds") + .stub(frameRepository, "getFrameIds") .returns(Promise.resolve(frameIds)); }); |