/** Tabs, history, bookmarks, about: pages. */ function bareSummonHome(ctx) { var env = ctx && ctx.env && typeof ctx.env === 'object' ? ctx.env : {} if (env.HOME) return String(env.HOME).replace(/\/+$/, '') return '/home/guest' } function bareSummonPaths(ctx) { var h = bareSummonHome(ctx) return { dir: h + '/.summon', config: h + '/.summon/config.json', cookies: h + '/.summon/cookies.json', bookmarks: h + '/.summon/bookmarks.json', history: h + '/.summon/history.jsonl' } } function bareSummonCreateTab(url) { return { url: url || 'about:summon', title: '', loading: false, status: 0, error: '', doc: null, layout: null, source: '', reader: false, find: '', history: { back: [], forward: [] }, scriptsBlocked: 0, stylesBlocked: 0 } } function bareSummonCreateSession(ctx, opts) { opts = opts || {} var jar = bareSummonCreateCookieJar() return { ctx: ctx, jar: jar, tabs: [bareSummonCreateTab(opts.url || 'about:summon')], current: 0, bookmarks: Array.isArray(opts.bookmarks) ? opts.bookmarks.slice() : [], config: opts.config || { javascript: true, images: 'alt', numbers: true }, cols: opts.cols || 80 } } function bareSummonCurrent(session) { return session.tabs[session.current] || session.tabs[0] } function bareSummonAboutBody(kind, session) { if (kind === 'blank') return '
' if (kind === 'bookmarks') { var items = (session.bookmarks || []) .map(function (b) { return ( 'HTTP via ctx.httpFetch. Allow/deny: BARE_OS_HTTP_ALLOWLIST / DENYLIST.
' } return ( 'Text browser for Bare OS. Page JS runs in a Bare VM (ctx.bare.bareVm) when present.
' + 'Type a URL (g) or open a bookmark. Try about:bookmarks. Toggle JS with J.
' ) } function bareSummonCollectInlineSheets(doc) { var inline = [] function collectStyle(n) { if (n && n.type === 'element' && n.name === 'style') { inline.push(bareSummonParseStylesheet(bareSummonTextContent(n))) } var ch = (n && n.children) || [] var i for (i = 0; i < ch.length; i++) collectStyle(ch[i]) } if (doc) { collectStyle(doc.head) collectStyle(doc.body) } return inline } function bareSummonFinishLayout(session, tab, url) { var sheets = (tab.inlineSheets || []).concat(tab.fetchedSheets || []) tab.sheets = sheets var layout = bareSummonLayout(tab.doc, { cols: session.cols, sheets: sheets, numbers: session.config.numbers !== false }) tab.layout = layout tab.title = layout.title || (tab.doc && tab.doc.title) || url || tab.url return tab } function bareSummonApplyDocument(session, tab, html, url) { var doc = bareSummonParseHtml(html, {}) tab.doc = doc tab.source = html tab.url = url tab.inlineSheets = bareSummonCollectInlineSheets(doc) tab.fetchedSheets = tab.fetchedSheets || [] tab.scriptsBlocked = doc.scriptsBlocked || 0 tab.js = tab.js || { ran: 0, engine: 'off', errors: [], logs: [] } bareSummonFinishLayout(session, tab, url) } async function bareSummonEnhanceDocument(session, tab, url) { if (!tab || !tab.doc) return tab var hrefs = tab.doc.stylesheets || [] var fetched = [] var i for (i = 0; i < hrefs.length && i < 4; i++) { var abs = bareSummonResolveUrl(hrefs[i], url) if (!abs || !bareSummonIsHttp(abs)) continue try { var res = await bareSummonFetch(session.ctx, abs.href, { jar: session.jar }) if (res && res.body) fetched.push(bareSummonParseStylesheet(res.body)) else tab.stylesBlocked = (tab.stylesBlocked || 0) + 1 } catch (e) { tab.stylesBlocked = (tab.stylesBlocked || 0) + 1 } } tab.fetchedSheets = fetched if (session.config && session.config.javascript !== false) { tab.js = await bareSummonRunDocumentJs(session, tab.doc, url) tab.inlineSheets = bareSummonCollectInlineSheets(tab.doc) tab.scriptsBlocked = tab.js.blocked tab.title = (tab.doc && tab.doc.title) || tab.title } else { tab.js = { ran: 0, engine: 'off', blocked: (tab.doc.scripts && tab.doc.scripts.length) || tab.scriptsBlocked || 0, errors: [], logs: [] } } bareSummonFinishLayout(session, tab, url) return tab } function bareSummonRelayout(session) { var tab = bareSummonCurrent(session) if (!tab || !tab.doc) return tab tab.layout = bareSummonLayout(tab.doc, { cols: session.cols, sheets: tab.sheets || [], numbers: session.config.numbers !== false }) return tab } async function bareSummonNavigate(session, urlStr, opts) { opts = opts || {} var tab = bareSummonCurrent(session) var resolved = bareSummonResolveUrl(urlStr, tab.url) if (!resolved || !bareSummonUrlOk(resolved)) { tab.error = 'bad URL' tab.loading = false return tab } if (!opts.replace && tab.url && tab.url !== resolved.href) { tab.history.back.push(tab.url) tab.history.forward = [] } tab.loading = true tab.error = '' tab.reader = false if (resolved.protocol === 'about:') { var kind = resolved.pathname || 'summon' if (kind.charAt(0) === ':') kind = kind.slice(1) bareSummonApplyDocument( session, tab, bareSummonAboutBody(kind, session), resolved.href ) await bareSummonEnhanceDocument(session, tab, resolved.href) tab.status = 200 tab.loading = false return tab } if (resolved.protocol === 'file:') { var ctx = session.ctx var path = resolved.pathname || '/' try { var buf = ctx.vfs && ctx.vfs.readFile ? await ctx.vfs.readFile(path) : null var text = '' if (buf) { text = ctx.b4a && typeof ctx.b4a.toString === 'function' ? ctx.b4a.toString(buf) : String(buf) } bareSummonApplyDocument( session, tab, text || '(empty file)', resolved.href ) await bareSummonEnhanceDocument(session, tab, resolved.href) tab.status = buf ? 200 : 404 } catch (e) { tab.error = (e && e.message) || String(e) tab.status = 0 } tab.loading = false return tab } var fetched = await bareSummonFetch(session.ctx, resolved.href, { jar: session.jar, method: opts.method || 'GET', body: opts.body, headers: opts.headers }) if (!fetched.ok && !fetched.body) { tab.error = fetched.error || 'fetch failed' tab.status = fetched.status || 0 tab.loading = false return tab } tab.status = fetched.status var body = fetched.body || '' var ct = (fetched.headers && fetched.headers['content-type']) || '' if (!/html/i.test(ct) && body && body.charAt(0) !== '<') { body = '' +
body.replace(/&/g, '&').replace(/'
}
bareSummonApplyDocument(session, tab, body, fetched.url || resolved.href)
await bareSummonEnhanceDocument(session, tab, fetched.url || resolved.href)
tab.loading = false
return tab
}
function bareSummonBack(session) {
var tab = bareSummonCurrent(session)
if (!tab.history.back.length) return Promise.resolve(tab)
tab.history.forward.unshift(tab.url)
var prev = tab.history.back.pop()
return bareSummonNavigate(session, prev, { replace: true })
}
function bareSummonForward(session) {
var tab = bareSummonCurrent(session)
if (!tab.history.forward.length) return Promise.resolve(tab)
var next = tab.history.forward.shift()
return bareSummonNavigate(session, next, { replace: true })
}
function bareSummonAddBookmark(session, url, title) {
session.bookmarks.push({ url: url, title: title || url })
}
function bareSummonReload(session) {
var tab = bareSummonCurrent(session)
return bareSummonNavigate(session, tab.url, { replace: true })
}
function bareSummonFollowLink(session, id) {
var tab = bareSummonCurrent(session)
var links = (tab.layout && tab.layout.links) || []
var hit = null
var i
for (i = 0; i < links.length; i++) {
if (links[i].id === id) hit = links[i]
}
if (!hit) return Promise.resolve(tab)
return bareSummonNavigate(session, hit.href)
}
function bareSummonNewTab(session, url) {
session.tabs.push(bareSummonCreateTab(url || 'about:summon'))
session.current = session.tabs.length - 1
return bareSummonCurrent(session)
}
function bareSummonCloseTab(session) {
if (session.tabs.length <= 1) {
session.tabs[0] = bareSummonCreateTab('about:summon')
session.current = 0
return session.tabs[0]
}
session.tabs.splice(session.current, 1)
if (session.current >= session.tabs.length)
session.current = session.tabs.length - 1
return bareSummonCurrent(session)
}
function bareSummonSelectTab(session, index) {
if (index < 0 || index >= session.tabs.length)
return bareSummonCurrent(session)
session.current = index
return bareSummonCurrent(session)
}
function bareSummonCycleTab(session, dir) {
var n = session.tabs.length
if (!n) return bareSummonCurrent(session)
session.current = (session.current + dir + n) % n
return bareSummonCurrent(session)
}
function bareSummonBufToString(ctx, buf) {
if (buf == null) return ''
if (typeof buf === 'string') return buf
if (ctx && ctx.b4a && typeof ctx.b4a.toString === 'function') {
return ctx.b4a.toString(buf)
}
return String(buf)
}
async function bareSummonReadJson(ctx, path) {
if (!ctx || !ctx.vfs || typeof ctx.vfs.readFile !== 'function') return null
try {
var buf = ctx.vfs.readFile(path)
if (buf && typeof buf.then === 'function') buf = await buf
var t = String(bareSummonBufToString(ctx, buf) || '').trim()
if (!t) return null
return JSON.parse(t)
} catch (e) {
return null
}
}
async function bareSummonWriteJson(ctx, path, value) {
if (!ctx || !ctx.vfs || typeof ctx.vfs.writeFile !== 'function') return false
try {
var dir = path.slice(0, path.lastIndexOf('/'))
if (dir && ctx.vfs.mkdir) {
try {
var mk = ctx.vfs.mkdir(dir, { recursive: true })
if (mk && typeof mk.then === 'function') await mk
} catch (e) {
/* ignore */
}
}
var body = JSON.stringify(value, null, 2)
var w = ctx.vfs.writeFile(path, body)
if (w && typeof w.then === 'function') await w
return true
} catch (e2) {
return false
}
}
async function bareSummonLoadPersisted(session) {
var ctx = session.ctx
var paths = bareSummonPaths(ctx)
var cfg = await bareSummonReadJson(ctx, paths.config)
if (cfg && typeof cfg === 'object') {
session.config = Object.assign({}, session.config, cfg)
}
var marks = await bareSummonReadJson(ctx, paths.bookmarks)
if (Array.isArray(marks)) session.bookmarks = marks
var cookies = await bareSummonReadJson(ctx, paths.cookies)
if (Array.isArray(cookies)) session.jar.load(cookies)
return session
}
async function bareSummonSavePersisted(session) {
var ctx = session.ctx
var paths = bareSummonPaths(ctx)
await bareSummonWriteJson(ctx, paths.bookmarks, session.bookmarks || [])
await bareSummonWriteJson(ctx, paths.cookies, session.jar.toJSON())
await bareSummonWriteJson(ctx, paths.config, session.config || {})
}