summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authortaiy <53635909+taiyme@users.noreply.github.com>2025-09-10 09:22:12 +0900
committerGitHub <noreply@github.com>2025-09-10 09:22:12 +0900
commit7673874675630b68678e1d8603d0f08cd0eece31 (patch)
tree4092a1c7e5f98e22b8e71cd72101e0535c2c5ab9
parent[skip ci] Update CHANGELOG.md (prepend template) (diff)
downloadmisskey-7673874675630b68678e1d8603d0f08cd0eece31.tar.gz
misskey-7673874675630b68678e1d8603d0f08cd0eece31.tar.bz2
misskey-7673874675630b68678e1d8603d0f08cd0eece31.zip
fix(eslint): add window prefix rules to frontend-embed & frontend-shared (#16531)
-rw-r--r--packages/frontend-embed/eslint.config.js68
-rw-r--r--packages/frontend-embed/src/boot.ts12
-rw-r--r--packages/frontend-embed/src/components/EmImgWithBlurhash.vue4
-rw-r--r--packages/frontend-embed/src/components/EmInstanceTicker.vue2
-rw-r--r--packages/frontend-embed/src/components/EmMention.vue2
-rw-r--r--packages/frontend-embed/src/components/EmPagination.vue12
-rw-r--r--packages/frontend-embed/src/server-context.ts2
-rw-r--r--packages/frontend-embed/src/server-metadata.ts2
-rw-r--r--packages/frontend-embed/src/theme.ts10
-rw-r--r--packages/frontend-embed/src/ui.vue4
-rw-r--r--packages/frontend-shared/eslint.config.js68
-rw-r--r--packages/frontend-shared/js/config.ts8
-rw-r--r--packages/frontend-shared/js/scroll.ts10
-rw-r--r--packages/frontend-shared/js/use-document-visibility.ts8
14 files changed, 168 insertions, 44 deletions
diff --git a/packages/frontend-embed/eslint.config.js b/packages/frontend-embed/eslint.config.js
index 179d811e77..46247e40d5 100644
--- a/packages/frontend-embed/eslint.config.js
+++ b/packages/frontend-embed/eslint.config.js
@@ -46,9 +46,71 @@ export default [
allowSingleExtends: true,
}],
'import/consistent-type-specifier-style': ['error', 'prefer-top-level'],
- // window の禁止理由: グローバルスコープと衝突し、予期せぬ結果を招くため
- // e の禁止理由: error や event など、複数のキーワードの頭文字であり分かりにくいため
- 'id-denylist': ['error', 'window', 'e'],
+ // window ... グローバルスコープと衝突し、予期せぬ結果を招くため
+ // e ... error や event など、複数のキーワードの頭文字であり分かりにくいため
+ // close ... window.closeと衝突 or 紛らわしい
+ // open ... window.openと衝突 or 紛らわしい
+ // fetch ... window.fetchと衝突 or 紛らわしい
+ // location ... window.locationと衝突 or 紛らわしい
+ // document ... window.documentと衝突 or 紛らわしい
+ // history ... window.historyと衝突 or 紛らわしい
+ // scroll ... window.scrollと衝突 or 紛らわしい
+ // setTimeout ... window.setTimeoutと衝突 or 紛らわしい
+ // setInterval ... window.setIntervalと衝突 or 紛らわしい
+ // clearTimeout ... window.clearTimeoutと衝突 or 紛らわしい
+ // clearInterval ... window.clearIntervalと衝突 or 紛らわしい
+ 'id-denylist': ['error', 'window', 'e', 'close', 'open', 'fetch', 'location', 'document', 'history', 'scroll', 'setTimeout', 'setInterval', 'clearTimeout', 'clearInterval'],
+ 'no-restricted-globals': [
+ 'error',
+ {
+ 'name': 'open',
+ 'message': 'Use `window.open`.',
+ },
+ {
+ 'name': 'close',
+ 'message': 'Use `window.close`.',
+ },
+ {
+ 'name': 'fetch',
+ 'message': 'Use `window.fetch`.',
+ },
+ {
+ 'name': 'location',
+ 'message': 'Use `window.location`.',
+ },
+ {
+ 'name': 'document',
+ 'message': 'Use `window.document`.',
+ },
+ {
+ 'name': 'history',
+ 'message': 'Use `window.history`.',
+ },
+ {
+ 'name': 'scroll',
+ 'message': 'Use `window.scroll`.',
+ },
+ {
+ 'name': 'setTimeout',
+ 'message': 'Use `window.setTimeout`.',
+ },
+ {
+ 'name': 'setInterval',
+ 'message': 'Use `window.setInterval`.',
+ },
+ {
+ 'name': 'clearTimeout',
+ 'message': 'Use `window.clearTimeout`.',
+ },
+ {
+ 'name': 'clearInterval',
+ 'message': 'Use `window.clearInterval`.',
+ },
+ {
+ 'name': 'name',
+ 'message': 'Use `window.name`. もしくは name という変数名を定義し忘れている',
+ },
+ ],
'no-shadow': ['warn'],
'vue/attributes-order': ['error', {
alphabetical: false,
diff --git a/packages/frontend-embed/src/boot.ts b/packages/frontend-embed/src/boot.ts
index 9d69437c30..961cbcef66 100644
--- a/packages/frontend-embed/src/boot.ts
+++ b/packages/frontend-embed/src/boot.ts
@@ -33,7 +33,7 @@ import type { Theme } from '@/theme.js';
console.log('Misskey Embed');
//#region Embedパラメータの取得・パース
-const params = new URLSearchParams(location.search);
+const params = new URLSearchParams(window.location.search);
const embedParams = parseEmbedParams(params);
if (_DEV_) console.log(embedParams);
//#endregion
@@ -81,7 +81,7 @@ storeBootloaderErrors({ ...i18n.ts._bootErrors, reload: i18n.ts.reload });
//#endregion
// サイズの制限
-document.documentElement.style.maxWidth = '500px';
+window.document.documentElement.style.maxWidth = '500px';
// iframeIdの設定
function setIframeIdHandler(event: MessageEvent) {
@@ -114,16 +114,16 @@ app.provide(DI.embedParams, embedParams);
const rootEl = ((): HTMLElement => {
const MISSKEY_MOUNT_DIV_ID = 'misskey_app';
- const currentRoot = document.getElementById(MISSKEY_MOUNT_DIV_ID);
+ const currentRoot = window.document.getElementById(MISSKEY_MOUNT_DIV_ID);
if (currentRoot) {
console.warn('multiple import detected');
return currentRoot;
}
- const root = document.createElement('div');
+ const root = window.document.createElement('div');
root.id = MISSKEY_MOUNT_DIV_ID;
- document.body.appendChild(root);
+ window.document.body.appendChild(root);
return root;
})();
@@ -159,7 +159,7 @@ console.log(i18n.tsx._selfXssPrevention.description3({ link: 'https://misskey-hu
//#endregion
function removeSplash() {
- const splash = document.getElementById('splash');
+ const splash = window.document.getElementById('splash');
if (splash) {
splash.style.opacity = '0';
splash.style.pointerEvents = 'none';
diff --git a/packages/frontend-embed/src/components/EmImgWithBlurhash.vue b/packages/frontend-embed/src/components/EmImgWithBlurhash.vue
index 0bff048ce4..71f0ee9294 100644
--- a/packages/frontend-embed/src/components/EmImgWithBlurhash.vue
+++ b/packages/frontend-embed/src/components/EmImgWithBlurhash.vue
@@ -19,7 +19,7 @@ import { extractAvgColorFromBlurhash } from '@@/js/extract-avg-color-from-blurha
const canvasPromise = new Promise<WorkerMultiDispatch | HTMLCanvasElement>(resolve => {
// テスト環境で Web Worker インスタンスは作成できない
if (import.meta.env.MODE === 'test') {
- const canvas = document.createElement('canvas');
+ const canvas = window.document.createElement('canvas');
canvas.width = 64;
canvas.height = 64;
resolve(canvas);
@@ -34,7 +34,7 @@ const canvasPromise = new Promise<WorkerMultiDispatch | HTMLCanvasElement>(resol
);
resolve(workers);
} else {
- const canvas = document.createElement('canvas');
+ const canvas = window.document.createElement('canvas');
canvas.width = 64;
canvas.height = 64;
resolve(canvas);
diff --git a/packages/frontend-embed/src/components/EmInstanceTicker.vue b/packages/frontend-embed/src/components/EmInstanceTicker.vue
index 4a116e317a..7add3bb53f 100644
--- a/packages/frontend-embed/src/components/EmInstanceTicker.vue
+++ b/packages/frontend-embed/src/components/EmInstanceTicker.vue
@@ -29,7 +29,7 @@ const props = defineProps<{
// if no instance data is given, this is for the local instance
const instance = props.instance ?? {
name: serverMetadata.name,
- themeColor: (document.querySelector('meta[name="theme-color-orig"]') as HTMLMetaElement)?.content,
+ themeColor: (window.document.querySelector('meta[name="theme-color-orig"]') as HTMLMetaElement)?.content,
};
const faviconUrl = computed(() => props.instance ? mediaProxy.getProxiedImageUrlNullable(props.instance.faviconUrl, 'preview') : mediaProxy.getProxiedImageUrlNullable(serverMetadata.iconUrl, 'preview') ?? '/favicon.ico');
diff --git a/packages/frontend-embed/src/components/EmMention.vue b/packages/frontend-embed/src/components/EmMention.vue
index b5aaa95894..0a8ac9c05a 100644
--- a/packages/frontend-embed/src/components/EmMention.vue
+++ b/packages/frontend-embed/src/components/EmMention.vue
@@ -27,7 +27,7 @@ const canonical = props.host === localHost ? `@${props.username}` : `@${props.us
const url = `/${canonical}`;
-const bg = tinycolor(getComputedStyle(document.documentElement).getPropertyValue('--MI_THEME-mention'));
+const bg = tinycolor(getComputedStyle(window.document.documentElement).getPropertyValue('--MI_THEME-mention'));
bg.setAlpha(0.1);
const bgCss = bg.toRgbString();
</script>
diff --git a/packages/frontend-embed/src/components/EmPagination.vue b/packages/frontend-embed/src/components/EmPagination.vue
index 94a91305f4..bd49d127a9 100644
--- a/packages/frontend-embed/src/components/EmPagination.vue
+++ b/packages/frontend-embed/src/components/EmPagination.vue
@@ -134,7 +134,7 @@ const isBackTop = ref(false);
const empty = computed(() => items.value.size === 0);
const error = ref(false);
-const scrollableElement = computed(() => rootEl.value ? getScrollContainer(rootEl.value) : document.body);
+const scrollableElement = computed(() => rootEl.value ? getScrollContainer(rootEl.value) : window.document.body);
const visibility = useDocumentVisibility();
@@ -353,7 +353,7 @@ watch(visibility, () => {
BACKGROUND_PAUSE_WAIT_SEC * 1000);
} else { // 'visible'
if (timerForSetPause) {
- clearTimeout(timerForSetPause);
+ window.clearTimeout(timerForSetPause);
timerForSetPause = null;
} else {
isPausingUpdate = false;
@@ -447,11 +447,11 @@ onBeforeMount(() => {
init().then(() => {
if (props.pagination.reversed) {
nextTick(() => {
- setTimeout(toBottom, 800);
+ window.setTimeout(toBottom, 800);
// scrollToBottomでmoreFetchingボタンが画面外まで出るまで
// more = trueを遅らせる
- setTimeout(() => {
+ window.setTimeout(() => {
moreFetching.value = false;
}, 2000);
});
@@ -461,11 +461,11 @@ onBeforeMount(() => {
onBeforeUnmount(() => {
if (timerForSetPause) {
- clearTimeout(timerForSetPause);
+ window.clearTimeout(timerForSetPause);
timerForSetPause = null;
}
if (preventAppearFetchMoreTimer.value) {
- clearTimeout(preventAppearFetchMoreTimer.value);
+ window.clearTimeout(preventAppearFetchMoreTimer.value);
preventAppearFetchMoreTimer.value = null;
}
scrollObserver.value?.disconnect();
diff --git a/packages/frontend-embed/src/server-context.ts b/packages/frontend-embed/src/server-context.ts
index a84a1a726a..c061d5a6f1 100644
--- a/packages/frontend-embed/src/server-context.ts
+++ b/packages/frontend-embed/src/server-context.ts
@@ -4,7 +4,7 @@
*/
import * as Misskey from 'misskey-js';
-const providedContextEl = document.getElementById('misskey_embedCtx');
+const providedContextEl = window.document.getElementById('misskey_embedCtx');
export type ServerContext = {
clip?: Misskey.entities.Clip;
diff --git a/packages/frontend-embed/src/server-metadata.ts b/packages/frontend-embed/src/server-metadata.ts
index 6c94aacd48..ad9b5a1a91 100644
--- a/packages/frontend-embed/src/server-metadata.ts
+++ b/packages/frontend-embed/src/server-metadata.ts
@@ -6,7 +6,7 @@
import * as Misskey from 'misskey-js';
import { misskeyApi } from '@/misskey-api.js';
-const providedMetaEl = document.getElementById('misskey_meta');
+const providedMetaEl = window.document.getElementById('misskey_meta');
const _serverMetadata: Misskey.entities.MetaDetailed | null = (providedMetaEl && providedMetaEl.textContent) ? JSON.parse(providedMetaEl.textContent) : null;
diff --git a/packages/frontend-embed/src/theme.ts b/packages/frontend-embed/src/theme.ts
index c9b1c0d0c6..c7bc5df85d 100644
--- a/packages/frontend-embed/src/theme.ts
+++ b/packages/frontend-embed/src/theme.ts
@@ -35,15 +35,15 @@ export function assertIsTheme(theme: Record<string, unknown>): theme is Theme {
export function applyTheme(theme: Theme, persist = true) {
if (timeout) window.clearTimeout(timeout);
- document.documentElement.classList.add('_themeChanging_');
+ window.document.documentElement.classList.add('_themeChanging_');
timeout = window.setTimeout(() => {
- document.documentElement.classList.remove('_themeChanging_');
+ window.document.documentElement.classList.remove('_themeChanging_');
}, 1000);
const colorScheme = theme.base === 'dark' ? 'dark' : 'light';
- document.documentElement.dataset.colorScheme = colorScheme;
+ window.document.documentElement.dataset.colorScheme = colorScheme;
// Deep copy
const _theme = JSON.parse(JSON.stringify(theme));
@@ -55,7 +55,7 @@ export function applyTheme(theme: Theme, persist = true) {
const props = compile(_theme);
- for (const tag of document.head.children) {
+ for (const tag of window.document.head.children) {
if (tag.tagName === 'META' && tag.getAttribute('name') === 'theme-color') {
tag.setAttribute('content', props['htmlThemeColor']);
break;
@@ -63,7 +63,7 @@ export function applyTheme(theme: Theme, persist = true) {
}
for (const [k, v] of Object.entries(props)) {
- document.documentElement.style.setProperty(`--MI_THEME-${k}`, v.toString());
+ window.document.documentElement.style.setProperty(`--MI_THEME-${k}`, v.toString());
}
// iframeを正常に透過させるために、cssのcolor-schemeは `light dark;` 固定にしてある。style.scss参照
diff --git a/packages/frontend-embed/src/ui.vue b/packages/frontend-embed/src/ui.vue
index 4ba5968a91..711d0eae6d 100644
--- a/packages/frontend-embed/src/ui.vue
+++ b/packages/frontend-embed/src/ui.vue
@@ -52,8 +52,8 @@ function safeURIDecode(str: string): string {
}
}
-const page = location.pathname.split('/')[2];
-const contentId = safeURIDecode(location.pathname.split('/')[3]);
+const page = window.location.pathname.split('/')[2];
+const contentId = safeURIDecode(window.location.pathname.split('/')[3]);
if (_DEV_) console.log(page, contentId);
const embedParams = inject(DI.embedParams, defaultEmbedParams);
diff --git a/packages/frontend-shared/eslint.config.js b/packages/frontend-shared/eslint.config.js
index 6453be0042..b972cfdb27 100644
--- a/packages/frontend-shared/eslint.config.js
+++ b/packages/frontend-shared/eslint.config.js
@@ -51,9 +51,71 @@ export default [
allowSingleExtends: true,
}],
'import/consistent-type-specifier-style': ['error', 'prefer-top-level'],
- // window の禁止理由: グローバルスコープと衝突し、予期せぬ結果を招くため
- // e の禁止理由: error や event など、複数のキーワードの頭文字であり分かりにくいため
- 'id-denylist': ['error', 'window', 'e'],
+ // window ... グローバルスコープと衝突し、予期せぬ結果を招くため
+ // e ... error や event など、複数のキーワードの頭文字であり分かりにくいため
+ // close ... window.closeと衝突 or 紛らわしい
+ // open ... window.openと衝突 or 紛らわしい
+ // fetch ... window.fetchと衝突 or 紛らわしい
+ // location ... window.locationと衝突 or 紛らわしい
+ // document ... window.documentと衝突 or 紛らわしい
+ // history ... window.historyと衝突 or 紛らわしい
+ // scroll ... window.scrollと衝突 or 紛らわしい
+ // setTimeout ... window.setTimeoutと衝突 or 紛らわしい
+ // setInterval ... window.setIntervalと衝突 or 紛らわしい
+ // clearTimeout ... window.clearTimeoutと衝突 or 紛らわしい
+ // clearInterval ... window.clearIntervalと衝突 or 紛らわしい
+ 'id-denylist': ['error', 'window', 'e', 'close', 'open', 'fetch', 'location', 'document', 'history', 'scroll', 'setTimeout', 'setInterval', 'clearTimeout', 'clearInterval'],
+ 'no-restricted-globals': [
+ 'error',
+ {
+ 'name': 'open',
+ 'message': 'Use `window.open`.',
+ },
+ {
+ 'name': 'close',
+ 'message': 'Use `window.close`.',
+ },
+ {
+ 'name': 'fetch',
+ 'message': 'Use `window.fetch`.',
+ },
+ {
+ 'name': 'location',
+ 'message': 'Use `window.location`.',
+ },
+ {
+ 'name': 'document',
+ 'message': 'Use `window.document`.',
+ },
+ {
+ 'name': 'history',
+ 'message': 'Use `window.history`.',
+ },
+ {
+ 'name': 'scroll',
+ 'message': 'Use `window.scroll`.',
+ },
+ {
+ 'name': 'setTimeout',
+ 'message': 'Use `window.setTimeout`.',
+ },
+ {
+ 'name': 'setInterval',
+ 'message': 'Use `window.setInterval`.',
+ },
+ {
+ 'name': 'clearTimeout',
+ 'message': 'Use `window.clearTimeout`.',
+ },
+ {
+ 'name': 'clearInterval',
+ 'message': 'Use `window.clearInterval`.',
+ },
+ {
+ 'name': 'name',
+ 'message': 'Use `window.name`. もしくは name という変数名を定義し忘れている',
+ },
+ ],
'no-shadow': ['warn'],
'vue/attributes-order': ['error', {
alphabetical: false,
diff --git a/packages/frontend-shared/js/config.ts b/packages/frontend-shared/js/config.ts
index ac5c5629f3..6272d3f6b9 100644
--- a/packages/frontend-shared/js/config.ts
+++ b/packages/frontend-shared/js/config.ts
@@ -4,15 +4,15 @@
*/
// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
-const address = new URL(document.querySelector<HTMLMetaElement>('meta[property="instance_url"]')?.content || location.href);
-const siteName = document.querySelector<HTMLMetaElement>('meta[property="og:site_name"]')?.content;
+const address = new URL(window.document.querySelector<HTMLMetaElement>('meta[property="instance_url"]')?.content || window.location.href);
+const siteName = window.document.querySelector<HTMLMetaElement>('meta[property="og:site_name"]')?.content;
export const host = address.host;
export const hostname = address.hostname;
export const url = address.origin;
export const port = address.port;
-export const apiUrl = location.origin + '/api';
-export const wsOrigin = location.origin;
+export const apiUrl = window.location.origin + '/api';
+export const wsOrigin = window.location.origin;
export const lang = localStorage.getItem('lang') ?? 'en-US';
export const langs = _LANGS_;
export const version = _VERSION_;
diff --git a/packages/frontend-shared/js/scroll.ts b/packages/frontend-shared/js/scroll.ts
index 9057b896c6..5578cffdec 100644
--- a/packages/frontend-shared/js/scroll.ts
+++ b/packages/frontend-shared/js/scroll.ts
@@ -51,7 +51,7 @@ export function onScrollTop(el: HTMLElement, cb: (topVisible: boolean) => unknow
// - toleranceの範囲内に収まる程度の微量なスクロールが発生した
let prevTopVisible = firstTopVisible;
const onScroll = () => {
- if (!document.body.contains(el)) return;
+ if (!window.document.body.contains(el)) return;
const topVisible = isHeadVisible(el, tolerance);
if (topVisible !== prevTopVisible) {
@@ -78,7 +78,7 @@ export function onScrollBottom(el: HTMLElement, cb: () => unknown, tolerance = 1
const containerOrWindow = container ?? window;
const onScroll = () => {
- if (!document.body.contains(el)) return;
+ if (!window.document.body.contains(el)) return;
if (isTailVisible(el, 1, container)) {
cb();
if (once) removeListener();
@@ -145,8 +145,8 @@ export function isTailVisible(el: HTMLElement, tolerance = 1, container = getScr
// https://ja.javascript.info/size-and-scroll-window#ref-932
export function getBodyScrollHeight() {
return Math.max(
- document.body.scrollHeight, document.documentElement.scrollHeight,
- document.body.offsetHeight, document.documentElement.offsetHeight,
- document.body.clientHeight, document.documentElement.clientHeight,
+ window.document.body.scrollHeight, window.document.documentElement.scrollHeight,
+ window.document.body.offsetHeight, window.document.documentElement.offsetHeight,
+ window.document.body.clientHeight, window.document.documentElement.clientHeight,
);
}
diff --git a/packages/frontend-shared/js/use-document-visibility.ts b/packages/frontend-shared/js/use-document-visibility.ts
index b1197e68da..a87c1f1bab 100644
--- a/packages/frontend-shared/js/use-document-visibility.ts
+++ b/packages/frontend-shared/js/use-document-visibility.ts
@@ -7,18 +7,18 @@ import { onMounted, onUnmounted, ref } from 'vue';
import type { Ref } from 'vue';
export function useDocumentVisibility(): Ref<DocumentVisibilityState> {
- const visibility = ref(document.visibilityState);
+ const visibility = ref(window.document.visibilityState);
const onChange = (): void => {
- visibility.value = document.visibilityState;
+ visibility.value = window.document.visibilityState;
};
onMounted(() => {
- document.addEventListener('visibilitychange', onChange);
+ window.document.addEventListener('visibilitychange', onChange);
});
onUnmounted(() => {
- document.removeEventListener('visibilitychange', onChange);
+ window.document.removeEventListener('visibilitychange', onChange);
});
return visibility;