Skip to content

Commit e9cf808

Browse files
committed
setting-page-filter: use original blocked page html
1 parent 1a4d5ed commit e9cf808

5 files changed

Lines changed: 211 additions & 10 deletions

File tree

webextensions/edge/blocked.html

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
<!DOCTYPE html>
2+
<html lang="ja">
3+
<head>
4+
<meta charset="UTF-8">
5+
<title>アクセスがブロックされました</title>
6+
<style>
7+
body {
8+
margin: 0;
9+
padding: 3rem 1.5rem;
10+
font-family: "Segoe UI", "Meiryo", sans-serif;
11+
color: #202124;
12+
background: #f8f9fa;
13+
}
14+
main {
15+
max-width: 34rem;
16+
margin: 0 auto;
17+
background: #fff;
18+
border: 1px solid #dadce0;
19+
border-radius: 8px;
20+
padding: 2rem;
21+
}
22+
h1 {
23+
margin: 0 0 1rem;
24+
font-size: 1.25rem;
25+
}
26+
p {
27+
margin: 0 0 1rem;
28+
line-height: 1.7;
29+
}
30+
#url {
31+
display: block;
32+
padding: 0.75rem;
33+
background: #f1f3f4;
34+
border-radius: 4px;
35+
font-family: Consolas, monospace;
36+
font-size: 0.9rem;
37+
word-break: break-all;
38+
}
39+
#url:empty {
40+
display: none;
41+
}
42+
@media (prefers-color-scheme: dark) {
43+
body { color: #e8eaed; background: #202124; }
44+
main { background: #292a2d; border-color: #5f6368; }
45+
#url { background: #35363a; }
46+
}
47+
</style>
48+
</head>
49+
<body>
50+
<main>
51+
<h1>アクセスがブロックされました</h1>
52+
<p>拡張機能のポリシーにより、このページは表示できません。</p>
53+
<span id="url"></span>
54+
</main>
55+
<!-- The extension page CSP forbids inline scripts, so this lives in a file. -->
56+
<script src="blocked.js"></script>
57+
</body>
58+
</html>

webextensions/edge/blocked.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
'use strict';
2+
3+
const blocked = new URLSearchParams(location.search).get('url');
4+
if (blocked) {
5+
document.getElementById('url').textContent = blocked;
6+
}

webextensions/edge/setting-page-filter.js

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -36,21 +36,28 @@ export const SettingPageFilter = {
3636
return this.blockedPrefixes.some(prefix => url.startsWith(prefix));
3737
},
3838

39+
// onBeforeNavigate cannot cancel or redirect the way a blocking webRequest
40+
// listener can, so the tab is sent somewhere else instead. The explanation is
41+
// a page bundled with the extension rather than a data: URL, because Chromium
42+
// restricts top frame navigation to data: URLs.
43+
blockedPageUrl(blockedUrl) {
44+
return chrome.runtime.getURL('blocked.html') +
45+
'?url=' + encodeURIComponent(blockedUrl);
46+
},
47+
3948
onBeforeNavigate(details) {
4049
if (details.frameId !== 0) return;
4150
if (!this.enabled) return;
4251
if (!this.isBlockedUrl(details.url)) return;
52+
53+
if (this.notifyOnBlocked) {
54+
chrome.tabs.update(details.tabId, { url: this.blockedPageUrl(details.url) });
55+
return;
56+
}
57+
4358
chrome.tabs.goBack(details.tabId).catch(() =>
4459
chrome.tabs.update(details.tabId, { url: 'about:blank' })
4560
);
46-
if (this.notifyOnBlocked) {
47-
chrome.notifications.create('settings-blocked', {
48-
type: 'basic',
49-
iconUrl: 'misc/128x128.png',
50-
title: '設定画面へのアクセスはブロックされています',
51-
message: `拡張機能のポリシーにより ${details.url} は表示できません。`,
52-
});
53-
}
5461
},
5562
}
5663

webextensions/make.ps1

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -156,7 +156,8 @@ function Invoke-Clean {
156156
# --- package ----------------------------------------------------------------
157157

158158
# Collect the files shipped with the extension.
159-
# JS files are discovered from the directory, so adding one cannot be forgotten.
159+
# Scripts and pages are discovered from the directory, so adding one cannot be
160+
# forgotten.
160161
function Copy-ExtensionFile([string]$Destination) {
161162
if (Test-Path $Destination) {
162163
Remove-Item -LiteralPath $Destination -Recurse -Force
@@ -166,7 +167,8 @@ function Copy-ExtensionFile([string]$Destination) {
166167
Copy-Item (Join-Path $EdgeDir 'manifest.json') -Destination $Destination
167168
Copy-Item (Join-Path $EdgeDir 'misc') -Destination $Destination -Recurse
168169
Copy-Item (Join-Path $EdgeDir '_locales') -Destination $Destination -Recurse
169-
Get-ChildItem -Path $EdgeDir -Filter '*.js' -File | Copy-Item -Destination $Destination
170+
Get-ChildItem -Path $EdgeDir -File -Include '*.js', '*.html' -Recurse -Depth 0 |
171+
Copy-Item -Destination $Destination
170172
}
171173

172174
# Rename the dev edition so it can be installed alongside the production one.
Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
'use strict';
2+
3+
import { describe, it, beforeEach } from 'node:test';
4+
import assert from 'node:assert/strict';
5+
6+
// setting-page-filter reaches for chrome.* when it blocks something, so a stub
7+
// stands in for the browser and records what it was asked to do.
8+
const calls = { updates: [], goBacks: [] };
9+
10+
globalThis.chrome = {
11+
runtime: {
12+
getURL: path => `chrome-extension://testid/${path}`,
13+
},
14+
tabs: {
15+
update: (tabId, props) => {
16+
calls.updates.push({ tabId, ...props });
17+
return Promise.resolve();
18+
},
19+
goBack: tabId => {
20+
calls.goBacks.push(tabId);
21+
return Promise.resolve();
22+
},
23+
},
24+
};
25+
26+
const { SettingPageFilter } = await import('../edge/setting-page-filter.js');
27+
28+
const EMPTY = {
29+
Enabled: true,
30+
NotifyOnBlocked: true,
31+
BlockedPrefixes: ['edge://settings'],
32+
};
33+
34+
function configure(overrides) {
35+
SettingPageFilter.applyConfig({ ...EMPTY, ...overrides });
36+
}
37+
38+
function navigate(url, frameId = 0) {
39+
return SettingPageFilter.onBeforeNavigate({ frameId, tabId: 7, url });
40+
}
41+
42+
beforeEach(() => {
43+
calls.updates = [];
44+
calls.goBacks = [];
45+
configure({});
46+
});
47+
48+
describe('isBlockedUrl', () => {
49+
it('matches a configured prefix', () => {
50+
assert.equal(SettingPageFilter.isBlockedUrl('edge://settings/privacy'), true);
51+
assert.equal(SettingPageFilter.isBlockedUrl('https://example.com/'), false);
52+
});
53+
54+
it('matches any of several prefixes', () => {
55+
configure({ BlockedPrefixes: ['edge://settings', 'edge://flags'] });
56+
57+
assert.equal(SettingPageFilter.isBlockedUrl('edge://flags/#foo'), true);
58+
assert.equal(SettingPageFilter.isBlockedUrl('edge://policy'), false);
59+
});
60+
});
61+
62+
describe('applyConfig', () => {
63+
it('ignores a missing config rather than clearing everything', () => {
64+
configure({ BlockedPrefixes: ['edge://policy'] });
65+
66+
SettingPageFilter.applyConfig(undefined);
67+
68+
assert.deepEqual(SettingPageFilter.blockedPrefixes, ['edge://policy']);
69+
});
70+
});
71+
72+
describe('onBeforeNavigate', () => {
73+
it('leaves an allowed address alone', () => {
74+
navigate('https://example.com/');
75+
76+
assert.deepEqual(calls.updates, []);
77+
assert.deepEqual(calls.goBacks, []);
78+
});
79+
80+
it('ignores navigation in a sub frame', () => {
81+
navigate('edge://settings/privacy', 1);
82+
83+
assert.deepEqual(calls.updates, []);
84+
});
85+
86+
it('does nothing while disabled', () => {
87+
configure({ Enabled: false });
88+
89+
navigate('edge://settings/privacy');
90+
91+
assert.deepEqual(calls.updates, []);
92+
assert.deepEqual(calls.goBacks, []);
93+
});
94+
95+
it('sends the tab to the bundled page carrying the blocked address', () => {
96+
navigate('edge://settings/privacy');
97+
98+
assert.equal(calls.updates.length, 1);
99+
const { tabId, url } = calls.updates[0];
100+
assert.equal(tabId, 7);
101+
assert.ok(url.startsWith('chrome-extension://testid/blocked.html?'));
102+
assert.equal(
103+
new URL(url).searchParams.get('url'),
104+
'edge://settings/privacy');
105+
// The explanation replaces going back, so history is left alone.
106+
assert.deepEqual(calls.goBacks, []);
107+
});
108+
109+
it('goes back silently when notifications are turned off', () => {
110+
configure({ NotifyOnBlocked: false });
111+
112+
navigate('edge://settings/privacy');
113+
114+
assert.deepEqual(calls.goBacks, [7]);
115+
assert.deepEqual(calls.updates, []);
116+
});
117+
});
118+
119+
describe('blockedPageUrl', () => {
120+
// A crafted address must not be able to add parameters of its own.
121+
it('escapes the address it carries', () => {
122+
const url = SettingPageFilter.blockedPageUrl('edge://settings/?a=1&b=2#x');
123+
124+
assert.equal(
125+
new URL(url).searchParams.get('url'),
126+
'edge://settings/?a=1&b=2#x');
127+
});
128+
});

0 commit comments

Comments
 (0)