aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/background/Application.ts32
-rw-r--r--src/background/di.ts6
-rw-r--r--src/background/infrastructures/FindPortListener.ts23
-rw-r--r--src/background/operators/impls/FindNextOperator.ts93
-rw-r--r--src/background/operators/impls/FindOperatorFactoryChain.ts10
-rw-r--r--src/background/operators/impls/FindPrevOperator.ts94
-rw-r--r--src/background/presenters/FramePresenter.ts12
-rw-r--r--src/background/repositories/FindRepository.ts3
-rw-r--r--src/background/repositories/ReadyFrameRepository.ts70
-rw-r--r--src/background/usecases/StartFindUseCase.ts23
-rw-r--r--src/content/Application.ts16
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() {