ํ๋ฐํธ์๋ ๊ฐ๋ฐ์ ๊ฐ์ฅ ๋ง์ ์ํฅ์ ์ฃผ๋ ํฌ๋กฌ ๋ธ๋ผ์ฐ์ ์ ๋ฒ์ ๋ณ ๋ณ๊ฒฝ ์์ ํญ๋ชฉ์ ์ ๋ฆฌ ๋ฐ ๊ณต์ ํ๋ค.
๐ก ๊ฐ ํญ๋ชฉ์ Chrome Platform Status์ Roadmap๊ณผ ํ ๋ฌ๊ฐ์ blink-dev ํ๋ ์์ฝ์ ๋ฐํ์ผ๋ก ์ ๋ฆฌํ๋ค.
๐ก ๊ฐ ํญ๋ชฉ์ โ ๏ธ๋ ์ง์ ์ค๋จ(Deprecated), โ ๋ ์๋ก์ด ๊ธฐ๋ฅ(Enabled by default), ๐งช๋ ๋ฏธ๋ฆฌ ๋ณด๊ธฐ(Developer Trial, Origin Trial)๋ฅผ ์๋ฏธํ๋ค.
๐ก ๊ฐ ํญ๋ชฉ ์ค ๊ธฐ์กด ์๋น์ค์ ๋ฏธ์น๋ ์ํฅ์ด ํฌ๋ค๊ณ ํ๋จํ ํญ๋ชฉ์ ์์ ๋ชฉ ๋ค์ ๐ ํ์๋ฅผ ํ๋ค.
๐ก ์ง์ ์ค๋จ(โ ๏ธ) ์ธ์ ํญ๋ชฉ์ ๊ณต์ ๊ฐ์น๊ฐ ์๋ค๊ณ ํ๋จํ ๊ฒฝ์ฐ์๋ง ํฌํจํ๋ค.
๐ก ๋ฏธ๋ฆฌ ๋ณด๊ธฐ(๐งช)๋ chrome://flags
ํ์ด์ง์์ Experimental Web Platform features
๋ฅผ ํ์ฑํ(Developer Trial) ๋๋ ์ถ์ฒ ๋ฏธ๋ฆฌ ๋ณด๊ธฐ(Origin Trial)๋ฅผ ์ ์ฒญํด ์ฌ์ฉํ ์ ์๋ค.
๐ก ๊ฐ ํญ๋ชฉ์ ๋ํ ์ฃผ์ ๋ธ๋ผ์ฐ์ ๋ฐ ์น ๊ฐ๋ฐ์์ ์๊ฒฌ์ Chrome Platform Status๋ฅผ ๊ทธ๋๋ก ์ธ์ฉํ๋ค.
Chrome 125
enable-new-base-url-inheritance-behavior
๊ธฐ๋ณธ๊ฐ Disabled
๋ก ๋ณ๊ฒฝmousemove
์ด๋ฒคํธ ์ทจ์ ์ ๋์ ๋ณ๊ฒฝ ๐Chrome 127
Chrome NEXT
DOMParser.parseFromString()
์ includeShadowRoots
์ต์
์ง์ ์ค๋จenable-new-base-url-inheritance-behavior
์ค์ ์ ๊ฑฐchrome://flags#enable-new-base-url-inheritance-behavior
์ค์ ์ ์ ๊ฑฐํ๋ค.
์ ์ค์ ์ ์์ ์ฐฝ์์ srcdoc
์ ์ฌ์ฉํ๊ฑฐ๋ about:blank
ํ์ด์ง๋ฅผ ๋ ๋๋งํ ๋ ๋ถ๋ชจ์ <base>
์์ฑ์ ๋ช
์ธ์์ ์ ์ํ ๋๋ก ์์๋ฐ์ง ์๋ ๋ฒ๊ทธ์ ์์ ์กฐ์น๋ฅผ ์ํด ์ถ๊ฐํ ์์ฑ์ด๋ค. Chrome 118๋ถํฐ ๊ธฐ๋ณธ๊ฐ์ด Enabled
์์ผ๋ฉฐ, ํด๋น ๋ฒ๊ทธ๋ฅผ ์์ ํด ์์ ์ถ๊ฐํ๋ ์ค์ ์ ์ ๊ฑฐํ๋ค.
์ด ํญ๋ชฉ์ ๋ํ ์ฃผ์ ๋ธ๋ผ์ฐ์ ๋ฐ ์น ๊ฐ๋ฐ์์ ์๊ฒฌ์ ๋ค์๊ณผ ๊ฐ๋ค.
window-management
๋ ๋ธ๋ผ์ฐ์ ์ฐฝ์ ์ ์ดํ๋ Window Management API๋ฅผ ์ฌ์ฉํ๊ธฐ ์ํ ๊ถํ์ด๋ค.
์ฒ์์ ์ด API๋ ํ์ ์ฐฝ(window.open
)์ ์์น๋ฅผ ์ ์ดํ๋ ๊ธฐ๋ฅ์๋ง ์ด์ ์ ๋์๋ค. ๊ถํ์ ์ด๋ฆ๋ window-placement
์์ผ๋, API๊ฐ ์ฐฝ์ ํฌ๊ธฐ ๋ฑ ์์น ๋ง๊ณ ๋ ๋ง์ ๊ธฐ๋ฅ์ ์ง์ํ๋๋ก ๋ฐ๋๋ฉด์ ๋ณด๋ค ๋์ ์๋ฏธ์ window-management
๋ก ๋ช
์ธ๋ฅผ ์์ ํ๋ค.
๋นํ์ค API์ธ ๋งํผ Chrome ํ์ ๋ฐ๋ก ์ง์ ์ค๋จ ๊ธฐ๊ฐ ์์ด ์ด๋ฒ ์
๋ฐ์ดํธ์์ ๋ฐ๋ก window-placement
๋ฅผ ์ ๊ฑฐํ ์์ ์ด๋ค. Chrome 111๋ถํฐ window-management
๋ฅผ ์ฌ์ฉํ ์ ์์ผ๋ ์
๋ฐ์ดํธ ์ ์ ๋ฏธ๋ฆฌ ๋ณ๊ฒฝํ๊ธฐ๋ฅผ ๊ถ์ฅํ๋ค.
์ด ํญ๋ชฉ์ ๋ํ ์ฃผ์ ๋ธ๋ผ์ฐ์ ๋ฐ ์น ๊ฐ๋ฐ์์ ์๊ฒฌ์ ๋ค์๊ณผ ๊ฐ๋ค.
WebSocket
์์ฑ์๊ฐ http
๋๋ https
๋ฅผ ํ์ฉํ๋๋ก ๋ณ๊ฒฝํ๋ค. ๊ธฐ์กด์๋ ws
๋ wss
์ ์ฌ์ฉํ๋ ์ ๋ ๊ฒฝ๋ก์ URL๋ง ํ์ฉํด WebSocket
์ฌ์ฉ ์ ๋ถํธํจ์ด ์์๋ค. ์
๋ฐ์ดํธ ์ดํ์๋ http
, https
ํ๋กํ ์ฝ๊ณผ ์๋ ๊ฒฝ๋ก URL์ ๋ชจ๋ ์ฌ์ฉํ ์ ์์ด WebSocket
๊ฐ๋ฐ์ด ์ข ๋ ์ฌ์์ง ์์ ์ด๋ค.
// ํ์ฌ๋ ์๋ ๊ฒฝ๋ก ์ฌ์ฉ ์ ์ค๋ฅ๊ฐ ๋ฐ์ํ๋ค.
// Failed to construct 'WebSocket': The URL '/channel' is invalid.
const socket = new WebSocket('/channel');
WebSocket
์์ฑ์๋ ์ฒซ ๋ฒ์งธ ์ธ์๊ฐ http(s)
ํ๋กํ ์ฝ์ด๋ ์๋ ๊ฒฝ๋ก์ผ ๊ฒฝ์ฐ, URL์ ๋์ํ๋ ws(s)
๋ก ๋ฐ๊พธ์ด ์์ฒญํ๋ค.
์ด ํญ๋ชฉ์ ๋ํ ์ฃผ์ ๋ธ๋ผ์ฐ์ ๋ฐ ์น ๊ฐ๋ฐ์์ ์๊ฒฌ์ ๋ค์๊ณผ ๊ฐ๋ค.
Compute Pressure API๋ฅผ ์ถ๊ฐํ๋ค. ์์คํ ์ ๋ถํ ์ ๋์ ๋ํ ์ ๋ณด๋ฅผ ์ค์๊ฐ์ผ๋ก ์ ๊ณตํ๋ API๋ก, ํ์ ํ์๋ ๊ฒ์ ๋ฑ ๋ฌด๊ฑฐ์ด ์น ์ฑ์์ ์ต์ ํ๋ฅผ ์ํด ์ฌ์ฉํ ์ ์๋ค.
if ('PressureObserver' in globalThis) {
const observer = new PressureObserver(
(records) => {
records.forEach((record) => {
if (record.source === 'cpu') {
console.log(`CPU ๋ถํ ์ํ(time: ${record.time}): ${record.state}`);
// ์์) CPU ๋ถํ ์ํ(time: 1708051909444): critical
if (record.state === 'critical') {
setResolution(360);
} else if (record.state === 'serious') {
setResolution(720);
}
}
});
},
{
sampleInterval: 2000, // ๋จ์: ๋ฐ๋ฆฌ์ด(ms)
}
);
await observer.observe('cpu');
buttonElement.onclick = () => {
observer.disconnect();
};
}
์ด๋ฒ ์ ๋ฐ์ดํธ์์๋ CPU ์ํ๋ง ์ง์ํ๋, ๋์ค์๋ ์จ๋๋ ๋ฐฐํฐ๋ฆฌ ์ํ ๋ฑ์ ์ ๋ณด๋ ํฌํจํ ์์ ์ด๋ค. ๋ค๋ง Safari๋ ํด๋น ๊ธฐ๋ฅ์ ๋ถ์ ์ ์ธ ์ ์ฅ์ด๋ค. ์ค์๊ฐ์ผ๋ก ๋ณํ๋ ๋ถํ ์ ๋ณด๋ฅผ ์ ํํ ์ ๋ฌํ๊ธฐ ์ด๋ ต๊ณ , ์์คํ ์ ๋ณด๋ฅผ ์ฌ์ฉํ๋ ๋งํผ ๋ณด์ ๋ฌธ์ ๊ฐ ์์ ์ ์๋ค๋ ์๊ฒฌ์ด๋ค.
Chrome์ Safari์ ์๊ฒฌ์ ๋ฐ์๋ค์ฌ ์ผ๋จ ๋ถํ ์ํ๋ฅผ ๊ฐ๋ตํ๊ฒ๋ง ์ ๊ณตํ๋ ๊ฒ์ผ๋ก ๋ช ์ธ๋ฅผ ์์ ํ๋ค. ์์ง ์ ์ ์ค์ธ API์ธ ๋งํผ ๋ช ์ธ๊ฐ ๋ฐ๋ ์ ์์ผ๋ ์ฐธ๊ณ ํ์.
์ด ํญ๋ชฉ์ ๋ํ ์ฃผ์ ๋ธ๋ผ์ฐ์ ๋ฐ ์น ๊ฐ๋ฐ์์ ์๊ฒฌ์ ๋ค์๊ณผ ๊ฐ๋ค.
ํน์ ์์์ ๊ธฐ๋ฐํด ํ์ฌ ์์๋ฅผ ๋ฐฐ์นํ ์ ์๋ Anchor ๋ฐฐ์น ๋ชจ๋์ ์ถ๊ฐํ๋ค. ํนํ ํดํ ๋ฑ์ ๊ตฌํํ ๋ ์ ์ฉํ๋ฐ, absolute
๋ฐฐ์น์ ๋น์ทํ๋ DOM ๊ตฌ์กฐ์ ๊ตฌ์ ๋ฐ์ง ์๊ณ ํดํ์ ๊ตฌํํ ์ ์๋ค๋ ์ ์ด ๋ค๋ฅด๋ค.
.anchor {
anchor-name: --tooltip;
}
.tooltip {
position: fixed;
position-anchor: --tooltip;
inset-area: top right;
position-try: flip-block;
min-width: anchor-size(width);
}
์์ ์ฒ๋ผ ์ฝ๋๋ฅผ ์์ฑํ๋ฉด .tooltip
์ .anchor
์์์ ์ค๋ฅธ์ชฝ ์์ ๋ํ๋๋ค. ๋ง์ฝ .tooltip
์ด ๋ธ๋ผ์ฐ์ ํ๋ฉด์ ๋ฒ์ด๋๋ค๋ฉด position-try: flip-block
์ ์ํด .anchor
์์์ ์๋์ ๋ฐฐ์น๋๋ค.
anchor-name
์๋ ํด๋น Anchor์ ์ด๋ฆ์ ์ ์ธํ๋ฉฐ, ํด๋น Anchor์ ๋ฐฐ์นํ ์์์์ ์ด ์ด๋ฆ์ผ๋ก ํด๋น ์์์ ์์น๋ ํฌ๊ธฐ ๋ฑ์ ์ ๊ทผํ ์ ์๋ค. anchor()
ํจ์๋ ํด๋น Anchor์ ์์น๊ฐ์, anchor-size()
๋ ํด๋น Anchor์ ํฌ๊ธฐ๊ฐ์ ๋ฐํํ๋ ํจ์๋ก, ๊ธฐ๋ณธ์ ์ผ๋ก ์๋์ฒ๋ผ Anchor์ ์ด๋ฆ์ ์ธ์๋ก ๋ฐ๋๋ค.
.tooltip {
bottom: anchor(--tooltip top);
max-width: anchor-size(--tooltip width);
}
๋ง์ฝ ํด๋น ์์์ position-anchor
๋ฅผ ์ค์ ํ๋ค๋ฉด ์ฒซ ๋ฒ์งธ ์ธ์๋ฅผ ์๋ตํ ์ ์๋ค.
.tooltip {
position-anchor: --tooltip;
bottom: anchor(top);
max-width: anchor-size(width);
}
@position-try
๊ท์น์ ์ ์ธํ๋ฉด ํด๋น ์์๊ฐ ๋ธ๋ผ์ฐ์ ํ๋ฉด์ ๋ฒ์ด๋ ๊ฒฝ์ฐ์ ๋ํ ์คํ์ผ์ ์ ์ํ ์ ์๋ค.
@position-try --tooltip-position {
@try {
top: anchor(--tooltip bottom);
left: anchor(--tooltip left);
}
@try {
bottom: anchor(--tooltip top);
left: anchor(--tooltip left);
}
@try {
top: anchor(--tooltip bottom);
right: anchor(--tooltip right);
}
}
#tooltip {
position: fixed;
position-try-options: --tooltip-position;
}
์ ์ฝ๋๋ ๋จผ์ Anchor์ ์ผ์ชฝ ์๋์ ๋ฐฐ์น๋ฅผ ์๋ํ๋ฉฐ, ๋ง์ฝ ํ๋ฉด์ ๋ฒ์ด๋๋ฉด Anchor์ ์ผ์ชฝ ์, ๋ง์ง๋ง์ผ๋ก Anchor์ ์ค๋ฅธ์ชฝ ์๋์ #tooltip
์ ๋ฐฐ์นํ๋ผ๋ ์๋ฏธ์ด๋ค.
์ด ๊ธฐ๋ฅ์ ์์ง ์ด์์ผ๋ก ๋ช ์ธ๋ ๋์์ด ๋ฐ๋ ์ ์์์ ์ฃผ์ํ์.
์ด ํญ๋ชฉ์ ๋ํ ์ฃผ์ ๋ธ๋ผ์ฐ์ ๋ฐ ์น ๊ฐ๋ฐ์์ ์๊ฒฌ์ ๋ค์๊ณผ ๊ฐ๋ค.
๊ฐ์ ์ฐ์ฐํ ๋ ์ฌ์ฉํ ์ ์๋ round()
, mod()
, rem()
CSS ํจ์๋ฅผ ์ถ๊ฐํ๋ค.
round()
๋ ์ฒซ ๋ฒ์งธ ์ธ์์ ๊ฐ์ ๋ฐ๋ผ ๋ ๋ฒ์งธ ๊ฐ์ ์ฐ์ฐํ ๊ฒฐ๊ณผ๋ฅผ ๋ฐํํ๋ค.
width: round(nearest, var(--height), 50px);
round()
์ ์ฒซ ๋ฒ์งธ ์ธ์๋ก๋ up
, down
, to-zero
, nearest
๋ฅผ ์ธ ์ ์๋ค. ์ด๋ค์ ๊ฐ๊ฐ ์ฌ๋ฆผ, ๋ด๋ฆผ, ๋ฒ๋ฆผ, ๋ฐ์ฌ๋ฆผ์ ์ธ ๋ฒ์งธ ์ธ์๋ฅผ ๊ธฐ์ค์ผ๋ก ์ํํ๋ฉฐ, ๊ธฐ๋ณธ๊ฐ์ nearest
์ด๋ค.
width: round(up, 101px, 50px); /* 150px */
width: round(down, -101px, 10px); /* -110px */
width: round(to-zero, -101px, 10px); /* -100px */
width: round(27px, 5px); /* 25px, ์ฒซ ๋ฒ์งธ ์ธ์๋ฅผ ์๋ตํ๋ฉด ๊ธฐ๋ณธ๊ฐ์ธ nearest๋ฅผ ์ฌ์ฉํ๋ค. */
mod()
์ rem()
์ ๋๋จธ์ง๋ฅผ ๋ฐํํ๋ ํจ์๋ก ์๋ฐ์คํฌ๋ฆฝํธ์ %
์ฐ์ฐ์์ ๊ธฐ๋ฅ์ ์ผ๋ก ์ ์ฌํ๋ค. ๋ ํจ์๋ ๋ถํธ๋ฅผ ๊ฐ์ ธ์ค๋ ๋ฐฉ์์ด ๋ค๋ฅธ๋ฐ, mod()
๋ ๋ถํธ๋ฅผ ๋ ๋ฒ์งธ ์ธ์์์ ๊ฐ์ ธ์ค๋ฉฐ, rem()
์ ์ฒซ ๋ฒ์งธ ์ธ์์์ ๋ถํธ๋ฅผ ๊ฐ์ ธ์จ๋ค.
rotate: mod(-90deg, 20deg); /* 10deg */
rotate: mod(90deg, -20deg); /* -10deg */
rotate: rem(-90deg, 20deg); /* -10deg */
rotate: rem(90deg, -20deg); /* 10deg */
์ด ํญ๋ชฉ์ ๋ํ ์ฃผ์ ๋ธ๋ผ์ฐ์ ๋ฐ ์น ๊ฐ๋ฐ์์ ์๊ฒฌ์ ๋ค์๊ณผ ๊ฐ๋ค.
mousemove
์ด๋ฒคํธ ์ทจ์ ์ ๋์ ๋ณ๊ฒฝ ๐๋ช
์ธ์ ๋ฐ๋ผ mousemove
์ด๋ฒคํธ๋ฅผ ์ทจ์(event.preventDefault()
)ํ์ ๋ ํ
์คํธ ์ ํ, ๋๋๊ทธ ์ค ๋๋กญ์ ์ทจ์ํ์ง ์๋๋ก ๋ณ๊ฒฝํ๋ค. ํ
์คํธ ์ ํ๊ณผ ๋๋๊ทธ ์ค ๋๋กญ์ mousemove
์ด๋ฒคํธ์ ๊ธฐ๋ณธ ๋์์์ ์ ์ธํ๋ ๊ฒ์ผ๋ก, Safari์ Firefox๋ ์๋
ํ๋ฐ๊ธฐ์ ํด๋น ๊ธฐ๋ฅ์ ๋ฐ์ํ๋ค.
๊ธฐ์กด์ฒ๋ผ ํ
์คํธ ์ ํ์ selectstart
์ด๋ฒคํธ๋ฅผ, ๋๋๊ทธ ์ค ๋๋กญ์ dragstart
์ด๋ฒคํธ๋ฅผ ์ทจ์ํ๋ ๊ฒ์ผ๋ก ๊ธฐ๋ณธ ๋์์ ๋ง์ ์ ์๋ค. ๋ง์ฝ mousemove
์ด๋ฒคํธ๋ฅผ ์ทจ์ํ๋ ๊ฒ์ผ๋ก ์ ๊ธฐ๋ฅ์ ๋ง์๋ค๋ฉด selectstart
์ dragstart
์ด๋ฒคํธ๋ฅผ ์ทจ์ํ๋ ๊ฒ์ผ๋ก ๋ณ๊ฒฝํด์ผ ํ๋ค.
element.addEventListener('selectstart', (event) => {
event.preventDefault();
});
element.addEventListener('dragstart', (event) => {
event.preventDefault();
});
์ด ํญ๋ชฉ์ ๋ํ ์ฃผ์ ๋ธ๋ผ์ฐ์ ๋ฐ ์น ๊ฐ๋ฐ์์ ์๊ฒฌ์ ๋ค์๊ณผ ๊ฐ๋ค.
<video>
์์์ ์ผ๋ถ ์ ๋์ฌ API ์ ๊ฑฐ ๋ฏธ๋ฆฌ ๋ณด๊ธฐ๋ฅผ ์ถ๊ฐํ๋ค.
์ง๊ธ์ ๋๋ถ๋ถ์ ๋ธ๋ผ์ฐ์ ๊ฐ Element.requestFullscreen()
๋ฉ์๋๋ฅผ ์ฌ์ฉํด <video>
์์๊ฐ ์๋๋ผ๋ ํน์ ์์๋ฅผ ์ ์ฒด ํ๋ฉด์ผ๋ก ๋์ธ ์ ์๋ค. ์ ๊ฑฐ ์์ ์ธ API๋ ๊ณผ๊ฑฐ <video>
์์์ ์ ์ฒด ํ๋ฉด ์ง์์ ์ํด ์ ๋์ฌ(webkit
)๋ฅผ ๋ถ์ฌ ์ถ๊ฐํ๋ ์์ฑ์ผ๋ก, Chrome 38 ๋ฒ์ ๋ถํฐ ์ง์ ์ค๋จ๋์๋ค.
Chrome๋ 71 ๋ฒ์ ๋ถํฐ Fullscreen API๋ฅผ ์ง์ํ๋ ์์ง ์ด์ API๋ฅผ ์ฌ์ฉํ๋ค๋ฉด ํด๋น API๋ก ์ ํํด์ผ ํ๋ค. ์ด๋ฒ์ ์ ๊ฑฐ๋๋ ์์ฑ์ ๋ค์๊ณผ ๊ฐ๋ค.
- boolean webkitSupportsFullscreen;
- boolean webkitDisplayingFullscreen;
- void webkitEnterFullscreen();
- void webkitExitFullscreen();
- void webkitEnterFullScreen();
- void webkitExitFullScreen();
์ด ํญ๋ชฉ์ ๋ํ ์ฃผ์ ๋ธ๋ผ์ฐ์ ๋ฐ ์น ๊ฐ๋ฐ์์ ์๊ฒฌ์ ๋ค์๊ณผ ๊ฐ๋ค.
ํด๋๋ธ ๊ธฐ๊ธฐ์ ์ ์/ํผ์นจ ์ํ๋ฅผ ์ ์ ์๋ Device Posture API์ ํ๋ฉด ๋ถํ ๊ฐ์์ ๋ฐ๋ฅธ ๋ฏธ๋์ด ์ฟผ๋ฆฌ๋ฅผ ์์ฑํ ์ ์๋ Viewport Segments Enumeration API์ ์ถ์ฒ ๋ฏธ๋ฆฌ ๋ณด๊ธฐ๋ฅผ ์ถ๊ฐํ๋ค. ๋ API๋ฅผ ์ฌ์ฉํ๋ฉด ํด๋๋ธ ๊ธฐ๊ธฐ์ ์ํ์ ๋ฐ๋ผ ๋ง์ถค UI๋ฅผ ๊ตฌ์ฑํ ์ ์๋ค.
navigator.devicePosture.addEventListener('change', () => {
const postureType = navigator.devicePosture.type; // "continuous" ๋๋ "folded"
console.log(postureType === 'folded' ? '์ ํ' : 'ํผ์นจ');
});
@media (device-posture: folded) and (horizontal-viewport-segments: 2) and (vertical-viewport-segments: 1) {
.main {
grid-template-columns: repeat(3, 200px);
}
}
horizontal-viewport-segments
์ vertical-viewport-segments
๋ ๊ฐ๊ฐ ํ์ง ๋ฑ์ผ๋ก ๋๋์ด์ง ๊ฐ๋ก, ์ธ๋ก ๋
ผ๋ฆฌ์ ํ๋ฉด์ ๊ฐ์๋ฅผ ๋งํ๋ค. ์ด API๋ ์์ง ์ด์์ผ๋ก ๋ช
์ธ๊ฐ ๋ฐ๋ ์ ์์ผ๋ ์ฐธ๊ณ ํ์.
์ด ํญ๋ชฉ์ ๋ํ ์ฃผ์ ๋ธ๋ผ์ฐ์ ๋ฐ ์น ๊ฐ๋ฐ์์ ์๊ฒฌ์ ๋ค์๊ณผ ๊ฐ๋ค.
Chrome 125๋ 2024๋ 5์ 8์ผ์ ์ ์ ๋ฐฐํฌ ์์ ์ด๋ค.
MutationEvent
๋ DOM ์์์ ๋ณ๊ฒฝ์ด ์์ ๋ ๋ฐ์ํ๋ ์ด๋ฒคํธ๋ก, DOMSubtreeModified
, DOMNodeInserted
, DOMNodeRemoved
๋ฑ์ด ์๋ค.
element.addEventListener(
'DOMNodeInserted',
(event) => {
console.log('์๋ก์ด ์์๋ฅผ ๊ฐ์งํ์ต๋๋ค.', event.srcElement);
},
false
);
element.append(document.createElement('span'));
// ์๋ก์ด ์์๋ฅผ ๊ฐ์งํ์ต๋๋ค. <span></span>
์ด ์ด๋ฒคํธ๋ฅผ ์ฌ์ฉํ๋ฉด DOM ๋ณ๊ฒฝ์ ์ถ์ ํ ์ ์์ผ๋, ์ค๊ณ์ ๊ฒฐํจ๊ณผ ์น๋ช ์ ์ธ ์ฑ๋ฅ ๋ฌธ์ ๋ฅผ ์ด์ ๋ก 2011๋ ์ ํ์ค์์ ํด์ถ๋์๋ค.
2012๋
์ ๋ ์์ ์ ์ธ MutationObserver
๊ฐ ๋ฑ์ฅํ์ฌ ํ์ฌ ๋๋ถ๋ถ์ ๋ธ๋ผ์ฐ์ ์์ ์ด ์ด๋ฒคํธ ์ฌ์ฉ ์ ์ฝ์์ ๊ฒฝ๊ณ ๋ฅผ ๋
ธ์ถํ๋ค. ์
๋ฐ์ดํธ ํ์๋ ํด๋น ์ด๋ฒคํธ๋ฅผ ์ฌ์ฉํ ์ ์๊ธฐ์ ์์ง MutationEvent
๋ฅผ ์ฌ์ฉํ๋ค๋ฉด MutationObserver
๋ก ์ ํํด์ผ ํ๋ค.
const observer = new MutationObserver((mutations) => {
for (const mutation of mutations) {
if (mutation.type === 'childList') {
console.log('์๋ก์ด ์์๋ฅผ ๊ฐ์งํ์ต๋๋ค.', ...mutation.addedNodes);
}
}
});
observer.observe(element, { childList: true });
element.append(document.createElement('span'));
// ์๋ก์ด ์์๋ฅผ ๊ฐ์งํ์ต๋๋ค. <span></span>
๋ง์ฝ ์ข ๋ MutationEvent
๋ฅผ ์ฌ์ฉํด์ผ๋ง ํ๋ค๋ฉด ์ถ์ฒ ๋ฏธ๋ฆฌ ๋ณด๊ธฐ๋ฅผ ์ ์ฒญํด Chrome 134(2025๋
3์ ๋ฐฐํฌ ์์ ) ๋ฒ์ ๊น์ง MutationEvent
๋ฅผ ์ฌ์ฉํ ์ ์๋ค.
์ด ํญ๋ชฉ์ ๋ํ ์ฃผ์ ๋ธ๋ผ์ฐ์ ๋ฐ ์น ๊ฐ๋ฐ์์ ์๊ฒฌ์ ๋ค์๊ณผ ๊ฐ๋ค.
๊ณต๊ฐ ์น ์ฌ์ดํธ์์ ๋ด๋ถ๋ง์ ํ์ ์์(.js
, .css
๋ฑ) ์์ฒญ ์ ๋ณด์ ์ปจํ
์คํธ(TLS)๋ฅผ ์ฌ์ฉํ๋๋ก ๊ฐ์ ํ๋ค. Private Network Access ์ ์ฉ์ ์ผ๋ถ๋ก, ์ฌ์ฉ์์ ๊ธฐ๊ธฐ ๋ฑ ๋ด๋ถ๋ง์ ์์์ ์์ฒญํ ๋๋ ๋ฐ๋์ ๋ณด์ ์ฐ๊ฒฐ์ ์ฌ์ฉํด์ผ ํ๋ค.
๋ธ๋ผ์ฐ์ ๋ ์์ฒญ์ ๋ณด๋ด๊ธฐ ์ฐ๊ฒฐ์ด ๋ณด์ ์ปจํ ์คํธ๋ฅผ ์ฌ์ฉํ๋์ง ํ์ธํ๋ฉฐ, ๋ณด์ ์ฐ๊ฒฐ์ ์ฌ์ฉํ์ง ์๋ ๊ฒฝ์ฐ ์์ฒญ์ ์คํจ ์ฒ๋ฆฌํ๋ค. Chrome 124์์ ํด๋น ๊ท์น์ ์ํ ๊ธฐ๋ฅ์ ํด๋น ๊ท์น์ ์ํ ๊ธฐ๋ฅ์ ์ ์ฉํ์ผ๋ ์ฐธ๊ณ ํ์.
์ด ํญ๋ชฉ์ ๋ํ ์ฃผ์ ๋ธ๋ผ์ฐ์ ๋ฐ ์น ๊ฐ๋ฐ์์ ์๊ฒฌ์ ๋ค์๊ณผ ๊ฐ๋ค.
Chrome 127๋ 2024๋ 7์ 17์ผ์ ์ ์ ๋ฐฐํฌ ์์ ์ด๋ค.
DOMParser.parseFromString()
์ includeShadowRoots
์ต์
์ง์ ์ค๋จDOMParser.parseFromString()
์ includeShadowRoots
์ต์
์ ์ง์ ์ค๋จํ๋ค. DOMParser.parseFromString()
์ ์ธ์๋ก ๋ฐ์ HTML์ ํ์ฑํด ๋ฌธ์ ๊ฐ์ฒด๋ฅผ ๋ฐํํ๋ ๋ฉ์๋์ด๋ค.
const html = `
<div>
<template shadowroot="open"></template>
</div>
`;
const fragment = new DOMParser().parseFromString(html, 'text/html', {
includeShadowRoots: true,
});
// output: <html>...<template>#document-fragment</template>...</html>
์ด ShadowRoot API์ ์ด์์์๋ includeShadowRoots
๋ฅผ ์ฌ์ฉํด ์๋ DOM์ ํฌํจ ์ฌ๋ถ๋ฅผ ๊ฒฐ์ ํ๋ค. ํ์ง๋ง ๋
ผ์ ๊ณผ์ ์์ ๋ช
์ธ๊ฐ ๋ฐ๋์ด parseHTMLUnsafe()
๋ฅผ ์ฐ๋ ๊ฒ์ผ๋ก ๋ณ๊ฒฝํ๋ค.
์ด๋ฒ ์
๋ฐ์ดํธ์์๋ includeShadowRoots
์ต์
์ ๊ฑฐ ๋ฏธ๋ฆฌ ๋ณด๊ธฐ๋ฅผ ์ถ๊ฐํ๋ค. ๋ง์ฝ ์ด ์ต์
์ ์ฌ์ฉํ๋ค๋ฉด ์
๋ฐ์ดํธ ์ ์ Chrome 124์์ ๋ฐฐํฌ๋ parseHTMLUnsafe()
๋ฅผ ์ฌ์ฉํ๋๋ก ๋ณ๊ฒฝํด์ผ ํ๋ค.
์ด ํญ๋ชฉ์ ๋ํ ์ฃผ์ ๋ธ๋ผ์ฐ์ ๋ฐ ์น ๊ฐ๋ฐ์์ ์๊ฒฌ์ ๋ค์๊ณผ ๊ฐ๋ค.
๋ด๋ถ๋ง ๋ณด์ ๊ฐํ๋ฅผ ์ํ ๋ด๋ถ๋ง ์ ๊ทผ ๊ท์น(Private Network Access)์ ์ผ๋ถ๋ฅผ ์ ์ฉํ๋ค. ๋ด๋ถ๋ง์์ ํ์ด์ง ์ด๋ ์ ์ ๊ทผ ๊ฐ๋ฅ ์ฌ๋ถ๋ฅผ ๋จผ์ ํ์ธํ๋ฉฐ, ์คํจ ์ ๊ฐ๋ฐ์ ๋๊ตฌ์ ๊ฒฝ๊ณ ๋ฅผ ๋ ธ์ถํ๋ค.
๋ด๋ถ๋ง์์ ํ์ด์ง๋ฅผ ์ด๋ํ๋ฉด ๋ธ๋ผ์ฐ์ ๋ ์๋ฒ๊ฐ ๋ณด์ ์ปจํ ์คํธ๋ฅผ ์ฌ์ฉํ๋์ง ํ์ธํ๋ค. ๊ทธ๋ฆฌ๊ณ ์ฌ์ ์์ฒญ์ ๋ณด๋ด ์๋ฒ๊ฐ ๋ด๋ถ๋ง ์ ๊ทผ์ ํ์ฉํ๋์ง ํ์ธํ๋ค.
Access-Control-Allow-Origin: https://example.com
Access-Control-Allow-Methods: GET
Access-Control-Allow-Credentials: true
Access-Control-Allow-Private-Network: true
์๋ฒ๊ฐ ์์ฒ๋ผ ์ ๊ทผ์ ํ์ฉํ๋ฉด ์์ฒญ์ ๊ทธ๋๋ก ์งํํ๋ฉฐ, ๊ฑฐ๋ถํ๋ฉด ๊ฐ๋ฐ์ ๋๊ตฌ์ ๊ฒฝ๊ณ ๋ฅผ ๋
ธ์ถํ๋ค. Chrome์ ๋ณด์ ๊ฐํ๋ฅผ ์ํด ์ ์ฐจ ๊ฒฝ๊ณ ๋
ธ์ถ ๋์ ์์ฒญ์ ์คํจ ์ฒ๋ฆฌํ ์์ ์ด๋ค. Chrome 104์์ ํ์ ๋ฆฌ์์ค(.js
, .css
๋ฑ)์๋ ๋ฏธ๋ฆฌ ๊ฐ์ ๊ธฐ๋ฅ์ ์ ์ฉํ์ผ๋ ์ฐธ๊ณ ํ์.
์ด ํญ๋ชฉ์ ๋ํ ์ฃผ์ ๋ธ๋ผ์ฐ์ ๋ฐ ์น ๊ฐ๋ฐ์์ ์๊ฒฌ์ ๋ค์๊ณผ ๊ฐ๋ค.
unload
์ด๋ฒคํธ๋ ํด๋น ๋ฌธ์๋ฅผ ๋ ๋๊ฑฐ๋ ํ์ ์์(.js
, .css
๋ฑ)์ ๋ฉ๋ชจ๋ฆฌ์์ ํด์ ํ ๋ ๋ฐ์ํ๋ ์ด๋ฒคํธ๋ค. ์ด ์ด๋ฒคํธ๋ฅผ ์ด์ฉํ๋ฉด ์ฌ์ฉ์๊ฐ ํ์ด์ง๋ฅผ ์ข
๋ฃํ๊ธฐ ์ ์ ํน์ ๋์์ ์คํํ ์ ์๋ค.
window.addEventListener('unload', (event) => {
navigator.sendBeacon('/api/log', 'unload!');
});
ํ์ง๋ง unload
์ด๋ฒคํธ๋ beforeunload
์ด๋ฒคํธ์ ๋ฌ๋ฆฌ ๊ธฐ๋ณธ ๋์์ ์ทจ์(event.preventDefault()
)ํ ์ ์์ด ์ด๋ฒคํธ๊ฐ ์์ ๋ฐ์ํ์ง ์๊ธฐ๋ ํ๋ค. W3C์ ํต๊ณ์ ๋ฐ๋ฅด๋ฉด unload
์ด๋ฒคํธ๊ฐ ์ ๋๋ก ๋ฐ์ํ๋ ๊ฒฝ์ฐ๊ฐ Chrome ๋ฐ์คํฌํ์์๋ 95%, ๋ชจ๋ฐ์ผ์์๋ 57% ~ 68% ์ ๋๋ก, ์ด๋ ๋ชจ๋ฐ์ผ ํ๊ฒฝ์ ํน์ฑ์ ๋ธ๋ผ์ฐ์ ๊ฐ ์๋ ๋ค๋ฅธ ์ฑ์ ๋ณด๋ค๊ฐ ๋ธ๋ผ์ฐ์ ์์ฒด๋ฅผ ์ข
๋ฃํ๋ ๊ฒฝ์ฐ๊ฐ ๋น๋ฒํ๊ธฐ ๋๋ฌธ์ด๋ค.
์ํฅ๋๊ฐ ํฐ ๋งํผ, Chrome์ ์ ์ง์ ์ผ๋ก unload
์ด๋ฒคํธ๋ฅผ ์ ๊ฑฐํ ๊ณํ์ด๋ค. ๋จผ์ Permissions-Policy
๋ฅผ ์ถ๊ฐํด ์ฌ์ฉ ์ฌ๋ถ ํ์ฉ์ ๊ฒฐ์ ํ ์ ์๋๋ก ํ๊ณ , ๋์ค์๋ ํด๋น ๊ถํ์ ๊ธฐ๋ณธ ๊ฐ์ deny
๋ก ๋ฐ๊ฟ ์์ ์ด๋ค. Chrome 117์์ unload
์ด๋ฒคํธ์ ํ์ฉ ์ฌ๋ถ๋ฅผ ๊ฒฐ์ ํ๋ Permissions-Policy
์ ๋ฏธ๋ฆฌ ๋ณด๊ธฐ๋ฅผ ์ถ๊ฐํ์ผ๋ ์ฐธ๊ณ ํ์.
์ด ํญ๋ชฉ์ ๋ํ ์ฃผ์ ๋ธ๋ผ์ฐ์ ๋ฐ ์น ๊ฐ๋ฐ์์ ์๊ฒฌ์ ๋ค์๊ณผ ๊ฐ๋ค.
CSS์ ์ฌ์ฉ์ ์ง์ ์ํ ๋ฌธ๋ฒ์ ์ฌ์ฉ์๊ฐ ์ง์ ์์์ ์ํ๋ฅผ ์ง์ ํด ๊ฐ์ ์ ํ์ ๋ฑ์ผ๋ก ์ฌ์ฉํ ์ ์๋ ๊ธฐ๋ฅ์ผ๋ก, ์์ง ํ์ค์ด ์๋ ์ด์ ์ํ๋ค.
// MyCustomElement.js
class MyCustomElement extends HTMLElement {
set checked(flag) {
if (flag) {
this._internals.states.add('--foo');
} else {
this._internals.states.delete('--foo');
}
console.log(this._internals.states.has('--foo'));
}
}
/* styles.css */
.custom:--foo {
color: red;
}
๊ธฐ์กด์๋ ์์์ฒ๋ผ --foo
์ ๊ฐ์ ๋ฌธ๋ฒ์ ์ฌ์ฉํ์ผ๋, ์ด ๋ฌธ๋ฒ์ด ํ์ฌ ์ ์ ์ค์ธ @custom-selectors
๋ฌธ๋ฒ๊ณผ ๊ฒน์ณ :state(foo)
๋ก ๋ช
์ธ๋ฅผ ๋ณ๊ฒฝํ๋ค.
/* styles.css */
.custom:state(foo) {
color: red;
}
ํ์ฌ Chrome 90์์ ๊ตฌํํ ๊ตฌํ ๋ฌธ๋ฒ์ ์ฌ์ฉํ๋ ์ฌ์ดํธ๋ ์ฝ 0.03%์ผ๋ก, Chrome ํ์ ๊ตฌํ ๋ฌธ๋ฒ์ ์ง์ ์ค๋จํ๊ธฐ ์ํ ์ผ์ ์ ๋ ผ์ ์ค์ด๋ค. Chrome 122 ์ด์์์ ๋ฏธ๋ฆฌ ๋ณด๊ธฐ๋ก ์ ๋ฌธ๋ฒ์ ์ฌ์ฉํด ๋ณผ ์ ์์ผ๋ ์ฐธ๊ณ ํ์.
์ด ํญ๋ชฉ์ ๋ํ ์ฃผ์ ๋ธ๋ผ์ฐ์ ๋ฐ ์น ๊ฐ๋ฐ์์ ์๊ฒฌ์ ๋ค์๊ณผ ๊ฐ๋ค.
์ 3์ ์ฟ ํค(Third-Party Cookies)๋ ํ์ฌ ๋๋ฉ์ธ๊ณผ ๋ค๋ฅธ ๋๋ฉ์ธ์์ ๋ฐํํ ์ฟ ํค๋ฅผ ๋งํ๋ค. ์ 3์ ์ฟ ํค๋ฅผ ํ์ฉํ๋ฉด ์ฌ์ฉ์์ ์ทจํฅ, ํ๋ ์ ๋ณด ๋ฑ์ ํ์ ํ ์ ์์ด ๊ด๊ณ ๋ง์ผํ ๋ถ์ผ์์ ์์ฃผ ์ฌ์ฉํ์ผ๋, ์ํ์ง ์๋ ๊ฐ์ธ์ ๋ณด๊น์ง ์ ์ถ๋ ์ ์๋ค๋ ํฐ ๋จ์ ์ด ์๋ค.
ํฌ๋กฌ์ ์ด๋ฏธ ๊ด๋ จ ๋ฒ ์ค์๋ฅผ ์ํด 2020๋ 1์๋ถํฐ ์ 3์ ์ฟ ํค์ ์ค๋จ์ ์๊ณ ํ๋ค. ๊ทธ๋์ ์ํฅ์ด ํฐ ์ ์ ๊ณ ๋ คํด ์ง์ ์ค๋จ์ ๊ณ์ ๋ฏธ๋ค์์ผ๋, 2024๋ ์๋ ์ ์ง์ ์ผ๋ก ์ 3์ ์ฟ ํค๋ฅผ ์ง์ ์ค๋จํ ์์ ์ด๋ค.
๊ตฌ๊ธ์ด ๋์์ผ๋ก ์ฌ์ฉ์์ ๊ฐ์ธ์ ๋ณด๋ ๋ณดํธํ๋, ๊ธฐ์กด์ ์ 3์ ์ฟ ํค๊ฐ ํ๋ ์ญํ ์ ๋์ฒดํ ์ ์๋ Privacy Sandbox๋ฅผ ๊ฐ๋ฐ ์ค์ด๋ ์ฐธ๊ณ ํ์.
์ด ํญ๋ชฉ์ ๋ํ ์ฃผ์ ๋ธ๋ผ์ฐ์ ๋ฐ ์น ๊ฐ๋ฐ์์ ์๊ฒฌ์ ๋ค์๊ณผ ๊ฐ๋ค.