aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/background/Application.ts40
-rw-r--r--src/background/clients/FindClient.ts45
-rw-r--r--src/background/controllers/FindController.ts12
-rw-r--r--src/background/di.ts8
-rw-r--r--src/background/infrastructures/ContentMessageListener.ts22
-rw-r--r--src/background/infrastructures/FindPortListener.ts23
-rw-r--r--src/background/operators/impls/FindNextOperator.ts91
-rw-r--r--src/background/operators/impls/FindOperatorFactoryChain.ts49
-rw-r--r--src/background/operators/impls/FindPrevOperator.ts92
-rw-r--r--src/background/operators/impls/OperatorFactoryImpl.ts3
-rw-r--r--src/background/repositories/FindRepository.ts58
-rw-r--r--src/background/repositories/ReadyFrameRepository.ts70
-rw-r--r--src/background/usecases/FindUseCase.ts15
-rw-r--r--src/background/usecases/StartFindUseCase.ts56
-rw-r--r--src/console/app/hooks.ts11
-rw-r--r--src/content/Application.ts53
-rw-r--r--src/content/MessageListener.ts7
-rw-r--r--src/content/client/FindClient.ts23
-rw-r--r--src/content/client/FindMasterClient.ts27
-rw-r--r--src/content/controllers/FindController.ts13
-rw-r--r--src/content/di.ts10
-rw-r--r--src/content/operators/impls/FindNextOperator.ts15
-rw-r--r--src/content/operators/impls/FindOperatorFactoryChain.ts25
-rw-r--r--src/content/operators/impls/FindPrevOperator.ts15
-rw-r--r--src/content/operators/impls/OperatorFactoryImpl.ts3
-rw-r--r--src/content/presenters/FindPresenter.ts8
-rw-r--r--src/content/repositories/FindRepository.ts17
-rw-r--r--src/content/usecases/FindUseCase.ts63
-rw-r--r--src/shared/messages.ts23
-rw-r--r--src/shared/settings/Settings.ts2
-rw-r--r--src/shared/settings/validate.js613
31 files changed, 610 insertions, 902 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);
+ }
+}
diff --git a/src/console/app/hooks.ts b/src/console/app/hooks.ts
index eefdea3..00ac05c 100644
--- a/src/console/app/hooks.ts
+++ b/src/console/app/hooks.ts
@@ -103,13 +103,10 @@ export const useExecCommand = () => {
export const useExecFind = () => {
const execFind = React.useCallback((text?: string) => {
- window.top.postMessage(
- JSON.stringify({
- type: messages.CONSOLE_ENTER_FIND,
- text,
- }),
- "*"
- );
+ browser.runtime.sendMessage({
+ type: messages.CONSOLE_ENTER_FIND,
+ keyword: text,
+ });
}, []);
return execFind;
};
diff --git a/src/content/Application.ts b/src/content/Application.ts
index b09edfa..6b881fe 100644
--- a/src/content/Application.ts
+++ b/src/content/Application.ts
@@ -1,6 +1,5 @@
import { injectable } from "tsyringe";
import MessageListener from "./MessageListener";
-import FindController from "./controllers/FindController";
import MarkController from "./controllers/MarkController";
import FollowMasterController from "./controllers/FollowMasterController";
import FollowSlaveController from "./controllers/FollowSlaveController";
@@ -14,6 +13,7 @@ import SettingController from "./controllers/SettingController";
import ConsoleFrameController from "./controllers/ConsoleFrameController";
import NavigateController from "./controllers/NavigateController";
import * as messages from "../shared/messages";
+import FindController from "./controllers/FindController";
type Message = messages.Message;
@@ -22,7 +22,6 @@ export default class Application {
// eslint-disable-next-line max-params
constructor(
private messageListener: MessageListener,
- private findController: FindController,
private markController: MarkController,
private followMasterController: FollowMasterController,
private followSlaveController: FollowSlaveController,
@@ -33,7 +32,8 @@ export default class Application {
private addonEnabledController: AddonEnabledController,
private settingController: SettingController,
private consoleFrameController: ConsoleFrameController,
- private navigateController: NavigateController
+ private navigateController: NavigateController,
+ private findController: FindController
) {}
init(): Promise<void> {
@@ -41,18 +41,26 @@ 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() {
this.messageListener.onWebMessage((msg: Message, sender: Window) => {
switch (msg.type) {
- case messages.CONSOLE_ENTER_FIND:
- return this.findController.start(msg);
- case messages.FIND_NEXT:
- return this.findController.next(msg);
- case messages.FIND_PREV:
- return this.findController.prev(msg);
case messages.CONSOLE_UNFOCUS:
return this.consoleFrameController.unfocus(msg);
case messages.FOLLOW_START:
@@ -64,16 +72,6 @@ export default class Application {
}
return undefined;
});
-
- this.messageListener.onBackgroundMessage((msg: Message) => {
- switch (msg.type) {
- case messages.ADDON_ENABLED_QUERY:
- return this.addonEnabledController.getAddonEnabled(msg);
- case messages.TAB_SCROLL_TO:
- return this.markController.scrollTo(msg);
- }
- return undefined;
- });
}
private routeCommonComponents(): Promise<void> {
@@ -109,6 +107,21 @@ export default class Application {
return this.navigateController.openLinkPrev(msg);
case messages.CONSOLE_RESIZE:
return this.consoleFrameController.resize(msg);
+ case messages.FIND_NEXT:
+ return this.findController.findNext(msg.keyword);
+ case messages.FIND_PREV:
+ return this.findController.findPrev(msg.keyword);
+ case messages.FIND_CLEAR_SELECTION:
+ return this.findController.clearSelection();
+ }
+
+ if (window.self === window.top) {
+ switch (msg.type) {
+ case messages.ADDON_ENABLED_QUERY:
+ return this.addonEnabledController.getAddonEnabled(msg);
+ case messages.TAB_SCROLL_TO:
+ return this.markController.scrollTo(msg);
+ }
}
});
diff --git a/src/content/MessageListener.ts b/src/content/MessageListener.ts
index 3fe1dcd..31cc1ae 100644
--- a/src/content/MessageListener.ts
+++ b/src/content/MessageListener.ts
@@ -27,7 +27,12 @@ export default class MessageListener {
) {
browser.runtime.onMessage.addListener(
(msg: any, sender: WebExtMessageSender) => {
- return listener(valueOf(msg), sender);
+ try {
+ return Promise.resolve(listener(valueOf(msg), sender));
+ } catch (e) {
+ console.warn(e);
+ return;
+ }
}
);
}
diff --git a/src/content/client/FindClient.ts b/src/content/client/FindClient.ts
deleted file mode 100644
index 7da5069..0000000
--- a/src/content/client/FindClient.ts
+++ /dev/null
@@ -1,23 +0,0 @@
-import * as messages from "../../shared/messages";
-
-export default interface FindClient {
- getGlobalLastKeyword(): Promise<string | null>;
-
- setGlobalLastKeyword(keyword: string): Promise<void>;
-}
-
-export class FindClientImpl implements FindClient {
- async getGlobalLastKeyword(): Promise<string | null> {
- const keyword = await browser.runtime.sendMessage({
- type: messages.FIND_GET_KEYWORD,
- });
- return keyword as string;
- }
-
- async setGlobalLastKeyword(keyword: string): Promise<void> {
- await browser.runtime.sendMessage({
- type: messages.FIND_SET_KEYWORD,
- keyword: keyword,
- });
- }
-}
diff --git a/src/content/client/FindMasterClient.ts b/src/content/client/FindMasterClient.ts
deleted file mode 100644
index 9c3f812..0000000
--- a/src/content/client/FindMasterClient.ts
+++ /dev/null
@@ -1,27 +0,0 @@
-import * as messages from "../../shared/messages";
-
-export default interface FindMasterClient {
- findNext(): void;
-
- findPrev(): void;
-}
-
-export class FindMasterClientImpl implements FindMasterClient {
- findNext(): void {
- window.top.postMessage(
- JSON.stringify({
- type: messages.FIND_NEXT,
- }),
- "*"
- );
- }
-
- findPrev(): void {
- window.top.postMessage(
- JSON.stringify({
- type: messages.FIND_PREV,
- }),
- "*"
- );
- }
-}
diff --git a/src/content/controllers/FindController.ts b/src/content/controllers/FindController.ts
index 3087d5d..adcdb0d 100644
--- a/src/content/controllers/FindController.ts
+++ b/src/content/controllers/FindController.ts
@@ -1,20 +1,19 @@
import { injectable } from "tsyringe";
-import * as messages from "../../shared/messages";
import FindUseCase from "../usecases/FindUseCase";
@injectable()
export default class FindController {
constructor(private findUseCase: FindUseCase) {}
- async start(m: messages.ConsoleEnterFindMessage): Promise<void> {
- await this.findUseCase.startFind(m.text);
+ findNext(keyword: string): boolean {
+ return this.findUseCase.findNext(keyword);
}
- async next(_: messages.FindNextMessage): Promise<void> {
- await this.findUseCase.findNext();
+ findPrev(keyword: string): boolean {
+ return this.findUseCase.findPrev(keyword);
}
- async prev(_: messages.FindPrevMessage): Promise<void> {
- await this.findUseCase.findPrev();
+ clearSelection() {
+ return this.findUseCase.clearSelection();
}
}
diff --git a/src/content/di.ts b/src/content/di.ts
index e74d7ac..4c85e76 100644
--- a/src/content/di.ts
+++ b/src/content/di.ts
@@ -6,10 +6,6 @@ import { AddressRepositoryImpl } from "./repositories/AddressRepository";
import { ClipboardRepositoryImpl } from "./repositories/ClipboardRepository";
import { ConsoleClientImpl } from "./client/ConsoleClient";
import { ConsoleFramePresenterImpl } from "./presenters/ConsoleFramePresenter";
-import { FindClientImpl } from "./client/FindClient";
-import { FindMasterClientImpl } from "./client/FindMasterClient";
-import { FindPresenterImpl } from "./presenters/FindPresenter";
-import { FindRepositoryImpl } from "./repositories/FindRepository";
import { FocusPresenterImpl } from "./presenters/FocusPresenter";
import { FollowKeyRepositoryImpl } from "./repositories/FollowKeyRepository";
import { FollowMasterClientImpl } from "./client/FollowMasterClient";
@@ -31,6 +27,7 @@ import { TabsClientImpl } from "./client/TabsClient";
import { container } from "tsyringe";
import OperatorFactoryImpl from "./operators/impls/OperatorFactoryImpl";
import { URLRepositoryImpl } from "./operators/impls/URLRepository";
+import { FindPresenterImpl } from "./presenters/FindPresenter";
container.register("FollowMasterClient", {
useValue: new FollowMasterClientImpl(window.top),
@@ -49,10 +46,6 @@ container.register("ConsoleClient", { useClass: ConsoleClientImpl });
container.register("ConsoleFramePresenter", {
useClass: ConsoleFramePresenterImpl,
});
-container.register("FindClient", { useClass: FindClientImpl });
-container.register("FindMasterClient", { useClass: FindMasterClientImpl });
-container.register("FindPresenter", { useClass: FindPresenterImpl });
-container.register("FindRepository", { useClass: FindRepositoryImpl });
container.register("FocusPresenter", { useClass: FocusPresenterImpl });
container.register("FollowKeyRepository", {
useClass: FollowKeyRepositoryImpl,
@@ -79,6 +72,7 @@ container.register("NavigationPresenter", {
});
container.register("OperationClient", { useClass: OperationClientImpl });
container.register("ScrollPresenter", { useClass: ScrollPresenterImpl });
+container.register("FindPresenter", { useClass: FindPresenterImpl });
container.register("SettingClient", { useClass: SettingClientImpl });
container.register("SettingRepository", { useClass: SettingRepositoryImpl });
container.register("URLRepository", { useClass: URLRepositoryImpl });
diff --git a/src/content/operators/impls/FindNextOperator.ts b/src/content/operators/impls/FindNextOperator.ts
deleted file mode 100644
index c67f6d9..0000000
--- a/src/content/operators/impls/FindNextOperator.ts
+++ /dev/null
@@ -1,15 +0,0 @@
-import Operator from "../Operator";
-import FindMasterClient from "../../client/FindMasterClient";
-
-export default class FindNextOperator implements Operator {
- constructor(
- private readonly findMasterClient: FindMasterClient,
- private readonly repeat: number
- ) {}
-
- async run(): Promise<void> {
- for (let i = 0; i < this.repeat; ++i) {
- this.findMasterClient.findNext();
- }
- }
-}
diff --git a/src/content/operators/impls/FindOperatorFactoryChain.ts b/src/content/operators/impls/FindOperatorFactoryChain.ts
deleted file mode 100644
index b3524c1..0000000
--- a/src/content/operators/impls/FindOperatorFactoryChain.ts
+++ /dev/null
@@ -1,25 +0,0 @@
-import { inject, injectable } from "tsyringe";
-import Operator from "../Operator";
-import OperatorFactoryChain from "../OperatorFactoryChain";
-import FindNextOperator from "./FindNextOperator";
-import FindPrevOperator from "./FindPrevOperator";
-import FindMasterClient from "../../client/FindMasterClient";
-import * as operations from "../../../shared/operations";
-
-@injectable()
-export default class FindOperatorFactoryChain implements OperatorFactoryChain {
- constructor(
- @inject("FindMasterClient")
- private readonly findMasterClient: FindMasterClient
- ) {}
-
- create(op: operations.Operation, repeat: number): Operator | null {
- switch (op.type) {
- case operations.FIND_NEXT:
- return new FindNextOperator(this.findMasterClient, repeat);
- case operations.FIND_PREV:
- return new FindPrevOperator(this.findMasterClient, repeat);
- }
- return null;
- }
-}
diff --git a/src/content/operators/impls/FindPrevOperator.ts b/src/content/operators/impls/FindPrevOperator.ts
deleted file mode 100644
index f73e605..0000000
--- a/src/content/operators/impls/FindPrevOperator.ts
+++ /dev/null
@@ -1,15 +0,0 @@
-import Operator from "../Operator";
-import FindMasterClient from "../../client/FindMasterClient";
-
-export default class FindPrevOperator implements Operator {
- constructor(
- private readonly findMasterClient: FindMasterClient,
- private readonly repeat: number
- ) {}
-
- async run(): Promise<void> {
- for (let i = 0; i < this.repeat; ++i) {
- this.findMasterClient.findPrev();
- }
- }
-}
diff --git a/src/content/operators/impls/OperatorFactoryImpl.ts b/src/content/operators/impls/OperatorFactoryImpl.ts
index 22b35c8..bc9bbee 100644
--- a/src/content/operators/impls/OperatorFactoryImpl.ts
+++ b/src/content/operators/impls/OperatorFactoryImpl.ts
@@ -7,7 +7,6 @@ import { Operation } from "../../../shared/operations";
import OperationClient from "../../client/OperationClient";
import AddonOperatorFactoryChain from "./AddonOperatorFactoryChain";
import ClipboardOperatorFactoryChain from "./ClipboardOperatorFactoryChain";
-import FindOperatorFactoryChain from "./FindOperatorFactoryChain";
import FocusOperatorFactoryChain from "./FocusOperatorFactoryChain";
import FollowOperatorFactoryChain from "./FollowOperatorFactoryChain";
import MarkOperatorFactoryChain from "./MarkOperatorFactoryChain";
@@ -20,7 +19,6 @@ export default class OperatorFactoryImpl implements OperatorFactory {
constructor(
addonOperatorFactoryChain: AddonOperatorFactoryChain,
clipboardOperatorFactoryChain: ClipboardOperatorFactoryChain,
- findOperatorFactoryChain: FindOperatorFactoryChain,
focusOperatorFactoryChain: FocusOperatorFactoryChain,
followOperatorFactoryChain: FollowOperatorFactoryChain,
markOperatorFactoryChain: MarkOperatorFactoryChain,
@@ -31,7 +29,6 @@ export default class OperatorFactoryImpl implements OperatorFactory {
this.factoryChains = [
addonOperatorFactoryChain,
clipboardOperatorFactoryChain,
- findOperatorFactoryChain,
focusOperatorFactoryChain,
followOperatorFactoryChain,
markOperatorFactoryChain,
diff --git a/src/content/presenters/FindPresenter.ts b/src/content/presenters/FindPresenter.ts
index b25190c..569f161 100644
--- a/src/content/presenters/FindPresenter.ts
+++ b/src/content/presenters/FindPresenter.ts
@@ -7,16 +7,10 @@ export default interface FindPresenter {
export class FindPresenterImpl implements FindPresenter {
find(keyword: string, backwards: boolean): boolean {
const caseSensitive = false;
- const wrapScan = true;
+ const wrapScan = false;
// NOTE: aWholeWord dows not implemented, and aSearchInFrames does not work
// because of same origin policy
- const found = window.find(keyword, caseSensitive, backwards, wrapScan);
- if (found) {
- return found;
- }
- this.clearSelection();
-
return window.find(keyword, caseSensitive, backwards, wrapScan);
}
diff --git a/src/content/repositories/FindRepository.ts b/src/content/repositories/FindRepository.ts
deleted file mode 100644
index aeb200f..0000000
--- a/src/content/repositories/FindRepository.ts
+++ /dev/null
@@ -1,17 +0,0 @@
-export default interface FindRepository {
- getLastKeyword(): string | null;
-
- setLastKeyword(keyword: string): void;
-}
-
-let current: string | null = null;
-
-export class FindRepositoryImpl implements FindRepository {
- getLastKeyword(): string | null {
- return current;
- }
-
- setLastKeyword(keyword: string): void {
- current = keyword;
- }
-}
diff --git a/src/content/usecases/FindUseCase.ts b/src/content/usecases/FindUseCase.ts
index bff0eee..9e77124 100644
--- a/src/content/usecases/FindUseCase.ts
+++ b/src/content/usecases/FindUseCase.ts
@@ -1,67 +1,22 @@
-import { injectable, inject } from "tsyringe";
+import { inject, injectable } from "tsyringe";
import FindPresenter from "../presenters/FindPresenter";
-import FindRepository from "../repositories/FindRepository";
-import FindClient from "../client/FindClient";
-import ConsoleClient from "../client/ConsoleClient";
@injectable()
export default class FindUseCase {
constructor(
- @inject("FindPresenter") private presenter: FindPresenter,
- @inject("FindRepository") private repository: FindRepository,
- @inject("FindClient") private client: FindClient,
- @inject("ConsoleClient") private consoleClient: ConsoleClient
+ @inject("FindPresenter")
+ private readonly findPresenter: FindPresenter
) {}
- async startFind(keyword?: string): Promise<void> {
- this.presenter.clearSelection();
- if (keyword) {
- this.saveKeyword(keyword);
- } else {
- const lastKeyword = await this.getKeyword();
- if (!lastKeyword) {
- return this.showNoLastKeywordError();
- }
- this.saveKeyword(lastKeyword);
- }
- return this.findNext();
+ findNext(keyword: string): boolean {
+ return this.findPresenter.find(keyword, false);
}
- findNext(): Promise<void> {
- return this.findNextPrev(false);
+ findPrev(keyword: string): boolean {
+ return this.findPresenter.find(keyword, true);
}
- findPrev(): Promise<void> {
- return this.findNextPrev(true);
- }
-
- private async findNextPrev(backwards: boolean): Promise<void> {
- const keyword = await this.getKeyword();
- if (!keyword) {
- return this.showNoLastKeywordError();
- }
- const found = this.presenter.find(keyword, backwards);
- if (found) {
- this.consoleClient.info("Pattern found: " + keyword);
- } else {
- this.consoleClient.error("Pattern not found: " + keyword);
- }
- }
-
- private async getKeyword(): Promise<string | null> {
- let keyword = this.repository.getLastKeyword();
- if (!keyword) {
- keyword = await this.client.getGlobalLastKeyword();
- }
- return keyword;
- }
-
- private async saveKeyword(keyword: string): Promise<void> {
- this.repository.setLastKeyword(keyword);
- await this.client.setGlobalLastKeyword(keyword);
- }
-
- private async showNoLastKeywordError(): Promise<void> {
- await this.consoleClient.error("No previous search keywords");
+ clearSelection() {
+ this.findPresenter.clearSelection();
}
}
diff --git a/src/shared/messages.ts b/src/shared/messages.ts
index 59329e7..8b9e598 100644
--- a/src/shared/messages.ts
+++ b/src/shared/messages.ts
@@ -37,8 +37,7 @@ export const TAB_SCROLL_TO = "tab.scroll.to";
export const FIND_NEXT = "find.next";
export const FIND_PREV = "find.prev";
-export const FIND_GET_KEYWORD = "find.get.keyword";
-export const FIND_SET_KEYWORD = "find.set.keyword";
+export const FIND_CLEAR_SELECTION = "find.clear.selection";
export const ADDON_ENABLED_QUERY = "addon.enabled.query";
export const ADDON_ENABLED_RESPONSE = "addon.enabled.response";
@@ -73,7 +72,7 @@ export interface ConsoleEnterCommandMessage {
export interface ConsoleEnterFindMessage {
type: typeof CONSOLE_ENTER_FIND;
- text?: string;
+ keyword?: string;
}
export interface ConsoleShowCommandMessage {
@@ -229,20 +228,16 @@ export interface TabScrollToMessage {
export interface FindNextMessage {
type: typeof FIND_NEXT;
+ keyword: string;
}
export interface FindPrevMessage {
type: typeof FIND_PREV;
+ keyword: string;
}
-export interface FindGetKeywordMessage {
- type: typeof FIND_GET_KEYWORD;
-}
-
-export interface FindSetKeywordMessage {
- type: typeof FIND_SET_KEYWORD;
- keyword: string;
- found: boolean;
+export interface FindClearSelection {
+ type: typeof FIND_CLEAR_SELECTION;
}
export interface AddonEnabledQueryMessage {
@@ -324,8 +319,7 @@ export type Message =
| TabScrollToMessage
| FindNextMessage
| FindPrevMessage
- | FindGetKeywordMessage
- | FindSetKeywordMessage
+ | FindClearSelection
| AddonEnabledQueryMessage
| AddonEnabledResponseMessage
| AddonToggleEnabledMessage
@@ -362,8 +356,7 @@ export const valueOf = (o: any): Message => {
case TAB_SCROLL_TO:
case FIND_NEXT:
case FIND_PREV:
- case FIND_GET_KEYWORD:
- case FIND_SET_KEYWORD:
+ case FIND_CLEAR_SELECTION:
case ADDON_ENABLED_QUERY:
case ADDON_ENABLED_RESPONSE:
case ADDON_TOGGLE_ENABLED:
diff --git a/src/shared/settings/Settings.ts b/src/shared/settings/Settings.ts
index 6c4fa0a..2294c67 100644
--- a/src/shared/settings/Settings.ts
+++ b/src/shared/settings/Settings.ts
@@ -44,7 +44,7 @@ export default class Settings {
if (!valid) {
const message = (validate as any)
.errors!.map((err: Ajv.ErrorObject) => {
- return `'${err.dataPath}' ${err.message}`;
+ return `'${err.instancePath}' ${err.message}`;
})
.join("; ");
throw new TypeError(message);
diff --git a/src/shared/settings/validate.js b/src/shared/settings/validate.js
index 30f7888..b784854 100644
--- a/src/shared/settings/validate.js
+++ b/src/shared/settings/validate.js
@@ -1,612 +1 @@
-'use strict';
-var equal = require('ajv/lib/compile/equal');
-var validate = (function() {
- var pattern0 = new RegExp('.*');
- var refVal = [];
- return function validate(data, dataPath, parentData, parentDataProperty, rootData) {
- 'use strict';
- var vErrors = null;
- var errors = 0;
- if ((data && typeof data === "object" && !Array.isArray(data))) {
- var errs__0 = errors;
- var valid1 = true;
- for (var key0 in data) {
- var isAdditional0 = !(false || key0 == 'keymaps' || key0 == 'search' || key0 == 'properties' || key0 == 'blacklist');
- if (isAdditional0) {
- valid1 = false;
- validate.errors = [{
- keyword: 'additionalProperties',
- dataPath: (dataPath || '') + "",
- schemaPath: '#/additionalProperties',
- params: {
- additionalProperty: '' + key0 + ''
- },
- message: 'should NOT have additional properties'
- }];
- return false;
- break;
- }
- }
- if (valid1) {
- var data1 = data.keymaps;
- if (data1 === undefined) {
- valid1 = true;
- } else {
- var errs_1 = errors;
- if ((data1 && typeof data1 === "object" && !Array.isArray(data1))) {
- var errs__1 = errors;
- var valid2 = true;
- for (var key1 in data1) {
- if (pattern0.test(key1)) {
- var data2 = data1[key1];
- var errs_2 = errors;
- if ((data2 && typeof data2 === "object" && !Array.isArray(data2))) {
- if (true) {
- var errs__2 = errors;
- var valid3 = true;
- if (data2.type === undefined) {
- valid3 = false;
- validate.errors = [{
- keyword: 'required',
- dataPath: (dataPath || '') + '.keymaps[\'' + key1 + '\']',
- schemaPath: '#/properties/keymaps/patternProperties/.*/required',
- params: {
- missingProperty: 'type'
- },
- message: 'should have required property \'type\''
- }];
- return false;
- } else {
- var errs_3 = errors;
- if (typeof data2.type !== "string") {
- validate.errors = [{
- keyword: 'type',
- dataPath: (dataPath || '') + '.keymaps[\'' + key1 + '\'].type',
- schemaPath: '#/properties/keymaps/patternProperties/.*/properties/type/type',
- params: {
- type: 'string'
- },
- message: 'should be string'
- }];
- return false;
- }
- var valid3 = errors === errs_3;
- }
- }
- } else {
- validate.errors = [{
- keyword: 'type',
- dataPath: (dataPath || '') + '.keymaps[\'' + key1 + '\']',
- schemaPath: '#/properties/keymaps/patternProperties/.*/type',
- params: {
- type: 'object'
- },
- message: 'should be object'
- }];
- return false;
- }
- var valid2 = errors === errs_2;
- if (!valid2) break;
- } else valid2 = true;
- }
- } else {
- validate.errors = [{
- keyword: 'type',
- dataPath: (dataPath || '') + '.keymaps',
- schemaPath: '#/properties/keymaps/type',
- params: {
- type: 'object'
- },
- message: 'should be object'
- }];
- return false;
- }
- var valid1 = errors === errs_1;
- }
- if (valid1) {
- var data1 = data.search;
- if (data1 === undefined) {
- valid1 = true;
- } else {
- var errs_1 = errors;
- if ((data1 && typeof data1 === "object" && !Array.isArray(data1))) {
- if (true) {
- var errs__1 = errors;
- var valid2 = true;
- if (data1.default === undefined) {
- valid2 = false;
- validate.errors = [{
- keyword: 'required',
- dataPath: (dataPath || '') + '.search',
- schemaPath: '#/properties/search/required',
- params: {
- missingProperty: 'default'
- },
- message: 'should have required property \'default\''
- }];
- return false;
- } else {
- var errs_2 = errors;
- if (typeof data1.default !== "string") {
- validate.errors = [{
- keyword: 'type',
- dataPath: (dataPath || '') + '.search.default',
- schemaPath: '#/properties/search/properties/default/type',
- params: {
- type: 'string'
- },
- message: 'should be string'
- }];
- return false;
- }
- var valid2 = errors === errs_2;
- }
- if (valid2) {
- var data2 = data1.engines;
- if (data2 === undefined) {
- valid2 = false;
- validate.errors = [{
- keyword: 'required',
- dataPath: (dataPath || '') + '.search',
- schemaPath: '#/properties/search/required',
- params: {
- missingProperty: 'engines'
- },
- message: 'should have required property \'engines\''
- }];
- return false;
- } else {
- var errs_2 = errors;
- if ((data2 && typeof data2 === "object" && !Array.isArray(data2))) {
- var errs__2 = errors;
- var valid3 = true;
- for (var key2 in data2) {
- if (pattern0.test(key2)) {
- var errs_3 = errors;
- if (typeof data2[key2] !== "string") {
- validate.errors = [{
- keyword: 'type',
- dataPath: (dataPath || '') + '.search.engines[\'' + key2 + '\']',
- schemaPath: '#/properties/search/properties/engines/patternProperties/.*/type',
- params: {
- type: 'string'
- },
- message: 'should be string'
- }];
- return false;
- }
- var valid3 = errors === errs_3;
- if (!valid3) break;
- } else valid3 = true;
- }
- } else {
- validate.errors = [{
- keyword: 'type',
- dataPath: (dataPath || '') + '.search.engines',
- schemaPath: '#/properties/search/properties/engines/type',
- params: {
- type: 'object'
- },
- message: 'should be object'
- }];
- return false;
- }
- var valid2 = errors === errs_2;
- }
- }
- }
- } else {
- validate.errors = [{
- keyword: 'type',
- dataPath: (dataPath || '') + '.search',
- schemaPath: '#/properties/search/type',
- params: {
- type: 'object'
- },
- message: 'should be object'
- }];
- return false;
- }
- var valid1 = errors === errs_1;
- }
- if (valid1) {
- var data1 = data.properties;
- if (data1 === undefined) {
- valid1 = true;
- } else {
- var errs_1 = errors;
- if ((data1 && typeof data1 === "object" && !Array.isArray(data1))) {
- var errs__1 = errors;
- var valid2 = true;
- if (data1.hintchars === undefined) {
- valid2 = true;
- } else {
- var errs_2 = errors;
- if (typeof data1.hintchars !== "string") {
- validate.errors = [{
- keyword: 'type',
- dataPath: (dataPath || '') + '.properties.hintchars',
- schemaPath: '#/properties/properties/properties/hintchars/type',
- params: {
- type: 'string'
- },
- message: 'should be string'
- }];
- return false;
- }
- var valid2 = errors === errs_2;
- }
- if (valid2) {
- if (data1.smoothscroll === undefined) {
- valid2 = true;
- } else {
- var errs_2 = errors;
- if (typeof data1.smoothscroll !== "boolean") {
- validate.errors = [{
- keyword: 'type',
- dataPath: (dataPath || '') + '.properties.smoothscroll',
- schemaPath: '#/properties/properties/properties/smoothscroll/type',
- params: {
- type: 'boolean'
- },
- message: 'should be boolean'
- }];
- return false;
- }
- var valid2 = errors === errs_2;
- }
- if (valid2) {
- if (data1.complete === undefined) {
- valid2 = true;
- } else {
- var errs_2 = errors;
- if (typeof data1.complete !== "string") {
- validate.errors = [{
- keyword: 'type',
- dataPath: (dataPath || '') + '.properties.complete',
- schemaPath: '#/properties/properties/properties/complete/type',
- params: {
- type: 'string'
- },
- message: 'should be string'
- }];
- return false;
- }
- var valid2 = errors === errs_2;
- }
- if (valid2) {
- var data2 = data1.colorscheme;
- if (data2 === undefined) {
- valid2 = true;
- } else {
- var errs_2 = errors;
- if (typeof data2 !== "string") {
- validate.errors = [{
- keyword: 'type',
- dataPath: (dataPath || '') + '.properties.colorscheme',
- schemaPath: '#/properties/properties/properties/colorscheme/type',
- params: {
- type: 'string'
- },
- message: 'should be string'
- }];
- return false;
- }
- var schema2 = validate.schema.properties.properties.properties.colorscheme.enum;
- var valid2;
- valid2 = false;
- for (var i2 = 0; i2 < schema2.length; i2++)
- if (equal(data2, schema2[i2])) {
- valid2 = true;
- break;
- } if (!valid2) {
- validate.errors = [{
- keyword: 'enum',
- dataPath: (dataPath || '') + '.properties.colorscheme',
- schemaPath: '#/properties/properties/properties/colorscheme/enum',
- params: {
- allowedValues: schema2
- },
- message: 'should be equal to one of the allowed values'
- }];
- return false;
- }
- var valid2 = errors === errs_2;
- }
- }
- }
- }
- } else {
- validate.errors = [{
- keyword: 'type',
- dataPath: (dataPath || '') + '.properties',
- schemaPath: '#/properties/properties/type',
- params: {
- type: 'object'
- },
- message: 'should be object'
- }];
- return false;
- }
- var valid1 = errors === errs_1;
- }
- if (valid1) {
- var data1 = data.blacklist;
- if (data1 === undefined) {
- valid1 = true;
- } else {
- var errs_1 = errors;
- if (Array.isArray(data1)) {
- var errs__1 = errors;
- var valid1;
- for (var i1 = 0; i1 < data1.length; i1++) {
- var data2 = data1[i1];
- var errs_2 = errors;
- var errs__2 = errors;
- var valid2 = false;
- var errs_3 = errors;
- if (typeof data2 !== "string") {
- var err = {
- keyword: 'type',
- dataPath: (dataPath || '') + '.blacklist[' + i1 + ']',
- schemaPath: '#/properties/blacklist/items/anyOf/0/type',
- params: {
- type: 'string'
- },
- message: 'should be string'
- };
- if (vErrors === null) vErrors = [err];
- else vErrors.push(err);
- errors++;
- }
- var valid3 = errors === errs_3;
- valid2 = valid2 || valid3;
- if (!valid2) {
- var errs_3 = errors;
- if ((data2 && typeof data2 === "object" && !Array.isArray(data2))) {
- if (true) {
- var errs__3 = errors;
- var valid4 = true;
- if (data2.url === undefined) {
- valid4 = false;
- var err = {
- keyword: 'required',
- dataPath: (dataPath || '') + '.blacklist[' + i1 + ']',
- schemaPath: '#/properties/blacklist/items/anyOf/1/required',
- params: {
- missingProperty: 'url'
- },
- message: 'should have required property \'url\''
- };
- if (vErrors === null) vErrors = [err];
- else vErrors.push(err);
- errors++;
- } else {
- var errs_4 = errors;
- if (typeof data2.url !== "string") {
- var err = {
- keyword: 'type',
- dataPath: (dataPath || '') + '.blacklist[' + i1 + '].url',
- schemaPath: '#/properties/blacklist/items/anyOf/1/properties/url/type',
- params: {
- type: 'string'
- },
- message: 'should be string'
- };
- if (vErrors === null) vErrors = [err];
- else vErrors.push(err);
- errors++;
- }
- var valid4 = errors === errs_4;
- }
- if (valid4) {
- var data3 = data2.keys;
- if (data3 === undefined) {
- valid4 = false;
- var err = {
- keyword: 'required',
- dataPath: (dataPath || '') + '.blacklist[' + i1 + ']',
- schemaPath: '#/properties/blacklist/items/anyOf/1/required',
- params: {
- missingProperty: 'keys'
- },
- message: 'should have required property \'keys\''
- };
- if (vErrors === null) vErrors = [err];
- else vErrors.push(err);
- errors++;
- } else {
- var errs_4 = errors;
- if (Array.isArray(data3)) {
- var errs__4 = errors;
- var valid4;
- for (var i4 = 0; i4 < data3.length; i4++) {
- var errs_5 = errors;
- if (typeof data3[i4] !== "string") {
- var err = {
- keyword: 'type',
- dataPath: (dataPath || '') + '.blacklist[' + i1 + '].keys[' + i4 + ']',
- schemaPath: '#/properties/blacklist/items/anyOf/1/properties/keys/items/type',
- params: {
- type: 'string'
- },
- message: 'should be string'
- };
- if (vErrors === null) vErrors = [err];
- else vErrors.push(err);
- errors++;
- }
- var valid5 = errors === errs_5;
- if (!valid5) break;
- }
- } else {
- var err = {
- keyword: 'type',
- dataPath: (dataPath || '') + '.blacklist[' + i1 + '].keys',
- schemaPath: '#/properties/blacklist/items/anyOf/1/properties/keys/type',
- params: {
- type: 'array'
- },
- message: 'should be array'
- };
- if (vErrors === null) vErrors = [err];
- else vErrors.push(err);
- errors++;
- }
- var valid4 = errors === errs_4;
- }
- }
- }
- } else {
- var err = {
- keyword: 'type',
- dataPath: (dataPath || '') + '.blacklist[' + i1 + ']',
- schemaPath: '#/properties/blacklist/items/anyOf/1/type',
- params: {
- type: 'object'
- },
- message: 'should be object'
- };
- if (vErrors === null) vErrors = [err];
- else vErrors.push(err);
- errors++;
- }
- var valid3 = errors === errs_3;
- valid2 = valid2 || valid3;
- }
- if (!valid2) {
- var err = {
- keyword: 'anyOf',
- dataPath: (dataPath || '') + '.blacklist[' + i1 + ']',
- schemaPath: '#/properties/blacklist/items/anyOf',
- params: {},
- message: 'should match some schema in anyOf'
- };
- if (vErrors === null) vErrors = [err];
- else vErrors.push(err);
- errors++;
- validate.errors = vErrors;
- return false;
- } else {
- errors = errs__2;
- if (vErrors !== null) {
- if (errs__2) vErrors.length = errs__2;
- else vErrors = null;
- }
- }
- var valid2 = errors === errs_2;
- if (!valid2) break;
- }
- } else {
- validate.errors = [{
- keyword: 'type',
- dataPath: (dataPath || '') + '.blacklist',
- schemaPath: '#/properties/blacklist/type',
- params: {
- type: 'array'
- },
- message: 'should be array'
- }];
- return false;
- }
- var valid1 = errors === errs_1;
- }
- }
- }
- }
- }
- } else {
- validate.errors = [{
- keyword: 'type',
- dataPath: (dataPath || '') + "",
- schemaPath: '#/type',
- params: {
- type: 'object'
- },
- message: 'should be object'
- }];
- return false;
- }
- validate.errors = vErrors;
- return errors === 0;
- };
-})();
-validate.schema = {
- "type": "object",
- "properties": {
- "keymaps": {
- "type": "object",
- "patternProperties": {
- ".*": {
- "type": "object",
- "properties": {
- "type": {
- "type": "string"
- }
- },
- "required": ["type"]
- }
- }
- },
- "search": {
- "type": "object",
- "properties": {
- "default": {
- "type": "string"
- },
- "engines": {
- "type": "object",
- "patternProperties": {
- ".*": {
- "type": "string"
- }
- }
- }
- },
- "required": ["default", "engines"]
- },
- "properties": {
- "type": "object",
- "properties": {
- "hintchars": {
- "type": "string"
- },
- "smoothscroll": {
- "type": "boolean"
- },
- "complete": {
- "type": "string"
- },
- "colorscheme": {
- "type": "string",
- "enum": ["system", "light", "dark"]
- }
- }
- },
- "blacklist": {
- "type": "array",
- "items": {
- "anyOf": [{
- "type": "string"
- }, {
- "type": "object",
- "properties": {
- "url": {
- "type": "string"
- },
- "keys": {
- "type": "array",
- "items": {
- "type": "string"
- }
- }
- },
- "required": ["url", "keys"]
- }]
- }
- }
- },
- "additionalProperties": false
-};
-validate.errors = null;
-module.exports = validate; \ No newline at end of file
+"use strict";module.exports = validate20;module.exports.default = validate20;const schema22 = {"type":"object","properties":{"keymaps":{"type":"object","patternProperties":{".*":{"type":"object","properties":{"type":{"type":"string"}},"required":["type"]}}},"search":{"type":"object","properties":{"default":{"type":"string"},"engines":{"type":"object","patternProperties":{".*":{"type":"string"}}}},"required":["default","engines"]},"properties":{"type":"object","properties":{"hintchars":{"type":"string"},"smoothscroll":{"type":"boolean"},"complete":{"type":"string"},"colorscheme":{"type":"string","enum":["system","light","dark"]}}},"blacklist":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"object","properties":{"url":{"type":"string"},"keys":{"type":"array","items":{"type":"string"}}},"required":["url","keys"]}]}}},"additionalProperties":false};const pattern0 = new RegExp(".*", "u");const func0 = require("ajv/dist/runtime/equal").default;function validate20(data, {instancePath="", parentData, parentDataProperty, rootData=data}={}){let vErrors = null;let errors = 0;if(errors === 0){if(data && typeof data == "object" && !Array.isArray(data)){const _errs1 = errors;for(const key0 in data){if(!((((key0 === "keymaps") || (key0 === "search")) || (key0 === "properties")) || (key0 === "blacklist"))){validate20.errors = [{instancePath,schemaPath:"#/additionalProperties",keyword:"additionalProperties",params:{additionalProperty: key0},message:"must NOT have additional properties"}];return false;break;}}if(_errs1 === errors){if(data.keymaps !== undefined){let data0 = data.keymaps;const _errs2 = errors;if(errors === _errs2){if(data0 && typeof data0 == "object" && !Array.isArray(data0)){var valid1 = true;for(const key1 in data0){if(pattern0.test(key1)){let data1 = data0[key1];const _errs4 = errors;if(errors === _errs4){if(data1 && typeof data1 == "object" && !Array.isArray(data1)){let missing0;if((data1.type === undefined) && (missing0 = "type")){validate20.errors = [{instancePath:instancePath+"/keymaps/" + key1.replace(/~/g, "~0").replace(/\//g, "~1"),schemaPath:"#/properties/keymaps/patternProperties/.*/required",keyword:"required",params:{missingProperty: missing0},message:"must have required property '"+missing0+"'"}];return false;}else {if(data1.type !== undefined){if(typeof data1.type !== "string"){validate20.errors = [{instancePath:instancePath+"/keymaps/" + key1.replace(/~/g, "~0").replace(/\//g, "~1")+"/type",schemaPath:"#/properties/keymaps/patternProperties/.*/properties/type/type",keyword:"type",params:{type: "string"},message:"must be string"}];return false;}}}}else {validate20.errors = [{instancePath:instancePath+"/keymaps/" + key1.replace(/~/g, "~0").replace(/\//g, "~1"),schemaPath:"#/properties/keymaps/patternProperties/.*/type",keyword:"type",params:{type: "object"},message:"must be object"}];return false;}}var valid1 = _errs4 === errors;if(!valid1){break;}}}}else {validate20.errors = [{instancePath:instancePath+"/keymaps",schemaPath:"#/properties/keymaps/type",keyword:"type",params:{type: "object"},message:"must be object"}];return false;}}var valid0 = _errs2 === errors;}else {var valid0 = true;}if(valid0){if(data.search !== undefined){let data3 = data.search;const _errs8 = errors;if(errors === _errs8){if(data3 && typeof data3 == "object" && !Array.isArray(data3)){let missing1;if(((data3.default === undefined) && (missing1 = "default")) || ((data3.engines === undefined) && (missing1 = "engines"))){validate20.errors = [{instancePath:instancePath+"/search",schemaPath:"#/properties/search/required",keyword:"required",params:{missingProperty: missing1},message:"must have required property '"+missing1+"'"}];return false;}else {if(data3.default !== undefined){const _errs10 = errors;if(typeof data3.default !== "string"){validate20.errors = [{instancePath:instancePath+"/search/default",schemaPath:"#/properties/search/properties/default/type",keyword:"type",params:{type: "string"},message:"must be string"}];return false;}var valid3 = _errs10 === errors;}else {var valid3 = true;}if(valid3){if(data3.engines !== undefined){let data5 = data3.engines;const _errs12 = errors;if(errors === _errs12){if(data5 && typeof data5 == "object" && !Array.isArray(data5)){var valid4 = true;for(const key2 in data5){if(pattern0.test(key2)){const _errs14 = errors;if(typeof data5[key2] !== "string"){validate20.errors = [{instancePath:instancePath+"/search/engines/" + key2.replace(/~/g, "~0").replace(/\//g, "~1"),schemaPath:"#/properties/search/properties/engines/patternProperties/.*/type",keyword:"type",params:{type: "string"},message:"must be string"}];return false;}var valid4 = _errs14 === errors;if(!valid4){break;}}}}else {validate20.errors = [{instancePath:instancePath+"/search/engines",schemaPath:"#/properties/search/properties/engines/type",keyword:"type",params:{type: "object"},message:"must be object"}];return false;}}var valid3 = _errs12 === errors;}else {var valid3 = true;}}}}else {validate20.errors = [{instancePath:instancePath+"/search",schemaPath:"#/properties/search/type",keyword:"type",params:{type: "object"},message:"must be object"}];return false;}}var valid0 = _errs8 === errors;}else {var valid0 = true;}if(valid0){if(data.properties !== undefined){let data7 = data.properties;const _errs16 = errors;if(errors === _errs16){if(data7 && typeof data7 == "object" && !Array.isArray(data7)){if(data7.hintchars !== undefined){const _errs18 = errors;if(typeof data7.hintchars !== "string"){validate20.errors = [{instancePath:instancePath+"/properties/hintchars",schemaPath:"#/properties/properties/properties/hintchars/type",keyword:"type",params:{type: "string"},message:"must be string"}];return false;}var valid5 = _errs18 === errors;}else {var valid5 = true;}if(valid5){if(data7.smoothscroll !== undefined){const _errs20 = errors;if(typeof data7.smoothscroll !== "boolean"){validate20.errors = [{instancePath:instancePath+"/properties/smoothscroll",schemaPath:"#/properties/properties/properties/smoothscroll/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"}];return false;}var valid5 = _errs20 === errors;}else {var valid5 = true;}if(valid5){if(data7.complete !== undefined){const _errs22 = errors;if(typeof data7.complete !== "string"){validate20.errors = [{instancePath:instancePath+"/properties/complete",schemaPath:"#/properties/properties/properties/complete/type",keyword:"type",params:{type: "string"},message:"must be string"}];return false;}var valid5 = _errs22 === errors;}else {var valid5 = true;}if(valid5){if(data7.colorscheme !== undefined){let data11 = data7.colorscheme;const _errs24 = errors;if(typeof data11 !== "string"){validate20.errors = [{instancePath:instancePath+"/properties/colorscheme",schemaPath:"#/properties/properties/properties/colorscheme/type",keyword:"type",params:{type: "string"},message:"must be string"}];return false;}if(!(((data11 === "system") || (data11 === "light")) || (data11 === "dark"))){validate20.errors = [{instancePath:instancePath+"/properties/colorscheme",schemaPath:"#/properties/properties/properties/colorscheme/enum",keyword:"enum",params:{allowedValues: schema22.properties.properties.properties.colorscheme.enum},message:"must be equal to one of the allowed values"}];return false;}var valid5 = _errs24 === errors;}else {var valid5 = true;}}}}}else {validate20.errors = [{instancePath:instancePath+"/properties",schemaPath:"#/properties/properties/type",keyword:"type",params:{type: "object"},message:"must be object"}];return false;}}var valid0 = _errs16 === errors;}else {var valid0 = true;}if(valid0){if(data.blacklist !== undefined){let data12 = data.blacklist;const _errs26 = errors;if(errors === _errs26){if(Array.isArray(data12)){var valid6 = true;const len0 = data12.length;for(let i0=0; i0<len0; i0++){let data13 = data12[i0];const _errs28 = errors;const _errs29 = errors;let valid7 = false;const _errs30 = errors;if(typeof data13 !== "string"){const err0 = {instancePath:instancePath+"/blacklist/" + i0,schemaPath:"#/properties/blacklist/items/anyOf/0/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err0];}else {vErrors.push(err0);}errors++;}var _valid0 = _errs30 === errors;valid7 = valid7 || _valid0;if(!valid7){const _errs32 = errors;if(errors === _errs32){if(data13 && typeof data13 == "object" && !Array.isArray(data13)){let missing2;if(((data13.url === undefined) && (missing2 = "url")) || ((data13.keys === undefined) && (missing2 = "keys"))){const err1 = {instancePath:instancePath+"/blacklist/" + i0,schemaPath:"#/properties/blacklist/items/anyOf/1/required",keyword:"required",params:{missingProperty: missing2},message:"must have required property '"+missing2+"'"};if(vErrors === null){vErrors = [err1];}else {vErrors.push(err1);}errors++;}else {if(data13.url !== undefined){const _errs34 = errors;if(typeof data13.url !== "string"){const err2 = {instancePath:instancePath+"/blacklist/" + i0+"/url",schemaPath:"#/properties/blacklist/items/anyOf/1/properties/url/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err2];}else {vErrors.push(err2);}errors++;}var valid8 = _errs34 === errors;}else {var valid8 = true;}if(valid8){if(data13.keys !== undefined){let data15 = data13.keys;const _errs36 = errors;if(errors === _errs36){if(Array.isArray(data15)){var valid9 = true;const len1 = data15.length;for(let i1=0; i1<len1; i1++){const _errs38 = errors;if(typeof data15[i1] !== "string"){const err3 = {instancePath:instancePath+"/blacklist/" + i0+"/keys/" + i1,schemaPath:"#/properties/blacklist/items/anyOf/1/properties/keys/items/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err3];}else {vErrors.push(err3);}errors++;}var valid9 = _errs38 === errors;if(!valid9){break;}}}else {const err4 = {instancePath:instancePath+"/blacklist/" + i0+"/keys",schemaPath:"#/properties/blacklist/items/anyOf/1/properties/keys/type",keyword:"type",params:{type: "array"},message:"must be array"};if(vErrors === null){vErrors = [err4];}else {vErrors.push(err4);}errors++;}}var valid8 = _errs36 === errors;}else {var valid8 = true;}}}}else {const err5 = {instancePath:instancePath+"/blacklist/" + i0,schemaPath:"#/properties/blacklist/items/anyOf/1/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err5];}else {vErrors.push(err5);}errors++;}}var _valid0 = _errs32 === errors;valid7 = valid7 || _valid0;}if(!valid7){const err6 = {instancePath:instancePath+"/blacklist/" + i0,schemaPath:"#/properties/blacklist/items/anyOf",keyword:"anyOf",params:{},message:"must match a schema in anyOf"};if(vErrors === null){vErrors = [err6];}else {vErrors.push(err6);}errors++;validate20.errors = vErrors;return false;}else {errors = _errs29;if(vErrors !== null){if(_errs29){vErrors.length = _errs29;}else {vErrors = null;}}}var valid6 = _errs28 === errors;if(!valid6){break;}}}else {validate20.errors = [{instancePath:instancePath+"/blacklist",schemaPath:"#/properties/blacklist/type",keyword:"type",params:{type: "array"},message:"must be array"}];return false;}}var valid0 = _errs26 === errors;}else {var valid0 = true;}}}}}}else {validate20.errors = [{instancePath,schemaPath:"#/type",keyword:"type",params:{type: "object"},message:"must be object"}];return false;}}validate20.errors = vErrors;return errors === 0;} \ No newline at end of file