#include "i.h" enum { ALERTLINELEN = 80, // max number of characters in an alert line SP = 8, // a spacer for between controls SP2 = 4 // half of SP }; Channel* gochan; AuthInfo* auths = nil; Channel* popupans; int popupactive = 0; History history; CtlLayout ctllay; ProgLayout proglay; PopupLayout popuplay; Loc* keyfocus; int pgrp = 0; int dbgres = 0; Frame* top; Frame* curframe; Frame* ctlframe; Frame* progframe; Frame* popupframe; Image* popupwin; static void start(void); static void redrawctl(int resized); static void redrawmain(int resized); static void redrawprog(int resized); static void dopopup(int kind, Rune* s); static void redrawpopup(void); static void finishpopup(int code); static void freectls(Control** a, int n); static Rune* ctlentrytext(Control* c); static Loc* frameloc(Control* c, Frame* f); static void resetkeyfocus(Frame* oldf); static GoSpec* handlemouse(EV e); static GoSpec* handlekey(EV e); static void go(void* arg); static void goproc(void* arg); static int get(GoSpec* g, Frame* f, int origkind, HistNode* hn); static void go_local(Frame* f, Rune* loc); static void checkrefresh(Frame* f); static Frame* findnamedframe(Frame* f, Rune* name); static Frame* findframe(Frame* f, int id); static GoSpec* anchorgospec(Item* it, Anchor* a, Point p); static GoSpec* pushaction(Control* c, Loc* loc); static GoSpec* form_submit(Frame* fr, Form* frm, Point p, Control* submitctl); static Rune* ucvt(Rune* s); static int hexdigit(int v); static void form_reset(Frame* fr, Form* frm); static GoSpec* formaction(int frameid, int formid, int ftype); static ParsedUrl* findhit(Map* map, Point p, int w, int h, int* ptarget); static int d2pix(Dimen d, int tot); static GoSpec* newget(ParsedUrl* url, int target); static GoSpec* newpost(ParsedUrl* url, Rune* body, int target); static GoSpec* newspecial(int kind, HistNode* hn); static GoSpec* copygospec(GoSpec* g); static int gospecequal(GoSpec* a, GoSpec* b); static int docconfigequal(DocConfig* a, DocConfig* b); static int docconfigequalarray(DocConfig** a1, int n1, DocConfig** a2, int n2); static DocConfig* newdocconfig(Rune* fname, Rune* title, int initconfig, GoSpec* g); static DocConfig* copydocconfig(DocConfig* d); static HistNode_list* newhistnodelist(HistNode* hn, HistNode_list* l); static void histaddedge(HistNode* a, HistNode* b, int atob); static HistNode* histnodecopy(HistNode* a); static HistNode* newhistnode(DocConfig* top, DocConfig** kids, int nkids, HistNode_list* preds, HistNode_list* succs); static void histadd(Frame* f, GoSpec* g, int navkind); static void histupdate(Frame* f); static HistNode* histfind(int gokind); static void histprint(); static HistNode_list* remhnode(HistNode_list* l, HistNode* hn); static void printhnodeindices(char* label, HistNode_list* l); static void dumphistory(void); static AuthInfo* getauth(Rune* chal); static Rune* tobase64(Rune* a); static int c64(int c); static void dosaveas(ByteSource* bsmain); static void handleprogress(EV e); static void showstatus(Rune* msg); static void alert(void *arg); static void showurl(Rune* u); void threadmain(int argc, char* argv[]) { int showprog; ResourceState curres; ResourceState newres; int v; GoSpec* g; void* alertargs[2]; int dfile; EV ev; uchar* fname; meminit(GRmain); if(initdraw(nil, nil, "i") < 0) fatalerror("initdraw failed"); iutilsinit(argc, argv); dbgres = config.dbg['r']; showprog = config.showprogress; if(dbg && config.dbgfile != nil) { fname = fromStr(config.dbgfile, Strlen(config.dbgfile), UTF_8); dfile = create((char*)fname, OWRITE, 0644); if(dfile >= 0) { dup(dfile, 1); trace("debug output\n"); } } curres = curresstate(); if(dbgres) { resstateprint(startres, "starting resources"); curres = curresstate(); } guiinit(); if(dbgres) { newres = curresstate(); resstateprint(resstatesince(newres, curres), "difference after made screen windows"); curres = newres; } start(); gochan = chancreate(sizeof(GoSpec*), 1); if(gochan == nil) fatalerror("Can't make go channel"); g = newget(config.starturl, FTtop); if(dbgres) { newres = curresstate(); resstateprint(resstatesince(newres, curres), "difference after initial configure"); curres = newres; } if(threadcreate(netget, nil, STACKSIZE) < 0) fatalerror("Can't make netget thread!"); if(threadcreate(go, g, STACKSIZE) < 0) fatalerror("Can't make go thread!"); while(1) { if(recv(evchan, &ev) == -1) goto maindone; if(dbg > 1) { if(ev.tag == EVmousetag) { if(dbg > 2 || ev.u.mouse.mtype != Mmove) trace("Ev: %M\n", &ev); } else trace("Ev: %M\n", &ev); } switch(ev.tag) { case EVkeytag: switch(ev.u.keychar) { case Kdown: yscroll(curframe, CAscrollpage, 1); break; case Kup: yscroll(curframe, CAscrollpage, -1); break; case Khome: yscroll(curframe, CAscrollpage, -10000); break; default: g = handlekey(ev); break; } break; case EVmousetag: g = handlemouse(ev); break; case EVresizetag: redrawctl(1); redrawmain(1); redrawprog(1); curframe = top; g = newspecial(GoHistnode, histfind(0)); break; case EVexposetag: g = nil; break; case EVhidetag: g = nil; break; case EVquittag: goto maindone; break; case EValerttag: alertargs[0] = ev.u.alert.msg; alertargs[1] = ev.u.alert.sync; if(proccreate(alert, (void*)alertargs, STACKSIZESMALL) < 0) { trace("can't create alert proc\n"); if(sendul((Channel*)ev.u.alert.sync, 1) < 0) finish(); } g = nil; break; case EVformtag: g = formaction(ev.u.form.frameid, ev.u.form.formid, ev.u.form.ftype); break; case EVgotag: if(ev.u.go.url == nil) g = newspecial(GoHistnode, histfind(0)); else g = newget(ev.u.go.url, ev.u.go.target); break; case EVprogresstag: if(showprog) handleprogress(ev); g = nil; break; default: trace("unknown event tag %d\n", ev.tag); g = nil; } if(g != nil) { abortgo(); v = nbsend(gochan, &g); if(v < 0) goto maindone; if(v == 0) { // discard the gospec: last one hasn't been // acted on yet. // (We do this to avoid having events pile up // while waiting for abort to have effect) trace("go pileup; discard\n"); g = nil; } } } maindone: finish(); } static void start(void) { Image* i; Image* m; top = newframe(); ctlframe = newframe(); // not really a frame, but need cim pointer progframe = newframe(); // not really a frame, but need cim pointer popupframe = newframe(); // not really a frame, but need cim pointer curframe = top; ctllay.logoicon = geticon(IClogo, nil); i = geticon(ICback, &m); ctllay.controls[CLCbackbut] = newbutton(ctlframe, i, m, L"Go back", nil, 1, 1); i = geticon(ICfwd, &m); ctllay.controls[CLCfwdbut] = newbutton(ctlframe, i, m, L"Go forward", nil, 1, 1); i = geticon(ICreload, &m); ctllay.controls[CLCreloadbut] = newbutton(ctlframe, i, m, L"Reload current page", nil, 1, 1); i = geticon(ICstop, &m); ctllay.controls[CLCstopbut] = newbutton(ctlframe, i, m, L"Stop", nil, 1, 1); i = geticon(IChist, &m); ctllay.controls[CLChistbut] = newbutton(ctlframe, i, m, L"Show history", nil, 1, 1); i = geticon(ICbmark, &m); ctllay.controls[CLCbmarkbut] = newbutton(ctlframe, i, m, L"Show bookmarks", nil, 1, 1); i = geticon(ICexit, &m); ctllay.controls[CLCexitbut] = newbutton(ctlframe, i, m, L"Exit", nil, 1, 1); ctllay.controls[CLCentry] = newentry(ctlframe, 30, 1, 0); disable(ctllay.controls[CLCbackbut]); disable(ctllay.controls[CLCfwdbut]); disable(ctllay.controls[CLCstopbut]); ctllay.status = nil; keyfocus = frameloc(ctllay.controls[CLCentry], ctlframe); gainfocus(ctllay.controls[CLCentry]); popuplay.kind = PopupNone; popupans = chancreate(sizeof(PopupAns), 0); redrawctl(1); redrawmain(1); redrawprog(1); } static void redrawctl(int resized) { Rectangle r; Point p; Image* li; int lw; int lh; int i; Control* b; int x; int y; pushclipr(rctl); r = insetrect(rctl, ReliefBd); drawfill(screen, r, Grey); drawrelief(screen, r, ReliefRaised); replaceclipr(r); p = ctllay.logopos; li = ctllay.logoicon; lw = Dx(li->r); lh = Dy(li->r); if(resized) { ctlframe->r = insetrect(rctl, 2*ReliefBd); ctlframe->cr = r; ctlframe->cim = screen; p = addpt(r.min, Pt(7, 7)); ctllay.logopos = p; x = p.x + lw; y = p.y; x += SP; for(i = 0; i < NUMCLCS; i++) { b = ctllay.controls[i]; b->r = rectsubpt(b->r, b->r.min); if(i == CLCentry || i == CLCexitbut) continue; b->r = rectaddpt(b->r, Pt(x, y)); x += Dx(b->r) + SP2; } x += SP2; ctllay.entrypos = Pt(x, y); ctllay.controls[CLCentry]->r = Rpt(ctllay.entrypos, Pt(r.max.x - 7, y + 22)); ctllay.controls[CLCentry]->r.max.x -= Dx(ctllay.controls[CLCexitbut]->r) + SP2; ctllay.controls[CLCexitbut]->r = rectaddpt(ctllay.controls[CLCexitbut]->r, Pt(ctllay.controls[CLCentry]->r.max.x + SP2, y)); } ctllay.statuspos = Pt(p.x + lw + SP, p.y + Dy((ctllay.controls[CLCbackbut])->r) + SP); draw(screen, Rpt(p, addpt(p, Pt(lw, lh))), li, nil, ZP); for(i = 0; i < NUMCLCS; i++) drawctl(ctllay.controls[i], 0); showstatus(ctllay.status); popclipr(); flushimage(display, 1); } static void redrawmain(int resized) { if(resized) { top->r = insetrect(rmain, 2*ReliefBd); top->cr = top->r; top->cim = screen; resetframe(top); imcacheresetlimits(); } pushclipr(rmain); drawrelief(screen, insetrect(top->r, -ReliefBd), ReliefRaised); drawrelief(screen, top->r, ReliefSunk); replaceclipr(top->r); drawfill(screen, top->r, White); popclipr(); flushimage(display, 1); } static void redrawprog(int resized) { Rectangle r; int i; Control** newbox; Point p; int nbox; int nboxold; Control* c; int vo; if(!config.showprogress) return; pushclipr(rprog); r = insetrect(rprog, ReliefBd); drawfill(screen, r, Grey); drawrelief(screen, r, ReliefRaised); replaceclipr(r); if(resized) { p = proglay.first; nbox = proglay.boxlen; progframe->r = insetrect(rprog, 2*ReliefBd); progframe->cr = progframe->r; progframe->cim = screen; nboxold = nbox; c = newprogbox(progframe); vo = (Dy(r) - Dy(c->r))/2; p = addpt(r.min, Pt(7, vo)); proglay.first = p; proglay.dx = Dx(c->r) + SP2; nbox = (Dx(r) - 2*7 - SP2)/proglay.dx; if(nboxold != nbox) { newbox = (Control**)erealloc(proglay.box, nbox * sizeof(Control*)); proglay.box = newbox; proglay.boxlen = nbox; for(i = nboxold; i < nbox; i++) proglay.box[i] = newprogbox(progframe); } for(i = 0; i < nbox; i++) { c = proglay.box[i]; c->r = rectsubpt(c->r, c->r.min); c->r = rectaddpt(c->r, Pt(p.x + i*proglay.dx, p.y)); } } for(i = 0; i < proglay.nused; i++) drawctl(proglay.box[i], 0); popclipr(); flushimage(display, 1); } // Display popup of given kind (s is more info for drawing, depending on kind), // and return user's answer on popupans as (code, string), where code will // be -1 for error, 0 when the user hit cancel, and 1 when the user hit OK. static void dopopup(int kind, Rune* s) { int w; int h; int n; int nw; int i; int k; int nline; int curlen; int curlinestart; Control* cok; Control* ccancel; Point p; Control* oldc; Control* newc; Control* chead; Control* crealm; Control* cunlab; Control* cpwlab; Control* cuser; Control* cpass; Control* clab; Control* cfile; Rune* l; Control* c; PopupAns pans; Rune* words[BIGBUFSIZE]; int wordlens[BIGBUFSIZE]; Rune* lines[SMALLBUFSIZE]; cok = newbutton(popupframe, nil, nil, L"Ok", nil, 1, 1); ccancel = newbutton(popupframe, nil, nil, L"Cancel", nil, 1, 1); p = Pt(0, 0); switch(kind) { case PopupAuth: popuplay.ncontrols = 8; popuplay.controls = (Control**)emalloc(popuplay.ncontrols*sizeof(Control*)); popuplay.controls[0] = cuser = newentry(popupframe, 30, 1, 0); popuplay.controls[1] = cpass = newentry(popupframe, 30, 1, 0); popuplay.controls[2] = chead = newlabel(popupframe, L"Type your user name and password"); popuplay.controls[3] = crealm = newlabel(popupframe, Strdup2(L"Resource: ", s)); popuplay.controls[4] = cunlab = newlabel(popupframe, Strdup(L"User Name: ")); popuplay.controls[5] = cpwlab = newlabel(popupframe, Strdup(L"Password: ")); popuplay.controls[6] = cok; popuplay.controls[7] = ccancel; w = SP + max(chead->r.max.x, max(crealm->r.max.x, cunlab->r.max.x + cuser->r.max.x)) + SP; w = min(w, Dx(rmain) - 2*SP); h = SP + chead->r.max.y + SP + crealm->r.max.y + SP + cuser->r.max.y + SP + cpass->r.max.y + 2*SP + cok->r.max.y + SP; popupwin = makepopup(w, h); if(popupwin == nil) { pans.code = -1; pans.s = nil; if(send(popupans, &pans) < 0) finish(); freectls(popuplay.controls, popuplay.ncontrols); return; } p = addpt(popupwin->r.min, Pt(SP, SP)); chead->r = rectaddpt(chead->r, p); p.y += Dy(chead->r) + SP; crealm->r = rectaddpt(crealm->r, p); p.y += Dy(crealm->r) + SP; cunlab->r = rectaddpt(cunlab->r, p); cuser->r = rectaddpt(cuser->r, addpt(p, Pt(Dx(cunlab->r), 0))); p.y += Dy(cuser->r) + SP; cpwlab->r = rectaddpt(cpwlab->r, p); cpass->r = rectaddpt(cuser->r, Pt(0, Dy(cuser->r) + SP)); p.y += Dy(cpass->r) + 2*SP; break; case PopupSaveAs: popuplay.ncontrols = 4; popuplay.controls = (Control**)emalloc(popuplay.ncontrols*sizeof(Control*)); popuplay.controls[0] = cfile = newentry(popupframe, 40, 1, 0); popuplay.controls[1] = clab = newlabel(popupframe, L"Save As: "); popuplay.controls[2] = cok; popuplay.controls[3] = ccancel; ((Centry*)cfile)->s = s; w = SP + clab->r.max.x + cfile->r.max.x + SP; h = SP + cfile->r.max.y + 2*SP + cok->r.max.y + SP; popupwin = makepopup(w, h); if(popupwin == nil) { pans.code = -1; pans.s = nil; if(send(popupans, &pans) < 0) finish(); freectls(popuplay.controls, popuplay.ncontrols); return; } p = addpt(popupwin->r.min, Pt(SP, SP)); clab->r = rectaddpt(clab->r, p); cfile->r = rectaddpt(cfile->r, addpt(p, Pt(Dx(clab->r) + SP, 0))); p.y += Dy(clab->r) + 2*SP; break; case PopupAlert: ccancel = nil; // split s up into lines of at most ALERTLINELEN characters each n = splitall(s, Strlen(s), L" \t\n\r", words, wordlens, BIGBUFSIZE); curlen = 0; curlinestart = 0; nline = 0; for(i = 0; i < n; i++) { nw = wordlens[i]; if(curlen + nw >= ALERTLINELEN) { l = newstr(curlen); if(nline < SMALLBUFSIZE) lines[nline++] = l; for(k = curlinestart; k < i; k++) { l = Stradd(l, words[k], wordlens[k]); if(k < i-1) *l++ = ' '; } *l = 0; curlinestart = i; curlen = nw; } else curlen += nw + (curlen > 0); } if(curlen != 0) { l = newstr(curlen); if(nline < SMALLBUFSIZE) lines[nline++] = l; for(k = curlinestart; k < i; k++) { l = Stradd(l, words[k], wordlens[k]); if(k < i-1) *l++ = ' '; } *l = 0; } popuplay.ncontrols = nline+1; popuplay.controls = (Control**)emalloc(popuplay.ncontrols*sizeof(Control*)); popuplay.controls[0] = cok; w = SP + cok->r.max.x + SP; h = SP + 2*SP + cok->r.max.y + SP; for(k = 0; k < nline; k++) { l = lines[k]; c = newlabel(popupframe, l); popuplay.controls[k+1] = c; w = max(w, SP + Dx(c->r) + SP); h += Dy(c->r); } popupwin = makepopup(w, h); if(popupwin == nil) { pans.code = -1; pans.s = nil; if(send(popupans, &pans) < 0) finish(); freectls(popuplay.controls, popuplay.ncontrols); return; } p = addpt(popupwin->r.min, Pt(SP, SP)); for(k = 1; k <= nline; k++) { c = popuplay.controls[k]; c->r = rectaddpt(c->r, p); p.y += Dy(c->r); } p.y += 2*SP; break; default: assert(0); break; } if(ccancel == nil) { p.x += (Dx(popupwin->r) - cok->r.max.x)/2 - SP; cok->r = rectaddpt(cok->r, p); } else { p.x += (Dx(popupwin->r) - (cok->r.max.x + SP + ccancel->r.max.x))/2 - SP; cok->r = rectaddpt(cok->r, p); p.x += Dx(cok->r) + SP; ccancel->r = rectaddpt(ccancel->r, p); } popupframe->cim = popupwin; popuplay.kind = kind; popuplay.okbut = cok; popuplay.cancelbut = ccancel; popupactive = 1; oldc = keyfocus->le[keyfocus->n - 1].control; if(oldc != nil) losefocus(oldc); newc = popuplay.controls[0]; keyfocus = frameloc(newc, popupframe); gainfocus(newc); redrawpopup(); } static void redrawpopup(void) { Rectangle r; int i; if(popuplay.kind == PopupNone) return ; assert(popupwin != nil); popupwin->clipr = popupwin->r; r = insetrect(popupwin->r, ReliefBd); drawfill(popupwin, r, Grey); drawrelief(popupwin, r, ReliefRaised); popupwin->clipr = r; for(i = 0; i < popuplay.ncontrols; i++) drawctl(popuplay.controls[i], 0); flushimage(display, 1); } static void finishpopup(int code) { PopupAns pans; pans.code = code; pans.s = nil; switch(popuplay.kind) { case PopupAuth: pans.s = Strdup3(ctlentrytext(popuplay.controls[0]), L":", ctlentrytext(popuplay.controls[1])); break; case PopupSaveAs: pans.s = ctlentrytext(popuplay.controls[0]); break; } popuplay.kind = PopupNone; popuplay.controls = nil; popuplay.okbut = nil; popuplay.cancelbut = nil; popupframe->cim = nil; popupwin = nil; popupactive = 0; freectls(popuplay.controls, popuplay.ncontrols); if(send(popupans, &pans) < 0) finish(); } // assuming c is an entry control, return its contents // (caller must dup, if it wants to save the result) static Rune* ctlentrytext(Control* c) { assert(c->tag == Centrytag); return ((Centry*)c)->s; } // Return a Loc representing a control in the frame f static Loc* frameloc(Control* c, Frame* f) { Loc* loc; loc = newloc(); addloc(loc, LEframe, f->r.min); loc->le[loc->n - 1].frame = f; addloc(loc, LEcontrol, c->r.min); loc->le[loc->n - 1].control = c; return loc; } // Frame oldf is being reset, so change keyfocus back to ctllay.entry static void resetkeyfocus(Frame* oldf) { USED(oldf); keyfocus = frameloc(ctllay.controls[CLCentry], ctlframe); } // If mouse event results in command to navigate somewhere else, // return a GoSpec ref, else nil. // TODO: deactivate activated controls if mouse leaves the area; // perhaps do grabs? static GoSpec* handlemouse(EV e) { Point p; GoSpec* g; Control* oldc; Control* c; Anchor* a; Item* it; // ScriptEvent* se; // int hasscripts; Frame* f; int n1; Loc* loc; Rune* msg; int i; Cprogbox* pc; int ns; char buf[SMALLBUFSIZE]; p = e.u.mouse.p; g = nil; if(popupactive) { for(i = 0; i < popuplay.ncontrols; i++) { c = popuplay.controls[i]; if(ptinrect(p, c->r)) { if(dbg > 1) trace("mouse in popup control\n"); switch(domouse(c, p, e.u.mouse.mtype)) { case CAbuttonpush: if(c == popuplay.okbut) finishpopup(1); else if(c == popuplay.cancelbut) finishpopup(0); break; case CAkeyfocus: if(dbg > 1) printloc(keyfocus, "old focus"); oldc = keyfocus->le[keyfocus->n - 1].control; if(oldc != nil) losefocus(oldc); keyfocus = frameloc(c, popupframe); gainfocus(c); if(dbg > 1) printloc(keyfocus, "new focus"); break; } } } } else if(ptinrect(p, rctl)) { for(i = 0; i < NUMCLCS; i++) { c = ctllay.controls[i]; if(ptinrect(p, c->r)) { if(dbg > 1) trace("mouse in controlwin control\n"); switch(domouse(c, p, e.u.mouse.mtype)) { case CAbuttonpush: switch(i) { case CLCbackbut: g = newspecial(GoHistnode, histfind(-1)); break; case CLCfwdbut: g = newspecial(GoHistnode, histfind(1)); break; case CLCreloadbut: g = newspecial(GoHistnode, histfind(0)); break; case CLChistbut: g = newspecial(GoHistory, nil); break; case CLCbmarkbut: g = newspecial(GoBookmarks, nil); break; case CLCstopbut: g = newspecial(GoStop, nil); break; case CLCexitbut: finish(); } break; case CAflyover: if(c->tag == Cbuttontag) showstatus(((Cbutton*)c)->label); break; case CAkeyfocus: if(dbg > 1) printloc(keyfocus, "old focus"); oldc = keyfocus->le[keyfocus->n - 1].control; if(oldc != nil) losefocus(oldc); keyfocus = frameloc(c, ctlframe); gainfocus(c); if(dbg > 1) printloc(keyfocus, "new focus"); break; } break; } } } else if(ptinrect(p, rmain)) { loc = findloc(top, p, nil); if(loc != nil) { if(dbg > 1) printloc(loc, "mouse loc"); f = lastframe(loc); // hasscripts = f->doc->hasscripts; if(e.u.mouse.mtype != Mmove) curframe = f; n1 = loc->n - 1; switch(loc->le[n1].kind) { case LEitem: it = loc->le[n1].item; if(it->anchorid >= 0) { for(a = f->doc->anchors; a != nil; a = a->next) { if(a->index == it->anchorid) { if(dbg > 1) trace("in anchor %d, href=%U\n", a->index, a->href); if(e.u.mouse.mtype == Mlbuttonup) g = anchorgospec(it, a, loc->pos); else if(e.u.mouse.mtype == Mmbuttonup) { showstatus(a->href->url); } } } } break; case LEcontrol: c = loc->le[n1].control; switch(domouse(c, p, e.u.mouse.mtype)) { case CAbuttonpush: // if(hasscripts && c->ff != nil && c->ff->events != nil) { // se = copyScriptEvent((ScriptEvent(Aonclick, f->id, // c->ff->form->formid, c->ff->fieldid, -1, e->p.x, e->p.y, 1)); // /* TODO spawn */do_on(se); // return nil; // } g = pushaction(c, loc); break; case CAkeyfocus: if(dbg > 1) printloc(keyfocus, "old focus"); oldc = keyfocus->le[keyfocus->n - 1].control; if(oldc != nil) losefocus(oldc); keyfocus = frameloc(c, ctlframe); gainfocus(c); if(dbg > 1) printloc(keyfocus, "new focus"); break; } break; } } } else if(ptinrect(p, rprog)) { for(i = 0; i < proglay.boxlen; i++) { c = proglay.box[i]; if(ptinrect(p, c->r)) { if(dbg > 1) trace("mouse in progbox control %d\n", i); switch(domouse(c, p, e.u.mouse.mtype)) { case CAbuttonpush: switch(c->tag) { case Cprogboxtag: assert(c->tag == Cprogboxtag); pc = (Cprogbox*)c; ns = snprint(buf, sizeof(buf), "%S, %d%% done", pc->src, pc->pcnt); if(pc->err != nil) ns += snprint(buf+ns, sizeof(buf)-ns, ", %S", pc->err); if(dbg) ns += snprint(buf+ns, sizeof(buf)-ns, ", bsid=%d", pc->bsid); msg = toStr((uchar*)buf, ns, UTF_8); showstatus(msg); break; } break; } } } } return g; } // If key event results in command to navigate somewhere else, // return a GoSpec ref, else nil. static GoSpec* handlekey(EV e) { Loc* loc; int n1; ParsedUrl* u; Rune* s; Centry* ce; Control* c; loc = keyfocus; if(dbg > 1) printloc(loc, "key focus loc"); n1 = loc->n - 1; switch(loc->le[n1].kind) { case LEcontrol: c = loc->le[n1].control; switch(c->tag) { case Centrytag: ce = (Centry*)c; switch(dokey(c, e.u.keychar)) { case CAreturnkey: if(c == ctllay.controls[CLCentry]) { s = ce->s; if(s != nil) { u = makeurl(s, 1); return newget(u, FTtop); } } else if(popupactive) { finishpopup(1); return nil; } else if(c->ff != nil) return form_submit(c->f, c->ff->form, ZP, c); break; case CAtabkey: break; } break; } break; } return nil; } // Run as separate thread // Arg has first GoSpec* as arg static void go(void* arg) { GoSpec* g; int origkind; int rv; HistNode* hn; Frame* f; ParsedUrl* url; Rune* s; EV prog; g = (GoSpec*)arg; while(1) { origkind = g->kind; hn = nil; switch(g->kind) { case GoNormal: break; case GoHistnode: hn = g->histnode; if(hn == nil) return; g = hn->topconfig->gospec; break; case GoBookmarks: s = Strdup3(L"file:", config.userdir, L"/bookmarks.html"); url = makeurl(s, 0); g = newget(url, FTtop); break; case GoHistory: s = Strdup3(L"file:", config.userdir, L"/history.html"); url = makeurl(s, 0); dumphistory(); g = newget(url, FTtop); break; } switch(g->target) { case FTtop: curframe = top; break; case FTself: break; // curframe is already OK case FTparent: if(curframe->parent != nil) curframe = curframe->parent; break; case FTblank: curframe = top; // we don't create new browsers... break; default: // this is recommended "current practice" curframe = findnamedframe(curframe, targetname(g->target)); if(curframe == nil) { curframe = findnamedframe(top, targetname(g->target)); if(curframe == nil) curframe = top; } } if(g->kind == GoStop) { if(dbg) trace("\n\nSTOP\n"); showstatus(L"Stopped"); } else { f = curframe; if(dbg) { trace("\n\nGO TO %U\n", g->url); if(g->target != FTtop) trace("target frame name=%S\n", f->name); } if(g->url->nfrag != 0 && origkind == GoNormal && f->doc != nil && f->doc->src != nil && urlequal(g->url, f->doc->src)) { go_local(f, g->url->frag); return ; } if(config.showprogress) { prog.tag = EVprogresstag; prog.u.progress.bsid = -1; prog.u.progress.state = 0; prog.u.progress.pcnt = 0; prog.u.progress.s = nil; if(send(evchan, &prog) < 0) finish(); } enable(ctllay.controls[CLCstopbut]); rv = get(g, f, origkind, hn); disable(ctllay.controls[CLCstopbut]); if(rv) { showstatus(L"Done"); checkrefresh(f); } } if(recv(gochan, &g) < 0) finish(); } } // The important invariant in get/layout is that we keep // track of every ByteSource returned by startreq, and // ensure that we freebs each of them. // And we can't start a new get() until all the ones started // for the current get() have been freed. static int get(GoSpec* g, Frame* f, int origkind, HistNode* hn) { ResourceState curres; ResourceState newres; Rune* sdest; ByteSource* bsmain; Header* hdr; ReqInfo* ri; int authtried; AuthInfo* auth; int nredirs; ByteSource* bs; int use; int error; Rune* challenge; ParsedUrl* newurl; GoSpec* gs; Framelist* kl; Frame* k; int i; Rune* msg; uchar* body; int bodylen; if(dbgres) { imcacheclear(); curres = curresstate(); } sdest = g->url->url; msg = Strdup2(L"Fetching ", sdest); showstatus(msg); if(g->body != nil) { body = fromStr(g->body, Strlen(g->body), UTF_8); bodylen = strlen((char*)body); } else { body = nil; bodylen = 0; } ri = newreqinfo(g->url, g->meth, body, bodylen, g->auth, g->target); authtried = 0; auth = nil; for(nredirs = 0; ; nredirs++) { bsmain = startreq(ri); if(bsmain->err) { showstatus(errphrase(bsmain->err)); freebs(bsmain); goto errret; } bs = waitreq(); if(bs == nil) goto errret; assert(bs == bsmain); if(bsmain->err != 0) { showstatus(errphrase(bsmain->err)); freebs(bsmain); goto errret; } hdr = bsmain->hdr; use = hdraction(bsmain, 1, nredirs, &error, &challenge, &newurl); if(challenge != nil) { if(authtried) { error = ERRauthfailed; use = 1; } else { auth = getauth(challenge); if(auth != nil) { ri->auth = auth->credentials; authtried = 1; freebs(bsmain); continue; } else { error = ERRauthfailed; use = 1; } } } if(error) showstatus(errphrase(error)); else { showstatus(hcphrase(hdr->code)); if(authtried) { auth->next = auths; auths = auth; } } if(newurl != nil) { ri->url = newurl; ri->method = HGet; freebs(bsmain); continue; } if(use == 0) { freebs(bsmain); goto errret; } break; } if(dbgres > 1) { newres = curresstate(); resstateprint(resstatesince(newres, curres), "resources to get header"); curres = newres; } if(hdr->length > 0 && (hdr->mtype == TextHtml || hdr->mtype == TextPlain || supported(hdr->mtype))) { showurl(sdest); histadd(f, g, origkind); resetkeyfocus(f); layout(f, bsmain, nil); histupdate(f); if(dbgres > 1) { newres = curresstate(); resstateprint(resstatesince(newres, curres), "resources to get page and do layout"); curres = newres; } if(f->kids != nil) { i = 0; for(kl = f->kids; kl != nil; kl = kl->next) { k = kl->val; if(k->src != nil) { if(hn != nil) gs = hn->kidconfigs[i]->gospec; else gs = newget(copyurl(k->src), FTself); if(dbg) trace("get child frame %U\n", gs->url); if(!get(gs, k, GoNormal, nil)) goto errret; } i++; } } if(g->url->nfrag != 0) go_local(f, g->url->frag); } else { if(hdr->length == 0) { showstatus(L"Empty page"); freebs(bsmain); } else { msg = Strdup2(L"Unsupported media type: ", mnames[hdr->mtype]); showstatus(msg); dosaveas(bsmain); // frees bsmain when done } } if(dbgres == 1) { newres = curresstate(); resstateprint(resstatesince(newres, curres), "resources to do page"); curres = newres; } return 1; errret: return 0; } static void go_local(Frame* f, Rune* loc) { Loc* dloc; Point p; DestAnchor* d; if(dbg) trace("go to local destination %S\n", loc); for(d = f->doc->dests; d != nil; d = d->next) { if(!Strcmp(d->name, loc)) { dloc = findloc(f, ZP, d->item); if(dloc == nil) { if(warn) trace("couldn't find item for destination anchor %S\n", loc); return ; } p = sptolp(f, dloc->le[dloc->n - 1].pos); yscroll(f, CAscrollabs, p.y); return; } } if(warn) trace("couldn't find destination anchor %S\n", loc); } // If refresh has been set in f (i.e., client pull), // pause the appropriate amount of time and then go to new place static void checkrefresh(Frame* f) { Rune* s; int seconds; ParsedUrl* url; int n; EV e; EV* goe; Rune* a[2]; int na[2]; if(f->doc != nil && f->doc->refresh != nil) { seconds = 0; url = nil; n = splitall(f->doc->refresh, Strlen(f->doc->refresh), L"; ", a, na, 2); if(n > 0) { seconds = Strtol(a[0], nil, 10); if(n > 1) { s = a[1]; if(na[1] > 4 && !Strncmpci(s, 4, L"url=")) { s = Strndup(s+4, na[1]-4); url = makeurl(s, 0); url = makeabsoluteurl(url, f->doc->base); } } } goe = (EV*)emalloc(sizeof(EV)); if(url == nil) *goe = evgo(nil, FTtop, EGreload, f->id); else *goe = evgo(copyurl(url), targetid(f->name), EGnormal, f->id); e.tag = EVdelaytag; e.genframeid = f->id; e.u.delay.millisecs = seconds*1000; e.u.delay.ev = goe; if(send(evchan, &e) < 0) finish(); } } // Do depth first search from f, looking for frame with given name. static Frame* findnamedframe(Frame* f, Rune* name) { Framelist* kl; Frame* a; if(!Strcmp(f->name, name)) return f; for(kl = f->kids; kl != nil; kl = kl->next) { a = findnamedframe(kl->val, name); if(a != nil) return a; } return nil; } // Similar, but look for frame id, starting from f static Frame* findframe(Frame* f, int id) { Framelist* kl; Frame* a; if(f->id == id) return f; for(kl = f->kids; kl != nil; kl = kl->next) { a = findframe(kl->val, id); if(a != nil) return a; } return nil; } // Return Gospec resulting from button up in anchor a, at offset pos inside item it. static GoSpec* anchorgospec(Item* it, Anchor* a, Point p) { GoSpec* g; ParsedUrl* u; int target; int x; int y; Rune* sx; Rune* sy; Rune* q; CImage* ci; Iimage* i; Ifloat* f; g = nil; target = a->target; u = nil; switch(it->tag) { case Iimagetag: i = (Iimage*)it; ci = i->ci; if(ci->mims != nil) { if(i->map != nil) { u = findhit(i->map, p, ci->width, ci->height, &target); } else if(a->href != nil && (it->state&IFsmap)) { x = min(max(p.x - (i->hspace + i->border), 0), ci->width - 1); y = min(max(p.y - (i->vspace + i->border), 0), ci->height - 1); sx = ltoStr(x); sy = ltoStr(y); q = Strdup3(sx, L",", sy); u = makequeryurl(a->href, q); } } break; case Ifloattag: f = (Ifloat*)it; return anchorgospec(f->item, a, p); default: u = copyurl(a->href); } if(u != nil) g = newget(u, target); return g; } // Control c has been pushed. // Find the form it is in and perform required action (reset, or submit). // If a submit, the return value is the place to go to. static GoSpec* pushaction(Control* c, Loc* loc) { Formfield* ff; Frame* f; Cbutton* b; if(c->tag == Cbuttontag) { b = (Cbutton*)c; ff = b->ff; f = b->f; if(ff != nil) { switch(ff->ftype) { case Fsubmit: case Fimage: return form_submit(c->f, ff->form, loc->pos, c); break; case Freset: form_reset(f, ff->form); break; } } } return nil; } static GoSpec* form_submit(Frame* fr, Form* frm, Point p, Control* submitctl) { Rune* v; Rune* sep; Rune* t; Rune* z; Strlist* radiodone; Formfield* f; int nnonhidden; Strlist* rl; int checked; int i; int n; Cselect* cs; Rune* val; Control* c; ParsedUrl* action; if(submitctl != nil && submitctl->tag == Centrytag) { nnonhidden = 0; for(f = frm->fields; f != nil; f = f->next) { if(f->ftype != Fhidden) nnonhidden++; } if(nnonhidden > 1) return nil; } v = nil; sep = nil; radiodone = nil; for(f = frm->fields; f != nil; f = f->next) { if(f->name == nil) continue; val = nil; if(f->ctlid >= 0) c = fr->controls[f->ctlid]; else c = nil; switch(f->ftype) { case Ftext: case Fpassword: case Ftextarea: if(c != nil) val = ((Centry*)c)->s; if(val != nil && !Strcmp(f->name, L"_ISINDEX_")) { if(sep != nil) v = Strdup2(v, sep); t = ucvt(val); v = Strdup2(v, t); goto floop_done; } break; case Fcheckbox: case Fradio: if(f->ftype == 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 = rl->next) if(!Strcmp(rl->val, f->name)) goto floop_continue; } checked = 0; if(c != nil) if(c->tag == Ccheckboxtag) checked = ((Ccheckbox*)c)->flags&CFactive; if(checked) { val = f->value; if(f->ftype == Fradio) radiodone = newstrlist(f->name, radiodone); } else goto floop_continue; break; case Fhidden: val = f->value; break; case Fsubmit: if(submitctl != nil && f == submitctl->ff && Strcmp(f->name, L"_no_name_submit_")) val = f->value; else goto floop_continue; break; case Fselect: if(c != nil) { assert(c->tag == Cselecttag); cs = (Cselect*)c; n = listlen((List*)cs->options); for(i = 0; i < n; i++) { if(cs->options[i].selected) { if(sep != nil) v = Strdup2(v, sep); sep = L"&"; t = ucvt(f->name); v = Strdup3(v, t, L"="); t = ucvt(cs->options[i].value); v = Strdup2(v, t); } } goto floop_continue; } break; case Fimage: if(submitctl != nil && f == submitctl->ff) { if(sep != nil) v = Strdup2(v, sep); sep = L"&"; z = Strdup2(f->name, L".x"); t = ucvt(z); v = Strdup3(v, t, L"="); z = ltoStr(max(p.x, 0)); t = ucvt(z); v = Strdup3(v, t, sep); z = Strdup2(f->name, L".y"); t = ucvt(z); v = Strdup3(v, t, L"="); z = ltoStr(max(p.y, 0)); t = ucvt(z); v = Strdup2(v, t); goto floop_continue; } break; } if(val != nil) { if(sep != nil) v = Strdup2(v, sep); sep = L"&"; t = ucvt(f->name); v = Strdup3(v, t, L"="); if(val != nil) { t = ucvt(val); v = Strdup2(v, t); } } floop_continue: ; } floop_done: if(frm->method == HPost) return newpost(copyurl(frm->action), v, frm->target); else { action = makequeryurl(frm->action, v); return newget(action, frm->target); } } static Rune* ucvt(Rune* s) { Rune* u; int i; int c; int n; int j; int len; n = Strlen(s); len = 0; for(i = 0; i < n; i++) { c = s[i]; if(inclass(c, L"- /$_@.!*'(),a-zA-Z0-9")) len++; else len += 3; } u = newstr(len); j = 0; for(i = 0; i < n; i++) { c = s[i]; if(inclass(c, L"-/$_@.!*'(),a-zA-Z0-9")) u[j++] = c; else if(c == ' ') u[j++] = '+'; else { u[j++] = '%'; u[j++] = hexdigit((c >> 4)&15); u[j++] = hexdigit(c&15); } } u[j] = 0; return u; } static int hexdigit(int v) { if(0 <= v && v <= 9) return '0' + v; else return 'A' + v - 10; } static void form_reset(Frame* fr, Form* frm) { Formfield* a; for(a = frm->fields; a != nil; a = a->next) { if(a->ctlid >= 0) resetctl(fr->controls[a->ctlid]); } flushimage(display, 1); } static GoSpec* formaction(int frameid, int formid, int ftype) { Frame* f; Form* frm; Docinfo* d; trace("formaction %d %d %d\n", frameid, formid, ftype); f = findframe(top, frameid); if(f != nil) { d = f->doc; if(d != nil) { for(frm = d->forms; frm != nil; frm = frm->next) { if(frm->formid == formid) { if(ftype == EFsubmit) { return form_submit(f, frm, Pt(0, 0), nil); } else { form_reset(f, frm); return nil; } } } } } return nil; } // Find hit in a local map. // If found, return action url, and target frame in *ptarget. static ParsedUrl* findhit(Map* map, Point p, int w, int h, int* ptarget) { int x; int y; ParsedUrl* dflt; int dflttarg; int xd; int yd; double xi; double yi; double xj; double yj; int np; double xr; double yr; int j; int i; Area *a; Dimen* c; int nc; int x1; int y1; int x2; int y2; int hit; x = p.x; y = p.y; dflt = nil; dflttarg = FTself; for(a = map->areas; a != nil; a = a->next) { c = a->coords; nc = a->ncoords; 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; switch(a->shape) { case SHrect: if(nc == 4) hit = x1 <= x && x <= x2 && y1 <= y && y <= y2; break; case SHcircle: if(nc == 3) { xd = x - x1; yd = y - y1; hit = xd*xd + yd*yd <= x2*x2; } break; case SHpoly: np = nc/2; hit = 0; xr = (double)x; yr = (double)y; j = np - 1; for(i = 0; i < np; j = i++) { xi = (double)d2pix(c[2*i], w); yi = (double)d2pix(c[2*i + 1], h); xj = (double)d2pix(c[2*j], w); yj = (double)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; } break; default: dflt = a->href; dflttarg = a->target; } if(hit) { *ptarget = a->target; return copyurl(a->href); } } *ptarget = dflttarg; return copyurl(dflt); } static int d2pix(Dimen d, int tot) { int ans; ans = dimenspec(d); if(dimenkind(d) == Dpercent) ans = (ans*tot)/100; return ans; } static GoSpec* newget(ParsedUrl* url, int target) { GoSpec* g; g = (GoSpec*)emallocz(sizeof(GoSpec)); g->kind = GoNormal; g->url = url; g->meth = HGet; g->target = target; return g; } static GoSpec* newpost(ParsedUrl* url, Rune* body, int target) { GoSpec* g; g = (GoSpec*)emallocz(sizeof(GoSpec)); g->kind = GoNormal; g->url = url; g->meth = HPost; g->body = body; g->target = target; return g; } static GoSpec* newspecial(int kind, HistNode* hn) { GoSpec* g; g = (GoSpec*)emallocz(sizeof(GoSpec)); g->kind = kind; g->histnode = hn; return g; } static GoSpec* copygospec(GoSpec* g) { GoSpec* ans; ans = (GoSpec*)emalloc(sizeof(GoSpec)); ans->kind = g->kind; ans->url = copyurl(g->url); ans->meth = g->meth; ans->body = Strdup(g->body); ans->auth = g->auth; ans->histnode = g->histnode; return ans; } static int gospecequal(GoSpec* a, GoSpec* b) { if(a->url == nil || b->url == nil) return 0; return urlequal(a->url, b->url) && a->meth == b->meth && !Strcmp(a->body, b->body); } static DocConfig* newdocconfig(Rune* fname, Rune* title, int initconfig, GoSpec* g) { DocConfig* d; d = (DocConfig*)emalloc(sizeof(DocConfig)); d->framename = fname; d->title = title; d->initconfig = initconfig; d->gospec = g; return d; } static DocConfig* copydocconfig(DocConfig* d) { DocConfig* ans; ans = (DocConfig*)emalloc(sizeof(DocConfig)); ans->framename = d->framename; ans->title = d->title; ans->initconfig = d->initconfig; ans->gospec = d->gospec; return ans; } static int docconfigequal(DocConfig* a, DocConfig* b) { return !Strcmp(a->framename, b->framename) && gospecequal(a->gospec, b->gospec); } static int docconfigequalarray(DocConfig** a1, int n1, DocConfig** a2, int n2) { int i; if(n1 != n2) return 0; for(i = 0; i < n1; i++) { if(a1[i] == nil || a2[i] == nil) continue; if(!docconfigequal((a1[i]), a2[i])) return 0; } return 1; } static HistNode_list* newhistnodelist(HistNode* hn, HistNode_list* l) { HistNode_list* ans; ans = (HistNode_list*)emalloc(sizeof(HistNode_list)); ans->histnode = hn; ans->next = l; return ans; } // 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. static void histaddedge(HistNode* a, HistNode* b, int atob) { HistNode_list* oldl; int there; HistNode_list* l; HistNode_list* newl; if(atob) oldl = a->succs; else oldl = a->preds; there = 0; for(l = oldl; l != nil; l = l->next) if(l->histnode == b) { there = 1; break; } if(there) newl = newhistnodelist(b, remhnode(oldl, b)); else newl = newhistnodelist(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). static HistNode_list* remhnode(HistNode_list* l, HistNode* hn) { HistNode* hdl; HistNode_list* ans; if(l == nil) return nil; hdl = l->histnode; if(hdl == hn) { ans = l->next; return ans; } return newhistnodelist(hdl, remhnode(l->next, hn)); } static HistNode* newhistnode(DocConfig* top, DocConfig** kids, int nkids, HistNode_list* preds, HistNode_list* succs) { HistNode* h; h = (HistNode*)emalloc(sizeof(HistNode)); h->topconfig = top; h->kidconfigs = kids; h->nkids = nkids; h->preds = preds; h->succs = succs; return h; } // Copy of a, with new kidconfigs array (so that it can be changed independent // of a), and clear the preds and succs. static HistNode* histnodecopy(HistNode* a) { int n; DocConfig** kc; int i; n = a->nkids; kc = nil; if(n > 0) { kc = (DocConfig**)emalloc(n * sizeof(DocConfig*)); for(i = 0; i < n; i++) kc[i] = copydocconfig(a->kidconfigs[i]); } return newhistnode(a->topconfig, kc, n, nil, 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. // In any case reorder the history array to put latest last in array. static void histadd(Frame* f, GoSpec* g, int navkind) { HistNode* oldcur; DocConfig* dc; HistNode* hnode; int hnodepos; int i; DocConfig* kc; int kidpos; int k; if(history.hlen <= history.n) { history.hlen += 20; history.h = (HistNode**)erealloc(history.h, history.hlen * sizeof(HistNode*)); } if(history.n > 0) oldcur = history.h[history.n - 1]; else oldcur = nil; dc = newdocconfig(Strdup(f->name), Strdup(g->url->url), navkind != GoHistnode, copygospec(g)); hnode = newhistnode(dc, nil, 0, nil, nil); if(f == top) g->target = FTtop; else if(oldcur != nil) { kidpos = -1; for(i = 0; i < oldcur->nkids; i++) { kc = oldcur->kidconfigs[i]; if(kc != nil && !Strcmp(kc->framename, f->name)) { kidpos = i; break; } } if(kidpos == -1) { if(dbg) trace("history botch\n"); } else { hnode = histnodecopy(oldcur); hnode->kidconfigs[kidpos] = dc; } } // see if equivalent node to hnode is already in history hnodepos = -1; for(i = 0; i < history.n; i++) { if(docconfigequal(hnode->topconfig, history.h[i]->topconfig)) { if((hnode->kidconfigs == nil && history.h[i]->topconfig->initconfig) || docconfigequalarray(hnode->kidconfigs, hnode->nkids, history.h[i]->kidconfigs, history.h[i]->nkids)) { hnodepos = i; hnode = history.h[i]; break; } } } if(hnodepos == -1) { hnodepos = history.n; history.h[history.n++] = hnode; } if(oldcur != nil && hnode != oldcur && navkind != GoHistnode) { histaddedge(oldcur, hnode, 1); histaddedge(hnode, oldcur, 0); } if(hnodepos != history.n - 1) { for(k = hnodepos; k < history.n - 1; k++) history.h[k] = history.h[k + 1]; history.h[history.n - 1] = hnode; } if(hnode->preds != nil) enable(ctllay.controls[CLCbackbut]); else disable(ctllay.controls[CLCbackbut]); if(hnode->succs != nil) enable(ctllay.controls[CLCfwdbut]); else disable(ctllay.controls[CLCfwdbut]); } // This is called just after layout of f. // Now we can put in correct doctitle, and make kids array if necessary. static void histupdate(Frame* f) { HistNode* hnode; Framelist* kl; Frame* kf; DocConfig** kc; DocConfig* dc; int i; hnode = history.h[history.n - 1]; if(f == top) { hnode->topconfig->title = Strdup(f->doc->doctitle); if(f->kids != nil && hnode->kidconfigs == nil) { kc = (DocConfig**)emalloc(listlen((List*)f->kids) * sizeof(DocConfig*)); i = 0; for(kl = f->kids; kl != nil; kl = kl->next) { kf = kl->val; if(kf->src != nil) kc[i] = newdocconfig(Strdup(kf->name), Strdup(kf->src->url), 1, newget(copyurl(kf->src), FTself)); i++; } hnode->kidconfigs = kc; } } else { for(i = 0; i < hnode->nkids; i++) { dc = hnode->kidconfigs[i]; if(dc != nil && !Strcmp(dc->framename, f->name)) { hnode->kidconfigs[i]->title = Strdup(f->doc->doctitle); return; } } if(dbg) trace("history update botch\n"); } } // Find the gokind node (-1==Back, 0==Same, +1==Forward) static HistNode* histfind(int gokind) { HistNode* cur; if(history.n > 0) { cur = history.h[history.n - 1]; switch(gokind) { case 1: if(cur->succs != nil) return cur->succs->histnode; break; case -1: if(cur->preds != nil) return cur->preds->histnode; break; case 0: return cur; break; } } return nil; } // for debugging static void histprint(void) { int i; int j; HistNode* hn; DocConfig* dc; trace("History\n"); for(i = 0; i < history.n; i++) { hn = history.h[i]; trace("Node %d:\n", i); dc = hn->topconfig; trace("\tframe=%S, target=%S, url=%U\n", dc->framename, targetname(dc->gospec->target), dc->gospec->url); if(hn->kidconfigs != nil) { for(j = 0; j < hn->nkids; j++) { dc = hn->kidconfigs[j]; if(dc != nil) trace("\t\t%d: frame=%S, target=%S, url=%U\n", j, dc->framename, targetname(dc->gospec->target), dc->gospec->url); } } if(hn->preds != nil) printhnodeindices("Preds", hn->preds); if(hn->succs != nil) printhnodeindices("Succs", hn->succs); } trace("\n"); } static void printhnodeindices(char* label, HistNode_list* l) { HistNode* hn; int i; trace("\t%s:", label); for(; l != nil; l = l->next) { hn = l->histnode; for(i = 0; i < history.n; i++) { if(hn == history.h[i]) { trace(" %d", i); break; } } if(i == history.n) trace(" ?"); } trace("\n"); } // Create HTML representation of history in "history.html" in user's config directory. // This is called from go thread group. static void dumphistory(void) { // TODO /* Rune* fname; FD* fd; Rune* line; uchar* buf; uchar* aline; int bufpos; int i; int j; int n; int nl; HistNode* hn; DocConfig* dc; char buf[ATOMICIO]; snprint(buf, sizeof(buf), fname = Strdup2(config.userdir, L"/history.html", 0); fd = create(fname, OWRITE, 384); if(fd == nil) { if(warn) trace("can't create history file\n"); return ; } line = L"