🌐
Browser
⚙️
Settings
🗒️
Notes
📁
Files
⊞ Start
□ Desktop
🌐
Theme
🌐
⚙️
🗒️
📁
💬
🛍️
`; // Because script tags are stripped above, we add it in a tolerant way: html += injection; return html; } function safeHead() { return ``; } function absolutizeURL(u, baseURL) { try { // data:, mailto:, javascript: remain untouched if (/^(data:|mailto:|tel:|javascript:)/i.test(u)) return u; const url = new URL(u, baseURL); return url.toString(); } catch(e){ return u; } } function proxyURL(target) { if (!state.proxyBase) return target; // fallback: direct (may fail due to CORS/CSP) // If proxy expects path-append style: proxyBase + target return state.proxyBase.replace(/\/+$/,'/') + target; } async function fetchThroughProxy(target) { const prox = proxyURL(target); const res = await fetch(prox, { mode: 'cors' }); if (!res.ok) throw new Error(`HTTP ${res.status} ${res.statusText}`); const ct = res.headers.get('content-type') || ''; if (/text\/html|application\/xhtml\+xml/i.test(ct)) { return await res.text(); } else if (/text\/plain/i.test(ct)) { return await res.text(); } else { // If non-HTML (e.g., image), show a minimal viewer const blob = await res.blob(); const url = URL.createObjectURL(blob); return `Resource`; } } back.addEventListener('click', ()=>{ const s = sessions.find(x=>x.id===activeId); if (!s) return; if (s.index>0){ s.index--; navigateTo(s.history[s.index]); } }); fwd.addEventListener('click', ()=>{ const s = sessions.find(x=>x.id===activeId); if (!s) return; if (s.index < s.history.length-1){ s.index++; navigateTo(s.history[s.index]); } }); reload.addEventListener('click', ()=>{ const s = sessions.find(x=>x.id===activeId); if (!s) return; if (s.history[s.index]) navigateTo(s.history[s.index]); }); go.addEventListener('click', ()=> navigateTo(url.value.trim())); url.addEventListener('keydown', (e)=>{ if (e.key==='Enter') navigateTo(url.value.trim()); }); searchSel.addEventListener('change', ()=> state.searchEngine = searchSel.value); window.addEventListener('message', (ev)=>{ if (ev.data && ev.data.type==='webos-open') addTab(ev.data.url); }); // Start with one tab addTab('https://example.org'); }, { width: 980, height: 640 }); } /* ---------------------- Settings app ---------------------- */ function openSettings() { createWindow('Settings', (content, win)=>{ const pane = document.createElement('div'); pane.className='pane'; const net = document.createElement('div'); net.className='group'; net.innerHTML = `

Network

例: https://your-proxy/https://example.org のようにターゲットURLを末尾に連結する形式を想定しています。
`; const personalize = document.createElement('div'); personalize.className='group'; personalize.innerHTML = `

Personalization

`; const search = document.createElement('div'); search.className='group'; search.innerHTML = `

Search

`; pane.append(net, personalize, search); content.append(pane); const proxyBase = pane.querySelector('#proxyBase'); const saveProxy = pane.querySelector('#saveProxy'); proxyBase.value = state.proxyBase; saveProxy.addEventListener('click', ()=>{ state.proxyBase = proxyBase.value.trim(); localStorage.setItem('webos_proxy', state.proxyBase); toast('プロキシ設定を保存しました'); }); const themeSel = pane.querySelector('#themeSel'); themeSel.value = document.body.getAttribute('data-theme') || 'dark'; pane.querySelector('#applyTheme').addEventListener('click', ()=>{ document.body.setAttribute('data-theme', themeSel.value); localStorage.setItem('webos_theme', themeSel.value); toast('テーマを適用しました'); }); const wallSel = pane.querySelector('#wallSel'); pane.querySelector('#applyWall').addEventListener('click', ()=>{ wallpaperEl.className = 'wallpaper ' + (wallSel.value==='default' ? '' : wallSel.value); localStorage.setItem('webos_wall', wallSel.value); toast('壁紙を適用しました'); }); const searchSel = pane.querySelector('#searchEngine'); searchSel.value = state.searchEngine; pane.querySelector('#applySearch').addEventListener('click', ()=>{ state.searchEngine = searchSel.value; localStorage.setItem('webos_search', state.searchEngine); toast('検索エンジンを適用しました'); }); }, { width: 640, height: 420 }); } /* ---------------------- Notes app ---------------------- */ function openNotes() { createWindow('Notes', (content)=>{ const ta = document.createElement('textarea'); ta.className='note'; ta.placeholder='ここにメモ…'; ta.value = localStorage.getItem('webos_notes') || ''; ta.addEventListener('input', ()=> localStorage.setItem('webos_notes', ta.value)); content.append(ta); }, { width: 600, height: 480 }); } /* ---------------------- Files app (local storage viewer) ---------------------- */ function openFiles() { createWindow('Files', (content)=>{ const pane = document.createElement('div'); pane.className='pane'; const grp = document.createElement('div'); grp.className='group'; grp.innerHTML = `

Local storage

`; const list = document.createElement('div'); list.style.maxHeight='60vh'; list.style.overflow='auto'; for (let i=0; i{ localStorage.removeItem(key); row.remove(); }); row.append(k, v, del); list.append(row); } grp.append(list); pane.append(grp); content.append(pane); }, { width: 720, height: 520 }); } /* ---------------------- About app ---------------------- */ function openAbout() { createWindow('About', (content)=>{ const pane = document.createElement('div'); pane.className='pane'; const grp = document.createElement('div'); grp.className='group'; grp.innerHTML = `

webOS について

これはHTML/CSS/JavaScriptだけで再現したWindows 11風のWebデスクトップです。CORSプロキシ経由での表示に対応し、タブブラウズ・履歴・検索切替・テーマ・壁紙を備えています。

完全互換のブラウザではありません。サイトのCSPやX-Frame-Optionsなどにより、表示できないページや挙動差があります。

`; pane.append(grp); content.append(pane); }, { width: 560, height: 360 }); } /* ---------------------- Startup preferences ---------------------- */ (function bootstrap(){ const savedProxy = localStorage.getItem('webos_proxy'); if (savedProxy) state.proxyBase = savedProxy; const savedTheme = localStorage.getItem('webos_theme'); if (savedTheme) document.body.setAttribute('data-theme', savedTheme); const savedWall = localStorage.getItem('webos_wall'); if (savedWall) wallpaperEl.className = 'wallpaper ' + (savedWall==='default'?'':savedWall); const savedSearch = localStorage.getItem('webos_search'); if (savedSearch) state.searchEngine = savedSearch; })(); /* ---------------------- Start search quick launcher ---------------------- */ document.getElementById('startSearch').addEventListener('keydown', (e)=>{ if (e.key==='Enter') { const q = e.target.value.trim(); if (!q) return; openBrowser(); // Delay to allow window creation setTimeout(()=>{ window.postMessage({ type:'webos-open', url: q }, '*'); }, 50); startMenu.classList.remove('show'); } });