// stringname=startWindowJS
/*********************************************************************
This file contains support javascript functions used by a browser.
They are easier to write here in javascript than in C using the js api.
And it is portable amongst all js engines.
This file is converted into a C string and compiled and run
at the start of each javascript window.
Please take advantage of this machinery and put functions here,
including prototypes and getter / setter support functions,
whenever it makes sense to do so.

edbrowse support functions and native methods often start with eb$,
hoping they will not accidentally collide with js functions in the wild.
Example: eb$newLocation, a native method that redirects this web page to another.

It would be nice to run this file stand-alone, outside of edbrowse,
even if the functionality is limited.
To this end, I create the window object if it isn't already there,
using the obvious window = this.
*********************************************************************/
"use strict";
if(!this.window) {
this.window = this;
this.document = {};
this.eb$ctx = 77;
// Stubs for native methods that are normally provided by edbrowse.
// Example: alert, which we can replace with print,
// or console.log, or anything present in the command line js interpreter.
if(!window.print) this.print = console.log;
this.alert = print;
this.eb$nullfunction = function() { return null}
this.eb$voidfunction = function() { }
this.eb$truefunction = function() { return true}
this.eb$falsefunction = function() { return false}
this.db$flags = eb$falsefunction;
this.eb$newLocation = function (s) { print("new location " + s)}
this.eb$logElement = function(o, tag) { print("pass tag " + tag + " to edbrowse")}
this.eb$parent = function() { return this}
this.eb$top = function() { return this}
this.eb$frameElement = function() { return this}
this.eb$getter_cd = function() { return null}
this.eb$getter_cw = function() { return null}
;(function() { const void_functions = ["addEventListener",
    "removeEventListener",
    "eb$hasFocus", "eb$write", "eb$writeln"];
for(let k of void_functions)
window[k] = eb$voidfunction; })();
this.my$win = function() { return window}
this.my$doc = function() { return document}
}

// the third party deminimization stuff is in mw$, the master window.
// Other stuff too, that can be shared.
// The window should just be there from C, but in case it isn't.
if(!window.mw$) {
// mw$.share = 0 means I made up that window out of thin air
    this.mw$ = {share:0, URL:{}};
this.mw$.alert = this.mw$.alert3 = this.mw$.alert4 = print
    this.mw$.dispatchEvent = () => undefined;
    this.mw$.addEventListener = () => undefined;
    this.mw$.removeEventListener = () => undefined;
    this.mw$.getComputedStyle = () => {};
    this.mw$.structuredClone = () => {};
    this.mw$.attr = {};
    this.mw$.setupClasses = () => {};
// classes that setupClasses would have built, but didn't.
    this.URL = function(){}
    this.Node = function(){}
    this.Window = function(){}
    this.EventTarget = function(){}
    this.CSSStyleDeclaration = function(){}
}

// We need some shorthand for this rather large file.
// Think of these as macros; they are deleted at the end so they don't persist.
this.odp = Object.defineProperty;
// remember, we can't use odp inside function that run later:
// constructors, setters, methods, etc.

// set a window property, unseen, unchanging
this.swp = function(k, v) { odp(window, k, {value:v})}
// visible (enumerable), but still protected
this.swpv = function(k, v) { odp(window, k, {value:v,enumerable:true})}
// unseen, but changeable
this.swpc = function(k, v) { odp(window, k, {value:v, writable:true, configurable:true})}

// establish the prototype for inheritance, then set dom$class
// this is called as each html element is built
// If our made-up class is z$Foo, dom$class becomes Foo
// Letters mean set window member prototype
this.swpp = function(c, inherit) {
    const v = c.replace(/^z\$/, "");
    if(inherit)
        odp(window[c], "prototype", {value:new inherit})
    odp(window[c].prototype, "dom$class", {value:v})
}

// set document property, analogs of the set window property functions
this.sdp = function(k, v) { odp(document, k, {value:v})}
// set document property changeable
this.sdpc = function(k, v) { odp(document, k, {value:v, writable:true, configurable:true})}

/* Extremely useful even if non-standard hence the Eb$ prefix but use a named
    class as if people see it it really doesn't matter and makes the definition
    work better.

    Mostly based on the IterableWeakMap example from
    https://github.com/tc39/proposal-weakrefs with some reformatting.

    It needs to be in here so that the private properties end up as this
    window's versions of the objects.
*/
class Eb$IterableWeakMap {
    #weakMap = new WeakMap();
    #refSet = new Set();
    #finalizationGroup = new FinalizationRegistry(Eb$IterableWeakMap.#cleanup);

    static #cleanup({ set, ref }) {
        set.delete(ref);
    }

    constructor(iterable) {
        if (iterable)
            for (const [key, value] of iterable) this.set(key, value);
    }

    set(key, value) {
        const ref = new WeakRef(key);
        this.#weakMap.set(key, { value, ref });
        this.#refSet.add(ref);
        this.#finalizationGroup.register(key, {
            set: this.#refSet,
            ref
        }, ref);
    }

    get(key) {
        const entry = this.#weakMap.get(key);
        return entry && entry.value;
    }

    delete(key) {
        const entry = this.#weakMap.get(key);
        if (!entry) return false;
        this.#weakMap.delete(key);
        this.#refSet.delete(entry.ref);
        this.#finalizationGroup.unregister(entry.ref);
        return true;
    }

    has(key) {
        return this.#weakMap.has(key);
    }

    /* Use generators so that we don't end up holding a list of strong refs
        during iteration */

    *[Symbol.iterator]() {
        for (const ref of this.#refSet) {
            const key = ref.deref();
            if (!key) continue;
            const { value } = this.#weakMap.get(key);
            yield [key, value];
        }
    }

    entries() {
        return this[Symbol.iterator]();
    }

    *keys() {
        for (const [key, value] of this)  yield key;
    }

    *values() {
        for (const [key, value] of this)  yield value;
    }

    // This may not be accurate as we don't deref the keys
    get size() {
        return this.#refSet.size;
    }
}

// * don't understand all the error codes and subcodes.
// This is just a stub for now, to make acid 25 work.
Error.prototype.NAMESPACE_ERR = 1;

// use dom$class to make our own toString function, so that
// document.createElement("div").toString() says "[object HTMLDiv?Element]" as it should
// This is important to some websites!
swp("toString$nat", toString);
/* toString has to be replaceable by other websites, which happens more often
than you think. Apparently sometimes people also want to grab the toString
function directly from an object and expect to be able to call it without the
lack of a this binding causing problems. */
this.toString = Object.prototype.toString = function() {
    return this ? (
        this.dom$class ? "[object "+this.dom$class+"]" : toString$nat.call(this)
    ) : toString$nat.call(this);
}
odp(window, "toString", {enumerable:false})

/*********************************************************************
a node list is and isn't an array; I don't really understand it.
I'll just have it inherit from array, until someone tells me I'm wrong.
Similarly for HTMLCollection.
I seed it with an optional array, for my own convenience.
Users aren't suppose to instantiate anyways, so I can't get in trouble by doing this.
*********************************************************************/
swp("NodeList", function(v){
if(Array.isArray(v))
for(var i=0; i<v.length; ++i)
this.push(v[i])
})
swpp("NodeList", Array)
NodeList.prototype.toString = function(){return "[object NodeList]"}
swp("HTMLCollection", function(v){
if(Array.isArray(v))
for(var i=0; i<v.length; ++i)
this.push(v[i])
})
swpp("HTMLCollection", Array)
HTMLCollection.prototype.toString = function(){return "[object HTMLCollection]"}

/*********************************************************************
    Originally I developed the shared window for efficiency.
    There's no point in "compiling" the entire dom every time we bring up a new web page. Other browsers don't do that!
    That still holds but now there is another consideration: the context that holds startwindow.js never goes away, even if we free it.
    So the less in startwindow, the better.
    To this end I will try to move more stuff to the shared window.
This includes the definition of most of the DOM classes.
They still have to be "built" at runtime however; it's not a true compile.
Here's why - using URL as an example.
There are websites that replace URL.prototype.toString with their own function.
They want to change the way URLs stringify, or whatever. I can't
prevent sites from doing that, things might not work properly without it!
So, if site A does that in the shared window, and site B invokes
a.href.toString, directly or indirectly, B is calling a function from
the unrelated website A.
This could screw things up, or worse, site A could use it to hack into
site B, hoping site B is your banking site or something important.
So I can't define URL over there and say URL = mw$.url over here.
However, the shared window can "build" the URL class over here,
when asked to do so, and then the user is free to muck with the class
or its prototype methods or anything else.
So here is the line that does a lot, including the creation of the document object,
which is an instance of Document, as it should be.
*********************************************************************/

mw$.setupClasses(window);

/*********************************************************************
window is and must remain the global object.
Plenty of sites set window.foo = something, then refer to foo.
Conversely, plenty of sites set bar = something, then refer to window.bar.
There is no compromise on this matter.
At the same time, window has to be an instance of Window,
has to be an instance of EventTarget.
This line accomplishes that, and so far I haven't seen any side effects.
I hope I never do, because I can't think of any other way.
*********************************************************************/

Object.setPrototypeOf(window, Window.prototype)

// find the constructor for a custom element. This is part of html parsing.
// It is either the original browse, innerHTML, or document.write.
swp("findClass4Tag", function(tagname, above) {
    if(!above) alert4(`searching for custom ${tagname}`);
    else alert4(`searching for custom ${tagname} under ${above.nodeName} ${above.eb$seqno}`);
    let ce = customElements;
// see if there is another registry in our scope
    while(above) {
        if(above.custom$Elements) {
            ce = above.custom$elements;
            break;
        }
// don't go up past document
        if(above.nodeType == 9) break;
        above = above.parentNode;
    }
    let f = ce.get(tagname);
    if(!f) return new HTMLElement; // revert back to the default constructor
    let f2 = new f;
// f is almost always an extension of HTMLElement, it will therefore have
// eb$apch1, which we need to link this into the growing html tree.
// But if it doesn't, we have to provide it.
    if(!f2.eb$apch1)
        Object.defineProperty(f2, "eb$apch1", {value:mw$.eb$apch1});
    Object.defineProperty(f2, "connectedCallback$pending", {value:!!f2.connectedCallback, writable:true})
    return f2;
});

// make sure to wrap global dispatchEvent, so this becomes this window,
// and not the shared window.
swp("dispatchEvent", mw$.dispatchEvent.bind(window))
swp("addEventListener", mw$.addEventListener.bind(window))
swp("removeEventListener", mw$.removeEventListener.bind(window))

/* Apparently people want to muck with DOMException so can't be shared as
otherwise we end up with read-only prototype chain issues */
this.DOMException = function(message, name) {
    this.message = message
    this.name = name
    var error = Error(message)
    this.stack = error.stack
}
DOMException.prototype = Object.create(Error.prototype)
DOMException.prototype.constructor = DOMException

// importNode is the same as cloneNode, except it is copying a tree
// of objects from another context into the current context.
// Set the second parameter to true to indicate this.
sdp("importNode", function(start,deep) {
    window.cloneRoot1 = start;
    return mw$.clone1 (start,deep, true);
})

// point to shared methods in the master window
swp("UnsupportedError", mw$.UnsupportedError);
swp("my$win", mw$.my$win)
swp("my$doc", mw$.my$doc)
swp("natok", mw$.natok)
swp("db$flags", mw$.db$flags)
swp("eb$voidfunction", mw$.eb$voidfunction)
swp("eb$nullfunction", mw$.eb$nullfunction)
swp("eb$truefunction", mw$.eb$truefunction)
swp("eb$falsefunction", mw$.eb$falsefunction)
swpv("close", mw$.win$close)
swp("eb$visible", mw$.eb$visible)
swpc("atob", mw$.atob)
swpc("btoa", mw$.btoa)
swpv("prompt", mw$.prompt)
swpv("confirm", mw$.confirm)
swp("eb$newLocation", mw$.eb$newLocation)
swp("eb$logElement", mw$.eb$logElement)
swpv("alert", mw$.alert)
swp("alert3", mw$.alert3)
swp("alert4", mw$.alert4)
this.print = function() { alert("javascript is trying to print this document")}
this.stop = function() { alert("javascript is trying to stop the browse process")}
swp("dumptree", mw$.dumptree)
swp("uptrace", mw$.uptrace)
swp("by_esn", mw$.by_esn)
swp("showscripts", mw$.showscripts)
swp("searchscripts", mw$.searchscripts)
swp("showframes", mw$.showframes)
swp("snapshot", mw$.snapshot)
swp("aloop", mw$.aloop)
swp("showarg", mw$.showarg)
swp("showarglist", mw$.showarglist)
swp("set_location_hash", mw$.set_location_hash)
sdp("nodeContains", mw$.nodeContains)
swp("NodeFilter", mw$.NodeFilter)
sdpc("createNodeIterator", mw$.createNodeIterator)
sdpc("createTreeWalker", mw$.createTreeWalker)
swp("rowReindex", mw$.rowReindex)
swp("formReindex", mw$.formReindex)
swp("connectedCallbackStart", () => mw$.connectedCallbackCheck(my$doc()));
swpc("getComputedStyle", mw$.getComputedStyle.bind(window))
swp("mutFixup", mw$.mutFixup)
swp("makeSheets", mw$.makeSheets)
swpc("structuredClone", mw$.structuredClone.bind(window))

swp("dom$class", "Window")
// next two are overwritten if xml
sdpc("eb$xml", false)
swpv("scroll", eb$voidfunction)
swpv("scrollTo", eb$voidfunction)
swpv("scrollBy", eb$voidfunction)
swpv("scrollByLines", eb$voidfunction)
swpv("scrollByPages", eb$voidfunction)
sdp("close", eb$voidfunction)
swpv("blur", ()=>(document.activeElement = null))
swpv("focus", ()=>(document.activeElement = document.body))

swpv("self", window)
window.top = eb$top();
window.parent = window.eb$parent();
odp(window, "frameElement", {get: eb$frameElement,enumerable:true});

sdp("write", eb$write)
sdp("writeln", eb$writeln)
sdp("hasFocus", eb$hasFocus)
sdp("eb$ctx", eb$ctx)
sdp("eb$seqno", 0)

/* An ok (object keys) function for javascript/dom debugging.
 * This is in concert with the jdb command in edbrowse.
 * I know, it doesn't start with eb$ but I wanted an easy,
 * mnemonic command that I could type in quickly.
 * If a web page creates an ok function it will override this one.
And that does happen, e.g. the react system, so $ok is an alias for this. */
swpc("ok", Object.keys)
swpc("$ok", ok)

swp("nodeName", "WINDOW") // in case you want to start at the top.
sdpc("ownerDocument", null)

// produce a stack for debugging purposes
swp("step$stack", function(){
var s = "you shouldn't see this";
try { 'use strict'; eval("yyz$"); } catch(e) { s = e.stack; }
// Lop off some leading lines that don't mean anything.
for(var i = 0; i<5; ++i)
s = s.replace(/^.*\n/, "");
return s;
})

if(top == window) {
swpc("step$l", 0)
swpc("step$val", "")
swpc("step$go", "")
// First line of js in the base file of your snapshot might be
// step$l = 0, step$go = "c275";
// to start tracing at c275
} else {
// step$l should control the entire session, all frames.
// This is a trick to have a global variable across all frames.
odp(window, "step$l", {get:function(){return top.step$l}, set:function(x){top.step$l=x}});
odp(window, "step$go", {get:function(){return top.step$go}, set:function(x){top.step$go=x}});
// I don't use this trick on step$exp, because an expression should really live within its frame
}

swp("$zct", {}) // counters for trace points
function trace$ch(k) {
var c=($zct[k]>=0?++$zct[k]:($zct[k]=1));
step$val = k+":"+c;
var trip=false;
if(k === step$go||typeof step$exp==='string'&&eval(step$exp)) trip = true;
return trip ? 2 : step$l;
}

sdp("open", function() { return this })

/* Some visual attributes of the window.
 * These are simulations as edbrowse has no screen.
 * Better to have something than nothing at all. */
swp("height", 768)
swp("width", 1024)
swpv("pageXOffset", 0)
swpv("scrollX", 0)
swpv("pageYOffset", 0)
swpv("scrollY", 0)
swpv("devicePixelRatio", 1.0)
// document.status is removed because it creates a conflict with
// the status property of the XMLHttpRequest implementation
swp("defaultStatus", 0)
swp("returnValue", true)
swpv("menubar", mw$.generalbar)
swpv("statusbar", mw$.generalbar)
swpv("scrollbars", mw$.generalbar)
swpv("toolbar", mw$.generalbar)
swpv("personalbar", mw$.generalbar)
swp("resizable", true)
swp("directories", false)
if(window == top) {
swpv("name", "unspecifiedFrame")
} else {
odp(window, "name", {get:function(){return frameElement.name}});
// there is no setter here, should there be? Can we set name to something?
// Should it propagate back up to the frame element name?
}

sdp("bgcolor", "white")
sdp("contentType", "text/html")
function readyStateComplete() {
    document.readyState$2 = "complete"; document.activeElement = document.body;
    let e = new Event;
    e.initEvent("readystatechange", true, true);
    document.dispatchEvent(e);
}

swpv("screen", {
height: 768, width: 1024,
availHeight: 768, availWidth: 1024, availTop: 0, availLeft: 0,
colorDepth: 24})

swp("console", {
debug: function(obj) { mw$.logtime(3, "debug", obj)},
log: function(obj) { mw$.logtime(3, "log", obj)},
info: function(obj) { mw$.logtime(3, "info", obj)},
warn: function(obj) { mw$.logtime(3, "warn", obj)},
error: function(obj) { mw$.logtime(3, "error", obj)},
timeStamp: function(label) { if(label === undefined) label = "x"; return label.toString() + (new Date).getTime(); }
})

swpv("navigator", {})
navigator.appName = "edbrowse";
navigator["appCode Name"] = "edbrowse C/quickjs";
/* not sure what product is about */
navigator.product = "edbrowse";
navigator.productSub = "3.7";
navigator.vendor = "Karl Dahlke";
navigator.javaEnabled = eb$falsefunction;
navigator.taintEnabled = eb$falsefunction;
navigator.cookieEnabled = true;
navigator.onLine = true;
navigator.mimeTypes = [];
navigator.plugins = [];
// the rest of navigator, and of course the plugins,
// must be filled in at run time based on the config file.
// This is overwritten at startup by edbrowse.
navigator.userAgent = "edbrowse/3.0.0";
// might be useful to pretend like we have low bandwidth,
// so the website doesn't send down all sorts of visual crap.
navigator.connection = {
downlink: 50,
downlinkMax: 100,
effectiveType: "2g",
rtt: 8,
saveData: false,
type: "unknown",
addEventListener: eb$voidfunction,
removeEventListener: eb$voidfunction,
};

// There's no history in edbrowse.
// Only the current file is known, hence length is 1.
swpv("history", {
length: 1,
next: "",
previous: "",
back: eb$voidfunction,
forward: eb$voidfunction,
go: eb$voidfunction,
pushState: eb$voidfunction,
replaceState: eb$voidfunction,
toString: function() {  return "Sorry, edbrowse does not maintain a browsing history."}
})

swp("CSS", mw$.CSS)
swp("Intl", mw$.Intl)

// some base arrays - lists of things we'll probably need
sdp("heads", [])
sdp("bases", [])
sdp("links", [])
sdp("metas", [])
sdp("styles", [])
sdp("bodies", [])
sdp("forms", [])
sdp("elements", [])
sdp("divs", [])
sdp("labels", [])
sdp("htmlobjs", [])
sdp("scripts", [])
sdp("paragraphs", [])
sdp("headers", [])
sdp("footers", [])
sdp("tables", [])
sdp("images", [])
// styleSheets is a placeholder for now; I don't know what to do with it.
sdp("styleSheets", [])

swpc("frames$2", []);
swpv("frames", {})
odp(frames, "length", {get:function(){return frames$2.length}})
odp(window, "length", {get:function(){return frames$2.length},enumerable:true})

// pending jobs, mostly to debug promise functions.
swp("$pjobs", [])
swp("$pjobsa", [])
swpc("promiseCatchFunctionNative", eb$voidfunction)
swp("promiseCatchFunction", function(e) {
// use alert 3 so this will fold into the debug stream,  db>debug.log
alert3("promise error");
alert3(e);
alert3(e.stack);
// call the original native catch function, which sets up for
// promise.catch(), which the web page may be expecting.
return promiseCatchFunctionNative(e);
})

String.prototype.at = function(n) {
if(typeof n != "number") return undefined;
var l = this.length;
if(n >= 0) {
if(n >= l) return undefined;
return this.charAt(n);
}
n = -n;
if(n > l) return undefined;
return this.charAt(l-n);
}

/*********************************************************************
This is a special routine for textarea.innerHTML = "some html text";
I assume, with very little data to go on, that the html is rendered
in some fashion, i.e. turned into text, then pushed into the text area.
This is just a first step. If there is a text node below then I
cross that over to textarea.value. If it's anything more complicated
than that, I throw up my hands and give up.
Yes, I found this in the real world when trying to unsubscribe from
	https://www.credomobile.com
I remove the textNode below, because it would be rendered by edbrowse,
and the text that was just put in the buffer would also be on the main page.
Note the chain of setters.
Javascript calls innerHTML, which is a setter written in C.
That calls this routine, which pushes the rendered string into value,
which is another setter, writtten in C.
If all this works I'll be amazed.
*********************************************************************/

swp("textarea$html$crossover", function(t) {
if(!t || t.dom$class != "HTMLElement" || t.type != "textarea")
return;
t.value = "";
// It's a textarea - what is below?
if(t.childNodes.length == 0) return; // nothing below
var tn; // our textNode
if(t.childNodes.length == 1 && (tn = t.firstChild) &&
tn.dom$class == "Text") {
var d = (tn.data ? tn.data : "");
t.value = d;
t.removeChild(tn);
return;
}
alert3("textarea.innerHTML is too complicated for me to render");
})

// the performance registry
swp("pf$registry", {mark:{},measure:{},measure0:{},resourceTiming:{}})
odp(pf$registry, "measure0", {enumerable:false});
swpv("Performance", function(){})
Performance.prototype = {
// timeOrigin is the start time of this window, I guess
timeOrigin: Date.now(),
now:function(){ return Date.now()},
mark: function(name) { pf$registry.mark[name] = Date.now()},
clearMarks: function(e) { var m = pf$registry.mark; if(e) delete m[e]; else for(var i in m) delete m[i];},
measure:function(name,s,e) { var m = pf$registry.mark,  n = m[s] && m[e] ? m[e]-m[s] : 0; pf$registry.measure[name] = n; pf$registry.measure0[name] = this.now();},
clearMeasures: function(e) { var m = pf$registry.measure, m0 = pf$registry.measure0; if(e) delete m[e],delete m0[e]; else for(var i in m) delete m[i],delete m0[i];},
clearResourceTimings: function(e) { var m = pf$registry.resourceTiming; if(e) delete m[e]; else for(var i in m) delete m[i];},
getEntriesByType:function(type){var top = pf$registry[type];
var list = []; if(!top) return list;
for(var i in top) list.push({name:i, entryType:type, timeStamp:(type==="measure"?pf$registry.measure0[i]:top[i]), duration:(type==="measure"?top[i]:0)})
mw$.sortTime(list);
return list;
},
getEntriesByName:function(name,type){
var list = [];
if(type) {
var top = pf$registry[type];
if(top && top[name])
list.push({name:name, entryType:type, timeStamp:(type==="measure"?pf$registry.measure0[name]:top[name]), duration:(type==="measure"?top[name]:0)})
} else {
for(type in pf$registry) {
var m = pf$registry[type];
if(m[name])
list.push({name:name, entryType:type, timeStamp:(type==="measure"?pf$registry.measure0[name]:m[name]), duration:(type==="measure"?m[name]:0)})
}
mw$.sortTime(list);
}
return list;
},
getEntries:function(){
var list = [], r = pf$registry;
for(var type in r) {
var m = r[type];
for(var i in m) list.push({name:i, entryType:type, timeStamp:(type==="measure"?r.measure0[i]:m[i]), duration:(type==="measure"?m[i]:0)})
}
mw$.sortTime(list);
return list;
},
// at least have the object, even if it doesn't have any timestamps in it
timing:{navigationStart:0},
}
odp(window, "performance", {get: function(){return new Performance}});

// this is a stub, I hope I don't have to implement this stuff.
swp("PerformanceObserver", {
supportedEntryTypes: {
// no types are supported
includes: eb$falsefunction
}
})

/*********************************************************************
When a script runs it may call document.write. But where to put those nodes?
I think they belong under the script object, I think that's intuitive,
but most browsers put them under body,
or at least under the parent of the script object,
but always in position, as though they were right here in place of the script.
This function lifts the nodes from the script object to its parent,
in position, just after the script.
Watch out! If the script has inline text, it is a proper child of the script,
and should not be moved. Check for eb$nomove.
*********************************************************************/

swp("eb$uplift", function(s) {
var p = s.parentNode;
if(!p) {
// this can happen if a script removes itself from the tree.
// And perhaps it created some nodes via document.write.
// No I've never seen this combination in the wild.
p = s.orig$parent;
}
if(!p) return;
var before = s.nextSibling;
var c = s.firstChild;
if(c && c.nodeType == 3 && c.eb$nomove) c = c.nextSibling;
while(c) {
var hold = c.nextSibling;
if(before) p.insertBefore(c, before);
else p.appendChild(c);
c = hold;
}
})

swp("onmessage$$queue", []);
swpv("postMessage", function (message,target_origin, transfer) {
    let locstring = `${window.location.protocol}//${window.location.hostname}:${window.location.port}`;
    if(!window.location.port)
        locstring += window.mw$.setDefaultPort(window.location.protocol);
    const my_win = my$win();
    if (!target_origin || target_origin == '/') {
        let l = my_win.location;
        target_origin = l.protocol + "//" + l.hostname;
    }

    if(target_origin != '*' && !target_origin.match(/:\d*$/)) {
        // We need a port but don't have one
        let target_protocol = target_origin.replace(/:.*/, ":");
        let standard_port = window.mw$.setDefaultPort(target_protocol);
        target_origin += ":" + standard_port;
    }
    if (target_origin == locstring || target_origin == "*") {
        const me = {};
        let w = my$win();
        let l = w.location;
        me.origin = l.protocol + "//" + l.hostname;
        me.data = window.structuredClone(message);
        me.source = w;
        if(transfer) {
            me.ports = transfer;
            // If these objects had a context, they are now owned by this one.
            for(let i = 0; i < transfer.length; ++i)
                if(transfer[i].eb$ctx) transfer[i].eb$ctx = window.eb$ctx;
        }
        window.onmessage$$queue.push(me);
        alert3("posting message of length " + message.length + " to window context " + window.eb$ctx + " ↑" +
            (message.length >= 200 ? "long" : message)
            + "↑");
    } else {
        alert3("postMessage mismatch " + locstring + " | " + target_origin + " carrying ↑" +
            (message.length >= 200 ? "long" : message)
            + "↑");
    }
})
swp("onmessage$$running", mw$.onmessage$$running)

// The Attr class and getAttributeNode().
swp("Attr", function(){ this.owner = null; this.name = ""})
swpp("Attr", null)
Attr.prototype.isId = function() { return this.name === "id"; }
Attr.prototype.cloneNode = mw$.cloneAttr

// this is sort of an array and sort of not.
// For one thing, you can call setAttribute("length", "snork"), so I can't use length.
swp("NamedNodeMap", function() { this.length = 0})
swpp("NamedNodeMap", null)
NamedNodeMap.prototype.push = function(s) { this[this.length++] = s; }
NamedNodeMap.prototype.item = function(n) { return this[n]; }
NamedNodeMap.prototype.getNamedItem = function(name) { return this[name.toLowerCase()]; }
NamedNodeMap.prototype.setNamedItem = function(name, v) { this.owner.setAttribute(name, v);}
NamedNodeMap.prototype.removeNamedItem = function(name) { this.owner.removeAttribute(name);}

swp("MediaQueryList", function() {
    this.matches = false;
    this.media = "";
});
swpp("MediaQueryList", null)
MediaQueryList.prototype.addEventListener = mw$.addEventListener;
MediaQueryList.prototype.removeEventListener = mw$.removeEventListener;
MediaQueryList.prototype.nodeName = "MediaQueryList";
MediaQueryList.prototype.addListener = function(f) { this.addEventListener("mediaChange", f, false); };
MediaQueryList.prototype.removeListener = function(f) { this.removeEventListener("mediaChange", f, false); };

swpv("matchMedia", function(s) {
var q = new MediaQueryList;
q.media = s;
q.matches = eb$media(s);
return q;
})

// Most of the instance method for the Node class are defined
// in the shared window. These are here because they reference
// NodeList or HTMLCollection.

swp("live$wrapper", function(f, start, arg) {
// get the result as an array
var a = f.call(start, arg)
var c = new HTMLCollection(a)
// set an observer to watch the subtree based at start,
// and when children are added or removed, recompute the array.
// This is called a live array.
// It is not yet implemented.
return c})

this.nodep = Node.prototype;
nodep.getElementsByTagName = function(t) { return live$wrapper(mw$.getElementsByTagName, this, t)}
nodep.getElementsByName = function(t) { return live$wrapper(mw$.getElementsByName, this, t)}
nodep.getElementsByClassName = function(t) { return live$wrapper(mw$.getElementsByClassName, this, t)}

sdp("implementation", {
owner: document,
/*********************************************************************
This is my tentative implementation of hasFeature:
hasFeature: function(mod, v) {
// I'll say edbrowse supports html5
var supported = { "html": "5", "Core": "?", "XML": "?"};
if(!supported[mod]) return false;
if(v == undefined) return true; // no version specified
return (v <= supported[mod]);
},
But this page says we're moving to a world where this function is always true,
https://developer.mozilla.org/en-US/docs/Web/API/Document/implementation
so I don't know what the point is.
*********************************************************************/
hasFeature: eb$truefunction,
createDocumentType: function(tag, pubid, sysid) {
if(!tag.match(/^\w+:\w+$/) &&
!tag.match(/^https?:\/\//)) {
// acid 25 says we throw an exception
let e = new Error;
e.code = e.NAMESPACE_ERR;
e.INVALID_ACCESS_ERR = 15;
throw(e);
}
let d = new DocumentType;
// need to set the properties of d based on the parameters of this function
return d;
},
// https://developer.mozilla.org/en-US/docs/Web/API/DOMImplementation/createHTMLDocument
createHTMLDocument: function(t) {
if(t == undefined) t = "Empty"; // the title
var f = this.owner.createElement("iframe");
var d = f.contentDocument; // this is the created document
d.title = t;
return d;
},
createDocument: function(uri, str, t) {
// I don't know if this is right at all, but it's quick and easy
var doc = document.createElementNS(uri, "document");
if(str) {
var below = document.createElementNS(uri, str);
doc.appendChild(below);
}
return doc;
}
})

// a simpler version of handlerCompile, for setTimeout().
// We don't need to bind to this or return a value.
swp("handlerCompile",  function(f) {
let cf; // the compiled function
try {
cf = eval(`(function(){${f}})`);
} catch(e) {
cf = eval("(function(){})");
alert3("timeout syntax error <" + f + ">");
}
cf.body = f;
cf.toString = function() { return this.body; }
return cf;
})

// Request, Response, Headers, fetch; link to third party code in master window.
// fetch calls XMLHttpRequest, but puts the Response in a Promise
swpc("Headers", mw$.Headers)
swpc("Request", mw$.Request)
swpc("Response", mw$.Response)
swpc("fetch", mw$.fetch)

// pages seem to want document.style to exist
sdp("style", new CSSStyleDeclaration)
document.style.element = document;
document.style.bgcolor = "white";

// originally ms extension pre-DOM, we don't fully support it
//but offer the legacy document.all.tags method.
sdp("all", {})
document.all.tags = function(s) {
return mw$.gebtn(document.body, s.toLowerCase(), false, true);
}

/*********************************************************************
We may want to capture the mod time from the http headers, when we download,
and remember it and use it here. It would be more efficient,
and obviate the case where the head request didn't work for some reason.
But retain only the base html modification time, not js files, css files,
frames on the page, etc.
And update if the page is replaced with another page.
So we have to be a bit careful.
There is also the matter of a local html file. It would be good to grab
the modification time on the file, stat().
That requires a native method, that we could put in the master window;
is there any security risk in doing that?
Meantime, this should serve.
We may want to put it on Document.prototype, not just the primary document,
I don't know if that makes sense.
*********************************************************************/
odp(document, "lastModified", {
get: function() {
return mw$.lastModifiedByHead(this.location);
}});

swp("eb$demin", mw$.deminimize)
swp("eb$watch", mw$.addTrace)
/*
swp("$uv", [])
swp("$uv$sn", 0)
*/
swpc("$jt$c", 'z')
swpc("$jt$sn", 0)

// Local storage, this is per window.
// Then there's sessionStorage, and honestly I don't understand the difference.
// This is NamedNodeMap, to take advantage of preexisting methods.
swp("localStorage", {})
swp("sessionStorage", {})
; (function() {
var cnlist = [localStorage, sessionStorage];
for(let cn of cnlist) {
odp( cn, "attributes", { get: function(){ if(!this.attributes$2) {
Object.defineProperty(this, "attributes$2", {value:new NamedNodeMap})
this.attributes$2.owner = this
this.attributes$2.ownerDocument = my$doc()
}
return this.attributes$2}})
// tell me we don't have to do NS versions of all these.
cn.getAttribute = cn.getItem = mw$.attr.getAttribute;
cn.setAttribute = cn.setItem = mw$.attr.setAttribute;
cn.removeAttribute = cn.removeItem = mw$.attr.removeAttribute;
cn.clear = function() {
var l;
while(l = this.attributes.length)
this.removeItem(this.attributes[l-1].name);
}
}
})()

// we seem to be missing Array.item
Array.prototype.item = function(x) { return this[x] };
odp(Array.prototype, "item", { enumerable: false});

// On the first call this setter just creates the url, the location of the
// current web page, But on the next call it has the side effect of replacing
// the web page with the new url.
odp(window, "location", {
get: function() { return window.location$2; },
set: function(h) {
if(!window.location$2) {
window.location$2 = new z$URL(h);
} else {
window.location$2.href = h;
}
}, enumerable:true});
// We need location$2 so we can define origin and replace etc
swpc("location$2", new URL)
odp(location$2, "origin", {get:function(){
return this.protocol ? this.protocol + "//" + this.host : null}});
odp(window, "origin", {get: function(){return location.origin}});
sdp("location$2", new URL)
odp(document, "location", {
get: function() { return this.location$2; },
set: function(h) {
if(!this.location$2) {
this.location$2 = new z$URL(h);
} else {
this.location$2.href = h;
}
}, enumerable:true});
    location.replace = document.location.replace = function(s) { this.href = s};
odp(window.location,'replace',{enumerable:false});
odp(document.location,'replace',{enumerable:false});
odp(window.location,'eb$ctx',{value:eb$ctx});
odp(document.location,'eb$ctx',{value:eb$ctx});

// nasa.gov and perhaps other sites check for self.constructor == Window.
// That is, Window should be the constructor of window.
// The constructor is Object by default.
swp("constructor", Window)

swp("eb$qs$start", function() { mw$.cssGather(true); mw$.frames$rebuild(window);})
swp("frames$rebuild", function() {mw$.frames$rebuild(window);})

swp("DOMParser", mw$.DOMParser)

swp("XMLSerializer", function(){})
XMLSerializer.prototype.serializeToString = function(root) {
alert3("trying to use XMLSerializer");
return "<div>XMLSerializer not yet implemented</div>"; }

swpc("css$ver", 0)
swpc("css_all", "")
swpc("last$css_all", "")
swpc("cssSource", [])
sdp("xmlVersion", 0)

swp("MutationObserver", function(f) {
    // We need to know what window we're in to queue the callback microtask
    this.observed$window = my$win();
    if (typeof f !== "function") throw new TypeError("not a function");
    this.callback = f;
    this.active = false;
    this.targets = new Eb$IterableWeakMap;
    this.async = true; // run as microtask by default
    this.notification$queue = [];
})
swpp("MutationObserver", null)
MutationObserver.prototype.disconnect = function() {
    const ts = this.targets.size;
    const nl = this.notification$queue.length;
    alert3(`MutationObserver disconnecting from ${ts} targets with ${nl} unprocessed records`);
    this.notification$queue.length =  0;
    this.active = false;
    for (const t of this.targets.keys()) {
        alert4(`MutationObserver disconnecting ${t.dom$class} tag ${t.eb$seqno}`);
        // Clear the strong reference in case the observer is being dropped
        t.eb$observers.delete(this);
        // Clear the weak reference in case the observer is being reused
        this.targets.delete(t);
    }
}
MutationObserver.prototype.observe = function(target, cfg) {
    /* Not sure if this ever happens in the wild but protect against someone
        accidentally externally altering the config for an observed target as
        the config is passed in by reference.
    */
    const cfg_copy = structuredClone(cfg);
    this.observe$target(target, cfg_copy);
    if(cfg_copy.subtree)
        this.observe$subtree(target, cfg_copy);
}
MutationObserver.prototype.observe$target = function (target, cfg, dbg=alert3) {
    // May have other valid targets so don't disconnect
    if(typeof cfg != "object" || !(target instanceof Node))
        throw new TypeError("invalid argument types");
    dbg(`observing ${target.dom$class} tag ${target.eb$seqno} config ${JSON.stringify(cfg)}`)
    this.targets.set(target, cfg);
    this.active = true;
    if (!target.eb$observers) {
        dbg("Attaching first observer");
        Object.defineProperty(target, "eb$observers", {value: new Set});
    }
    target.eb$observers.add(this);
}
MutationObserver.prototype.observe$subtree = function(target, cfg) {
    /* If we're observing subtrees then we need to directly observe those
        targets as well as the idea is that if a subtree is moved we keep
        observing that. We will fix appended children in mutFixup. This also
        means that, if the surrounding scripting doesn't care about this
        observer (i.e. doesn't hold any other strong references) and all its
        targets go away then it'll be cleaned up also. This is per spec, avoids
        a memory leak and makes the mutFixup code much simpler when it comes
        to handling advanced observer use-cases.
    */
    if (target.is$frame) return;
    let a = target.childNodes.slice();
    let i = 0;
    let n;
    while (i < a.length) {
        n = a[i++];
        if (!this.targets.has(n)) this.observe$target(n, cfg, alert4);
        if (n.is$frame) continue;
        if (n.childNodes) a.push(...n.childNodes);
    }
}
MutationObserver.prototype.takeRecords = function() {
    // Shallow clone as the records must refer to the DOM and are otherwise safe
    const ret = this.notification$queue.slice();
    /* Drop our copy of the records as we've processed them now and don't want
        to be impacted by external changes. */
    this.notification$queue.length = 0;
    return ret;
}

swp("MutationRecord", function(){})
swpp("MutationRecord", null)
swpv("crypto", {})
crypto.getRandomValues = function(a) {
if(typeof a != "object") return NULL;
var l = a.length;
for(var i=0; i<l; ++i) a[i] = Math.floor(Math.random()*0x100000000);
return a;
}

swpc("ra$step", 0)
swpv("requestAnimationFrame", function() {
// This absolutely doesn't do anything. What is edbrowse suppose to do with animation?
return ++ra$step;
})

swpv("cancelAnimationFrame", eb$voidfunction)

// link in the blob code
swp("Blob", mw$.Blob)
swp("File", mw$.File)
swp("FileReader", mw$.FileReader)
URL.createObjectURL = mw$.URL.createObjectURL
URL.revokeObjectURL = mw$.URL.revokeObjectURL
swp("FormData", mw$.FormData)
swp("TextEncoder", mw$.TextEncoder)
swp("TextDecoder", mw$.TextDecoder)
swp("MessagePort", mw$.MessagePort)
swp("MessageChannel", mw$.MessageChannel)
swp("mp$registry", []) // MessagePort registry
swpc("URLSearchParams", mw$.URLSearchParams)

swp("trustedTypes", function(){})
trustedTypes.createPolicy = function(pn,po){
var x = {policyName: pn};
for (var i in po) { x[i] = po[i]}
return x;
}
swp("AbortSignal", function(){})
AbortSignal.prototype = new EventTarget;
AbortSignal.prototype.aborted = false;
AbortSignal.prototype.reason = 0;
AbortSignal.prototype.throwIfAborted = eb$voidfunction;
AbortSignal.abort = function(){ var c = new AbortSignal(); c.aborted = true; return c; }
AbortSignal.timeout = function(ms){ var c = new AbortSignal();
// this is suppose to abort after a timeout period but I don't know how to do that
if(typeof ms == "number") alert3("abort after " + ms + "ms not implemented");
return c; }

swp("AbortController", function(){})
odp(AbortController.prototype, "signal",
{get:function(){return new AbortSignal}});
AbortController.prototype.abort = function(){
alert3("abort dom request not implemented"); }

/* quickjs-ng has a native implementation of queueMicrotask but quickjs
doesn't currently */
if (!window.queueMicrotask) {
    alert3("Using fallback for queueMicrotask");
    swpv("queueMicrotask", function(f) {
        if (typeof f !== "function") throw new TypeError("not a function");
/* Per the spec we need to wait until after the caller's executed but before
timers. This means we need to simulate with promises but the error handling
isn't quite right as I can't find a way to rethrow outside the promise chain.
This is simple and closer to the spec than we have been but better is to use the
implementation provided by quickjs-ng. */
        Promise.resolve().then(f).catch(
            (e) => alert3("Error in microtask: " + e)
        );
    });
}

swp("IntersectionObserverEntry", function(){})
swp("IntersectionObserver", function(callback, o){
this.callback = callback, this.root = null;
var h = 1.0;
if(typeof o == "object") {
if(o.root) this.root = o.root;
if(o.threshold) h = o.threshold;
}
var alertstring = "intersecting " + (this.root ? this.root : "viewport");
if(typeof h == "number") alertstring += " with threshold " + h;
else if(Array.isArray(h)) {
alertstring += " with threshold [";
for(var i = 0; i < h.length; ++i) {
var n = h[i];
if(i) alertstring += ',';
if(typeof n == "number") alertstring += n;
}
alertstring += ']';
}
alert3(alertstring);
})
/*********************************************************************
This is just trying to get something off the ground.
Assume our target is always visible.
I don't even know what visible means in edbrowse.
You have printed, or asked for, a line in the target area?
And what percentage of that target area is visible,
just because you printed a line therein?
This stuff is so visual it's almost impossible to simulate with any fidelity.
So for a start, everything is visible, and that might cause the
website to load anything you might ever look at or scroll down to,
making edbrowse even slower than it already is. But it's a start.
*********************************************************************/
IntersectionObserver.prototype.observe = function(t) {
var alertstring = "intersect with " + t;
if(t.eb$seqno) alertstring += "." + t.eb$seqno;
alert3(alertstring);
var e = new IntersectionObserverEntry;
e.target = t;
e.isIntersecting = true; // target is visible
e.intersectingRatio = 1.0; // the whole target is visible
// bounding rectangle is just the whole damn screen,
// hope nobody ever looks at it or expects it to be real.
e.boundingClientRect = this.root ? this.root.getBoundingClientRect() : document.getBoundingClientRect();
// I don't even know what these are!
e.rootBounds = e.intersectionRect = e.boundingClientRect;
// I guess we're ready to roll
queueMicrotask(() => this.callback([e]));
// in edbrowse the target remains visible forever, callback will never be called again.
// We don't have to remember target or the conditions of intersection etc.
}
IntersectionObserver.prototype.disconnect = eb$voidfunction
IntersectionObserver.prototype.unobserve = eb$voidfunction

// more visual stuff. But nothing resizes in edbrowse, ever,
// so this should be easy to stub out.
swp("ResizeObserver", function(){})
ResizeObserver.prototype.disconnect = eb$voidfunction;
ResizeObserver.prototype.observe = eb$voidfunction;
ResizeObserver.prototype.unobserve = eb$voidfunction;

// don't need these any more
;(function() {
    let names_to_delete = ["odp",
    "swgs", "swp", "swpv", "swpc", "swpp",
    "sdp", "sdpc", "nodep"];
    for (let k of names_to_delete) delete window[k]
})();
