<commit_before>/*
 * Copyright 2010-2013 Esrille Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "WindowProxy.h"

#include <new>
#include <iostream>
#include <boost/version.hpp>
#include <boost/bind.hpp>

#include "utf.h"

#include "BeforeUnloadEventImp.h"
#include "DOMImplementationImp.h"
#include "DocumentImp.h"
#include "HashChangeEventImp.h"
#include "KeyboardEventImp.h"
#include "MouseEventImp.h"
#include "NodeImp.h"
#include "css/BoxImage.h"
#include "css/Ico.h"
#include "css/ViewCSSImp.h"
#include "html/HTMLIFrameElementImp.h"
#include "html/HTMLLinkElementImp.h"
#include "html/HTMLScriptElementImp.h"
#include "http/HTTPConnection.h"

#include "Test.util.h"

namespace org { namespace w3c { namespace dom { namespace bootstrap {

WindowProxy::Parser::Parser(const DocumentPtr& document, int fd, const std::string& optionalEncoding) :
    stream(fd, boost::iostreams::close_handle),
    htmlInputStream(stream, optionalEncoding),
    tokenizer(&htmlInputStream),
    parser(document, &tokenizer)
{
    document->setCharacterSet(utfconv(htmlInputStream.getEncoding()));
}

WindowProxy::WindowProxy(unsigned short flags) :
    history(this),
    backgroundTask(this),
    thread(std::ref(backgroundTask)),
    view(0),
    viewFlags(0),
    flags(flags),
    detail(0),
    buttons(0),
    scrollWidth(0),
    scrollHeight(0),
    width(816),     // US letter size, 96 DPI
    height(1056),
    redisplay(false),
    zoomable(true),
    zoom(1.0f),
    faviconOverridable(false),
    windowDepth(0)
{
}

WindowProxy::~WindowProxy()
{
    if (auto parent = getParentProxy()) {
        for (auto i = parent->childWindows.begin(); i != parent->childWindows.end(); ++i) {
            if (i->get() == this) {
                parent->childWindows.erase(i);
                break;
            }
        }
    }
    backgroundTask.abort();
    thread.join();
}

WindowProxyPtr WindowProxy::createChildProxy(unsigned short flags, const HTMLIFrameElementPtr& iframe)
{
    auto child = std::make_shared<WindowProxy>(flags);
    if (child) {
        childWindows.push_back(child);
        child->parent = std::static_pointer_cast<WindowProxy>(self());
        child->setBase(getLocation().getHref());
        child->windowDepth = windowDepth + 1;
        child->frameElement = iframe;
    }
    return child;
}

void WindowProxy::setSize(unsigned w, unsigned h)
{
    if (width != w || height != h) {
        width = w;
        height = h;
        setViewFlags(Box::NEED_REFLOW);
        if (window && window->evaluateMedia())
            setViewFlags(Box::NEED_STYLE_RECALCULATION);
    }
}

void WindowProxy::enter()
{
    assert(window);
    window->enter(this);
}

void WindowProxy::exit()
{
    assert(window);
    window->exit(this);
}

void WindowProxy::enableZoom(bool value)
{
    zoomable = value;
}

float WindowProxy::getZoom() const
{
    return zoom;
}

void WindowProxy::setZoom(float value)
{
    if (zoomable) {
        zoom = value;
        if (view) {
            view->setZoom(zoom);
            redisplay = true;
        }
    }
}

void WindowProxy::updateView(ViewCSSImp* next)
{
    if (!next || view == next || !window || !window->getDocument())
        return;

    unsigned flags = 0;
    if (view) {
        flags |= view->gatherFlags();
        delete view;
    }
    view = next;
    setViewFlags(flags);
    view->setZoom(zoom);
    detail = 0;
    redisplay = true;

    if (!view->getTree()) {
        // cf. http://test.csswg.org/suites/css2.1/20110323/html4/root-box-003.htm
        canvas.shutdown();
        canvas.setup(width, height);
        canvas.beginRender(view->getBackgroundColor());
        canvas.endRender();
    }

    if (!getParentProxy()) {
        std::u16string title = view->getDocument()->getTitle();
        setWindowTitle(utfconv(title).c_str());
    }

    window->flushMediaQueryLists(view);
}

void WindowProxy::setWindowPtr(const WindowPtr& window)
{
    this->window = window;
    delete view;
    view = 0;
    viewFlags = 0;
    if (window)
        backgroundTask.restart(BackgroundTask::Cascade);
    detail = 0;
    redisplay = true;
    setFavicon();
}

bool WindowProxy::isBindingDocumentWindow()
{
    if (auto parent = getParentProxy()) {
        auto document = std::static_pointer_cast<DocumentImp>(parent->getDocument().self());
        return document->isBindingDocumentWindow(std::static_pointer_cast<WindowProxy>(self()));
    }
    return false;
}

bool WindowProxy::poll()
{
    if (!window)
        return false;
    auto document = window->getDocument();

    // Update the canvas before processing events.
    if (request.getReadyState() == HttpRequest::DONE && document && backgroundTask.getState() == BackgroundTask::Done) {
        ViewCSSImp* next = backgroundTask.getView();
        updateView(next);
        if (view) {
            unsigned short gathered = viewFlags | view->gatherFlags();
            if (gathered & Box::NEED_REPAINT) {
                gathered &= ~Box::NEED_REPAINT;
                redisplay = true;
                if (!gathered && (flags & Loading)) {
                    flags &= ~Loading;
                    document->decrementLoadEventDelayCount();
                }
                render(0);
            }
        }
    }

    while (backgroundTask.getState() == BackgroundTask::Done && !eventQueue.empty()) {
        const EventTask task = eventQueue.front();
        switch (task.type) {
        case EventTask::MouseDown:
        case EventTask::MouseUp:
            mouse(task);
            break;
        case EventTask::MouseMove:
            mouseMove(task);
            break;
        case EventTask::KeyDown:
            keydown(task);
            break;
        case EventTask::KeyUp:
            keyup(task);
            break;
        default:
            break;
        }
        eventQueue.pop_front();
    }

    for (auto i = childWindows.begin(); i != childWindows.end(); ++i) {
        auto child = *i;
        if (child->poll()) {
            redisplay |= true;
            if (view) {
                if (document && document->isBindingDocumentWindow(child))
                    view->setFlags(Box::NEED_SELECTOR_REMATCHING);
                else
                    view->setFlags(Box::NEED_REPAINT);
            }
        }
    }

    switch (request.getReadyState()) {
    case HttpRequest::UNSENT:
        break;
    case HttpRequest::OPENED:
    case HttpRequest::HEADERS_RECEIVED:
    case HttpRequest::LOADING:
        break;
    case HttpRequest::DONE:
        if (!document) {
            recordTime("%*shttp request done", windowDepth * 2, "");
            // TODO: Check header
            Document newDocument = getDOMImplementation()->createDocument(u"", u"", nullptr); // TODO: Create HTML document
            if ((document = std::dynamic_pointer_cast<DocumentImp>(newDocument.self()))) {
                // TODO: Fire a simple unload event.
                document->setDefaultView(std::static_pointer_cast<WindowProxy>(self()));
                document->setURL(request.getRequestMessage().getURL());
                document->setLastModified(request.getLastModified());
                window->setDocument(document);
                if (!request.getError())
                    history->update(window);
                else
                    document->setError(request.getError());
                document->enter();
                parser.reset(new(std::nothrow) Parser(document, request.getContentDescriptor(), request.getResponseMessage().getContentCharset()));
                document->exit();
                if (!parser)
                    break;  // TODO: error handling
            } else
                break;  // TODO: error handling
        }
        if (document->getReadyState() == u"loading") {
            // TODO: Note white it would be nice to parse the HTML docucment in
            // the background task, firstly we need to check if we can run JS
            // in the background.

            document->enter();

            if (!parser->processPendingParsingBlockingScript()) {
                document->exit();
                break;
            }
            // TODO: run this in the background
            Token token;
            do {
                token = parser->getToken();
                parser->processToken(token);
            } while (token.getType() != Token::Type::EndOfFile && !document->getPendingParsingBlockingScript());

            if (document->getPendingParsingBlockingScript()) {
                document->exit();
                break;
            }

            // TODO: Check if the parser has been aborted.
            document->resetStyleSheets();
            setViewFlags(Box::NEED_SELECTOR_REMATCHING);
            if (!(flags & Loading) && !isBindingDocumentWindow()) { // Note a binding document does not create its view.
                flags |= Loading;
                document->incrementLoadEventDelayCount();
            }

            parser.reset();
            document->exit();

            recordTime("%*shtml parsed", windowDepth * 2, "");
            if (4 <= getLogLevel())
                dumpTree(std::cerr, document);
        }

        if (!backgroundTask.isRestarting()) {
            switch (backgroundTask.getState()) {
            case BackgroundTask::Cascaded:
                HTMLElementImp::xblEnteredDocument(document);
                backgroundTask.wakeUp(BackgroundTask::Layout);
                break;
            case BackgroundTask::Init:
            case BackgroundTask::Done: {

                eventLoop();

                ViewCSSImp* next = backgroundTask.getView();
                updateView(next);
                if (document->getReadyState() == u"interactive") {
                    // TODO: Check if the parser has been aborted.

                    // Run from the step 3. of 'stops parsing'.
                    // cf. http://www.whatwg.org/specs/web-apps/current-work/multipage/the-end.html#the-end

                    // Process scripts that will execute when the document has finished parsing (i.e., defer).
                    // TODO: Check there's no style sheet that is blocking scripts.
                    if (document->processDeferScripts()) {
                        if (!document->hasContentLoaded()) {
                            if (events::Event event = std::make_shared<EventImp>()) {
                                event.initEvent(u"DOMContentLoaded", true, false);
                                document->dispatchEvent(event);
                            }
                            document->decrementLoadEventDelayCount();
                            document->setContentLoaded();

                            backgroundTask.restart(BackgroundTask::Cascade);
                        }
                    }
                }
                if (document->getReadyState() == u"complete") {
                }
                if (view) {
                    if (unsigned short gathered = viewFlags | view->gatherFlags()) {
                        viewFlags &= ~gathered;
                        if (gathered & Box::NEED_SELECTOR_REMATCHING) {
                            recordTime("%*strigger selector rematching", windowDepth * 2, "");
                            backgroundTask.restart(BackgroundTask::Cascade);
                            view = 0;
                        } else if (gathered & Box::NEED_SELECTOR_MATCHING) {
                            recordTime("%*strigger restyling", windowDepth * 2, "");
                            backgroundTask.wakeUp(BackgroundTask::Cascade);
                            view = 0;
                        } else if (gathered & (Box::NEED_STYLE_RECALCULATION | Box::NEED_EXPANSION | Box::NEED_CHILD_REFLOW | Box::NEED_REFLOW)) {
                            recordTime("%*strigger reflow", windowDepth * 2, "");
                            backgroundTask.wakeUp(BackgroundTask::Layout);
                            view = 0;
                        } else if (gathered & Box::NEED_REPAINT) {
                            redisplay = true;
                            if (flags & Loading) {
                                flags &= ~Loading;
                                document->decrementLoadEventDelayCount();
                            }
                        }
                    }
                }
                break;
            }
            default:
                break;
            }
        }
        break;
    default:
        break;
    }
    bool result = redisplay;
    redisplay = false;
    if (!result && view && view->hasExpired(getTick())) {
        view->setFlags(Box::NEED_REPAINT);
        result = true;
    }
    if (result)
        recordTime("%*strigger repaint", windowDepth * 2, "");
    return result;
}

void WindowProxy::render(ViewCSSImp* parentView)
{
    if (view) {
        std::string readyState = window->getDocument() ? utfconv(window->getDocument()->getReadyState()) : "";
        recordTime("%*srepaint begin: %s (%s)", windowDepth * 2, "", readyState.c_str(), view ? "render" : "canvas");
        if (view->gatherFlags() & Box::NEED_REPAINT) {
            view->clearFlags(Box::NEED_REPAINT);
            // TODO: if the size of the canvas has not been changed, reuse the same canvas.
            canvas.shutdown();
            canvas.setup(width, height);

            unsigned backgroundColor = view->getBackgroundColor();
            if (backgroundColor == 0 && !getParent())
                backgroundColor = 0xffffffff;
            canvas.beginRender(backgroundColor);

            view->render(parentView ? parentView->getClipCount() : 0);
            scrollWidth = view->getScrollWidth();
            scrollHeight = view->getScrollHeight();
            canvas.endRender();
        }
        if (2 <= getLogLevel() && backgroundTask.isIdle() && !view->gatherFlags()) {
            unsigned depth = 1;
            for (WindowProxyPtr w = getParentProxy(); w; w = w->getParentProxy())
                ++depth;
            std::cout << "\n## " << readyState;
            if (1 < depth)
                std::cout << " (" << depth << ')';
            std::cout << '\n';
            view->dump();
            std::cout << "##\n";
            std::cout.flush();
        }
        recordTime("%*srepaint end", windowDepth * 2, "");
    }
    canvas.render(width, height);
}

void WindowProxy::mouse(int button, int up, int x, int y, int modifiers)
{
    eventQueue.emplace_back(up ? EventTask::MouseUp : EventTask::MouseDown, modifiers, x, y, button);
}

void WindowProxy::mouseMove(int x, int y, int modifiers)
{
    if (!eventQueue.empty()) {
        EventTask& back = eventQueue.back();
        if (back.type == EventTask::MouseMove) {
            back.x = x;
            back.y = y;
            back.modifiers = modifiers;
            return;
        }
    }
    eventQueue.emplace_back(EventTask::MouseMove, modifiers, x, y);
}

void WindowProxy::keydown(unsigned charCode, unsigned keyCode, int modifiers)
{
    eventQueue.emplace_back(EventTask::KeyDown, modifiers, charCode, keyCode);
}

void WindowProxy::keyup(unsigned charCode, unsigned keyCode, int modifiers)
{
    eventQueue.emplace_back(EventTask::KeyUp, modifiers, charCode, keyCode);
}

void WindowProxy::mouse(const EventTask& task)
{
    int button = task.button;
    int up = (task.type == task.MouseUp) ? true : false;
    int x = task.x;
    int y = task.y;
    int modifiers = task.modifiers;

    if (!view)
        return;

    recordTime("%*smouse (%d, %d)", windowDepth * 2, "", x, y);

    int shift = button;
    if (shift == 1)
        shift = 2;
    else if (shift == 2)
        shift = 1;
    if (up)
        buttons &= ~(1u << shift);
    else
        buttons |= (1u << shift);

    Box* box = view->boxFromPoint(x, y);
    if (!box)
        return;
    if (WindowProxyPtr childWindow = box->getChildWindow()) {
        childWindow->mouse(button, up,
                           x - box->getX() - box->getBlankLeft(), y - box->getY() - box->getBlankTop(),
                           modifiers);
    }

    Element target = Box::getContainingElement(box->getTargetNode());

    // mousedown, mousemove
    if (auto event = std::make_shared<MouseEventImp>()) {
        if (!up) {
            clickTarget = target;
            ++detail;
        }
        event->initMouseEvent(up ? u"mouseup" : u"mousedown",
                            true, true, self(), detail, x, y, x, y,
                            modifiers & 2, modifiers & 4, modifiers & 1, false, button, nullptr);
        event->setButtons(buttons);
        target.dispatchEvent(event);
    }

    if (!up || detail == 0)
        return;
    Box* clickBox = view->boxFromPoint(x, y);
    if (box != clickBox)
        return;

    // click
    if (auto event = std::make_shared<MouseEventImp>()) {
        event->initMouseEvent(u"click",
                            true, true, self(), detail, x, y, x, y,
                            modifiers & 2, modifiers & 4, modifiers & 1, false, button, nullptr);
        event->setButtons(buttons);
        target.dispatchEvent(event);
    }
}

void WindowProxy::mouseMove(const EventTask& task)
{
    int x = task.x;
    int y = task.y;
    int modifiers = task.modifiers;

    if (!view)
        return;
    Box* box = view->boxFromPoint(x, y);
    if (!box)
        return;

    Element target = Box::getContainingElement(box->getTargetNode());
    Element prev = view->setHovered(target);

    if (prev != target) {
        if (html::Window c = interface_cast<html::HTMLIFrameElement>(prev).getContentWindow()) {
            if (auto w = std::dynamic_pointer_cast<WindowProxy>(c.self()))
                w->mouseMove(-1, -1, modifiers);
        }
    }
    if (auto childWindow = box->getChildWindow()) {
        childWindow->mouseMove(x - box->getX() - box->getBlankLeft(),
                               y - box->getY() - box->getBlankTop(),
                               modifiers);
    }
    if (prev != target) {
        // mouseout
        if (auto event = std::make_shared<MouseEventImp>()) {
            event->initMouseEvent(u"mouseout",
                                true, true, self(), 0, x, y, x, y,
                                modifiers & 2, modifiers & 4, modifiers & 1, false, 0, nullptr);
            event->setButtons(buttons);
            prev.dispatchEvent(event);
        }

        // mouseover
        if (auto event = std::make_shared<MouseEventImp>()) {
            event->initMouseEvent(u"mouseover",
                                true, true, self(), 0, x, y, x, y,
                                modifiers & 2, modifiers & 4, modifiers & 1, false, 0, nullptr);
            event->setButtons(buttons);
            target.dispatchEvent(event);
        }
    }

    // mousemove
    if (auto event = std::make_shared<MouseEventImp>()) {
        if (target != clickTarget)
            detail = 0;
        event->initMouseEvent(u"mousemove",
                            true, true, self(), detail, x, y, x, y,
                            modifiers & 2, modifiers & 4, modifiers & 1, false, 0, nullptr);
        event->setButtons(buttons);
        target.dispatchEvent(event);
    }
}

void WindowProxy::keydown(const EventTask& task)
{
    unsigned charCode = task.charCode;
    unsigned keyCode = task.keyCode;
    int modifiers = task.modifiers;

    Document document = window->getDocument();
    if (!document)
        return;
    Element e = document.getActiveElement();
    if (!e)
        return;

    if (auto iframe = std::dynamic_pointer_cast<HTMLIFrameElementImp>(e.self())) {
        if (auto child = std::dynamic_pointer_cast<WindowProxy>(iframe->getContentWindow().self()))
            child->keydown(charCode, keyCode, modifiers);
    }

    auto event = std::make_shared<KeyboardEventImp>(modifiers, charCode, keyCode, 0);
    event->initKeyboardEvent(u"keydown", true, true, self(), u"", u"", 0, u"", false, u"");
    e.dispatchEvent(event);

    if (!charCode)
        return;
    event = std::make_shared<KeyboardEventImp>(modifiers, charCode, keyCode, 0);
    event->initKeyboardEvent(u"keypress", true, true, self(), u"", u"", 0, u"", false, u"");
    e.dispatchEvent(event);
}

void WindowProxy::keyup(const EventTask& task)
{
    unsigned charCode = task.charCode;
    unsigned keyCode = task.keyCode;
    int modifiers = task.modifiers;

    Document document = window->getDocument();
    if (!document)
        return;
    Element e = document.getActiveElement();
    if (!e)
        return;

    if (auto iframe = std::dynamic_pointer_cast<HTMLIFrameElementImp>(e.self())) {
        if (auto child = std::dynamic_pointer_cast<WindowProxy>(iframe->getContentWindow().self()))
            child->keyup(charCode, keyCode, modifiers);
    }

    auto event = std::make_shared<KeyboardEventImp>(modifiers, charCode, keyCode, 0);
    event->initKeyboardEvent(u"keyup", true, true, self(), u"", u"", 0, u"", false, u"");
    e.dispatchEvent(event);
}

void WindowProxy::setFavicon(IcoImage* ico, std::FILE* file)
{
    if (auto parent = getParentProxy()) {
        if (parent->getFaviconOverridable())
            parent->setFavicon(ico, file);
        return;
    }
    for (size_t i = 0; i < ico->getPlaneCount(); ++i) {
        const IconDirectoryEntry& ent(ico->getEntry(i));
        if (BoxImage* image = ico->open(file, i)) {
            uint32_t* pixels = reinterpret_cast<uint32_t*>(image->getPixels());
            setIcon(i, ent.getWidth(), ent.getHeight(), pixels);
            delete image;
        }
    }
}

void WindowProxy::setFavicon(BoxImage* image)
{
    if (auto parent = getParentProxy()) {
        if (parent->getFaviconOverridable())
            parent->setFavicon(image);
        return;
    }
    uint32_t* pixels = reinterpret_cast<uint32_t*>(image->getPixels());
    setIcon(0, image->getNaturalWidth(), image->getNaturalHeight(), pixels);
}

void WindowProxy::setFavicon()
{
    for (WindowProxyPtr w = std::static_pointer_cast<WindowProxy>(self()); w; w = w->getParentProxy()) {
        if (w->getParentProxy() && !w->getParentProxy()->getFaviconOverridable())
            return;
        if (!w->window)
            continue;
        auto document = w->window->getDocument();
        if (!document)
            continue;
        html::HTMLHeadElement head = document->getHead();
        if (!head)
            continue;
        for (auto i = head.getFirstElementChild(); i; i = i.getNextElementSibling()) {
            if (auto link = std::dynamic_pointer_cast<HTMLLinkElementImp>(i.self())) {
                if (link->setFavicon(document))
                    return;
            }
        }
    }
}

//
// CSSOM View support operations
//

Element WindowProxy::elementFromPoint(float x, float y)
{
    if (!view)
        return nullptr;
    if (x < 0.0f || width < x || y < 0.0f || height < y)
        return nullptr;
    Box* box = view->boxFromPoint(x, y);
    if (!box)
        return nullptr;
    for (Node node = box->getTargetNode(); node; node = node.getParentNode()) {
        if (node.getNodeType() == Node::ELEMENT_NODE)
            return interface_cast<Element>(node);
    }
    return nullptr;
}

//
// html::Window
//

html::Window WindowProxy::getWindow()
{
    return self();
}

Any WindowProxy::getSelf()
{
    return self();
}

void WindowProxy::setSelf(Any self)
{
    // TODO: implement me!
}

Document WindowProxy::getDocument()
{
    return !window ? 0 : window->getDocument();
}

std::u16string WindowProxy::getName()
{
    return name;
}

void WindowProxy::setName(const std::u16string& name)
{
    if (!name.empty() && name[0] == u'_')
        return;
    this->name = name;<commit_msg>(WindowProxy::getSelf) : Work around for 0c895cbe1d<commit_after>/*
 * Copyright 2010-2013 Esrille Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "WindowProxy.h"

#include <new>
#include <iostream>
#include <boost/version.hpp>
#include <boost/bind.hpp>

#include "utf.h"

#include "BeforeUnloadEventImp.h"
#include "DOMImplementationImp.h"
#include "DocumentImp.h"
#include "HashChangeEventImp.h"
#include "KeyboardEventImp.h"
#include "MouseEventImp.h"
#include "NodeImp.h"
#include "css/BoxImage.h"
#include "css/Ico.h"
#include "css/ViewCSSImp.h"
#include "html/HTMLIFrameElementImp.h"
#include "html/HTMLLinkElementImp.h"
#include "html/HTMLScriptElementImp.h"
#include "http/HTTPConnection.h"

#include "Test.util.h"

namespace org { namespace w3c { namespace dom { namespace bootstrap {

WindowProxy::Parser::Parser(const DocumentPtr& document, int fd, const std::string& optionalEncoding) :
    stream(fd, boost::iostreams::close_handle),
    htmlInputStream(stream, optionalEncoding),
    tokenizer(&htmlInputStream),
    parser(document, &tokenizer)
{
    document->setCharacterSet(utfconv(htmlInputStream.getEncoding()));
}

WindowProxy::WindowProxy(unsigned short flags) :
    history(this),
    backgroundTask(this),
    thread(std::ref(backgroundTask)),
    view(0),
    viewFlags(0),
    flags(flags),
    detail(0),
    buttons(0),
    scrollWidth(0),
    scrollHeight(0),
    width(816),     // US letter size, 96 DPI
    height(1056),
    redisplay(false),
    zoomable(true),
    zoom(1.0f),
    faviconOverridable(false),
    windowDepth(0)
{
}

WindowProxy::~WindowProxy()
{
    if (auto parent = getParentProxy()) {
        for (auto i = parent->childWindows.begin(); i != parent->childWindows.end(); ++i) {
            if (i->get() == this) {
                parent->childWindows.erase(i);
                break;
            }
        }
    }
    backgroundTask.abort();
    thread.join();
}

WindowProxyPtr WindowProxy::createChildProxy(unsigned short flags, const HTMLIFrameElementPtr& iframe)
{
    auto child = std::make_shared<WindowProxy>(flags);
    if (child) {
        childWindows.push_back(child);
        child->parent = std::static_pointer_cast<WindowProxy>(self());
        child->setBase(getLocation().getHref());
        child->windowDepth = windowDepth + 1;
        child->frameElement = iframe;
    }
    return child;
}

void WindowProxy::setSize(unsigned w, unsigned h)
{
    if (width != w || height != h) {
        width = w;
        height = h;
        setViewFlags(Box::NEED_REFLOW);
        if (window && window->evaluateMedia())
            setViewFlags(Box::NEED_STYLE_RECALCULATION);
    }
}

void WindowProxy::enter()
{
    assert(window);
    window->enter(this);
}

void WindowProxy::exit()
{
    assert(window);
    window->exit(this);
}

void WindowProxy::enableZoom(bool value)
{
    zoomable = value;
}

float WindowProxy::getZoom() const
{
    return zoom;
}

void WindowProxy::setZoom(float value)
{
    if (zoomable) {
        zoom = value;
        if (view) {
            view->setZoom(zoom);
            redisplay = true;
        }
    }
}

void WindowProxy::updateView(ViewCSSImp* next)
{
    if (!next || view == next || !window || !window->getDocument())
        return;

    unsigned flags = 0;
    if (view) {
        flags |= view->gatherFlags();
        delete view;
    }
    view = next;
    setViewFlags(flags);
    view->setZoom(zoom);
    detail = 0;
    redisplay = true;

    if (!view->getTree()) {
        // cf. http://test.csswg.org/suites/css2.1/20110323/html4/root-box-003.htm
        canvas.shutdown();
        canvas.setup(width, height);
        canvas.beginRender(view->getBackgroundColor());
        canvas.endRender();
    }

    if (!getParentProxy()) {
        std::u16string title = view->getDocument()->getTitle();
        setWindowTitle(utfconv(title).c_str());
    }

    window->flushMediaQueryLists(view);
}

void WindowProxy::setWindowPtr(const WindowPtr& window)
{
    this->window = window;
    delete view;
    view = 0;
    viewFlags = 0;
    if (window)
        backgroundTask.restart(BackgroundTask::Cascade);
    detail = 0;
    redisplay = true;
    setFavicon();
}

bool WindowProxy::isBindingDocumentWindow()
{
    if (auto parent = getParentProxy()) {
        auto document = std::static_pointer_cast<DocumentImp>(parent->getDocument().self());
        return document->isBindingDocumentWindow(std::static_pointer_cast<WindowProxy>(self()));
    }
    return false;
}

bool WindowProxy::poll()
{
    if (!window)
        return false;
    auto document = window->getDocument();

    // Update the canvas before processing events.
    if (request.getReadyState() == HttpRequest::DONE && document && backgroundTask.getState() == BackgroundTask::Done) {
        ViewCSSImp* next = backgroundTask.getView();
        updateView(next);
        if (view) {
            unsigned short gathered = viewFlags | view->gatherFlags();
            if (gathered & Box::NEED_REPAINT) {
                gathered &= ~Box::NEED_REPAINT;
                redisplay = true;
                if (!gathered && (flags & Loading)) {
                    flags &= ~Loading;
                    document->decrementLoadEventDelayCount();
                }
                render(0);
            }
        }
    }

    while (backgroundTask.getState() == BackgroundTask::Done && !eventQueue.empty()) {
        const EventTask task = eventQueue.front();
        switch (task.type) {
        case EventTask::MouseDown:
        case EventTask::MouseUp:
            mouse(task);
            break;
        case EventTask::MouseMove:
            mouseMove(task);
            break;
        case EventTask::KeyDown:
            keydown(task);
            break;
        case EventTask::KeyUp:
            keyup(task);
            break;
        default:
            break;
        }
        eventQueue.pop_front();
    }

    for (auto i = childWindows.begin(); i != childWindows.end(); ++i) {
        auto child = *i;
        if (child->poll()) {
            redisplay |= true;
            if (view) {
                if (document && document->isBindingDocumentWindow(child))
                    view->setFlags(Box::NEED_SELECTOR_REMATCHING);
                else
                    view->setFlags(Box::NEED_REPAINT);
            }
        }
    }

    switch (request.getReadyState()) {
    case HttpRequest::UNSENT:
        break;
    case HttpRequest::OPENED:
    case HttpRequest::HEADERS_RECEIVED:
    case HttpRequest::LOADING:
        break;
    case HttpRequest::DONE:
        if (!document) {
            recordTime("%*shttp request done", windowDepth * 2, "");
            // TODO: Check header
            Document newDocument = getDOMImplementation()->createDocument(u"", u"", nullptr); // TODO: Create HTML document
            if ((document = std::dynamic_pointer_cast<DocumentImp>(newDocument.self()))) {
                // TODO: Fire a simple unload event.
                document->setDefaultView(std::static_pointer_cast<WindowProxy>(self()));
                document->setURL(request.getRequestMessage().getURL());
                document->setLastModified(request.getLastModified());
                window->setDocument(document);
                if (!request.getError())
                    history->update(window);
                else
                    document->setError(request.getError());
                document->enter();
                parser.reset(new(std::nothrow) Parser(document, request.getContentDescriptor(), request.getResponseMessage().getContentCharset()));
                document->exit();
                if (!parser)
                    break;  // TODO: error handling
            } else
                break;  // TODO: error handling
        }
        if (document->getReadyState() == u"loading") {
            // TODO: Note white it would be nice to parse the HTML docucment in
            // the background task, firstly we need to check if we can run JS
            // in the background.

            document->enter();

            if (!parser->processPendingParsingBlockingScript()) {
                document->exit();
                break;
            }
            // TODO: run this in the background
            Token token;
            do {
                token = parser->getToken();
                parser->processToken(token);
            } while (token.getType() != Token::Type::EndOfFile && !document->getPendingParsingBlockingScript());

            if (document->getPendingParsingBlockingScript()) {
                document->exit();
                break;
            }

            // TODO: Check if the parser has been aborted.
            document->resetStyleSheets();
            setViewFlags(Box::NEED_SELECTOR_REMATCHING);
            if (!(flags & Loading) && !isBindingDocumentWindow()) { // Note a binding document does not create its view.
                flags |= Loading;
                document->incrementLoadEventDelayCount();
            }

            parser.reset();
            document->exit();

            recordTime("%*shtml parsed", windowDepth * 2, "");
            if (4 <= getLogLevel())
                dumpTree(std::cerr, document);
        }

        if (!backgroundTask.isRestarting()) {
            switch (backgroundTask.getState()) {
            case BackgroundTask::Cascaded:
                HTMLElementImp::xblEnteredDocument(document);
                backgroundTask.wakeUp(BackgroundTask::Layout);
                break;
            case BackgroundTask::Init:
            case BackgroundTask::Done: {

                eventLoop();

                ViewCSSImp* next = backgroundTask.getView();
                updateView(next);
                if (document->getReadyState() == u"interactive") {
                    // TODO: Check if the parser has been aborted.

                    // Run from the step 3. of 'stops parsing'.
                    // cf. http://www.whatwg.org/specs/web-apps/current-work/multipage/the-end.html#the-end

                    // Process scripts that will execute when the document has finished parsing (i.e., defer).
                    // TODO: Check there's no style sheet that is blocking scripts.
                    if (document->processDeferScripts()) {
                        if (!document->hasContentLoaded()) {
                            if (events::Event event = std::make_shared<EventImp>()) {
                                event.initEvent(u"DOMContentLoaded", true, false);
                                document->dispatchEvent(event);
                            }
                            document->decrementLoadEventDelayCount();
                            document->setContentLoaded();

                            backgroundTask.restart(BackgroundTask::Cascade);
                        }
                    }
                }
                if (document->getReadyState() == u"complete") {
                }
                if (view) {
                    if (unsigned short gathered = viewFlags | view->gatherFlags()) {
                        viewFlags &= ~gathered;
                        if (gathered & Box::NEED_SELECTOR_REMATCHING) {
                            recordTime("%*strigger selector rematching", windowDepth * 2, "");
                            backgroundTask.restart(BackgroundTask::Cascade);
                            view = 0;
                        } else if (gathered & Box::NEED_SELECTOR_MATCHING) {
                            recordTime("%*strigger restyling", windowDepth * 2, "");
                            backgroundTask.wakeUp(BackgroundTask::Cascade);
                            view = 0;
                        } else if (gathered & (Box::NEED_STYLE_RECALCULATION | Box::NEED_EXPANSION | Box::NEED_CHILD_REFLOW | Box::NEED_REFLOW)) {
                            recordTime("%*strigger reflow", windowDepth * 2, "");
                            backgroundTask.wakeUp(BackgroundTask::Layout);
                            view = 0;
                        } else if (gathered & Box::NEED_REPAINT) {
                            redisplay = true;
                            if (flags & Loading) {
                                flags &= ~Loading;
                                document->decrementLoadEventDelayCount();
                            }
                        }
                    }
                }
                break;
            }
            default:
                break;
            }
        }
        break;
    default:
        break;
    }
    bool result = redisplay;
    redisplay = false;
    if (!result && view && view->hasExpired(getTick())) {
        view->setFlags(Box::NEED_REPAINT);
        result = true;
    }
    if (result)
        recordTime("%*strigger repaint", windowDepth * 2, "");
    return result;
}

void WindowProxy::render(ViewCSSImp* parentView)
{
    if (view) {
        std::string readyState = window->getDocument() ? utfconv(window->getDocument()->getReadyState()) : "";
        recordTime("%*srepaint begin: %s (%s)", windowDepth * 2, "", readyState.c_str(), view ? "render" : "canvas");
        if (view->gatherFlags() & Box::NEED_REPAINT) {
            view->clearFlags(Box::NEED_REPAINT);
            // TODO: if the size of the canvas has not been changed, reuse the same canvas.
            canvas.shutdown();
            canvas.setup(width, height);

            unsigned backgroundColor = view->getBackgroundColor();
            if (backgroundColor == 0 && !getParent())
                backgroundColor = 0xffffffff;
            canvas.beginRender(backgroundColor);

            view->render(parentView ? parentView->getClipCount() : 0);
            scrollWidth = view->getScrollWidth();
            scrollHeight = view->getScrollHeight();
            canvas.endRender();
        }
        if (2 <= getLogLevel() && backgroundTask.isIdle() && !view->gatherFlags()) {
            unsigned depth = 1;
            for (WindowProxyPtr w = getParentProxy(); w; w = w->getParentProxy())
                ++depth;
            std::cout << "\n## " << readyState;
            if (1 < depth)
                std::cout << " (" << depth << ')';
            std::cout << '\n';
            view->dump();
            std::cout << "##\n";
            std::cout.flush();
        }
        recordTime("%*srepaint end", windowDepth * 2, "");
    }
    canvas.render(width, height);
}

void WindowProxy::mouse(int button, int up, int x, int y, int modifiers)
{
    eventQueue.emplace_back(up ? EventTask::MouseUp : EventTask::MouseDown, modifiers, x, y, button);
}

void WindowProxy::mouseMove(int x, int y, int modifiers)
{
    if (!eventQueue.empty()) {
        EventTask& back = eventQueue.back();
        if (back.type == EventTask::MouseMove) {
            back.x = x;
            back.y = y;
            back.modifiers = modifiers;
            return;
        }
    }
    eventQueue.emplace_back(EventTask::MouseMove, modifiers, x, y);
}

void WindowProxy::keydown(unsigned charCode, unsigned keyCode, int modifiers)
{
    eventQueue.emplace_back(EventTask::KeyDown, modifiers, charCode, keyCode);
}

void WindowProxy::keyup(unsigned charCode, unsigned keyCode, int modifiers)
{
    eventQueue.emplace_back(EventTask::KeyUp, modifiers, charCode, keyCode);
}

void WindowProxy::mouse(const EventTask& task)
{
    int button = task.button;
    int up = (task.type == task.MouseUp) ? true : false;
    int x = task.x;
    int y = task.y;
    int modifiers = task.modifiers;

    if (!view)
        return;

    recordTime("%*smouse (%d, %d)", windowDepth * 2, "", x, y);

    int shift = button;
    if (shift == 1)
        shift = 2;
    else if (shift == 2)
        shift = 1;
    if (up)
        buttons &= ~(1u << shift);
    else
        buttons |= (1u << shift);

    Box* box = view->boxFromPoint(x, y);
    if (!box)
        return;
    if (WindowProxyPtr childWindow = box->getChildWindow()) {
        childWindow->mouse(button, up,
                           x - box->getX() - box->getBlankLeft(), y - box->getY() - box->getBlankTop(),
                           modifiers);
    }

    Element target = Box::getContainingElement(box->getTargetNode());

    // mousedown, mousemove
    if (auto event = std::make_shared<MouseEventImp>()) {
        if (!up) {
            clickTarget = target;
            ++detail;
        }
        event->initMouseEvent(up ? u"mouseup" : u"mousedown",
                            true, true, self(), detail, x, y, x, y,
                            modifiers & 2, modifiers & 4, modifiers & 1, false, button, nullptr);
        event->setButtons(buttons);
        target.dispatchEvent(event);
    }

    if (!up || detail == 0)
        return;
    Box* clickBox = view->boxFromPoint(x, y);
    if (box != clickBox)
        return;

    // click
    if (auto event = std::make_shared<MouseEventImp>()) {
        event->initMouseEvent(u"click",
                            true, true, self(), detail, x, y, x, y,
                            modifiers & 2, modifiers & 4, modifiers & 1, false, button, nullptr);
        event->setButtons(buttons);
        target.dispatchEvent(event);
    }
}

void WindowProxy::mouseMove(const EventTask& task)
{
    int x = task.x;
    int y = task.y;
    int modifiers = task.modifiers;

    if (!view)
        return;
    Box* box = view->boxFromPoint(x, y);
    if (!box)
        return;

    Element target = Box::getContainingElement(box->getTargetNode());
    Element prev = view->setHovered(target);

    if (prev != target) {
        if (html::Window c = interface_cast<html::HTMLIFrameElement>(prev).getContentWindow()) {
            if (auto w = std::dynamic_pointer_cast<WindowProxy>(c.self()))
                w->mouseMove(-1, -1, modifiers);
        }
    }
    if (auto childWindow = box->getChildWindow()) {
        childWindow->mouseMove(x - box->getX() - box->getBlankLeft(),
                               y - box->getY() - box->getBlankTop(),
                               modifiers);
    }
    if (prev != target) {
        // mouseout
        if (auto event = std::make_shared<MouseEventImp>()) {
            event->initMouseEvent(u"mouseout",
                                true, true, self(), 0, x, y, x, y,
                                modifiers & 2, modifiers & 4, modifiers & 1, false, 0, nullptr);
            event->setButtons(buttons);
            prev.dispatchEvent(event);
        }

        // mouseover
        if (auto event = std::make_shared<MouseEventImp>()) {
            event->initMouseEvent(u"mouseover",
                                true, true, self(), 0, x, y, x, y,
                                modifiers & 2, modifiers & 4, modifiers & 1, false, 0, nullptr);
            event->setButtons(buttons);
            target.dispatchEvent(event);
        }
    }

    // mousemove
    if (auto event = std::make_shared<MouseEventImp>()) {
        if (target != clickTarget)
            detail = 0;
        event->initMouseEvent(u"mousemove",
                            true, true, self(), detail, x, y, x, y,
                            modifiers & 2, modifiers & 4, modifiers & 1, false, 0, nullptr);
        event->setButtons(buttons);
        target.dispatchEvent(event);
    }
}

void WindowProxy::keydown(const EventTask& task)
{
    unsigned charCode = task.charCode;
    unsigned keyCode = task.keyCode;
    int modifiers = task.modifiers;

    Document document = window->getDocument();
    if (!document)
        return;
    Element e = document.getActiveElement();
    if (!e)
        return;

    if (auto iframe = std::dynamic_pointer_cast<HTMLIFrameElementImp>(e.self())) {
        if (auto child = std::dynamic_pointer_cast<WindowProxy>(iframe->getContentWindow().self()))
            child->keydown(charCode, keyCode, modifiers);
    }

    auto event = std::make_shared<KeyboardEventImp>(modifiers, charCode, keyCode, 0);
    event->initKeyboardEvent(u"keydown", true, true, self(), u"", u"", 0, u"", false, u"");
    e.dispatchEvent(event);

    if (!charCode)
        return;
    event = std::make_shared<KeyboardEventImp>(modifiers, charCode, keyCode, 0);
    event->initKeyboardEvent(u"keypress", true, true, self(), u"", u"", 0, u"", false, u"");
    e.dispatchEvent(event);
}

void WindowProxy::keyup(const EventTask& task)
{
    unsigned charCode = task.charCode;
    unsigned keyCode = task.keyCode;
    int modifiers = task.modifiers;

    Document document = window->getDocument();
    if (!document)
        return;
    Element e = document.getActiveElement();
    if (!e)
        return;

    if (auto iframe = std::dynamic_pointer_cast<HTMLIFrameElementImp>(e.self())) {
        if (auto child = std::dynamic_pointer_cast<WindowProxy>(iframe->getContentWindow().self()))
            child->keyup(charCode, keyCode, modifiers);
    }

    auto event = std::make_shared<KeyboardEventImp>(modifiers, charCode, keyCode, 0);
    event->initKeyboardEvent(u"keyup", true, true, self(), u"", u"", 0, u"", false, u"");
    e.dispatchEvent(event);
}

void WindowProxy::setFavicon(IcoImage* ico, std::FILE* file)
{
    if (auto parent = getParentProxy()) {
        if (parent->getFaviconOverridable())
            parent->setFavicon(ico, file);
        return;
    }
    for (size_t i = 0; i < ico->getPlaneCount(); ++i) {
        const IconDirectoryEntry& ent(ico->getEntry(i));
        if (BoxImage* image = ico->open(file, i)) {
            uint32_t* pixels = reinterpret_cast<uint32_t*>(image->getPixels());
            setIcon(i, ent.getWidth(), ent.getHeight(), pixels);
            delete image;
        }
    }
}

void WindowProxy::setFavicon(BoxImage* image)
{
    if (auto parent = getParentProxy()) {
        if (parent->getFaviconOverridable())
            parent->setFavicon(image);
        return;
    }
    uint32_t* pixels = reinterpret_cast<uint32_t*>(image->getPixels());
    setIcon(0, image->getNaturalWidth(), image->getNaturalHeight(), pixels);
}

void WindowProxy::setFavicon()
{
    for (WindowProxyPtr w = std::static_pointer_cast<WindowProxy>(self()); w; w = w->getParentProxy()) {
        if (w->getParentProxy() && !w->getParentProxy()->getFaviconOverridable())
            return;
        if (!w->window)
            continue;
        auto document = w->window->getDocument();
        if (!document)
            continue;
        html::HTMLHeadElement head = document->getHead();
        if (!head)
            continue;
        for (auto i = head.getFirstElementChild(); i; i = i.getNextElementSibling()) {
            if (auto link = std::dynamic_pointer_cast<HTMLLinkElementImp>(i.self())) {
                if (link->setFavicon(document))
                    return;
            }
        }
    }
}

//
// CSSOM View support operations
//

Element WindowProxy::elementFromPoint(float x, float y)
{
    if (!view)
        return nullptr;
    if (x < 0.0f || width < x || y < 0.0f || height < y)
        return nullptr;
    Box* box = view->boxFromPoint(x, y);
    if (!box)
        return nullptr;
    for (Node node = box->getTargetNode(); node; node = node.getParentNode()) {
        if (node.getNodeType() == Node::ELEMENT_NODE)
            return interface_cast<Element>(node);
    }
    return nullptr;
}

//
// html::Window
//

html::Window WindowProxy::getWindow()
{
    return self();
}

Any WindowProxy::getSelf()
{
    // TODO:
    // This is tricky. If the line below were,
    //   return self();
    // as in 0.3 series, the returned Any value holds a raw std::shared_ptr<WindowProxy>,
    // which is not an Object since 0.4.0. We should address this issue in a better way.
    return getWindow();
}

void WindowProxy::setSelf(Any self)
{
    // TODO: implement me!
}

Document WindowProxy::getDocument()
{
    return !window ? 0 : window->getDocument();
}

std::u16string WindowProxy::getName()
{
    return name;
}

void WindowProxy::setName(const std::u16string& name)
{
    if (!name.empty() && name[0] == u'_')
        return;
    this->name = name;
{'fraction_non_alphanumeric': 0.1320351655574727, 'fraction_numerical': 0.00554596992851861, 'mean_word_length': 2.074712643678161, 'pattern_counts': {'":': 0, '<': 116, '<?xml version=': 0, '>': 340, 'https://': 0, 'lorem ipsum': 0, 'www.': 4, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 2, 'word_list_counts': {'cursed_substrings.json': 2, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '766722', 'n_tokens_mistral': 14046, 'n_tokens_neox': 13631, 'n_words': 3080}