diff options
Diffstat (limited to 'appl/charon/charon.b')
| -rw-r--r-- | appl/charon/charon.b | 2171 |
1 files changed, 2171 insertions, 0 deletions
diff --git a/appl/charon/charon.b b/appl/charon/charon.b new file mode 100644 index 00000000..88ddadbe --- /dev/null +++ b/appl/charon/charon.b @@ -0,0 +1,2171 @@ +implement Charon; + +include "common.m"; +include "debug.m"; + +sys: Sys; +CU: CharonUtils; + ByteSource, MaskedImage, CImage, ImageCache, ReqInfo, Header, + ResourceState, config, max, min, X: import CU; + +D: Draw; + Point, Rect, Font, Image, Display, Screen: import D; + +S: String; +U: Url; + Parsedurl: import U; +L: Layout; + Frame, Loc, Control: import L; +I: Img; + ImageSource: import I; + +B: Build; + Item, Dimen: import B; + +E: Events; + Event: import E; + +J: Script; + +G: Gui; + +C : Ctype; + +include "sh.m"; + +# package up info related to a navigation command +GoSpec: adt { + kind: int; # GoNormal, etc. + url: ref Parsedurl; # destination (absolute) + meth: int; # HGet or HPost + body: string; # used if HPost + target: string; # name of target frame + auth: string; # optional auth info + histnode: ref HistNode; # if kind is GoHistnode + + newget: fn(kind: int, url: ref Parsedurl, target: string) : ref GoSpec; + newpost: fn(url: ref Parsedurl, body, target: string) : ref GoSpec; + newspecial: fn(kind: int, histnode: ref HistNode) : ref GoSpec; + equal: fn(a: self ref GoSpec, b: ref GoSpec) : int; +}; + +GoNormal, GoReplace, GoLink, GoHistnode, GoSettext: con iota; + +# Information about a set of frames making up the screen +DocConfig: adt { + framename: string; # nonempty, except possibly for topconfig + title: string; + initconfig: int; # true unless this is a frameset and some subframe changed + gospec: cyclic ref GoSpec; + # TODO: add current y pos and form field values + + equal: fn(a: self ref DocConfig, b: ref DocConfig) : int; + equalarray: fn(a1: array of ref DocConfig, a2: array of ref DocConfig) : int; +}; + +# Information about a particular screen configuration +HistNode: adt { + topconfig: cyclic ref DocConfig; # config of top (whole doc, or frameset root) + kidconfigs: cyclic array of ref DocConfig; # configs for kid frames (if a frameset) + preds: cyclic list of ref HistNode; # edges in (via normal navigation) + succs: cyclic list of ref HistNode; # edges out (via normal navigation) + findid : int; + findchain : cyclic list of ref HistNode; + + addedge: fn(a: self ref HistNode, b: ref HistNode, atob: int); + copy: fn(a: self ref HistNode) : ref HistNode; +}; + +History: adt { + h: array of ref HistNode; # all visited HistNodes, in LRU order + n: int; # h[0:n] is valid part of h + findid : int; + + add: fn(h: self ref History, f: ref Frame, g: ref GoSpec, navkind: int); + update: fn(h: self ref History, f: ref Frame); + find: fn(h: self ref History, k: int) : ref HistNode; + print: fn(h: self ref History); + histinfo: fn(h: self ref History) : (int, string, string, string); + findurl: fn(h: self ref History, s: string) : ref HistNode; +}; + +# Authentication strings +AuthInfo: adt { + realm: string; + credentials: string; +}; + +auths: list of ref AuthInfo = nil; + +history : ref History; +keyfocus: ref Control; +mouseover: ref B->Anchor; +mouseoverfr: ref Frame; +grabctl: ref Control; +popupctl: ref Control; + +SP : con 8; # a spacer for between controls +SP2 : con 4; # half of SP +SP3 : con 2; +pgrp := 0; +gopgrp := 0; +dbg := 0; +warn := 0; +dbgres := 0; +doscripts := 0; + +top, curframe: ref Frame; +mainwin: ref Image; +p0 := Point(0,0); + +context: ref Draw->Context; +opener: chan of string; + +sendopener(s: string) +{ + if(opener != nil){ + alt{ + opener <- = s => + ; + * => + ; + } + } +} + +hasopener(): int +{ + return opener != nil; +} + +init(ctxt: ref Draw->Context, argl: list of string) +{ + chctxt := ref Context(ctxt, argl, nil, nil, nil); + initc(chctxt); +} + +initc(ctxt: ref Context) +{ + sys = load Sys Sys->PATH; + if (ctxt == nil) + fatalerror("bad args\n"); + opener = ctxt.c; + argl := ctxt.args; + context = ctxt.ctxt; + + (retval, nil) := sys->stat("/net/tcp"); + if(retval < 0) + sys->bind("#I", "/net", sys->MREPL); + (retval, nil) = sys->stat("/net/cs"); + if(retval < 0) + startcs(); + + pgrp = sys->pctl(sys->NEWPGRP, nil); + CU = load CharonUtils CharonUtils->PATH; + if(CU == nil) + fatalerror(sys->sprint("Couldn't load %s\n", CharonUtils->PATH)); + + ech := chan of ref Event; + errpath := CU->init(load Charon SELF, CU, argl, ech, ctxt.cksrv, ctxt.ckclient); + if(errpath != "") + fatalerror(sys->sprint("Couldn't load %s\n", errpath)); + ctxt = nil; + + sys = load Sys Sys->PATH; + D = load Draw Draw->PATH; + S = load String String->PATH; + U = load Url Url->PATH; + if (U != nil) + U->init(); + E = CU->E; + L = CU->L; + I = CU->I; + B = CU->B; + J = CU->J; + G = CU->G; + C = CU->C; + + dbg = int (CU->config).dbg['d']; + warn = dbg || int (CU->config).dbg['w']; + dbgres = int (CU->config).dbg['r']; + doscripts = (CU->config).doscripts && J != nil; + if(dbg && (CU->config).dbgfile != "") { + dfile := sys->create((CU->config).dbgfile, sys->OWRITE, 8r666); + if(dfile != nil) { + sys->dup(dfile.fd, 1); + } + } + curres := ResourceState.cur(); + newres: ResourceState; + if(dbgres) { + (CU->startres).print("starting resources"); + curres = ResourceState.cur(); + } + + context = G->init(context, CU); + if(dbgres) { + newres = ResourceState.cur(); + newres.since(curres).print("difference after G->init (made screen windows)"); + curres = newres; + } + mainwin = G->mainwin; + + # L->init() was deferred until after G was inited + L->init(CU); + if(dbgres) { + newres = ResourceState.cur(); + newres.since(curres).print("difference after L->init (loaded Build, Lex)"); + curres = newres; + } + (CU->imcache).init(); + if(dbgres) { + newres = ResourceState.cur(); + newres.since(curres).print("difference after (CU->imcache).init"); + curres = newres; + } + start(); + if(J != nil) + J->frametreechanged(top); + startpage := config.starturl; + g := GoSpec.newget(GoNormal, CU->makeabsurl(startpage), "_top"); + if(dbgres) { + newres = ResourceState.cur(); + newres.since(curres).print("difference after initial configure"); + curres = newres; + } + spawn plumbwatch(); + spawn go(g); + + sendopener("B"); + +Forloop: + for(;;) { + ev := <- ech; + + if(dbg > 1) { + pick de := ev { + Emouse => + if(dbg > 2 || de.mtype != E->Mmove) + sys->print("%s\n", ev.tostring()); + * => + sys->print("%s\n", ev.tostring()); + } + } + pick e := ev { + Ekey => + g = nil; + case e.keychar { + E->Kdown => + curframe.yscroll(L->CAscrollpage, -1); + E->Kup => + curframe.yscroll(L->CAscrollpage, 1); + E->Khome => + curframe.yscroll(L->CAscrollpage, -10000); + E->Kend => + curframe.yscroll(L->CAscrollpage, 10000); + E->Kaup => + curframe.yscroll(L->CAscrollline, -1); + E->Kadown => + curframe.yscroll(L->CAscrollline, 1); + * => + handlekey(e); + } + Emouse => + g = handlemouse(e); + Ereshape => + mainwin = G->mainwin; + redraw(1); + curframe = top; + g = GoSpec.newspecial(GoHistnode, history.find(0)); + Equit => + break Forloop; + Estop => + if(gopgrp != 0) + stop(); + g = nil; + Eback => + g = GoSpec.newspecial(GoHistnode, history.find(-1)); + Efwd => + g = GoSpec.newspecial(GoHistnode, history.find(1)); + Eform => + formaction(e.frameid, e.formid, e.ftype, 0); + g = nil; + Eformfield => + formfieldaction(e.frameid, e.formid, e.fieldid, e.fftype); + g = nil; + Ego => + case e.gtype { + E->EGnormal => + url := CU->makeabsurl(e.url); + if (url != nil) + g = GoSpec.newget(GoNormal,url, e.target); + else + g = nil; + E->EGreplace => + g = GoSpec.newget(GoReplace, U->parse(e.url), e.target); + E->EGreload => + g = GoSpec.newspecial(GoHistnode, history.find(0)); + E->EGforward => + g = GoSpec.newspecial(GoHistnode, history.find(1)); + E->EGback => + g = GoSpec.newspecial(GoHistnode, history.find(-1)); + E->EGdelta => + g = GoSpec.newspecial(GoHistnode, history.find(e.delta)); + E->EGlocation => + g = GoSpec.newspecial(GoHistnode, history.findurl(e.url)); + } + Esubmit => + if(e.subkind == CU->HGet) + g = GoSpec.newget(GoNormal, e.action, e.target); + else { + g = GoSpec.newpost(e.action, e.data, e.target); + } + Escroll => + f := findframe(top, e.frameid); + if (f != nil) + f.scrollabs(e.pt); + g = nil; + Escrollr => + f := findframe(top, e.frameid); + if (f != nil) + f.scrollrel(e.pt); + g = nil; + Esettext => + f := findframe(top, e.frameid); + if (f != nil) + g = ref GoSpec (GoSettext, e.url, 0, e.text, f.name, "", nil); + Elostfocus => + setfocus(nil); + g = nil; + Edismisspopup => + if (popupctl != nil) + setfocus(popupctl.donepopup()); + popupctl = nil; + grabctl = nil; + } + + if (g == nil) + continue; + + if (g.kind != GoSettext) { + if (g.url != nil) { + scheme := g.url.scheme; + if (scheme == "javascript") { + if (doscripts) + spawn dojsurl(g); + continue; + } + if (!CU->schemeok(scheme)) { + url := g.url.tostring(); + if (plumbsend(url, "url") == -1) + G->setstatus(X("bad URL", "gui")+": "+url); + continue; + } + } + } + + if(gopgrp != 0) + stop(); + spawn go(g); + } + finish(); +} + +mkprog(c: Command, ctxt: ref Draw->Context, args: list of string) +{ + sys->pctl(Sys->NEWPGRP|Sys->NEWFD, list of {0, 1, 2}); + c->init(ctxt, args); +} + +start() +{ + top = Frame.new(); + curframe = top; + history = ref History(nil, 0, 0); + + keyfocus = nil; + mouseover = nil; + redraw(1); +} + +redraw(resized: int) +{ + im := mainwin; + if(resized) { +# top.r = im.r.inset(2*L->ReliefBd); + top.r = im.r; + top.cim = mainwin; + top.reset(); + (CU->imcache).resetlimits(); + } + im.clipr = im.r; +# L->drawrelief(im, top.r.inset(-L->ReliefBd), L->ReliefRaised); +# L->drawrelief(im, top.r, L->ReliefSunk); + L->drawfill(im, top.r, CU->White); + G->flush(im.r); +# im.clipr = top.r; +} + +# Return a Loc representing a control in the frame f +frameloc(c: ref Control, f: ref Frame) : ref Loc +{ + loc := Loc.new(); + loc.add(L->LEframe, f.r.min); + loc.le[loc.n-1].frame = f; + if (c != nil) { + loc.add(L->LEcontrol, c.r.min); + loc.le[loc.n-1].control = c; + } + return loc; +} + +resetkeyfocus(f: ref Frame) +{ + # determine if focus is in frame f or one of its sub-frames + if (keyfocus == nil) + return; + + for (focusf := keyfocus.f; focusf != nil; focusf = focusf.parent) { + if (focusf == f) { + keyfocus = nil; + break; + } + } + # current focus not in frameset being modified - leave as is +} + +ctlmouse(e: ref Event.Emouse, ctl, grab: ref Control): ref Control +{ + ev := E->SEnone; + (action, newgrab) := ctl.domouse(e.p, e.mtype, grab); + case (action) { + L->CAbuttonpush => + if(doscripts && ctl.ff != nil && ctl.ff.evmask) + ev = E->SEonclick; + else + pushaction(ctl, e.p.sub(ctl.r.min)); + L->CAkeyfocus => + setfocus(ctl); + L->CAchanged => + # Select Formfield - selection has changed + ev = E->SEonchange; + L->CAselected => + # text input Formfield - text selection has changed + ev = E->SEonselect; + L->CAdopopup => + popupctl = ctl.dopopup(); + if (popupctl != nil) + setfocus(popupctl); + L->CAdonepopup => + setfocus(ctl.donepopup()); + ev = E->SEonchange; + popupctl = nil; + } + if (doscripts && ctl.ff != nil && (ctl.ff.evmask & ev)) { + se := ref E->ScriptEvent(ev, ctl.f.id, ctl.ff.form.formid, ctl.ff.fieldid, + -1, -1, e.p.x, e.p.y, 1, nil, nil, 0); + J->jevchan <-= se; + } + return newgrab; +} + +mainwinmouse(e: ref Event.Emouse) : (ref GoSpec, ref Control) +{ + p := e.p; + g : ref GoSpec; + ctl : ref Control; + newgrab : ref Control; + domouseout := 0; + loc : ref Loc; + if(mouseover != nil) + domouseout = 1; + + loc = top.find(p, nil); + if(loc != nil) { + if(dbg > 1) + loc.print("mouse loc"); + f := loc.lastframe(); + hasscripts := f.doc.hasscripts; + if(e.mtype != E->Mmove) + curframe = f; + n1 := loc.n-1; + case loc.le[n1].kind { + L->LEitem => + it := loc.le[n1].item; + if (it.anchorid < 0) + break; + + a : ref Build->Anchor = nil; + for(al := f.doc.anchors; al != nil; al = tl al) { + a = hd al; + if(a.index == it.anchorid) + break; + } + if (al == nil) + break; + + if(dbg > 1) + sys->print("in anchor %d, href=%s\n", a.index, a.href.tostring()); + if(doscripts && a.evmask) { + if(a == mouseover) { + domouseout = 0; # still over same anchor + } else if(e.mtype == E->Mmove) { + if(domouseout) { + if(mouseover.evmask & E->SEonmouseout) { + se := ref E->ScriptEvent(E->SEonmouseout, mouseoverfr.id, -1, -1, mouseover.index, -1, 0, 0, 0, nil, nil, 0); + J->jevchan <-= se; + } + domouseout = 0; + } + mouseover = a; + mouseoverfr = f; + if(a.evmask & E->SEonmouseover) { + se := ref E->ScriptEvent(E->SEonmouseover, f.id, -1, -1, a.index, -1, e.p.x, e.p.y, 0, nil, nil, 0); + J->jevchan <-= se; + } + } + if (e.mtype == E->Mlbuttonup || e.mtype == E->Mldrop) { + if(a.evmask & E->SEonclick) { + se := ref E->ScriptEvent(E->SEonclick, f.id, -1, -1, a.index, -1, 0, 0, 0, nil, nil, 0); + J->jevchan <-= se; + break; + } + ctl = nil; + } + } + if(e.mtype == E->Mlbuttonup || e.mtype == E->Mldrop) { + g = anchorgospec(it, a, loc.pos); + if (g == nil) + break; + } else if(e.mtype == E->Mmbuttonup) { + g = anchorgospec(it, a, loc.pos); + if (g == nil) + break; + url := g.url.tostring(); + G->setstatus(url); + G->snarfput(url); + g = nil; + } + L->LEcontrol => + ctl = loc.le[n1].control; + } + } + if (ctl != nil) + newgrab = ctlmouse(e, ctl, nil); + if(newgrab == nil && domouseout && doscripts) { + if(mouseover.evmask & E->SEonmouseout) { + se := ref E->ScriptEvent(E->SEonmouseout, + mouseoverfr.id, -1, -1, mouseover.index, -1, 0, 0, 0, nil, nil, 0); + J->jevchan <-= se; + } + mouseoverfr = nil; + mouseover = nil; + } + return (g, newgrab); +} + +dojsurl(g : ref GoSpec) +{ + f := curframe; + case g.target { + "_top" => + f = top; + "_self" => + ; # curframe is already OK + "_parent" => + if(f.parent != nil) + f = f.parent; + "_blank" => + f = top; # we don't create new browsers... + * => + # this is recommended "current practice" + f = findnamedframe(f, g.target); + if(f == nil) { + f = findnamedframe(top, g.target); + if(f == nil) + f = top; + } + } + + jev := ref E->ScriptEvent (E->SEscript, f.id, -1, -1, -1, -1, 0, 0, 0, g.url.path, chan of string, 0); + J->jevchan <-= jev; + v := <- jev.reply; + if (v != nil) { + ev := ref Event.Esettext(f.id, g.url, v); + E->evchan <-= ev; + } +} + +# If mouse event results in command to navigate somewhere else, +# return a GoSpec ref, else nil. +handlemouse(e: ref Event.Emouse): ref GoSpec +{ + g: ref GoSpec; + ctl := grabctl; + if (popupctl != nil) + ctl = popupctl; + if (ctl != nil) + grabctl = ctlmouse(e, ctl, grabctl); + else if (e.p.in(mainwin.r)) + (g, grabctl) = mainwinmouse(e); + return g; +} + +setfocus(newc : ref Control) +{ + newf, oldf: ref Frame; + if (newc != nil) + newf = newc.f; + + oldc := keyfocus; + if (oldc != nil) + oldf = oldc.f; + + if (oldc != nil && oldc != newc) + oldc.losefocus(1); + if (oldf != nil && oldf != newf) + oldf.focus(0, 1); + if (newf != nil && newf != oldf) + newf.focus(1,1); + if (newc != nil && newc != oldc) + newc.gainfocus(1); + keyfocus = newc; +} + +handlekey(e: ref Event.Ekey) +{ + c := keyfocus; + if (c == nil) + return; + + pick ce := c { + Centry => + case c.dokey(e.keychar) { + L->CAreturnkey => + if(c.ff != nil) { + spawn form_submit(c.f, c.ff.form, p0, c, 1); + return; + } + L->CAtabkey => + # if control in a form - move focus to next focus-able control + if (c.ff != nil) { + found := 0; + form := c.ff.form; + nextff : ref B->Formfield; + for (ffl := form.fields; ffl != nil; ffl = tl ffl) { + ff := hd ffl; + if (ff == c.ff) { + found = 1; + continue; + } + if (ff.ftype == B->Ftext || ff.ftype == B->Fpassword) { + if (nextff == nil || found) + nextff = ff; + if (found) + break; + } + } + if (nextff != nil) + formfield_focus(c.f, nextff); + } + } + } + return; +} + +fileexist(file: string) :int +{ + fd := sys->open(file, sys->OREAD); + if (fd == nil) + return 0; + else + return 1; +} + +go(g: ref GoSpec) +{ + gopgrp = sys->pctl(sys->NEWPGRP, nil); + spawn goproc(g); + + # got to make netget the thread with the gopgrp thread, + # since it runs until killed, and killing a pgrp needs an active + # thread + CU->netget(); +} + +goproc(g: ref GoSpec) +{ + origkind := g.kind; + hn : ref HistNode = nil; + doctext := ""; + case origkind { + GoNormal or + GoReplace or + GoSettext => + ; + GoHistnode => + hn = g.histnode; + if(hn == nil) + return; + g = hn.topconfig.gospec; + } + case g.target { + "_top" => + curframe = top; + "_self" => + ; # curframe is already OK + "_parent" => + if(curframe.parent != nil) + curframe = curframe.parent; + "_blank" => + curframe = top; # we don't create new browsers... + * => + # this is recommended "current practice" + curframe = findnamedframe(curframe, g.target); + if(curframe == nil) { + curframe = findnamedframe(top, g.target); + if(curframe == nil) + curframe = top; + } + } + + f := curframe; + if(dbg) { + sys->print("\n\nGO TO %s\n", g.url.tostring()); + if(g.target != "_top") + sys->print("target frame name=%s\n", f.name); + } + G->progress <-= (-1, G->Pstart, 0, ""); + err := ""; + status := "Done"; + + if((origkind == GoNormal || origkind == GoReplace || origkind == GoLink) && g.url.frag != "" + && f.doc != nil && f.doc.src != nil && CU->urlequal(g.url, f.doc.src)) + go_local(f, g.url.frag); + else { + if (g.kind == GoSettext) + settext(g, f, g.body); + else + err = get(g, f, origkind, hn); + + if(doscripts && J->defaultStatus != "") + status = J->defaultStatus; + } + if(err != nil) { + status = err; + G->progress <-= (-1, G->Perr, 100, err); + } else + G->progress <-= (-1, G->Pdone, 0, nil); + + G->setstatus(status); + checkrefresh(f); +} + +settext(g : ref GoSpec, f : ref Frame, text : string) : string +{ + sdest := g.url.tostring(); + G->setstatus(X("Fetching", "gui") + " " + sdest); + bs := CU->stringreq(text); + G->seturl(sdest); + history.add(f, g, GoNormal); + resetkeyfocus(f); + L->layout(f, bs, 0); + if (J != nil) + J->framedone(f, f.doc.hasscripts); + history.update(f); + error := ""; + if(f.kids != nil) { + if(J != nil) + J->frametreechanged(f); + nkids := len f.kids; + kdone := chan of (ref Frame, string); + for(kl := f.kids; kl != nil; kl = tl kl) { + k := hd kl; + if(k.src != nil) { + gs := GoSpec.newget(GoNormal, k.src, "_self"); + if(dbg) + sys->print("get child frame %s\n", gs.url.tostring()); + spawn getproc(gs, k, GoNormal, nil, kdone); + } + } + while (nkids--) { + (k, e) := <- kdone; + if (error != nil) + error = e; + checkrefresh(k); + } + } + + if (J != nil) { +#this code should be split off as it is duplicated from get() + # at this point all sub-frames and images have been loaded + # Optimise this! so as only do it if a doc in the frameset + # has script/event code + J->jevchan <-= ref E->ScriptEvent(E->SEonload, f.id, -1, -1, -1, -1, -1, -1, -1, nil, nil, 0); + if (doscripts && f.doc.hasscripts) { + for(itl := f.doc.images; itl != nil; itl = tl itl) { + it := hd itl; + if(it.genattr == nil || !it.genattr.evmask) + continue; + ev := E->SEnone; + pick im := it { + Iimage => + case im.ci.complete { + # correct to equate these two ? + Img->Mimnone or + Img->Mimerror => + ev = E->SEonerror; + Img->Mimdone => + ev = E->SEonload; + } + if(im.genattr.evmask & ev) + J->jevchan <-= ref E->ScriptEvent(ev, f.id, -1, -1, -1, im.imageid, -1, -1, -1, nil, nil, 0); + } + } + } + } + return error; +} + +getproc(g: ref GoSpec, f: ref Frame, origkind: int, hn: ref HistNode, done : chan of (ref Frame, string)) +{ + done <-= (f, get(g, f, origkind, hn)); +} + +get(g: ref GoSpec, f: ref Frame, origkind: int, hn: ref HistNode) : string +{ + curres, newres: ResourceState; + if(dbgres) { + (CU->imcache).clear(); + curres = ResourceState.cur(); + } + sdest := g.url.tostring(); + G->setstatus(X("Fetching", "gui") + " " + sdest); + bsmain : ref ByteSource; + hdr : ref Header; + ri := ref ReqInfo(g.url, g.meth, array of byte g.body, g.auth, g.target); + authtried := 0; + realm := ""; + auth := ""; + error := ""; + for(nredirs := 0; ; nredirs++) { + bsmain = CU->startreq(ri); + error = bsmain.err; + if(error != "") { + CU->freebs(bsmain); + return error; + } + CU->waitreq(bsmain::nil); + error = bsmain.err; + if(error != "") { + CU->freebs(bsmain); + return error; + } + hdr = bsmain.hdr; + (use, e, challenge, newurl) := CU->hdraction(bsmain, 1, nredirs); + error = e; + if(challenge != nil) { + if(authtried) { + # we already tried once; give up + error = "Need authorization"; + use = 1; + } + else { + (realm, auth) = getauth(challenge); + if(auth != "") { + ri.auth = auth; + authtried = 1; + CU->freebs(bsmain); + continue; + } + else { + error = "Need authorization"; + use = 1; + } + } + } + if (error == nil) { + if (hdr.code != CU->HCOk) + error = CU->hcphrase(hdr.code); + if(authtried) { + # it succeeded; add to auths list so don't have to ask again + auths = ref AuthInfo(realm, auth) :: auths; + } + } + if(newurl != nil) { + ri.url = newurl; + # some sites (e.g., amazon.com) assume that POST turns into + # GET on redirect (maybe this is just http 1.0?) + ri.method = CU->HGet; + CU->freebs(bsmain); + continue; + } + if(use == 0) { + CU->freebs(bsmain); + return error; + } + break; + } + if(dbgres > 1) { + newres = ResourceState.cur(); + newres.since(curres).print("resources to get header"); + curres = newres; + } + if(hdr.mtype == CU->TextHtml || hdr.mtype == CU->TextPlain || + I->supported(hdr.mtype)) { + G->seturl(sdest); + history.add(f, g, origkind); + resetkeyfocus(f); + srcdata := L->layout(f, bsmain, origkind == GoLink); + if (J != nil) + J->framedone(f, f.doc.hasscripts); + history.update(f); + if(dbgres > 1) { + newres = ResourceState.cur(); + newres.since(curres).print("resources to get page and do layout"); + curres = newres; + } + if(f.kids != nil) { + if(J != nil) + J->frametreechanged(f); + i := 0; + nkids := len f.kids; + kdone := chan of (ref Frame, string); + for(kl := f.kids; kl != nil; kl = tl kl) { + k := hd kl; + if(k.src != nil) { + if(hn != nil) + gs := hn.kidconfigs[i].gospec; + else + gs = GoSpec.newget(GoNormal, k.src, "_self"); + if(dbg) + sys->print("get child frame %s\n", gs.url.tostring()); + gokind := GoLink; + if (origkind != GoLink) + gokind = GoNormal; + spawn getproc(gs, k, gokind, nil, kdone); + } + i++; + } + while (nkids--) { + (k, err) := <- kdone; + if (error == nil) + # we currently only capture the first error + # as we only have one palce to report it + error = err; + checkrefresh(k); + } + } + + if (J != nil) { + # at this point all sub-frames and images have been loaded + J->jevchan <-= ref E->ScriptEvent(E->SEonload, f.id, -1, -1, -1, -1, -1, -1, -1, nil, nil, 0); + if (doscripts && f.doc.hasscripts) { + for(itl := f.doc.images; itl != nil; itl = tl itl) { + it := hd itl; + if(it.genattr == nil || !it.genattr.evmask) + continue; + ev := E->SEnone; + pick im := it { + Iimage => + case im.ci.complete { + # correct to equate these two ? + Img->Mimnone or + Img->Mimerror => + ev = E->SEonerror; + Img->Mimdone => + ev = E->SEonload; + } + if(im.genattr.evmask & ev) + J->jevchan <-= ref E->ScriptEvent(ev, f.id, -1, -1, -1, im.imageid, -1, -1, -1, nil, nil, 0); + } + } + } + } + + if(g.url.frag != "") + go_local(f, g.url.frag); + } + else { + error = X("Unsupported media type", "gui")+ " "+CU->mnames[hdr.mtype]; + # Optionally put a save-as dialog up here. + if((CU->config).offersave) + dosaveas(bsmain); + CU->freebs(bsmain); + } + if(dbgres == 1) { + newres = ResourceState.cur(); + newres.since(curres).print("resources to do page"); + curres = newres; + } + return error; +} + +# Scroll frame f so that destination hyperlink loc is at top of view +go_local(f: ref Frame, loc: string) +{ + if(dbg) + sys->print("go to local destination %s\n", loc); + for(ld := f.doc.dests; ld != nil; ld = tl ld) { + d := hd ld; + if(d.name == loc) { + dloc := f.find(p0, d.item); + if(dloc == nil) { + if(warn) + sys->print("couldn't find item for destination anchor %s\n", loc); + return; + } + p := f.sptolp(dloc.le[dloc.n-1].pos); + f.yscroll(L->CAscrollabs, p.y); + return; + } + } + # special location names... + l := S->tolower(loc); + if(l == "top" || l == "home"){ + f.yscroll(L->CAscrollabs, 0); + return; + } + if(l == "end" || l=="bottom"){ + f.yscroll(L->CAscrollabs, f.totalr.max.y); + return; + } + if(warn) + sys->print("couldn't find destination anchor %s\n", loc); +} + +stripwhite(s: string) : string +{ + j := 0; + n := len s; + for(i := 0; i < n; i++) { + c := s[i]; + if(c < C->NCTYPE && C->ctype[c]==C->W) + continue; + s[j++] = c; + } + if(j < n) + s = s[0:j]; + return s; +} + +# If refresh has been set in f (i.e., client pull), +# pause the appropriate amount of time and then go to new place +checkrefresh(f: ref Frame) +{ + if(f.doc != nil && f.doc.refresh != "") { + seconds := 0; + url : ref Parsedurl = nil; + refresh := stripwhite(f.doc.refresh); + (n, l) := sys->tokenize(refresh, ";"); + if(n > 0) { + seconds = int hd l; + if(n > 1) { + s := hd tl l; + if(len s > 4 && S->tolower(s[0:4]) == "url=") { + url = U->mkabs(U->parse(s[4:]), f.doc.base); + } + } + } + spawn dorefresh(f, seconds, url); + } +} + +dorefresh(f: ref Frame, seconds: int, url: ref Parsedurl) +{ + sys->sleep(seconds * 1000); + e : ref Event; + if(url == nil) + e = ref Event.Ego(nil, f.name, 0, E->EGreload); + else + e = ref Event.Ego(url.tostring(), f.name, 0, E->EGnormal); + E->evchan <-= e; +} + +# Do depth first search from f, looking for frame with given name. +findnamedframe(f: ref Frame, name: string) : ref Frame +{ + if(f.name == name) + return f; + for(l := f.kids; l != nil; l = tl l) { + k := hd l; + a := findnamedframe(k, name); + if(a != nil) + return a; + } + return nil; +} + +# Similar, but look for frame id, starting from f +findframe(f: ref Frame, id: int) : ref Frame +{ + if(f.id == id) + return f; + for(l := f.kids; l != nil; l = tl l) { + k := hd l; + a := findframe(k, id); + if(a != nil) + return a; + } + return nil; +} + +# Return Gospec resulting from button up in anchor a, at offset pos inside item it. +anchorgospec(it: ref Item, a: ref B->Anchor, p: Point) : ref GoSpec +{ + g : ref GoSpec; + u := a.href; + target := a.target; + pick i := it { + Iimage => + ci := i.ci; + if(ci.mims != nil) { + if(i.map != nil) { + (u, target) = findhit(i.map, p, ci.width, ci.height); + } + else if(u != nil && u.scheme != "javascript" && (it.state&B->IFsmap)) { + # copy u, add ?x,y + x := min(max(p.x-(int i.hspace + int i.border),0),ci.width-1); + y := min(max(p.y-(int i.vspace + int i.border),0),ci.height-1); + u = ref *a.href; + u.query = string x + "," + string y; + } + } + Ifloat => + return anchorgospec(i.item, a, p); + } + + if(u != nil) + g = GoSpec.newget(GoLink, u, target); + return g; +} + +# Control c has been pushed. +# Find the form it is in and perform required action (reset, or submit). +pushaction(c: ref Control, pt: Point) +{ + pick b := c { + Cbutton => + ff := b.ff; + f := b.f; + if(ff != nil) { + case ff.ftype { + B->Fsubmit or B->Fimage => + spawn form_submit(c.f, ff.form, pt, c, 1); + B->Freset => + spawn form_reset(f, ff.form); + } + } + } +} + +# if onsubmit==1, then raise onsubmit event (if handler present) +form_submit(fr: ref Frame, frm: ref B->Form, p: Point, submitctl: ref Control, onsubmit: int) +{ + submitfield : ref B->Formfield; + if (submitctl != nil) + submitfield = submitctl.ff; + + if(submitctl != nil && tagof(submitctl) == tagof(Control.Centry)) { + # Via CR, so only submit if there is a submit button (first one is the default) + firstsubmit : ref B->Formfield; + for(l := frm.fields; l != nil; l = tl l) { + f := hd l; + if (f.ftype == B->Fsubmit) { + firstsubmit = f; + break; + } + } + if (firstsubmit == nil) + return; + submitfield = firstsubmit; + } + if(doscripts && fr.doc.hasscripts && onsubmit && (frm.evmask & E->SEonsubmit)) { + c := chan of string; + J->jevchan <-= ref E->ScriptEvent(E->SEonsubmit, fr.id, frm.formid, -1, -1, -1, -1, -1, -1, nil, c, 0); + if(<-c == nil) + return; + } + v := ""; + sep := ""; + radiodone : list of string = nil; +floop: + for(l := frm.fields; l != nil; l = tl l) { + f := hd l; + if(f.name == "") + continue; + val := ""; + c: ref Control; + if(f.ctlid >= 0) + c = fr.controls[f.ctlid]; + case f.ftype { + B->Ftext or B->Fpassword or B->Ftextarea => + if(c != nil) + pick e := c { + Centry => + val = e.s; + } + if(val != "" && f.name == "_ISINDEX_") { + # just the index terms after the "?" + if(sep != "") + v = v + sep; + sep = "&"; + v = v + ucvt(val); + break floop; + } + B->Fcheckbox or B->Fradio => + if(f.ftype == B->Fradio) { + # Need the following to catch case where there + # is more than one radiobutton with the same name + # and value. + for(rl := radiodone; rl != nil; rl = tl rl) + if(hd rl == f.name) + continue floop; + } + checked := 0; + if(c != nil) + pick cb := c { + Ccheckbox => + checked = cb.flags & L->CFactive; + } + if(checked) { + val = f.value; + if(f.ftype == B->Fradio) + radiodone = f.name :: radiodone; + } + else + continue; + B->Fhidden => + val = f.value; + B->Fsubmit => + if(submitctl != nil && f == submitctl.ff && f.name != "_no_name_submit_") + val = f.value; + else + continue; + B->Fselect => + if(c != nil) + pick s := c { + Cselect => + for(i := 0; i < len s.options; i++) { + if(s.options[i].selected) { + if(sep != "") + v = v + sep; + sep = "&"; + v = v + ucvt(f.name) + "=" + ucvt(s.options[i].value); + } + } + continue; + } + B->Fimage => + if(submitctl != nil && f == submitctl.ff) { + if(sep != "") + v = v + sep; + sep = "&"; + v = v + ucvt(f.name + ".x") + "=" + ucvt(string max(p.x,0)) + + sep + ucvt(f.name + ".y") + "=" + ucvt(string max(p.y,0)); + continue; + } + } +# if(val != "") { + if(sep != "") + v = v + sep; + sep = "&"; + v = v + ucvt(f.name) + "=" + ucvt(val); +# } + } + action := ref *frm.action; + if (frm.method == CU->HGet) { + if (action.query != "" && v != "") + action.query += "&"; + action.query += v; + v = ""; + } +# action.query = v; + E->evchan <-= ref Event.Esubmit(frm.method, action, v, frm.target); +} + +hexdigit := "0123456789ABCDEF"; +urlchars := array [128] of { + 'a' to 'z' => byte 1, + 'A' to 'Z' => byte 1, + '0' to '9' => byte 1, + '-' or '/' or '$' or '_' or '@' or '.' or '!' or '*' or '\'' or '(' or ')' => byte 1, + * => byte 0 +}; + +ucvt(s: string): string +{ + b := array of byte s; + u := ""; + for(i := 0; i < len b; i++) { + c := int b[i]; + if (c < len urlchars && int urlchars[c]) + u[len u] = c; + else if(c == ' ') + u[len u] = '+'; + else { + u[len u] = '%'; + u[len u] = hexdigit[(c>>4)&15]; + u[len u] = hexdigit[c&15]; + } + } + return u; +} + +form_reset(fr: ref Frame, frm: ref B->Form) +{ + if(doscripts && fr.doc.hasscripts && (frm.evmask & E->SEonreset)) { + c := chan of string; + J->jevchan <-= ref E->ScriptEvent(E->SEonreset, fr.id, frm.formid, -1, -1, -1, -1, -1, -1, nil, c, 0); + if(<-c == nil) + return; + } + for(fl := frm.fields; fl != nil; fl = tl fl) { + a := hd fl; + if(a.ctlid >= 0) + fr.controls[a.ctlid].reset(); + } +# fr.cim.flush(D->Flushnow); +} + +formaction(frameid, formid, ftype, onsubmit: int) +{ + if(dbg > 1) + sys->print("formaction %d %d %d %d\n", frameid, formid, ftype, onsubmit); + f := findframe(top, frameid); + if(f != nil) { + d := f.doc; + if(d != nil) { + for(fl := d.forms; fl != nil; fl = tl fl) { + frm := hd fl; + if(frm.formid == formid) { + if(ftype == E->EFsubmit) + spawn form_submit(f, frm, Point(0,0), nil, onsubmit); + else + spawn form_reset(f, frm); + } + } + } + } +} + +formfield_blur(f: ref Frame, ff: ref B->Formfield) +{ + if(ff.ftype != B->Fhidden) { + c := f.controls[ff.ctlid]; + if(!(c.flags & L->CFhasfocus)) + return; + # lose focus quietly - don't raise "onblur" event for the given control + c.losefocus(0); + setfocus(nil); + } +} + +formfield_focus(f: ref Frame, ff: ref B->Formfield) +{ + if(ff.ftype != B->Fhidden) { + c := f.controls[ff.ctlid]; + if(c.flags & L->CFhasfocus) + return; + # gain focus quietly - don't raise "onfocus" event for the given control + c.gainfocus(0); + setfocus(c); + } +} + +# simulate a mouse click, but don't trigger onclick event +formfield_click(f: ref Frame, frm: ref B->Form, ff: ref B->Formfield) +{ + c := f.controls[ff.ctlid]; + case ff.ftype { + B->Fcheckbox or + B->Fradio or + B->Fbutton => + c.domouse(p0, E->Mlbuttonup, nil); + B->Fsubmit => + spawn form_submit(f, frm, p0, c, 1); + B->Freset => + spawn form_reset(f, frm); + } +} + +formfield_select(f: ref Frame, ff: ref B->Formfield) +{ + case ff.ftype { + B->Ftext or + B->Fselect or + B->Ftextarea => + ctl := f.controls[ff.ctlid]; + pick c := ctl { + Centry => + c.sel = (0, len c.s); + ctl.draw(1); + } + } +} + +formfieldaction(frameid, formid, fieldid, fftype: int) +{ + if(dbg > 1) + sys->print("formfieldaction %d %d %d %d\n", frameid, formid, fieldid, fftype); + f := findframe(top, frameid); + if(f == nil || f.doc == nil) + return; + + # find form in frame + frm : ref B->Form; + for(fl := f.doc.forms; fl != nil; fl = tl fl) { + if((hd fl).formid == formid) { + frm = hd fl; + break; + } + } + if(frm == nil) + return; + + # find formfield in form + ff : ref B->Formfield; + for(ffl := frm.fields; ffl != nil; ffl = tl ffl) { + if((hd ffl).fieldid == fieldid) { + ff = hd ffl; + break; + } + } + if(ff == nil || ff.ctlid < 0) + return; + + # perform action + case fftype { + E->EFFblur => + formfield_blur(f, ff); + E->EFFfocus => + formfield_focus(f, ff); + E->EFFclick => + formfield_click(f, frm, ff); + E->EFFselect => + formfield_select(f, ff); + E->EFFredraw => + c := f.controls[ff.ctlid]; + pick ctl := c { + Cselect => + sel := 0; + for (i := 0; i < len ctl.options; i++) { + if (ctl.options[i].selected) { + sel = i; + break; + } + } + if (sel > len ctl.options - ctl.nvis) + sel = len ctl.options - ctl.nvis; + ctl.first = sel; + } + c.draw(1); + } +} + +# Find hit in a local map +findhit(map: ref B->Map, p: Point, w, h: int) : (ref Parsedurl, string) +{ + x := p.x; + y := p.y; + dflt : ref Parsedurl = nil; + dflttarg := ""; + for(al := map.areas; al != nil; al = tl al) { + a := hd al; + c := a.coords; + nc := len c; + x1 := 0; + y1 := 0; + x2 := 0; + y2 := 0; + if(nc >= 2) { + x1 = d2pix(c[0], w); + y1= d2pix(c[1], h); + if(nc > 2) { + x2 = d2pix(c[2], w); + if(nc > 3) + y2 = d2pix(c[3], h); + } + } + hit := 0; + case a.shape { + "rect" or "rectangle" => + if(nc == 4) + hit = x1 <= x && x <= x2 && + y1 <= y && y <= y2; + "circ" or "circle" => + if(nc == 3) { + xd := x - x1; + yd := y - y1; + hit = xd*xd + yd*yd <= x2*x2; + } + "poly" or "polygon" => + np := nc / 2; + hit = 0; + xr := real x; + yr := real y; + j := np - 1; + for(i := 0; i < np; j = i++) { + xi := real d2pix(c[2*i], w); + yi := real d2pix(c[2*i+1], h); + xj := real d2pix(c[2*j], w); + yj := real d2pix(c[2*j+1], h); + if ((((yi<=yr) && (yr<yj)) || + ((yj<=yr) && (yr<yi))) && + (xr < (xj - xi) * (yr - yi) / (yj - yi) + xi)) + hit = !hit; + } + "def" or "default" => + dflt = a.href; + dflttarg = a.target; + } + if(hit) + return (a.href, a.target); + } + return (dflt, dflttarg); +} + +d2pix(d: B->Dimen, tot: int) : int +{ + ans := d.spec(); + if(d.kind() == B->Dpercent) + ans = (ans * tot) / 100; + return ans; +} +GoSpec.newget(kind: int, url: ref Parsedurl, target: string) : ref GoSpec +{ + return ref GoSpec(kind, url, CU->HGet, "", target, "", nil); +} + +GoSpec.newpost(url: ref Parsedurl, body, target: string) : ref GoSpec +{ + return ref GoSpec(GoNormal, url, CU->HPost, body, target, "", nil); +} + +GoSpec.newspecial(kind: int, hn: ref HistNode) : ref GoSpec +{ + return ref GoSpec(kind, nil, 0, "", "", "", hn); +} + +GoSpec.equal(a: self ref GoSpec, b: ref GoSpec) : int +{ + if(a.url == nil || b.url == nil) + return 0; + return CU->urlequal(a.url, b.url) && a.meth == b.meth && a.body == b.body; +} + +DocConfig.equal(a: self ref DocConfig, b: ref DocConfig) : int +{ + return a.framename == b.framename && a.gospec.equal(b.gospec); +} + +DocConfig.equalarray(a1: array of ref DocConfig, a2: array of ref DocConfig) : int +{ + n := len a1; + if(n != len a2) + return 0; + for(i := 0; i < n; i++) { + if(a1[i] == nil || a2[i] == nil) + continue; + if(!(a1[i]).equal(a2[i])) + return 0; + } + return 1; +} + +# Put b in a.succs (if atob is true) or a.preds (if atob is false) +# at front of list. +# If it is already in the list, move it to the front. +HistNode.addedge(a: self ref HistNode, b: ref HistNode, atob: int) +{ + if(atob) + oldl := a.succs; + else + oldl = a.preds; + there := 0; + for(l := oldl; l != nil; l = tl l) + if(hd l == b) { + there = 1; + break; + } + if(there) + newl := b :: remhnode(oldl, b); + else + newl = b :: oldl; + if(atob) + a.succs = newl; + else + a.preds = newl; +} + +# return copy of l with hn removed (known that hn +# occurs at most once) +remhnode(l: list of ref HistNode, hn: ref HistNode) : list of ref HistNode +{ + if(l == nil) + return nil; + hdl := hd l; + if(hdl == hn) + return tl l; + return hdl :: remhnode(tl l, hn); +} + +# Copy of a, with new kidconfigs array (so that it can be changed independent +# of a), and clear the preds and succs. +HistNode.copy(a: self ref HistNode) : ref HistNode +{ + n := len a.kidconfigs; + kc : array of ref DocConfig = nil; + if(n > 0) { + kc = array[n] of ref DocConfig; + for(i := 0; i < n; i++) + kc[i] = a.kidconfigs[i]; + } + return ref HistNode(a.topconfig, kc, nil, nil, -1, nil); +} + +# This is called just before layout of f with result of getting g. +# (we don't yet know doctitle and whether this is a frameset). +# If navkind is not GoHistnode, update the history graph; but if +# navkind is GoReplace, replace oldcur with the new HistNode. +# In any case reorder the history array to put latest last in array. +History.add(h: self ref History, f: ref Frame, g: ref GoSpec, navkind: int) +{ + if(len h.h <= h.n) { + newh := array[len h.h + 20] of ref HistNode; + newh[0:] = h.h; + h.h = newh; + } + oldcur : ref HistNode; + if(h.n > 0) + oldcur = h.h[h.n-1]; + dc := ref DocConfig(f.name, g.url.tostring(), navkind != GoHistnode, g); + hnode := ref HistNode(dc, nil, nil, nil, -1, nil); + if(f == top) { + g.target = "_top"; + } + else if(oldcur != nil) { + # oldcur should be a frameset and f should be a kid in it + kidpos := -1; + for(i := 0; i < len oldcur.kidconfigs; i++) { + kc := oldcur.kidconfigs[i]; + if(kc != nil && kc.framename == f.name) { + kidpos = i; + break; + } + } + if(kidpos == -1) { + if(dbg) + sys->print("history botch\n"); + } + else { + hnode = oldcur.copy(); + hnode.kidconfigs[kidpos] = dc; + } + } + # see if equivalent node to hnode is already in history + hnodepos := -1; + for(i := 0; i < h.n; i++) { + if(hnode.topconfig.equal(h.h[i].topconfig)) { + if((hnode.kidconfigs==nil && h.h[i].topconfig.initconfig) || + DocConfig.equalarray(hnode.kidconfigs, h.h[i].kidconfigs)) { + hnodepos = i; + hnode = h.h[i]; + break; + } + } + } + if(hnodepos == -1) { + if(navkind == GoReplace && h.n > 0) + h.n--; + hnodepos = h.n; + h.h[h.n++] = hnode; + } + if(oldcur != nil && hnode != oldcur && navkind != GoHistnode) { + oldcur.addedge(hnode, 1); + if(navkind != GoReplace) + hnode.addedge(oldcur, 0); + else if(oldcur.preds != nil) + hnode.addedge(hd oldcur.preds, 0); + } + if(hnodepos != h.n-1) { + # move hnode to h.n-1, and shift rest back + for(k := hnodepos; k < h.n-1; k++) + h.h[k] = h.h[k+1]; + h.h[h.n-1] = hnode; + } + G->backbutton(hnode.preds != nil); + G->fwdbutton(hnode.succs != nil); +} + +# This is called just after layout of f. +# Now we can put in correct doctitle, and make kids array if necessary. +History.update(h: self ref History, f: ref Frame) +{ + hnode := h.h[h.n-1]; + if(f == top) { + hnode.topconfig.title = f.doc.doctitle; + if(f.kids != nil && hnode.kidconfigs == nil) { + kc := array[len f.kids] of ref DocConfig; + i := 0; + for(l := f.kids; l != nil; l = tl l) { + kf := hd l; + if(kf.src != nil) + kc[i] = ref DocConfig(kf.name, kf.src.tostring(), 1, GoSpec.newget(GoNormal, kf.src, "_self")); + i++; + } + hnode.kidconfigs = kc; + } + } + else { + # hnode should be a frameset and f should be a kid in it + for(i := 0; i < len hnode.kidconfigs; i++) { + kc := hnode.kidconfigs[i]; + if(kc != nil && kc.framename == f.name) { + hnode.kidconfigs[i].title = f.doc.doctitle; + return; + } + } + if(dbg) + sys->print("history update botch\n"); + } +} + +# Find the gokind node (-1==Back, 0==Same, +1==Forward) +# other gokind values come from JavaScript's History.go(delta) +History.find(h: self ref History, gokind: int) : ref HistNode +{ + if(h.n > 0) { + cur := h.h[h.n-1]; + case gokind { + 1 => + if(cur.succs != nil) + return hd cur.succs; + -1 => + if(cur.preds != nil) + return hd cur.preds; + 0 => + return cur; + * => +# BUG: follows circularities: gives rise to different behaviour to other +# browsers but maintains the property of find(n) being equivalent to +# the user pressing the (forward/back) button n times + + h.findid++; + while (gokind != 0 && cur != nil) { + hn : list of ref HistNode; + if (gokind > 0) { + gokind--; + hn = cur.succs; + } else { + gokind++; + hn = cur.preds; + } + if (cur.findid == h.findid) + hn = cur.findchain; + else + cur.findid = h.findid; + if (hn != nil) { + cur.findchain = tl hn; + cur = hd hn; + } else + cur = nil; + } + return cur; + } + } + return nil; +} + +# for debugging +History.print(h: self ref History) +{ + sys->print("History\n"); + for(i := 0; i < h.n; i++) { + hn := history.h[i]; + sys->print("Node %d:\n", i); + dc := hn.topconfig; + sys->print("\tframe=%s, target=%s, url=%s\n", dc.framename, dc.gospec.target, dc.gospec.url.tostring()); + if(hn.kidconfigs != nil) { + for(j := 0; j < len hn.kidconfigs; j++) { + dc = hn.kidconfigs[j]; + if(dc != nil) + sys->print("\t\t%d: frame=%s, target=%s, url=%s\n", + j, dc.framename, dc.gospec.target, dc.gospec.url.tostring()); + } + } + if(hn.preds != nil) + printhnodeindices(h, "Preds", hn.preds); + if(hn.succs != nil) + printhnodeindices(h, "Succs", hn.succs); + } + sys->print("\n"); +} + +# helpers for JavaScript's History object +History.histinfo(h: self ref History) : (int, string, string, string) +{ + length := 0; + current, next, previous : string; + + if(h.n > 0) { + hn := h.h[h.n-1]; + length = len hn.succs + len hn.preds + 1; + current = hn.topconfig.gospec.url.tostring(); + if(hn.succs != nil) { + fwd := hd hn.succs; + next = fwd.topconfig.gospec.url.tostring(); + } + if(hn.preds != nil) { + back := hd hn.preds; + previous = back.topconfig.gospec.url.tostring(); + } + } + return (length, current, next, previous); +} + +histinfo() : (int, string, string, string) +{ + return history.histinfo(); +} + +# does URL in hn contain s as a substring? +isurlsubstring(hn: ref HistNode, s: string) : int +{ + url := hn.topconfig.gospec.url.tostring(); + (l, r) := S->splitstrl(url, s); + if(r != nil) + return 1; + return 0; +} + +# for JavaScript's History.go(location) +# find nearest history entry whose URL contains s as a substring +# (search forward and backward from current "in parallel"?) +History.findurl(h: self ref History, s: string) : ref HistNode +{ + if(h.n > 0) { + hn := h.h[h.n-1]; + if(isurlsubstring(hn, s)) + return hn; + fwd := hn.succs; + back := hn.preds; + while(fwd != nil && back != nil) { + if(fwd != nil) { + if(isurlsubstring(hd fwd, s)) + return hd fwd; + fwd = tl fwd; + } + if(back != nil) { + if(isurlsubstring(hd back, s)) + return hd back; + back = tl back; + } + } + } + return nil; +} + +printhnodeindices(h: ref History, label: string, l: list of ref HistNode) +{ + sys->print("\t%s:", label); + for( ; l != nil; l = tl l) { + hn := hd l; + for(i := 0; i < h.n; i++) { + if(hn == h.h[i]) { + sys->print(" %d", i); + break; + } + } + if(i == h.n) + sys->print(" ?"); + } + sys->print("\n"); +} + +dumphistory() +{ + fname := config.userdir + "/history.html"; + fd := sys->create(fname, sys->OWRITE, 8r600); + if(fd == nil) { + if(warn) + sys->print("can't create history file\n"); + return; + } + line := "<HEAD><TITLE>History</TITLE>\n<META HTTP-EQUIV=\"content-type\" CONTENT=\"text/html; charset=utf8\">\n</HEAD>\n<BODY>\n"; + buf := array[Sys->ATOMICIO] of byte; + aline := array of byte line; + buf[0:] = aline; + bufpos := len aline; + for(i := history.n-1; i >= 0; i--) { + hn := history.h[i]; + dc := hn.topconfig; + line = "<A HREF=" + dc.gospec.url.tostring() + " TARGET=\"_top\">" + dc.title + "</A><BR>\n"; + if(hn.kidconfigs != nil) { + line += "<UL>"; + for(j := 0; j < len hn.kidconfigs; j++) { + dc = hn.kidconfigs[j]; + if(dc != nil) { + line += "<LI><A HREF=" + dc.gospec.url.tostring() + + " TARGET=\"" + dc.framename + "\">" + + dc.title + "</A>\n"; + } + } + line += "</UL>"; + } + aline = array of byte line; + if(bufpos + len aline > Sys->ATOMICIO) { + sys->write(fd, buf, bufpos); + bufpos = 0; + } + buf[bufpos:] = aline; + bufpos += len aline; + } + if(bufpos > 0) + sys->write(fd, buf, bufpos); +} + +# getauth returns the (realm, credentials), with "" for the credentials +# if we fail in getting authorization for some reason +getauth(chal: string) : (string, string) +{ + if(len chal < 12 || S->tolower(chal[0:12]) != "basic realm=") { + if(dbg || warn) + sys->print("unrecognized authorization challenge: %s\n", chal); + return ("", ""); + } + realm := chal[12:]; + if(realm[0] == '"') + realm = realm[1:len realm - 1]; + for(al := auths; al != nil; al = tl al) { + a := hd al; + if(realm == a.realm) + return (realm, a.credentials); + } + + c := chan of (int, string); + (code, uname, pword) := G->auth(realm); + if(code != 1) + return (nil, nil); + cred := uname + ":" + pword; + cred = tobase64(cred); + return (realm, cred); +} + +# Convert string to the base64 encoding +tobase64(a: string) : string +{ + n := len a; + if(n == 0) + return ""; + out := ""; + j := 0; + i := 0; + while(i < n) { + x := a[i++] << 16; + if(i < n) + x |= (a[i++]&255) << 8; + if(i < n) + x |= (a[i++]&255); + out[j++] = c64(x>>18); + out[j++] = c64(x>>12); + out[j++] = c64(x>> 6); + out[j++] = c64(x); + } + nmod3 := n % 3; + if(nmod3 != 0) { + out[j-1] = '='; + if(nmod3 == 1) + out[j-2] = '='; + } + return out; +} + +c64(c: int) : int +{ + v : con "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + return v[c&63]; +} + +dosaveas(bsmain: ref ByteSource) +{ + (code, ans) := G->prompt("Save as", nil); + if (code == -1) + return; + if(code == 1 && ans != "") { + if(ans[0] != '/') + ans = config.userdir + "/" + ans; + fd := sys->create(ans, sys->OWRITE, 8r644); + if(fd == nil) { + G->alert(X("Couldn't create", "gui") + " " + ans); + return; + } + G->setstatus(X("Saving", "gui") + " " + bsmain.hdr.actual.tostring()); + # TODO: should really use a different protocol that + # doesn't require getting whole file before proceeding + s := ""; + while(!bsmain.eof) { + CU->waitreq(bsmain::nil); + if(bsmain.err != "") { + s = bsmain.err; + break; + } + } + if(s == "") { + flen := bsmain.edata; + for(i := 0; i < bsmain.edata; ) { + n := sys->write(fd, bsmain.data[i:flen], flen-i); + if(n <= 0) + break; + i += n; + } + if(i != flen) + s = "whole file not written"; + } + if(s == "") + s = X("Created", "gui") + " " + ans; + G->setstatus(X("Created", "gui") + " " + ans); + # G->alert(s); + } + CU->freebs(bsmain); +} + +fatalerror(msg: string) +{ + sys->print("Fatal error: %s\n", msg); + finish(); +} + +pctoloc(mod: string, pc: int) : string +{ + ans := sys->sprint("pc=%d", pc); + db := load Debug Debug->PATH; + if(db == nil) + return ans; + Sym : import db; + db->init(); + modname := mod; + for(i := 0; i < len mod; i++) + if(mod[i] == '[') { + modname = mod[0:i]; + break; + } + sblname := ""; + case modname { + "Build" => + sblname = "build.sbl"; + "CharonUtils" => + sblname = "chutils.sbl"; + "Gui" => + sblname = "gui.sbl"; + "Img" => + sblname = "img.sbl"; + "Layout" => + sblname = "layout.sbl"; + "Lex" => + sblname = "lex.sbl"; + "Test" => + sblname = "test.sbl"; + } + if(sblname == "") + return ans; + (sym, nil) := db->sym(sblname); + if(sym == nil) + return ans; + src := sym.pctosrc(pc); + if(src == nil) + return ans; + return sys->sprint("%s:%d", src.start.file, src.start.line); +} + +startcs() +{ + cs := load Command "/dis/ndb/cs.dis"; + if (cs == nil) { + sys->print("failed to start cs\n"); + return; + } + spawn cs->init(nil, nil); + sys->sleep(1000); +} + +startcharon(url: string, c: chan of string) +{ + ctxt := ref Context; + ctxt.ctxt = context; + ctxt.args = "charon" :: url :: nil; + ctxt.c = c; + ctxt.cksrv = CU->CK; + ctxt.ckclient = CU->ckclient; + ch := load Charon "/dis/charon.dis"; + fdl := list of {0, 1, 2}; + if (CU->ckclient != nil) + fdl = (CU->ckclient).fd.fd :: fdl; + if(ch != nil){ + sys->pctl(Sys->NEWPGRP|Sys->NEWFD, fdl); + ch->initc(ctxt); + } +} + +# Kill all processes spawned by us, and exit +finish() +{ + if (CU != nil) { + CU->kill(pgrp, 1); + if(gopgrp != 0) + CU->kill(gopgrp, 1); + } + if(plumb != nil) + plumb->shutdown(); + sendopener("E"); + exit; +} + +include "plumbmsg.m"; + plumb: Plumbmsg; + Msg: import plumb; + +plumbwatch() +{ + plumb = load Plumbmsg Plumbmsg->PATH; + if (plumb == nil) + return; + if (plumb->init(1, (CU->config).plumbport, 0) == -1) { + # try to set up plumbing for sending only + if (plumb->init(1, nil, 0) == -1) + plumb = nil; + return; + } + while ((m := Msg.recv()) != nil) { + if (m.kind == "text") { + u := CU->makeabsurl(string m.data); + if (u != nil) + E->evchan <-= ref Event.Ego(u.tostring(), "_top", 0, E->EGnormal); + } + } +} + +plumbsend(s, dest: string): int +{ + if (plumb == nil) + return -1; + if (dest != nil) + dest = "type="+dest; + msg := ref Msg((CU->config).plumbport, nil, "", "text", dest, array of byte s); + if (msg.send() < 0) + return -1; + return 0; +} + +stop() +{ + stopped := X("Stopped", "gui"); + G->progress <-= (-1, G->Paborted, 0, stopped); + G->setstatus(stopped); + CU->abortgo(gopgrp); +} + +gettop(): ref Layout->Frame +{ + return top; +} |
