Skip to content
This repository was archived by the owner on Apr 11, 2023. It is now read-only.

Commit fa733c1

Browse files
authored
Merge pull request #1043 from ueokande/iframe-late-inject
iframe Late injection
2 parents 3cbaf4a + 690c9c0 commit fa733c1

18 files changed

+207
-55
lines changed

e2e/lib/Page.ts

+34-13
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,13 @@ export default class Page {
1515
private constructor(private webdriver: WebDriver) {}
1616

1717
static async currentContext(webdriver: WebDriver): Promise<Page> {
18-
await Page.waitForConsoleLoaded(webdriver);
18+
await Page.waitForPageCompleted(webdriver);
1919
return new Page(webdriver);
2020
}
2121

2222
static async navigateTo(webdriver: WebDriver, url: string): Promise<Page> {
2323
await webdriver.navigate().to(url);
24-
await Page.waitForConsoleLoaded(webdriver);
24+
await Page.waitForPageCompleted(webdriver);
2525
return new Page(webdriver);
2626
}
2727

@@ -34,18 +34,19 @@ export default class Page {
3434

3535
async navigateTo(url: string): Promise<Page> {
3636
await this.webdriver.navigate().to(url);
37-
await Page.waitForConsoleLoaded(this.webdriver);
37+
await Page.waitForPageCompleted(this.webdriver);
38+
39+
await new Promise((resolve) => setTimeout(resolve, 200));
40+
3841
return new Page(this.webdriver);
3942
}
4043

4144
async showConsole(): Promise<Console> {
42-
const iframe = this.webdriver.findElement(
43-
By.css("#vimvixen-console-frame")
44-
);
45-
4645
await this.sendKeys(":");
46+
const iframe = this.webdriver.findElement(By.id("vimvixen-console-frame"));
4747
await this.webdriver.wait(until.elementIsVisible(iframe));
48-
await this.webdriver.switchTo().frame(0);
48+
49+
await this.webdriver.switchTo().frame(iframe);
4950
await this.webdriver.wait(until.elementLocated(By.css("input")));
5051
return new Console(this.webdriver);
5152
}
@@ -54,9 +55,8 @@ export default class Page {
5455
const iframe = this.webdriver.findElement(
5556
By.css("#vimvixen-console-frame")
5657
);
57-
5858
await this.webdriver.wait(until.elementIsVisible(iframe));
59-
await this.webdriver.switchTo().frame(0);
59+
await this.webdriver.switchTo().frame(iframe);
6060
return new Console(this.webdriver);
6161
}
6262

@@ -113,14 +113,35 @@ export default class Page {
113113
return hints;
114114
}
115115

116-
private static async waitForConsoleLoaded(webdriver: WebDriver) {
116+
private static async waitForPageCompleted(
117+
webdriver: WebDriver
118+
): Promise<void> {
119+
this.waitForDocumentCompleted(webdriver);
120+
117121
const topFrame = await webdriver.executeScript(() => window.top === window);
118122
if (!topFrame) {
119123
return;
120124
}
125+
// style tag is injected at end of add-on loading
126+
await webdriver.wait(until.elementLocated(By.tagName("style")));
127+
128+
const iframe = await webdriver.findElements(
129+
By.id("vimvixen-console-frame")
130+
);
131+
if (iframe.length === 0) {
132+
return;
133+
}
134+
135+
await webdriver.switchTo().frame(iframe[0]);
136+
await Page.waitForDocumentCompleted(webdriver);
137+
await webdriver.switchTo().parentFrame();
138+
}
139+
140+
private static async waitForDocumentCompleted(webdriver: WebDriver) {
121141
await webdriver.wait(
122-
until.elementLocated(By.css("iframe.vimvixen-console-frame"))
142+
async () =>
143+
(await webdriver.executeScript("return document.readyState")) ===
144+
"complete"
123145
);
124-
await new Promise((resolve) => setTimeout(resolve, 100));
125146
}
126147
}

manifest.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
"all_frames": true,
1818
"matches": [ "<all_urls>" ],
1919
"js": [ "build/content.js" ],
20-
"run_at": "document_end",
20+
"run_at": "document_start",
2121
"match_about_blank": true
2222
}
2323
],

src/console/index.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import ReactDOM from "react-dom";
1111

1212
const store = createStore(reducers, applyMiddleware(promise));
1313

14-
window.addEventListener("load", () => {
14+
window.addEventListener("DOMContentLoaded", () => {
1515
const wrapper = document.getElementById("vimvixen-console");
1616
ReactDOM.render(
1717
<Provider store={store}>

src/content/Application.ts

+4-4
Original file line numberDiff line numberDiff line change
@@ -36,12 +36,12 @@ export default class Application {
3636
private navigateController: NavigateController
3737
) {}
3838

39-
run() {
40-
this.routeCommonComponents();
39+
init(): Promise<void> {
4140
this.routeFocusEvents();
4241
if (window.self === window.top) {
4342
this.routeMasterComponents();
4443
}
44+
return this.routeCommonComponents();
4545
}
4646

4747
private routeMasterComponents() {
@@ -76,7 +76,7 @@ export default class Application {
7676
});
7777
}
7878

79-
private routeCommonComponents() {
79+
private routeCommonComponents(): Promise<void> {
8080
this.messageListener.onWebMessage((msg: Message) => {
8181
switch (msg.type) {
8282
case messages.FOLLOW_REQUEST_COUNT_TARGETS:
@@ -117,7 +117,7 @@ export default class Application {
117117
inputDriver.onKey((key) => this.markKeyController.press(key));
118118
inputDriver.onKey((key) => this.keymapController.press(key));
119119

120-
this.settingController.initSettings();
120+
return this.settingController.initSettings();
121121
}
122122

123123
private routeFocusEvents() {

src/content/Bootstrap.ts

+27
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
type Callback = () => void;
2+
3+
export default class Bootstrap {
4+
constructor() {}
5+
6+
isReady(): boolean {
7+
return document.body !== null;
8+
}
9+
10+
waitForReady(callback: Callback): void {
11+
const observer = new MutationObserver(() => {
12+
if (document.body != null) {
13+
observer.disconnect();
14+
callback();
15+
}
16+
});
17+
18+
observer.observe(document, {
19+
attributes: false,
20+
attributeOldValue: false,
21+
characterData: false,
22+
characterDataOldValue: false,
23+
childList: true,
24+
subtree: true,
25+
});
26+
}
27+
}

src/content/index.ts

+20-13
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,29 @@
11
import "reflect-metadata";
22

33
import Application from "./Application";
4+
import Bootstrap from "./Bootstrap";
45
import consoleFrameStyle from "./site-style";
5-
import { ConsoleFramePresenterImpl } from "./presenters/ConsoleFramePresenter";
66
import { container } from "tsyringe";
77
import "./di";
88

9-
if (window.self === window.top) {
10-
new ConsoleFramePresenterImpl().initialize();
11-
}
9+
const initDom = () => {
10+
(async () => {
11+
try {
12+
const app = container.resolve(Application);
13+
await app.init();
14+
} catch (e) {
15+
console.error(e);
16+
}
17+
})();
1218

13-
try {
14-
const app = container.resolve(Application);
15-
app.run();
16-
} catch (e) {
17-
console.error(e);
18-
}
19+
const style = window.document.createElement("style");
20+
style.textContent = consoleFrameStyle;
21+
window.document.head.appendChild(style);
22+
};
1923

20-
const style = window.document.createElement("style");
21-
style.textContent = consoleFrameStyle;
22-
window.document.head.appendChild(style);
24+
const bootstrap = new Bootstrap();
25+
if (bootstrap.isReady()) {
26+
initDom();
27+
} else {
28+
bootstrap.waitForReady(() => initDom());
29+
}

src/content/operators/impls/AddonOperatorFactoryChain.ts

+10-4
Original file line numberDiff line numberDiff line change
@@ -7,32 +7,38 @@ import Operator from "../Operator";
77
import EnableAddonOperator from "./EnableAddonOperator";
88
import DisableAddonOperator from "./DisableAddonOperator";
99
import ToggleAddonOperator from "./ToggleAddonOperator";
10+
import ConsoleFramePresenter from "../../presenters/ConsoleFramePresenter";
1011

1112
@injectable()
1213
export default class AddonOperatorFactoryChain implements OperatorFactoryChain {
1314
constructor(
1415
@inject("AddonIndicatorClient")
1516
private readonly addonIndicatorClient: AddonIndicatorClient,
1617
@inject("AddonEnabledRepository")
17-
private readonly addonEnabledRepository: AddonEnabledRepository
18+
private readonly addonEnabledRepository: AddonEnabledRepository,
19+
@inject("ConsoleFramePresenter")
20+
private readonly consoleFramePresenter: ConsoleFramePresenter
1821
) {}
1922

2023
create(op: operations.Operation, _repeat: number): Operator | null {
2124
switch (op.type) {
2225
case operations.ADDON_ENABLE:
2326
return new EnableAddonOperator(
2427
this.addonIndicatorClient,
25-
this.addonEnabledRepository
28+
this.addonEnabledRepository,
29+
this.consoleFramePresenter
2630
);
2731
case operations.ADDON_DISABLE:
2832
return new DisableAddonOperator(
2933
this.addonIndicatorClient,
30-
this.addonEnabledRepository
34+
this.addonEnabledRepository,
35+
this.consoleFramePresenter
3136
);
3237
case operations.ADDON_TOGGLE_ENABLED:
3338
return new ToggleAddonOperator(
3439
this.addonIndicatorClient,
35-
this.addonEnabledRepository
40+
this.addonEnabledRepository,
41+
this.consoleFramePresenter
3642
);
3743
}
3844
return null;
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,18 @@
11
import Operator from "../Operator";
22
import AddonIndicatorClient from "../../client/AddonIndicatorClient";
33
import AddonEnabledRepository from "../../repositories/AddonEnabledRepository";
4+
import ConsoleFramePresenter from "../../presenters/ConsoleFramePresenter";
45

56
export default class DisableAddonOperator implements Operator {
67
constructor(
78
private readonly indicator: AddonIndicatorClient,
8-
private readonly repository: AddonEnabledRepository
9+
private readonly repository: AddonEnabledRepository,
10+
private readonly consoleFramePresenter: ConsoleFramePresenter
911
) {}
1012

1113
async run(): Promise<void> {
1214
this.repository.set(false);
15+
this.consoleFramePresenter.detach();
1316
await this.indicator.setEnabled(false);
1417
}
1518
}
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,18 @@
11
import Operator from "../Operator";
22
import AddonIndicatorClient from "../../client/AddonIndicatorClient";
33
import AddonEnabledRepository from "../../repositories/AddonEnabledRepository";
4+
import ConsoleFramePresenter from "../../presenters/ConsoleFramePresenter";
45

56
export default class EnableAddonOperator implements Operator {
67
constructor(
78
private readonly indicator: AddonIndicatorClient,
8-
private readonly repository: AddonEnabledRepository
9+
private readonly repository: AddonEnabledRepository,
10+
private readonly consoleFramePresenter: ConsoleFramePresenter
911
) {}
1012

1113
async run(): Promise<void> {
1214
this.repository.set(true);
15+
this.consoleFramePresenter.attach();
1316
await this.indicator.setEnabled(true);
1417
}
1518
}
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,23 @@
11
import Operator from "../Operator";
22
import AddonIndicatorClient from "../../client/AddonIndicatorClient";
33
import AddonEnabledRepository from "../../repositories/AddonEnabledRepository";
4+
import ConsoleFramePresenter from "../../presenters/ConsoleFramePresenter";
45

56
export default class ToggleAddonOperator implements Operator {
67
constructor(
78
private readonly indicator: AddonIndicatorClient,
8-
private readonly repository: AddonEnabledRepository
9+
private readonly repository: AddonEnabledRepository,
10+
private readonly consoleFramePresenter: ConsoleFramePresenter
911
) {}
1012

1113
async run(): Promise<void> {
12-
const current = this.repository.get();
13-
this.repository.set(!current);
14-
await this.indicator.setEnabled(!current);
14+
const enabled = !this.repository.get();
15+
this.repository.set(enabled);
16+
if (enabled) {
17+
this.consoleFramePresenter.attach();
18+
} else {
19+
this.consoleFramePresenter.detach();
20+
}
21+
await this.indicator.setEnabled(enabled);
1522
}
1623
}
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,43 @@
11
export default interface ConsoleFramePresenter {
2-
initialize(): void;
2+
attach(): void;
3+
4+
detach(): void;
35

46
blur(): void;
57

68
resize(width: number, height: number): void;
9+
10+
isTopWindow(): boolean;
711
}
812

913
export class ConsoleFramePresenterImpl implements ConsoleFramePresenter {
10-
initialize(): void {
14+
private static readonly IframeId = "vimvixen-console-frame" as const;
15+
16+
attach(): void {
17+
const ele = document.getElementById("vimvixen-console-frame");
18+
if (ele) {
19+
return;
20+
}
21+
1122
const iframe = document.createElement("iframe");
1223
iframe.src = browser.runtime.getURL("build/console.html");
13-
iframe.id = "vimvixen-console-frame";
24+
iframe.id = ConsoleFramePresenterImpl.IframeId;
1425
iframe.className = "vimvixen-console-frame";
1526
document.body.append(iframe);
1627
}
1728

29+
detach(): void {
30+
const ele = document.getElementById(ConsoleFramePresenterImpl.IframeId);
31+
if (!ele) {
32+
return;
33+
}
34+
ele.remove();
35+
}
36+
1837
blur(): void {
1938
const ele = document.getElementById("vimvixen-console-frame");
2039
if (!ele) {
21-
throw new Error("console frame not created");
40+
return;
2241
}
2342
ele.blur();
2443
}
@@ -30,4 +49,8 @@ export class ConsoleFramePresenterImpl implements ConsoleFramePresenter {
3049
}
3150
ele.style.height = `${height}px`;
3251
}
52+
53+
isTopWindow(): boolean {
54+
return window.top === window;
55+
}
3356
}

0 commit comments

Comments
 (0)