From b72728bdabf140c77f165b35813595dcaa060eea Mon Sep 17 00:00:00 2001 From: Shin'ya Ueoka Date: Thu, 23 Sep 2021 23:23:55 +0900 Subject: Create find targets by port connections --- src/background/Application.ts | 41 ++++++---- src/background/infrastructures/FindPortListener.ts | 23 ++++++ src/background/operators/impls/FindNextOperator.ts | 94 ++++++++++------------ src/background/operators/impls/FindPrevOperator.ts | 94 ++++++++++------------ src/background/repositories/FindRepository.ts | 3 +- .../repositories/ReadyFrameRepository.ts | 22 ++++- src/background/usecases/StartFindUseCase.ts | 11 +-- src/content/Application.ts | 16 +++- 8 files changed, 175 insertions(+), 129 deletions(-) create mode 100644 src/background/infrastructures/FindPortListener.ts (limited to 'src') diff --git a/src/background/Application.ts b/src/background/Application.ts index 87865e6..c7bcc42 100644 --- a/src/background/Application.ts +++ b/src/background/Application.ts @@ -1,5 +1,6 @@ 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"; @@ -20,6 +21,11 @@ export default class Application { private readonly frameRepository: ReadyFrameRepository ) {} + private readonly findPortListener = new FindPortListener( + this.onFindPortConnect.bind(this), + this.onFindPortDisconnect.bind(this) + ); + run() { this.settingController.reload(); @@ -35,24 +41,31 @@ 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(() => { 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/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 2aed9fb..99f1759 100644 --- a/src/background/operators/impls/FindNextOperator.ts +++ b/src/background/operators/impls/FindNextOperator.ts @@ -21,72 +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.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, - keyword, - }); + await this.findRepository.setLocalState(tabId, { frameId, keyword }); await this.consoleClient.showInfo(tabId, "Pattern found: " + keyword); return; } diff --git a/src/background/operators/impls/FindPrevOperator.ts b/src/background/operators/impls/FindPrevOperator.ts index 3c4411d..f8506b9 100644 --- a/src/background/operators/impls/FindPrevOperator.ts +++ b/src/background/operators/impls/FindPrevOperator.ts @@ -21,71 +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.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 = 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/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 index 725f604..72ae5a4 100644 --- a/src/background/repositories/ReadyFrameRepository.ts +++ b/src/background/repositories/ReadyFrameRepository.ts @@ -7,7 +7,9 @@ type State = { [tabId: number]: number[] }; export default interface ReadyFrameRepository { clearFrameIds(tabId: number): Promise; - addFrameId(tabId: number, fraemId: number): Promise; + addFrameId(tabId: number, frameId: number): Promise; + + removeFrameId(tabId: number, frameId: number): Promise; getFrameIds(tabId: number): Promise; } @@ -29,12 +31,26 @@ export class ReadyFrameRepositoryImpl implements ReadyFrameRepository { return Promise.resolve(); } - addFrameId(tabId: number, fraemId: number): Promise { + addFrameId(tabId: number, frameId: number): Promise { let state: State | undefined = this.cache.get(REPOSITORY_KEY); if (typeof state === "undefined") { state = {}; } - state[tabId] = (state[tabId] || []).concat(fraemId); + state[tabId] = (state[tabId] || []).concat(frameId); + this.cache.set(REPOSITORY_KEY, state); + return Promise.resolve(); + } + + removeFrameId(tabId: number, frameId: number): Promise { + 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(); + } + state[tabId] = ids.filter((id) => id != frameId); this.cache.set(REPOSITORY_KEY, state); return Promise.resolve(); } diff --git a/src/background/usecases/StartFindUseCase.ts b/src/background/usecases/StartFindUseCase.ts index a62462f..6aad962 100644 --- a/src/background/usecases/StartFindUseCase.ts +++ b/src/background/usecases/StartFindUseCase.ts @@ -40,16 +40,11 @@ export default class StartFindUseCase { 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() { -- cgit v1.2.3