179 lines
4.2 KiB
TypeScript
179 lines
4.2 KiB
TypeScript
////
|
|
// Our tiny browser, only for NOSE apps.
|
|
// Check /public/browser-nav.js for the 2nd part.
|
|
|
|
import { $, $$ } from "./dom"
|
|
import { focusInput } from "./focus"
|
|
import { scrollback } from "./dom"
|
|
import { addInput } from "./scrollback"
|
|
import { randomId } from "../shared/utils"
|
|
|
|
const HEIGHT = 540
|
|
const WIDTH = 960
|
|
|
|
const controls = $("browser-controls") as HTMLDivElement
|
|
const address = $("browser-address") as HTMLSpanElement
|
|
let iframe: HTMLIFrameElement
|
|
let realUrl = ""
|
|
let showInput = true
|
|
|
|
export function isBrowsing(): boolean {
|
|
return document.querySelector("iframe.browser.active") !== null
|
|
}
|
|
|
|
export function openBrowser(url: string, openedVia: "click" | "command" = "click") {
|
|
showInput = openedVia === "click"
|
|
|
|
iframe = $$("iframe.browser.active") as HTMLIFrameElement
|
|
iframe.src = url
|
|
iframe.sandbox.add("allow-scripts", "allow-same-origin", "allow-forms")
|
|
iframe.height = String(HEIGHT)
|
|
iframe.width = String(WIDTH)
|
|
iframe.tabIndex = 0
|
|
|
|
window.addEventListener("message", handleAppMessage)
|
|
window.addEventListener("keydown", handleBrowserKeydown)
|
|
controls.addEventListener("click", handleClick)
|
|
iframe.addEventListener("load", handlePageLoad)
|
|
|
|
controls.style.display = ""
|
|
const main = document.querySelector("#content")
|
|
main?.prepend(iframe)
|
|
|
|
setAddress(url)
|
|
}
|
|
|
|
function closeBrowser() {
|
|
window.removeEventListener("keydown", handleBrowserKeydown)
|
|
window.removeEventListener("message", handleAppMessage)
|
|
controls.removeEventListener("click", handleClick)
|
|
iframe.removeEventListener("load", handlePageLoad)
|
|
|
|
const id = randomId()
|
|
if (showInput)
|
|
addInput(id, "browse " + realUrl, "ok")
|
|
|
|
iframe.height = String(HEIGHT / 2)
|
|
iframe.width = String(WIDTH / 2)
|
|
iframe.style.pointerEvents = "none"
|
|
iframe.tabIndex = -1
|
|
iframe.classList.remove("fullscreen", "active")
|
|
iframe.style.border = "2px solid var(--c64-light-blue)"
|
|
scrollback.append(iframe)
|
|
|
|
controls.style.display = "none"
|
|
focusInput()
|
|
}
|
|
|
|
function handleAppMessage(event: MessageEvent) {
|
|
const origin = event.origin
|
|
if (!origin.includes('localhost') && !origin.match(/\.local$/)) {
|
|
return
|
|
}
|
|
|
|
const { type, data } = event.data
|
|
|
|
switch (type) {
|
|
case 'NAV_READY':
|
|
break
|
|
|
|
case 'URL_CHANGED':
|
|
setAddress(data.url)
|
|
break
|
|
|
|
case 'TITLE_CHANGED':
|
|
// TODO: browser titles
|
|
console.log('Page title:', data.title)
|
|
break
|
|
|
|
case 'NAV_BLOCKED':
|
|
showNavigationError(data.url, data.reason)
|
|
break
|
|
|
|
case 'KEYDOWN':
|
|
const keyEvent = new KeyboardEvent('keydown', {
|
|
key: data.key,
|
|
ctrlKey: data.ctrlKey,
|
|
shiftKey: data.shiftKey,
|
|
altKey: data.altKey,
|
|
metaKey: data.metaKey,
|
|
bubbles: true
|
|
})
|
|
window.dispatchEvent(keyEvent)
|
|
break
|
|
}
|
|
}
|
|
|
|
function handleBrowserKeydown(e: KeyboardEvent) {
|
|
if (e.key === "Escape" || (e.ctrlKey && e.key === "c")) {
|
|
e.preventDefault()
|
|
closeBrowser()
|
|
}
|
|
}
|
|
|
|
function handleClick(e: MouseEvent) {
|
|
const target = e.target
|
|
if (!(target instanceof HTMLElement)) return
|
|
|
|
switch (target.id) {
|
|
case "back-button":
|
|
navigateBack(); break
|
|
case "forward-button":
|
|
navigateForward(); break
|
|
case "stop-button":
|
|
stopLoading(); break
|
|
case "reload-button":
|
|
reloadBrowser(); break
|
|
case "fullscreen-button":
|
|
fullscreenBrowser(); break
|
|
case "close-button":
|
|
closeBrowser(); break
|
|
default:
|
|
return
|
|
}
|
|
|
|
e.preventDefault()
|
|
}
|
|
|
|
function handlePageLoad() {
|
|
}
|
|
|
|
function setAddress(url: string) {
|
|
realUrl = url
|
|
address.textContent = url.replace(/https?:\/\//, "")
|
|
}
|
|
|
|
function navigateBack() {
|
|
sendNavCommand('back')
|
|
}
|
|
|
|
function navigateForward() {
|
|
sendNavCommand('forward')
|
|
}
|
|
|
|
function reloadBrowser() {
|
|
sendNavCommand('reload')
|
|
}
|
|
|
|
function stopLoading() {
|
|
sendNavCommand('stop')
|
|
}
|
|
|
|
function fullscreenBrowser() {
|
|
controls.style.display = 'none'
|
|
iframe.classList.add('fullscreen')
|
|
document.body.append(iframe)
|
|
}
|
|
|
|
function sendNavCommand(action: 'back' | 'forward' | 'reload' | 'stop') {
|
|
if (!iframe.contentWindow) return
|
|
|
|
iframe.contentWindow.postMessage({
|
|
type: 'NAV_COMMAND',
|
|
action
|
|
}, '*')
|
|
}
|
|
|
|
function showNavigationError(url: string, reason: string) {
|
|
alert(`NAVIGATION BLOCKED\n\n${url}\n\n${reason}`)
|
|
} |