<commit_before>/*
 * CPPlatformWindow+DOM.j
 * AppKit
 *
 * Created by <NAME>.
 * Copyright 2008, 280 North, Inc.
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
 */


/*
 * THIS DOCUMENTATION STOLEN DIRECTLY FROM GOOGLE CLOSURE (licensed under Apache 2)
 *
 * Different web browsers have very different keyboard event handling. Most
 * importantly is that only certain browsers repeat keydown events:
 * IE, Opera, FF/Win32, and Safari 3 repeat keydown events.
 * FF/Mac and Safari 2 do not.
 *
 * For the purposes of this code, "Safari 3" means WebKit 525+, when WebKit
 * decided that they should try to match IE's key handling behavior.
 * Safari 3.0.4, which shipped with Leopard (WebKit 523), has the
 * Safari 2 behavior.
 *
 * Firefox, Safari, Opera prevent on keypress
 *
 * IE prevents on keydown
 *
 * Firefox does not fire keypress for shift, ctrl, alt
 * Firefox does fire keydown for shift, ctrl, alt, meta
 * Firefox does not repeat keydown for shift, ctrl, alt, meta
 *
 * Firefox does not fire keypress for up and down in an input
 *
 * Opera fires keypress for shift, ctrl, alt, meta
 * Opera does not repeat keypress for shift, ctrl, alt, meta
 *
 * Safari 2 and 3 do not fire keypress for shift, ctrl, alt
 * Safari 2 does not fire keydown for shift, ctrl, alt
 * Safari 3 *does* fire keydown for shift, ctrl, alt
 *
 * IE provides the keycode for keyup/down events and the charcode (in the
 * keycode field) for keypress.
 *
 * Mozilla provides the keycode for keyup/down and the charcode for keypress
 * unless it's a non text modifying key in which case the keycode is provided.
 *
 * Safari 3 provides the keycode and charcode for all events.
 *
 * Opera provides the keycode for keyup/down event and either the charcode or
 * the keycode (in the keycode field) for keypress events.
 *
 * Firefox x11 doesn't fire keydown events if a another key is already held down
 * until the first key is released. This can cause a key event to be fired with
 * a keyCode for the first key and a charCode for the second key.
 *
 * Safari 2 in keypress (not supported)
 *
 *        charCode keyCode which
 * ENTER:       13      13    13
 * F1:       63236   63236 63236
 * F8:       63243   63243 63243
 * ...
 * p:          112     112   112
 * P:           80      80    80
 *
 * Firefox, keypress:
 *
 *        charCode keyCode which
 * ENTER:        0      13    13
 * F1:           0     112     0
 * F8:           0     119     0
 * ...
 * p:          112       0   112
 * P:           80       0    80
 *
 * Opera, Mac+Win32, keypress:
 *
 *         charCode keyCode which
 * ENTER: undefined      13    13
 * F1:    undefined     112     0
 * F8:    undefined     119     0
 * ...
 * p:     undefined     112   112
 * P:     undefined      80    80
 *
 * IE7, keydown
 *
 *         charCode keyCode     which
 * ENTER: undefined      13 undefined
 * F1:    undefined     112 undefined
 * F8:    undefined     119 undefined
 * ...
 * p:     undefined      80 undefined
 * P:     undefined      80 undefined
 */

@import <Foundation/CPObject.j>
@import <Foundation/CPRunLoop.j>

@import "CPEvent.j"
@import "CPText.j"
@import "CPCompatibility.j"

@import "CPDOMWindowLayer.j"

@import "CPPlatform.j"
@import "CPPlatformWindow.j"
@import "CPPlatformWindow+DOMKeys.j"

#import "../../CoreGraphics/CGGeometry.h"

// List of all open native windows
var PlatformWindows = [CPSet set];

// Define up here so compressor knows about em.
var CPDOMEventGetClickCount,
    CPDOMEventStop,
    StopDOMEventPropagation,
    StopContextMenuDOMEventPropagation;

//right now we hard code q, w, r and t as keys to propogate
//these aren't normal keycodes, they are with modifier key codes
//might be mac only, we should investigate futher later.
var KeyCodesToPrevent = {},
    CharacterKeysToPrevent = {},
    KeyCodesToAllow = {},
    MozKeyCodeToKeyCodeMap = {
        61: 187,  // =, equals
        59: 186   // ;, semicolon
    },
    KeyCodesToUnicodeMap = {};

KeyCodesToPrevent[CPKeyCodes.A] = YES;

KeyCodesToAllow[CPKeyCodes.F1] = YES;
KeyCodesToAllow[CPKeyCodes.F2] = YES;
KeyCodesToAllow[CPKeyCodes.F3] = YES;
KeyCodesToAllow[CPKeyCodes.F4] = YES;
KeyCodesToAllow[CPKeyCodes.F5] = YES;
KeyCodesToAllow[CPKeyCodes.F6] = YES;
KeyCodesToAllow[CPKeyCodes.F7] = YES;
KeyCodesToAllow[CPKeyCodes.F8] = YES;
KeyCodesToAllow[CPKeyCodes.F9] = YES;
KeyCodesToAllow[CPKeyCodes.F10] = YES;
KeyCodesToAllow[CPKeyCodes.F11] = YES;
KeyCodesToAllow[CPKeyCodes.F12] = YES;

KeyCodesToUnicodeMap[CPKeyCodes.BACKSPACE]              = CPDeleteCharacter;
KeyCodesToUnicodeMap[CPKeyCodes.DELETE]                 = CPDeleteFunctionKey;
KeyCodesToUnicodeMap[CPKeyCodes.TAB]                    = CPTabCharacter;
KeyCodesToUnicodeMap[CPKeyCodes.ENTER]                  = CPCarriageReturnCharacter;
KeyCodesToUnicodeMap[CPKeyCodes.ESC]                    = CPEscapeFunctionKey;
KeyCodesToUnicodeMap[CPKeyCodes.PAGE_UP]                = CPPageUpFunctionKey;
KeyCodesToUnicodeMap[CPKeyCodes.PAGE_DOWN]              = CPPageDownFunctionKey;
KeyCodesToUnicodeMap[CPKeyCodes.LEFT]                   = CPLeftArrowFunctionKey;
KeyCodesToUnicodeMap[CPKeyCodes.UP]                     = CPUpArrowFunctionKey;
KeyCodesToUnicodeMap[CPKeyCodes.RIGHT]                  = CPRightArrowFunctionKey;
KeyCodesToUnicodeMap[CPKeyCodes.DOWN]                   = CPDownArrowFunctionKey;
KeyCodesToUnicodeMap[CPKeyCodes.SEMICOLON]              = ";";
KeyCodesToUnicodeMap[CPKeyCodes.DASH]                   = "-";
KeyCodesToUnicodeMap[CPKeyCodes.EQUALS]                 = "=";
KeyCodesToUnicodeMap[CPKeyCodes.COMMA]                  = ",";
KeyCodesToUnicodeMap[CPKeyCodes.PERIOD]                 = ".";
KeyCodesToUnicodeMap[CPKeyCodes.SLASH]                  = "/";
KeyCodesToUnicodeMap[CPKeyCodes.APOSTROPHE]             = "`";
KeyCodesToUnicodeMap[CPKeyCodes.SINGLE_QUOTE]           = "'";
KeyCodesToUnicodeMap[CPKeyCodes.OPEN_SQUARE_BRACKET]    = "[";
KeyCodesToUnicodeMap[CPKeyCodes.BACKSLASH]              = "\\";
KeyCodesToUnicodeMap[CPKeyCodes.CLOSE_SQUARE_BRACKET]   = "]";

var ModifierKeyCodes = [
    CPKeyCodes.META,
    CPKeyCodes.MAC_FF_META,
    CPKeyCodes.CTRL,
    CPKeyCodes.ALT,
    CPKeyCodes.SHIFT
];

var supportsNativeDragAndDrop = [CPPlatform supportsDragAndDrop];

@implementation CPPlatformWindow (DOM)

- (id)_init
{
    self = [super init];

    if (self)
    {
        _DOMWindow = window;
        _contentRect = _CGRectMakeZero();

        _windowLevels = [];
        _windowLayers = [CPDictionary dictionary];

        [self registerDOMWindow];
        [self updateFromNativeContentRect];

        _charCodes = {};
    }

    return self;
}

- (CGRect)nativeContentRect
{
    if (!_DOMWindow)
        return [self contentRect];

    if (_DOMWindow.cpFrame)
        return _DOMWindow.cpFrame();

    var contentRect = _CGRectMakeZero();

    if (window.screenTop)
        contentRect.origin = _CGPointMake(_DOMWindow.screenLeft, _DOMWindow.screenTop);

    else if (window.screenX)
        contentRect.origin = _CGPointMake(_DOMWindow.screenX, _DOMWindow.screenY);

    // Safari, Mozilla, Firefox, and Opera
    if (_DOMWindow.innerWidth)
        contentRect.size = _CGSizeMake(_DOMWindow.innerWidth, _DOMWindow.innerHeight);

    // Internet Explorer 6 in Strict Mode
    else if (document.documentElement && document.documentElement.clientWidth)
        contentRect.size = _CGSizeMake(_DOMWindow.document.documentElement.clientWidth, _DOMWindow.document.documentElement.clientHeight);

    // Internet Explorer X
    else
        contentRect.size = _CGSizeMake(_DOMWindow.document.body.clientWidth, _DOMWindow.document.body.clientHeight);

    return contentRect;
}

- (void)updateNativeContentRect
{
    if (!_DOMWindow)
        return;

    if (typeof _DOMWindow["cpSetFrame"] === "function")
        return _DOMWindow.cpSetFrame([self contentRect]);

    var origin = [self contentRect].origin,
        nativeOrigin = [self nativeContentRect].origin;

    if (origin.x !== nativeOrigin.x || origin.y !== nativeOrigin.y)
    {
        _DOMWindow.moveBy(origin.x - nativeOrigin.x, origin.y - nativeOrigin.y);
    }

    var size = [self contentRect].size,
        nativeSize = [self nativeContentRect].size;

    if (size.width !== nativeSize.width || size.height !== nativeSize.height)
    {
        _DOMWindow.resizeBy(size.width - nativeSize.width, size.height - nativeSize.height);
    }
}

- (void)orderBack:(id)aSender
{
    if (_DOMWindow)
        _DOMWindow.blur();
}

- (void)createDOMElements
{
    var theDocument = _DOMWindow.document;

    // This guy fixes an issue in Firefox where if you focus the URL field, we stop getting key events
    _DOMFocusElement = theDocument.createElement("input");

    _DOMFocusElement.style.position = "absolute";
    _DOMFocusElement.style.zIndex = "-1000";
    _DOMFocusElement.style.opacity = "0";
    _DOMFocusElement.style.filter = "alpha(opacity=0)";
    _DOMFocusElement.className = "cpdontremove";

    _DOMBodyElement.appendChild(_DOMFocusElement);

    // Create Native Pasteboard handler.
    _DOMPasteboardElement = theDocument.createElement("textarea");

    _DOMPasteboardElement.style.position = "absolute";
    _DOMPasteboardElement.style.top = "-10000px";
    _DOMPasteboardElement.style.zIndex = "999";
    _DOMPasteboardElement.className = "cpdontremove";

    _DOMBodyElement.appendChild(_DOMPasteboardElement);

    // Make sure the pastboard element is blurred.
    _DOMPasteboardElement.blur();

    // Create a full screen div to protect against iframes and other elements from consuming events during tracking
    // FIXME: multiple windows
    _DOMEventGuard = theDocument.createElement("div");
    _DOMEventGuard.style.position = "absolute";
    _DOMEventGuard.style.top = "0px";
    _DOMEventGuard.style.left = "0px";
    _DOMEventGuard.style.width = "100%";
    _DOMEventGuard.style.height = "100%";
    _DOMEventGuard.style.zIndex = "999";
    _DOMEventGuard.style.display = "none";
    _DOMEventGuard.className = "cpdontremove";
    _DOMBodyElement.appendChild(_DOMEventGuard);
    
    // We get scrolling deltas from this element
    _DOMScrollingElement = theDocument.createElement("div");
    _DOMScrollingElement.style.position = "absolute";
    _DOMScrollingElement.style.visibility = "hidden";
    _DOMScrollingElement.style.zIndex = "998";
    _DOMScrollingElement.style.height = "100px";
    _DOMScrollingElement.style.width = "100px";
    _DOMScrollingElement.style.overflow = "scroll";
    //_DOMScrollingElement.style.backgroundColor = "rgba(0,0,0,1.0)"; // debug help.
    _DOMScrollingElement.style.opacity = "0";
    _DOMScrollingElement.style.filter = "alpha(opacity=0)";
    _DOMScrollingElement.className = "cpdontremove";
    _DOMBodyElement.appendChild(_DOMScrollingElement);
    
    var _DOMInnerScrollingElement = theDocument.createElement("div");
    _DOMInnerScrollingElement.style.width = "300px";
    _DOMInnerScrollingElement.style.height = "300px";
    _DOMScrollingElement.appendChild(_DOMInnerScrollingElement);
    
    // Set an initial scroll offset
    _DOMScrollingElement.scrollTop = 150;
    _DOMScrollingElement.scrollLeft = 150;
}

- (void)registerDOMWindow
{
    var theDocument = _DOMWindow.document;

    _DOMBodyElement = theDocument.getElementById("cappuccino-body") || theDocument.body;

    // FIXME: Always do this?
    if (supportsNativeDragAndDrop)
        _DOMBodyElement.style["-khtml-user-select"] = "none";

    _DOMBodyElement.webkitTouchCallout = "none";

    [self createDOMElements];
    [self _addLayers];

    var theClass = [self class],

        dragEventImplementation = class_getMethodImplementation(theClass, @selector(dragEvent:)),
        dragEventCallback = function (anEvent) { dragEventImplementation(self, nil, anEvent); },

        resizeEventSelector = @selector(resizeEvent:),
        resizeEventImplementation = class_getMethodImplementation(theClass, resizeEventSelector),
        resizeEventCallback = function (anEvent) { resizeEventImplementation(self, nil, anEvent); },

        copyEventSelector = @selector(copyEvent:),
        copyEventImplementation = class_getMethodImplementation(theClass, copyEventSelector),
        copyEventCallback = function (anEvent) {copyEventImplementation(self, nil, anEvent); },

        pasteEventSelector = @selector(pasteEvent:),
        pasteEventImplementation = class_getMethodImplementation(theClass, pasteEventSelector),
        pasteEventCallback = function (anEvent) {pasteEventImplementation(self, nil, anEvent); },

        keyEventSelector = @selector(keyEvent:),
        keyEventImplementation = class_getMethodImplementation(theClass, keyEventSelector),
        keyEventCallback = function (anEvent) { keyEventImplementation(self, nil, anEvent); },

        mouseEventSelector = @selector(mouseEvent:),
        mouseEventImplementation = class_getMethodImplementation(theClass, mouseEventSelector),
        mouseEventCallback = function (anEvent) { mouseEventImplementation(self, nil, anEvent); },

        contextMenuEventSelector = @selector(contextMenuEvent:),
        contextMenuEventImplementation = class_getMethodImplementation(theClass, contextMenuEventSelector),
        contextMenuEventCallback = function (anEvent) { return contextMenuEventImplementation(self, nil, anEvent); },

        scrollEventSelector = @selector(scrollEvent:),
        scrollEventImplementation = class_getMethodImplementation(theClass, scrollEventSelector),
        scrollEventCallback = function (anEvent) { scrollEventImplementation(self, nil, anEvent); },

        touchEventSelector = @selector(touchEvent:),
        touchEventImplementation = class_getMethodImplementation(theClass, touchEventSelector),
        touchEventCallback = function (anEvent) { touchEventImplementation(self, nil, anEvent); };

    if (theDocument.addEventListener)
    {
        if ([CPPlatform supportsDragAndDrop])
        {
            theDocument.addEventListener("dragstart", dragEventCallback, NO);
            theDocument.addEventListener("drag", dragEventCallback, NO);
            theDocument.addEventListener("dragend", dragEventCallback, NO);
            theDocument.addEventListener("dragover", dragEventCallback, NO);
            theDocument.addEventListener("dragleave", dragEventCallback, NO);
            theDocument.addEventListener("drop", dragEventCallback, NO);
        }

        theDocument.addEventListener("mouseup", mouseEventCallback, NO);
        theDocument.addEventListener("mousedown", mouseEventCallback, NO);
        theDocument.addEventListener("mousemove", mouseEventCallback, NO);
        theDocument.addEventListener("contextmenu", contextMenuEventCallback, NO);

        theDocument.addEventListener("beforecopy", copyEventCallback, NO);
        theDocument.addEventListener("beforecut", copyEventCallback, NO);
        theDocument.addEventListener("beforepaste", pasteEventCallback, NO);

        theDocument.addEventListener("keyup", keyEventCallback, NO);
        theDocument.addEventListener("keydown", keyEventCallback, NO);
        theDocument.addEventListener("keypress", keyEventCallback, NO);

        theDocument.addEventListener("touchstart", touchEventCallback, NO);
        theDocument.addEventListener("touchend", touchEventCallback, NO);
        theDocument.addEventListener("touchmove", touchEventCallback, NO);
        theDocument.addEventListener("touchcancel", touchEventCallback, NO);

        _DOMWindow.addEventListener("DOMMouseScroll", scrollEventCallback, NO);
        _DOMWindow.addEventListener("mousewheel", scrollEventCallback, NO);

        _DOMWindow.addEventListener("resize", resizeEventCallback, NO);

        _DOMWindow.addEventListener("unload", function()
        {
            [self updateFromNativeContentRect];
            [self _removeLayers];

            theDocument.removeEventListener("mouseup", mouseEventCallback, NO);
            theDocument.removeEventListener("mousedown", mouseEventCallback, NO);
            theDocument.removeEventListener("mousemove", mouseEventCallback, NO);
            theDocument.removeEventListener("contextmenu", contextMenuEventCallback, NO);

            theDocument.removeEventListener("keyup", keyEventCallback, NO);
            theDocument.removeEventListener("keydown", keyEventCallback, NO);
            theDocument.removeEventListener("keypress", keyEventCallback, NO);

            theDocument.removeEventListener("beforecopy", copyEventCallback, NO);
            theDocument.removeEventListener("beforecut", copyEventCallback, NO);
            theDocument.removeEventListener("beforepaste", pasteEventCallback, NO);

            theDocument.removeEventListener("touchstart", touchEventCallback, NO);
            theDocument.removeEventListener("touchend", touchEventCallback, NO);
            theDocument.removeEventListener("touchmove", touchEventCallback, NO);

            _DOMWindow.removeEventListener("resize", resizeEventCallback, NO);

            //FIXME: does firefox really need a different value?
            _DOMWindow.removeEventListener("DOMMouseScroll", scrollEventCallback, NO);
            _DOMWindow.removeEventListener("mousewheel", scrollEventCallback, NO);

            //_DOMWindow.removeEventListener("beforeunload", this, NO);

            [PlatformWindows removeObject:self];

            self._DOMWindow = nil;
        }, NO);
    }
    else
    {
        theDocument.attachEvent("onmouseup", mouseEventCallback);
        theDocument.attachEvent("onmousedown", mouseEventCallback);
        theDocument.attachEvent("onmousemove", mouseEventCallback);
        theDocument.attachEvent("ondblclick", mouseEventCallback);
        theDocument.attachEvent("oncontextmenu", contextMenuEventCallback);

        theDocument.attachEvent("onkeyup", keyEventCallback);
        theDocument.attachEvent("onkeydown", keyEventCallback);
        theDocument.attachEvent("onkeypress", keyEventCallback);

        _DOMWindow.attachEvent("onresize", resizeEventCallback);

        _DOMWindow.onmousewheel = scrollEventCallback;
        theDocument.onmousewheel = scrollEventCallback;

        _DOMBodyElement.ondrag = function () { return NO; };
        _DOMBodyElement.onselectstart = function () { return _DOMWindow.event.srcElement === _DOMPasteboardElement; };

        _DOMWindow.attachEvent("onbeforeunload", function()
        {
            [self updateFromNativeContentRect];
            [self _removeLayers];

            theDocument.detachEvent("onmouseup", mouseEventCallback);
            theDocument.detachEvent("onmousedown", mouseEventCallback);
            theDocument.detachEvent("onmousemove", mouseEventCallback);
            theDocument.detachEvent("ondblclick", mouseEventCallback);
            theDocument.detachEvent("oncontextmenu", contextMenuEventCallback);

            theDocument.detachEvent("onkeyup", keyEventCallback);
            theDocument.detachEvent("onkeydown", keyEventCallback);
            theDocument.detachEvent("onkeypress", keyEventCallback);

            _DOMWindow.detachEvent("onresize", resizeEventCallback);

            _DOMWindow.onmousewheel = NULL;
            theDocument.onmousewheel = NULL;

            _DOMBodyElement.ondrag = NULL;
            _DOMBodyElement.onselectstart = NULL;

            //_DOMWindow.removeEvent("beforeunload", this);

            [PlatformWindows removeObject:self];

            self._DOMWindow = nil;
        }, NO);
    }
}

+ (CPSet)visiblePlatformWindows
{
    if ([[CPPlatformWindow primaryPlatformWindow] isVisible])
    {
        var set = [CPSet setWithSet:PlatformWindows];
        [set addObject:[CPPlatformWindow primaryPlatformWindow]];
        return set;
    }
    else
        return PlatformWindows;
}

- (void)orderFront:(id)aSender
{
    if (_DOMWindow)
        return _DOMWindow.focus();

    _DOMWindow = window.open("", "_blank", "menubar=no,location=no,resizable=yes,scrollbars=no,status=no,left=" + _CGRectGetMinX(_contentRect) + ",top=" + _CGRectGetMinY(_contentRect) + ",width=" + _CGRectGetWidth(_contentRect) + ",height=" + _CGRectGetHeight(_contentRect));

    [PlatformWindows addObject:self];

    // FIXME: cpSetFrame?
    _DOMWindow.document.write("<!DOCTYPE html><html lang='en'><head></head><body style='background-color:transparent;'></body></html>");
    _DOMWindow.document.close();

    if (![CPPlatform isBrowser])
    {
        _DOMWindow.cpWindowNumber = [self._only windowNumber];
        _DOMWindow.cpSetFrame(_contentRect);
        _DOMWindow.cpSetLevel(_level);
        _DOMWindow.cpSetHasShadow(_hasShadow);
        _DOMWindow.cpSetShadowStyle(_shadowStyle);
    }

    [self registerDOMWindow];

    _DOMBodyElement.style.cursor = [[CPCursor currentCursor] _cssString];
}

- (void)orderOut:(id)aSender
{
    if (!_DOMWindow)
        return;

    _DOMWindow.close();
}

- (void)dragEvent:(DOMEvent)aDOMEvent
{
    var type = aDOMEvent.type,
        dragServer = [CPDragServer sharedDragServer],
        location = _CGPointMake(aDOMEvent.clientX, aDOMEvent.clientY),
        pasteboard = [_CPDOMDataTransferPasteboard DOMDataTransferPasteboard];

    [pasteboard _setDataTransfer:aDOMEvent.dataTransfer];

    if (aDOMEvent.type === "dragstart")
    {
        [[CPRunLoop currentRunLoop] limitDateForMode:CPDefaultRunLoopMode];

        [pasteboard _setPasteboard:[dragServer draggingPasteboard]];

        var draggedWindow = [dragServer draggedWindow],
            draggedWindowFrame = [draggedWindow frame],
            DOMDragElement = draggedWindow._DOMElement;

        DOMDragElement.style.left = -_CGRectGetWidth(draggedWindowFrame) + "px";
        DOMDragElement.style.top = -_CGRectGetHeight(draggedWindowFrame) + "px";

        var parentNode = DOMDragElement.parentNode;

        if (parentNode)
            parentNode.removeChild(DOMDragElement);

        _DOMBodyElement.appendChild(DOMDragElement);

        var draggingOffset = [dragServer draggingOffset];

        aDOMEvent.dataTransfer.setDragImage(DOMDragElement, draggingOffset.width, draggingOffset.height);
        aDOMEvent.dataTransfer.effectAllowed = "all";

        [dragServer draggingStartedInPlatformWindow:self globalLocation:[CPPlatform isBrowser] ? location : _CGPointMake(aDOMEvent.screenX, aDOMEvent.screenY)];
    }

    else if (type === "drag")
    {
        var y = aDOMEvent.screenY;

        if (CPFeatureIsCompatible(CPHTML5DragAndDropSourceYOffBy1))
            y -= 1;

        [dragServer draggingSourceUpdatedWithGlobalLocation:[CPPlatform isBrowser] ? location : _CGPointMake(aDOMEvent.screenX, y)];
    }

    else if (type === "dragover" || type === "dragleave")
    {
        if (aDOMEvent.preventDefault)
            aDOMEvent.preventDefault();

        var dropEffect = "none",
            dragOperation = [dragServer draggingUpdatedInPlatformWindow:self location:location];

        if (dragOperation === CPDragOperationMove || dragOperation === CPDragOperationGeneric || dragOperation === CPDragOperationPrivate)
            dropEffect = "move";

        else if (dragOperation === CPDragOperationCopy)
            dropEffect = "copy";

        else if (dragOperation === CPDragOperationLink)
            dropEffect = "link";

        aDOMEvent.dataTransfer.dropEffect = dropEffect;
    }

    else if (type === "dragend")
    {
        var dropEffect = aDOMEvent.dataTransfer.dropEffect;

        if (dropEffect === "move")
            dragOperation = CPDragOperationMove;
        else if (dropEffect === "copy")
            dragOperation = CPDragOperationCopy;
        else if (dropEffect === "link")
            dragOperation = CPDragOperationLink;
        else
            dragOperation = CPDragOperationNone;

        [dragServer draggingEndedInPlatformWindow:self globalLocation:[CPPlatform isBrowser] ? location : _CGPointMake(aDOMEvent.screenX, aDOMEvent.screenY) operation:dragOperation];
    }

    else //if (type === "drop")
    {
        [dragServer performDragOperationInPlatformWindow:self];

        // W3C Model
        if (aDOMEvent.preventDefault)
            aDOMEvent.preventDefault();

        if (aDOMEvent.stopPropagation)
            aDOMEvent.stopPropagation();
    }

    [[CPRunLoop currentRunLoop] limitDateForMode:CPDefaultRunLoopMode];
}

- (void)keyEvent:(DOMEvent)aDOMEvent
{
    var event,
        timestamp = aDOMEvent.timeStamp || new Date(),
        sourceElement = aDOMEvent.target || aDOMEvent.srcElement,
        windowNumber = [[CPApp keyWindow] windowNumber],
        modifierFlags = (aDOMEvent.shiftKey ? CPShiftKeyMask : 0) |
                        (aDOMEvent.ctrlKey ? CPControlKeyMask : 0) |
                        (aDOMEvent.altKey ? CPAlternateKeyMask : 0) |
                        (aDOMEvent.metaKey ? CPCommandKeyMask : 0);

    // With a few exceptions, all key events are blocked from propagating to
    // the browser.  Here the following exceptions are being allowed:
    //
    //   - All keys pressed along with a ctrl or cmd key _unless_ they are in
    //     one of the two blacklists.
    //   - Any key listed in the whitelist.
    //
    // The ctrl/cmd keys are used for browser hotkeys as are the keys listed in
    // the whitelist (F1-F12 at the time of writing).
    //
    // If a key is listed in both the blacklist and whitelist, the blacklist is
    // checked first.  The key will be blocked from propagating in that case.

    StopDOMEventPropagation = YES;

    // Make sure it is not in the blacklists.
    if(! (CharacterKeysToPrevent[String.fromCharCode(aDOMEvent.keyCode || aDOMEvent.charCode).toLowerCase()] || KeyCodesToPrevent[aDOMEvent.keyCode]))
    {
        // It is not in the blacklist, let it through if the ctrl/cmd key is
        // also down or it's in the whitelist.
        if((modifierFlags & (CPControlKeyMask | CPCommandKeyMask)) || KeyCodesToAllow[aDOMEvent.keyCode])
        {
            StopDOMEventPropagation = NO;
        }
    }

    var isNativePasteEvent = NO,
        isNativeCopyOrCutEvent = NO,
        overrideCharacters = nil;

    switch (aDOMEvent.type)
    {
        case "keydown":     // Grab and store the keycode now since it is correct and consistent at this point.
                            if (aDOMEvent.keyCode in MozKeyCodeToKeyCodeMap)
                                _keyCode = MozKeyCodeToKeyCodeMap[aDOMEvent.keyCode];
                            else
                                _keyCode = aDOMEvent.keyCode;

                            var characters;

                            // Handle key codes for which String.fromCharCode won't work.
                            if (aDOMEvent.which === 0 || aDOMEvent.charCode === 0)
                                characters = KeyCodesToUnicodeMap[_keyCode];

                            if (!characters)
                                characters = String.fromCharCode(_keyCode).toLowerCase();

                            overrideCharacters = (modifierFlags & CPShiftKeyMask || _capsLockActive) ? characters.toUpperCase() : characters;

                            // check for caps lock state
                            if (_keyCode === CPKeyCodes.CAPS_LOCK)
                                _capsLockActive = YES;

                            if ([ModifierKeyCodes containsObject:_keyCode])
                            {
                                // A modifier key will never fire keypress. We don't need to do any other processing so we just fire it here and break.
                                event = [CPEvent keyEventWithType:CPFlagsChanged location:location modifierFlags:modifierFlags
                                            timestamp:timestamp windowNumber:windowNumber context:nil
                                            characters:nil charactersIgnoringModifiers:nil isARepeat:NO keyCode:_keyCode];

                                break;
                            }
                            else if (modifierFlags & (CPControlKeyMask | CPCommandKeyMask))
                            {
                                //we are simply going to skip all keypress events that use cmd/ctrl key
                                //this lets us be consistent in all browsers and send on the keydown
                                //which means we can cancel the event early enough, but only if sendEvent needs to

                                var eligibleForCopyPaste = [self _validateCopyCutOrPasteEvent:aDOMEvent flags:modifierFlags];

                                // If this could be a native PASTE event, then we need to further examine it before
                                // sending a CPEvent.  Select our element to see if anything gets pasted in it.
                                if (characters === "v" && eligibleForCopyPaste)
                                {
                                    if (!_ignoreNativePastePreparation)
                                    {
                                        _DOMPasteboardElement.select();
                                        _DOMPasteboardElement.value = "";
                                    }

                                    isNativePasteEvent = YES;
                                }

                                // However, of this could be a native COPY event, we need to let the normal event-process take place so it
                                // can capture our internal Cappuccino pasteboard.
                                else if ((characters == "c" || characters == "x") && eligibleForCopyPaste)
                                {
                                    isNativeCopyOrCutEvent = YES;

                                    if (_ignoreNativeCopyOrCutEvent)
                                        break;
                                }
                            }
                            else if (CPKeyCodes.firesKeyPressEvent(_keyCode, _lastKey, aDOMEvent.shiftKey, aDOMEvent.ctrlKey, aDOMEvent.altKey))
                            {
                                // this branch is taken by events which fire keydown, keypress, and keyup.
                                // this is the only time we'll ALLOW character keys to propagate (needed for text fields)
                                StopDOMEventPropagation = NO;
                                break;
                            }
                            else
                            {
                                //this branch is taken by "remedial" key events
                                // In this state we continue to keypress and send the CPEvent
                            }

        case "keypress":
                            // we unconditionally break on keypress events with modifiers,
                            // because we forced the event to be sent on the keydown
                            if (aDOMEvent.type === "keypress" && (modifierFlags & (CPControlKeyMask | CPCommandKeyMask)))
                                break;

                            var keyCode = _keyCode,
                                charCode = aDOMEvent.keyCode || aDOMEvent.charCode,
                                isARepeat = (_charCodes[keyCode] != nil);

                            _lastKey = keyCode;
                            _charCodes[keyCode] = charCode;

                            var characters = overrideCharacters;
                            // Is this a special key?
                            if (!characters && (aDOMEvent.which === 0 || aDOMEvent.charCode === 0))
                                characters = KeyCodesToUnicodeMap[charCode];

                            if (!characters)
                                characters = String.fromCharCode(charCode);

                            charactersIgnoringModifiers = characters.toLowerCase(); // FIXME: This isn't correct. It SHOULD include Shift.

                            // Safari won't send proper capitalization during cmd-key events
                            if (!overrideCharacters && (modifierFlags & CPCommandKeyMask) && ((modifierFlags & CPShiftKeyMask) || _capsLockActive))
                                characters = characters.toUpperCase();

                            event = [CPEvent keyEventWithType:CPKeyDown location:location modifierFlags:modifierFlags
                                        timestamp:timestamp windowNumber:windowNumber context:nil
                                        characters:characters charactersIgnoringModifiers:charactersIgnoringModifiers isARepeat:isARepeat keyCode:charCode];

                            if (isNativePasteEvent)
                            {
                                _pasteboardKeyDownEvent = event;
                                window.setNativeTimeout(function () { [self _checkPasteboardElement] }, 0);
                            }

                            break;

        case "keyup":       var keyCode = aDOMEvent.keyCode,
                                charCode = _charCodes[keyCode];

                            _keyCode = -1;
                            _lastKey = -1;
                            _charCodes[keyCode] = nil;
                            _ignoreNativeCopyOrCutEvent = NO;
                            _ignoreNativePastePreparation = NO;

                            // check for caps lock state
                            if (keyCode === CPKeyCodes.CAPS_LOCK)
                                _capsLockActive = NO;

                            if ([ModifierKeyCodes containsObject:keyCode])
                                break;

                            var characters = KeyCodesToUnicodeMap[charCode] || String.fromCharCode(charCode),
                                charactersIgnoringModifiers = characters.toLowerCase();

                            if (!(modifierFlags & CPShiftKeyMask) && (modifierFlags & CPCommandKeyMask) && !_capsLockActive)
                                characters = charactersIgnoringModifiers;

                            event = [CPEvent keyEventWithType:CPKeyUp location:location modifierFlags:modifierFlags
                                        timestamp: timestamp windowNumber:windowNumber context:nil
                                        characters:characters charactersIgnoringModifiers:charactersIgnoringModifiers isARepeat:NO keyCode:keyCode];
                            break;
    }

    if (event && !isNativePasteEvent)
    {
        event._DOMEvent = aDOMEvent;

        [CPApp sendEvent:event];

        if (isNativeCopyOrCutEvent)
        {
            // If this is a native copy event, then check if the pasteboard has anything in it.
            [self _primePasteboardElement];
        }
    }

    if (StopDOMEventPropagation)
        CPDOMEventStop(aDOMEvent, self);

    [[CPRunLoop currentRunLoop] limitDateForMode:CPDefaultRunLoopMode];
}

- (void)copyEvent:(DOMEvent)aDOMEvent
{
    if ([self _validateCopyCutOrPasteEvent:aDOMEvent flags:CPPlatformActionKeyMask] && !_ignoreNativeCopyOrCutEvent)
    {
        //we have to send out a fake copy or cut event so that we can force the copy/cut mechanisms to take place
        var cut = aDOMEvent.type === "beforecut",
            keyCode = cut ? CPKeyCodes.X : CPKeyCodes.C,
            characters = cut ? "x" : "c",
            timestamp = aDOMEvent.timeStamp ? aDOMEvent.timeStamp : new Date(),
            windowNumber = [[CPApp keyWindow] windowNumber],
            modifierFlags = CPPlatformActionKeyMask;

        event = [CPEvent keyEventWithType:CPKeyDown location:location modifierFlags:modifierFlags
                    timestamp:timestamp windowNumber:windowNumber context:nil
                    characters:characters charactersIgnoringModifiers:characters isARepeat:NO keyCode:keyCode];

        event._DOMEvent = aDOMEvent;
        [CPApp sendEvent:event];

        [self _primePasteboardElement];

        //then we have to IGNORE the real keyboard event to prevent a double copy
        //safari also sends the beforecopy event twice, so we additionally check here and prevent two events
        _ignoreNativeCopyOrCutEvent = YES;
    }

    [[CPRunLoop currentRunLoop] limitDateForMode:CPDefaultRunLoopMode];
}

- (void)pasteEvent:(DOMEvent)aDOMEvent
{
    if ([self _validateCopyCutOrPasteEvent:aDOMEvent flags:CPPlatformActionKeyMask])
    {
        _DOMPasteboardElement.focus();
        _DOMPasteboardElement.select();
        _DOMPasteboardElement.value = "";
        _ignoreNativePastePreparation = YES;
    }

    [[CPRunLoop currentRunLoop] limitDateForMode:CPDefaultRunLoopMode];
}

- (void)_validateCopyCutOrPasteEvent:(DOMEvent)aDOMEvent flags:(unsigned)modifierFlags
{
    return (
            ((aDOMEvent.target || aDOMEvent.srcElement).nodeName.toUpperCase() !== "INPUT" &&
             (aDOMEvent.target || aDOMEvent.srcElement).nodeName.toUpperCase() !== "TEXTAREA"
            ) || aDOMEvent.target === _DOMPasteboardElement
           ) &&
            (modifierFlags & CPPlatformActionKeyMask);
}

- (void)_primePasteboardElement
{
    var pasteboard = [CPPasteboard generalPasteboard],
        types = [pasteboard types];

    if (types.length)
    {
        if ([types indexOfObjectIdenticalTo:CPStringPboardType] != CPNotFound)
            _DOMPasteboardElement.value = [pasteboard stringForType:CPStringPboardType];
        else
            _DOMPasteboardElement.value = [pasteboard _generateStateUID];

        _DOMPasteboardElement.focus();
        _DOMPasteboardElement.select();

        window.setNativeTimeout(function() { [self _clearPasteboardElement]; }, 0);
    }
}

- (void)_checkPasteboardElement
{
    var value = _DOMPasteboardElement.value;

    if ([value length])
    {
        var pasteboard = [CPPasteboard generalPasteboard];

        if ([pasteboard _stateUID] != value)
        {
            [pasteboard declareTypes:[CPStringPboardType] owner:self];
            [pasteboard setString:value forType:CPStringPboardType];
        }
    }

    [self _clearPasteboardElement];

    [CPApp sendEvent:_pasteboardKeyDownEvent];

    _pasteboardKeyDownEvent = nil;

    [[CPRunLoop currentRunLoop] limitDateForMode:CPDefaultRunLoopMode];
}

- (void)_clearPasteboardElement
{
    _DOMPasteboardElement.value = "";
    _DOMPasteboardElement.blur();
}

- (void)scrollEvent:(DOMEvent)aDOMEvent
{
    if(!aDOMEvent)
        aDOMEvent = window.event;

    var location = nil;
    if (CPFeatureIsCompatible(CPJavaScriptMouseWheelValues_8_15))
    {
        var x = aDOMEvent._offsetX || 0.0,
            y = aDOMEvent._offsetY || 0.0,
            element = aDOMEvent.target;

        while (element.nodeType !== 1)
            element = element.parentNode;

        if (element.offsetParent)
        {
            do
            {
                x += element.offsetLeft;
                y += element.offsetTop;

            } while (element = element.offsetParent);
        }

        location = _CGPointMake((x + ((aDOMEvent.clientX - 8) / 15)), (y + ((aDOMEvent.clientY - 8) / 15)));
    }
    else if (aDOMEvent._overrideLocation)
        location = aDOMEvent._overrideLocation;
    else
        location = _CGPointMake(aDOMEvent.clientX, aDOMEvent.clientY);

    var deltaX = 0.0,
        deltaY = 0.0,
        windowNumber = 0,
        timestamp = aDOMEvent.timeStamp ? aDOMEvent.timeStamp : new Date(),
        modifierFlags = (aDOMEvent.shiftKey ? CPShiftKeyMask : 0) |
                        (aDOMEvent.ctrlKey ? CPControlKeyMask : 0) |
                        (aDOMEvent.altKey ? CPAlternateKeyMask : 0) |
                        (aDOMEvent.metaKey ? CPCommandKeyMask : 0);

    // Show the dom element
    _DOMScrollingElement.style.visibility = "visible";
    _DOMScrollingElement.style.top = (location.y - 50) + @"px";
    _DOMScrollingElement.style.left = (location.x - 50) + @"px";

    // We let the browser handle the scrolling
    StopDOMEventPropagation = NO;

    var theWindow = [self hitTest:location];

    if (!theWindow)
        return;

    var windowNumber = [theWindow windowNumber];

    location = [theWindow convertBridgeToBase:location];

    var event = [CPEvent mouseEventWithType:CPScrollWheel location:location modifierFlags:modifierFlags
                                  timestamp:timestamp windowNumber:windowNumber context:nil eventNumber:-1 clickCount:1 pressure:0];
    event._DOMEvent = aDOMEvent;
    
    if (_hideDOMScrollingElementTimeout)
        clearTimeout(_hideDOMScrollingElementTimeout);
    
    // We lag 1 event behind without this timeout.
    setTimeout(function(){     
           
        // Find the scroll delta
        event._deltaX = _DOMScrollingElement.scrollLeft - 150;
        event._deltaY = _DOMScrollingElement.scrollTop - 150;
        
        [CPApp sendEvent:event];

        // We set StopDOMEventPropagation = NO on line 1008
        //if (StopDOMEventPropagation)
        //    CPDOMEventStop(aDOMEvent, self);

        [[CPRunLoop currentRunLoop] limitDateForMode:CPDefaultRunLoopMode];
    
        // Reset the DOM elements scroll offset
        _DOMScrollingElement.scrollLeft = 150;
        _DOMScrollingElement.scrollTop = 150;
        
        // We hide it after a little bit
        _hideDOMScrollingElementTimeout = setTimeout(function(){
            _DOMScrollingElement.style.visibility = "hidden";
        }, 300);
        
    }, 0);
}

- (void)resizeEvent:(DOMEvent)aDOMEvent
{
    // FIXME: This is not the right way to do this.
    // We should pay attention to mouse down and mouse up in conjunction with this.
    //window.liveResize = YES;

    if ([CPPlatform isBrowser])
        [CPApp._activeMenu cancelTracking];

    var oldSize = [self contentRect].size;

    [self updateFromNativeContentRect];

    var levels = _windowLevels,
        layers = _windowLayers,
        levelCount = levels.length;

    while (levelCount--)
    {
        var windows = [layers objectForKey:levels[levelCount]]._windows,
            windowCount = windows.length;

        while (windowCount--)
            [windows[windowCount] resizeWithOldPlatformWindowSize:oldSize];
    }
<commit_msg>fix for clearing the hiding of the scroll element timeout not working. This improved performance. Also make the actual dom element smaller and the inner one bigger, since sometimes deltas need to be larger than 150px.<commit_after>/*
 * CPPlatformWindow+DOM.j
 * AppKit
 *
 * Created by <NAME>.
 * Copyright 2008, 280 North, Inc.
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
 */


/*
 * THIS DOCUMENTATION STOLEN DIRECTLY FROM GOOGLE CLOSURE (licensed under Apache 2)
 *
 * Different web browsers have very different keyboard event handling. Most
 * importantly is that only certain browsers repeat keydown events:
 * IE, Opera, FF/Win32, and Safari 3 repeat keydown events.
 * FF/Mac and Safari 2 do not.
 *
 * For the purposes of this code, "Safari 3" means WebKit 525+, when WebKit
 * decided that they should try to match IE's key handling behavior.
 * Safari 3.0.4, which shipped with Leopard (WebKit 523), has the
 * Safari 2 behavior.
 *
 * Firefox, Safari, Opera prevent on keypress
 *
 * IE prevents on keydown
 *
 * Firefox does not fire keypress for shift, ctrl, alt
 * Firefox does fire keydown for shift, ctrl, alt, meta
 * Firefox does not repeat keydown for shift, ctrl, alt, meta
 *
 * Firefox does not fire keypress for up and down in an input
 *
 * Opera fires keypress for shift, ctrl, alt, meta
 * Opera does not repeat keypress for shift, ctrl, alt, meta
 *
 * Safari 2 and 3 do not fire keypress for shift, ctrl, alt
 * Safari 2 does not fire keydown for shift, ctrl, alt
 * Safari 3 *does* fire keydown for shift, ctrl, alt
 *
 * IE provides the keycode for keyup/down events and the charcode (in the
 * keycode field) for keypress.
 *
 * Mozilla provides the keycode for keyup/down and the charcode for keypress
 * unless it's a non text modifying key in which case the keycode is provided.
 *
 * Safari 3 provides the keycode and charcode for all events.
 *
 * Opera provides the keycode for keyup/down event and either the charcode or
 * the keycode (in the keycode field) for keypress events.
 *
 * Firefox x11 doesn't fire keydown events if a another key is already held down
 * until the first key is released. This can cause a key event to be fired with
 * a keyCode for the first key and a charCode for the second key.
 *
 * Safari 2 in keypress (not supported)
 *
 *        charCode keyCode which
 * ENTER:       13      13    13
 * F1:       63236   63236 63236
 * F8:       63243   63243 63243
 * ...
 * p:          112     112   112
 * P:           80      80    80
 *
 * Firefox, keypress:
 *
 *        charCode keyCode which
 * ENTER:        0      13    13
 * F1:           0     112     0
 * F8:           0     119     0
 * ...
 * p:          112       0   112
 * P:           80       0    80
 *
 * Opera, Mac+Win32, keypress:
 *
 *         charCode keyCode which
 * ENTER: undefined      13    13
 * F1:    undefined     112     0
 * F8:    undefined     119     0
 * ...
 * p:     undefined     112   112
 * P:     undefined      80    80
 *
 * IE7, keydown
 *
 *         charCode keyCode     which
 * ENTER: undefined      13 undefined
 * F1:    undefined     112 undefined
 * F8:    undefined     119 undefined
 * ...
 * p:     undefined      80 undefined
 * P:     undefined      80 undefined
 */

@import <Foundation/CPObject.j>
@import <Foundation/CPRunLoop.j>

@import "CPEvent.j"
@import "CPText.j"
@import "CPCompatibility.j"

@import "CPDOMWindowLayer.j"

@import "CPPlatform.j"
@import "CPPlatformWindow.j"
@import "CPPlatformWindow+DOMKeys.j"

#import "../../CoreGraphics/CGGeometry.h"

// List of all open native windows
var PlatformWindows = [CPSet set];

// Define up here so compressor knows about em.
var CPDOMEventGetClickCount,
    CPDOMEventStop,
    StopDOMEventPropagation,
    StopContextMenuDOMEventPropagation;

//right now we hard code q, w, r and t as keys to propogate
//these aren't normal keycodes, they are with modifier key codes
//might be mac only, we should investigate futher later.
var KeyCodesToPrevent = {},
    CharacterKeysToPrevent = {},
    KeyCodesToAllow = {},
    MozKeyCodeToKeyCodeMap = {
        61: 187,  // =, equals
        59: 186   // ;, semicolon
    },
    KeyCodesToUnicodeMap = {};

KeyCodesToPrevent[CPKeyCodes.A] = YES;

KeyCodesToAllow[CPKeyCodes.F1] = YES;
KeyCodesToAllow[CPKeyCodes.F2] = YES;
KeyCodesToAllow[CPKeyCodes.F3] = YES;
KeyCodesToAllow[CPKeyCodes.F4] = YES;
KeyCodesToAllow[CPKeyCodes.F5] = YES;
KeyCodesToAllow[CPKeyCodes.F6] = YES;
KeyCodesToAllow[CPKeyCodes.F7] = YES;
KeyCodesToAllow[CPKeyCodes.F8] = YES;
KeyCodesToAllow[CPKeyCodes.F9] = YES;
KeyCodesToAllow[CPKeyCodes.F10] = YES;
KeyCodesToAllow[CPKeyCodes.F11] = YES;
KeyCodesToAllow[CPKeyCodes.F12] = YES;

KeyCodesToUnicodeMap[CPKeyCodes.BACKSPACE]              = CPDeleteCharacter;
KeyCodesToUnicodeMap[CPKeyCodes.DELETE]                 = CPDeleteFunctionKey;
KeyCodesToUnicodeMap[CPKeyCodes.TAB]                    = CPTabCharacter;
KeyCodesToUnicodeMap[CPKeyCodes.ENTER]                  = CPCarriageReturnCharacter;
KeyCodesToUnicodeMap[CPKeyCodes.ESC]                    = CPEscapeFunctionKey;
KeyCodesToUnicodeMap[CPKeyCodes.PAGE_UP]                = CPPageUpFunctionKey;
KeyCodesToUnicodeMap[CPKeyCodes.PAGE_DOWN]              = CPPageDownFunctionKey;
KeyCodesToUnicodeMap[CPKeyCodes.LEFT]                   = CPLeftArrowFunctionKey;
KeyCodesToUnicodeMap[CPKeyCodes.UP]                     = CPUpArrowFunctionKey;
KeyCodesToUnicodeMap[CPKeyCodes.RIGHT]                  = CPRightArrowFunctionKey;
KeyCodesToUnicodeMap[CPKeyCodes.DOWN]                   = CPDownArrowFunctionKey;
KeyCodesToUnicodeMap[CPKeyCodes.SEMICOLON]              = ";";
KeyCodesToUnicodeMap[CPKeyCodes.DASH]                   = "-";
KeyCodesToUnicodeMap[CPKeyCodes.EQUALS]                 = "=";
KeyCodesToUnicodeMap[CPKeyCodes.COMMA]                  = ",";
KeyCodesToUnicodeMap[CPKeyCodes.PERIOD]                 = ".";
KeyCodesToUnicodeMap[CPKeyCodes.SLASH]                  = "/";
KeyCodesToUnicodeMap[CPKeyCodes.APOSTROPHE]             = "`";
KeyCodesToUnicodeMap[CPKeyCodes.SINGLE_QUOTE]           = "'";
KeyCodesToUnicodeMap[CPKeyCodes.OPEN_SQUARE_BRACKET]    = "[";
KeyCodesToUnicodeMap[CPKeyCodes.BACKSLASH]              = "\\";
KeyCodesToUnicodeMap[CPKeyCodes.CLOSE_SQUARE_BRACKET]   = "]";

var ModifierKeyCodes = [
    CPKeyCodes.META,
    CPKeyCodes.MAC_FF_META,
    CPKeyCodes.CTRL,
    CPKeyCodes.ALT,
    CPKeyCodes.SHIFT
];

var supportsNativeDragAndDrop = [CPPlatform supportsDragAndDrop];

@implementation CPPlatformWindow (DOM)

- (id)_init
{
    self = [super init];

    if (self)
    {
        _DOMWindow = window;
        _contentRect = _CGRectMakeZero();

        _windowLevels = [];
        _windowLayers = [CPDictionary dictionary];

        [self registerDOMWindow];
        [self updateFromNativeContentRect];

        _charCodes = {};
    }

    return self;
}

- (CGRect)nativeContentRect
{
    if (!_DOMWindow)
        return [self contentRect];

    if (_DOMWindow.cpFrame)
        return _DOMWindow.cpFrame();

    var contentRect = _CGRectMakeZero();

    if (window.screenTop)
        contentRect.origin = _CGPointMake(_DOMWindow.screenLeft, _DOMWindow.screenTop);

    else if (window.screenX)
        contentRect.origin = _CGPointMake(_DOMWindow.screenX, _DOMWindow.screenY);

    // Safari, Mozilla, Firefox, and Opera
    if (_DOMWindow.innerWidth)
        contentRect.size = _CGSizeMake(_DOMWindow.innerWidth, _DOMWindow.innerHeight);

    // Internet Explorer 6 in Strict Mode
    else if (document.documentElement && document.documentElement.clientWidth)
        contentRect.size = _CGSizeMake(_DOMWindow.document.documentElement.clientWidth, _DOMWindow.document.documentElement.clientHeight);

    // Internet Explorer X
    else
        contentRect.size = _CGSizeMake(_DOMWindow.document.body.clientWidth, _DOMWindow.document.body.clientHeight);

    return contentRect;
}

- (void)updateNativeContentRect
{
    if (!_DOMWindow)
        return;

    if (typeof _DOMWindow["cpSetFrame"] === "function")
        return _DOMWindow.cpSetFrame([self contentRect]);

    var origin = [self contentRect].origin,
        nativeOrigin = [self nativeContentRect].origin;

    if (origin.x !== nativeOrigin.x || origin.y !== nativeOrigin.y)
    {
        _DOMWindow.moveBy(origin.x - nativeOrigin.x, origin.y - nativeOrigin.y);
    }

    var size = [self contentRect].size,
        nativeSize = [self nativeContentRect].size;

    if (size.width !== nativeSize.width || size.height !== nativeSize.height)
    {
        _DOMWindow.resizeBy(size.width - nativeSize.width, size.height - nativeSize.height);
    }
}

- (void)orderBack:(id)aSender
{
    if (_DOMWindow)
        _DOMWindow.blur();
}

- (void)createDOMElements
{
    var theDocument = _DOMWindow.document;

    // This guy fixes an issue in Firefox where if you focus the URL field, we stop getting key events
    _DOMFocusElement = theDocument.createElement("input");

    _DOMFocusElement.style.position = "absolute";
    _DOMFocusElement.style.zIndex = "-1000";
    _DOMFocusElement.style.opacity = "0";
    _DOMFocusElement.style.filter = "alpha(opacity=0)";
    _DOMFocusElement.className = "cpdontremove";

    _DOMBodyElement.appendChild(_DOMFocusElement);

    // Create Native Pasteboard handler.
    _DOMPasteboardElement = theDocument.createElement("textarea");

    _DOMPasteboardElement.style.position = "absolute";
    _DOMPasteboardElement.style.top = "-10000px";
    _DOMPasteboardElement.style.zIndex = "999";
    _DOMPasteboardElement.className = "cpdontremove";

    _DOMBodyElement.appendChild(_DOMPasteboardElement);

    // Make sure the pastboard element is blurred.
    _DOMPasteboardElement.blur();

    // Create a full screen div to protect against iframes and other elements from consuming events during tracking
    // FIXME: multiple windows
    _DOMEventGuard = theDocument.createElement("div");
    _DOMEventGuard.style.position = "absolute";
    _DOMEventGuard.style.top = "0px";
    _DOMEventGuard.style.left = "0px";
    _DOMEventGuard.style.width = "100%";
    _DOMEventGuard.style.height = "100%";
    _DOMEventGuard.style.zIndex = "999";
    _DOMEventGuard.style.display = "none";
    _DOMEventGuard.className = "cpdontremove";
    _DOMBodyElement.appendChild(_DOMEventGuard);
    
    // We get scrolling deltas from this element
    _DOMScrollingElement = theDocument.createElement("div");
    _DOMScrollingElement.style.position = "absolute";
    _DOMScrollingElement.style.visibility = "hidden";
    _DOMScrollingElement.style.zIndex = "998";
    _DOMScrollingElement.style.height = "60px";
    _DOMScrollingElement.style.width = "60px";
    _DOMScrollingElement.style.overflow = "scroll";
    //_DOMScrollingElement.style.backgroundColor = "rgba(0,0,0,1.0)"; // debug help.
    _DOMScrollingElement.style.opacity = "0";
    _DOMScrollingElement.style.filter = "alpha(opacity=0)";
    _DOMScrollingElement.className = "cpdontremove";
    _DOMBodyElement.appendChild(_DOMScrollingElement);
    
    var _DOMInnerScrollingElement = theDocument.createElement("div");
    _DOMInnerScrollingElement.style.width = "400px";
    _DOMInnerScrollingElement.style.height = "400px";
    _DOMScrollingElement.appendChild(_DOMInnerScrollingElement);
    
    // Set an initial scroll offset
    _DOMScrollingElement.scrollTop = 150;
    _DOMScrollingElement.scrollLeft = 150;
}

- (void)registerDOMWindow
{
    var theDocument = _DOMWindow.document;

    _DOMBodyElement = theDocument.getElementById("cappuccino-body") || theDocument.body;

    // FIXME: Always do this?
    if (supportsNativeDragAndDrop)
        _DOMBodyElement.style["-khtml-user-select"] = "none";

    _DOMBodyElement.webkitTouchCallout = "none";

    [self createDOMElements];
    [self _addLayers];

    var theClass = [self class],

        dragEventImplementation = class_getMethodImplementation(theClass, @selector(dragEvent:)),
        dragEventCallback = function (anEvent) { dragEventImplementation(self, nil, anEvent); },

        resizeEventSelector = @selector(resizeEvent:),
        resizeEventImplementation = class_getMethodImplementation(theClass, resizeEventSelector),
        resizeEventCallback = function (anEvent) { resizeEventImplementation(self, nil, anEvent); },

        copyEventSelector = @selector(copyEvent:),
        copyEventImplementation = class_getMethodImplementation(theClass, copyEventSelector),
        copyEventCallback = function (anEvent) {copyEventImplementation(self, nil, anEvent); },

        pasteEventSelector = @selector(pasteEvent:),
        pasteEventImplementation = class_getMethodImplementation(theClass, pasteEventSelector),
        pasteEventCallback = function (anEvent) {pasteEventImplementation(self, nil, anEvent); },

        keyEventSelector = @selector(keyEvent:),
        keyEventImplementation = class_getMethodImplementation(theClass, keyEventSelector),
        keyEventCallback = function (anEvent) { keyEventImplementation(self, nil, anEvent); },

        mouseEventSelector = @selector(mouseEvent:),
        mouseEventImplementation = class_getMethodImplementation(theClass, mouseEventSelector),
        mouseEventCallback = function (anEvent) { mouseEventImplementation(self, nil, anEvent); },

        contextMenuEventSelector = @selector(contextMenuEvent:),
        contextMenuEventImplementation = class_getMethodImplementation(theClass, contextMenuEventSelector),
        contextMenuEventCallback = function (anEvent) { return contextMenuEventImplementation(self, nil, anEvent); },

        scrollEventSelector = @selector(scrollEvent:),
        scrollEventImplementation = class_getMethodImplementation(theClass, scrollEventSelector),
        scrollEventCallback = function (anEvent) { scrollEventImplementation(self, nil, anEvent); },

        touchEventSelector = @selector(touchEvent:),
        touchEventImplementation = class_getMethodImplementation(theClass, touchEventSelector),
        touchEventCallback = function (anEvent) { touchEventImplementation(self, nil, anEvent); };

    if (theDocument.addEventListener)
    {
        if ([CPPlatform supportsDragAndDrop])
        {
            theDocument.addEventListener("dragstart", dragEventCallback, NO);
            theDocument.addEventListener("drag", dragEventCallback, NO);
            theDocument.addEventListener("dragend", dragEventCallback, NO);
            theDocument.addEventListener("dragover", dragEventCallback, NO);
            theDocument.addEventListener("dragleave", dragEventCallback, NO);
            theDocument.addEventListener("drop", dragEventCallback, NO);
        }

        theDocument.addEventListener("mouseup", mouseEventCallback, NO);
        theDocument.addEventListener("mousedown", mouseEventCallback, NO);
        theDocument.addEventListener("mousemove", mouseEventCallback, NO);
        theDocument.addEventListener("contextmenu", contextMenuEventCallback, NO);

        theDocument.addEventListener("beforecopy", copyEventCallback, NO);
        theDocument.addEventListener("beforecut", copyEventCallback, NO);
        theDocument.addEventListener("beforepaste", pasteEventCallback, NO);

        theDocument.addEventListener("keyup", keyEventCallback, NO);
        theDocument.addEventListener("keydown", keyEventCallback, NO);
        theDocument.addEventListener("keypress", keyEventCallback, NO);

        theDocument.addEventListener("touchstart", touchEventCallback, NO);
        theDocument.addEventListener("touchend", touchEventCallback, NO);
        theDocument.addEventListener("touchmove", touchEventCallback, NO);
        theDocument.addEventListener("touchcancel", touchEventCallback, NO);

        _DOMWindow.addEventListener("DOMMouseScroll", scrollEventCallback, NO);
        _DOMWindow.addEventListener("mousewheel", scrollEventCallback, NO);

        _DOMWindow.addEventListener("resize", resizeEventCallback, NO);

        _DOMWindow.addEventListener("unload", function()
        {
            [self updateFromNativeContentRect];
            [self _removeLayers];

            theDocument.removeEventListener("mouseup", mouseEventCallback, NO);
            theDocument.removeEventListener("mousedown", mouseEventCallback, NO);
            theDocument.removeEventListener("mousemove", mouseEventCallback, NO);
            theDocument.removeEventListener("contextmenu", contextMenuEventCallback, NO);

            theDocument.removeEventListener("keyup", keyEventCallback, NO);
            theDocument.removeEventListener("keydown", keyEventCallback, NO);
            theDocument.removeEventListener("keypress", keyEventCallback, NO);

            theDocument.removeEventListener("beforecopy", copyEventCallback, NO);
            theDocument.removeEventListener("beforecut", copyEventCallback, NO);
            theDocument.removeEventListener("beforepaste", pasteEventCallback, NO);

            theDocument.removeEventListener("touchstart", touchEventCallback, NO);
            theDocument.removeEventListener("touchend", touchEventCallback, NO);
            theDocument.removeEventListener("touchmove", touchEventCallback, NO);

            _DOMWindow.removeEventListener("resize", resizeEventCallback, NO);

            //FIXME: does firefox really need a different value?
            _DOMWindow.removeEventListener("DOMMouseScroll", scrollEventCallback, NO);
            _DOMWindow.removeEventListener("mousewheel", scrollEventCallback, NO);

            //_DOMWindow.removeEventListener("beforeunload", this, NO);

            [PlatformWindows removeObject:self];

            self._DOMWindow = nil;
        }, NO);
    }
    else
    {
        theDocument.attachEvent("onmouseup", mouseEventCallback);
        theDocument.attachEvent("onmousedown", mouseEventCallback);
        theDocument.attachEvent("onmousemove", mouseEventCallback);
        theDocument.attachEvent("ondblclick", mouseEventCallback);
        theDocument.attachEvent("oncontextmenu", contextMenuEventCallback);

        theDocument.attachEvent("onkeyup", keyEventCallback);
        theDocument.attachEvent("onkeydown", keyEventCallback);
        theDocument.attachEvent("onkeypress", keyEventCallback);

        _DOMWindow.attachEvent("onresize", resizeEventCallback);

        _DOMWindow.onmousewheel = scrollEventCallback;
        theDocument.onmousewheel = scrollEventCallback;

        _DOMBodyElement.ondrag = function () { return NO; };
        _DOMBodyElement.onselectstart = function () { return _DOMWindow.event.srcElement === _DOMPasteboardElement; };

        _DOMWindow.attachEvent("onbeforeunload", function()
        {
            [self updateFromNativeContentRect];
            [self _removeLayers];

            theDocument.detachEvent("onmouseup", mouseEventCallback);
            theDocument.detachEvent("onmousedown", mouseEventCallback);
            theDocument.detachEvent("onmousemove", mouseEventCallback);
            theDocument.detachEvent("ondblclick", mouseEventCallback);
            theDocument.detachEvent("oncontextmenu", contextMenuEventCallback);

            theDocument.detachEvent("onkeyup", keyEventCallback);
            theDocument.detachEvent("onkeydown", keyEventCallback);
            theDocument.detachEvent("onkeypress", keyEventCallback);

            _DOMWindow.detachEvent("onresize", resizeEventCallback);

            _DOMWindow.onmousewheel = NULL;
            theDocument.onmousewheel = NULL;

            _DOMBodyElement.ondrag = NULL;
            _DOMBodyElement.onselectstart = NULL;

            //_DOMWindow.removeEvent("beforeunload", this);

            [PlatformWindows removeObject:self];

            self._DOMWindow = nil;
        }, NO);
    }
}

+ (CPSet)visiblePlatformWindows
{
    if ([[CPPlatformWindow primaryPlatformWindow] isVisible])
    {
        var set = [CPSet setWithSet:PlatformWindows];
        [set addObject:[CPPlatformWindow primaryPlatformWindow]];
        return set;
    }
    else
        return PlatformWindows;
}

- (void)orderFront:(id)aSender
{
    if (_DOMWindow)
        return _DOMWindow.focus();

    _DOMWindow = window.open("", "_blank", "menubar=no,location=no,resizable=yes,scrollbars=no,status=no,left=" + _CGRectGetMinX(_contentRect) + ",top=" + _CGRectGetMinY(_contentRect) + ",width=" + _CGRectGetWidth(_contentRect) + ",height=" + _CGRectGetHeight(_contentRect));

    [PlatformWindows addObject:self];

    // FIXME: cpSetFrame?
    _DOMWindow.document.write("<!DOCTYPE html><html lang='en'><head></head><body style='background-color:transparent;'></body></html>");
    _DOMWindow.document.close();

    if (![CPPlatform isBrowser])
    {
        _DOMWindow.cpWindowNumber = [self._only windowNumber];
        _DOMWindow.cpSetFrame(_contentRect);
        _DOMWindow.cpSetLevel(_level);
        _DOMWindow.cpSetHasShadow(_hasShadow);
        _DOMWindow.cpSetShadowStyle(_shadowStyle);
    }

    [self registerDOMWindow];

    _DOMBodyElement.style.cursor = [[CPCursor currentCursor] _cssString];
}

- (void)orderOut:(id)aSender
{
    if (!_DOMWindow)
        return;

    _DOMWindow.close();
}

- (void)dragEvent:(DOMEvent)aDOMEvent
{
    var type = aDOMEvent.type,
        dragServer = [CPDragServer sharedDragServer],
        location = _CGPointMake(aDOMEvent.clientX, aDOMEvent.clientY),
        pasteboard = [_CPDOMDataTransferPasteboard DOMDataTransferPasteboard];

    [pasteboard _setDataTransfer:aDOMEvent.dataTransfer];

    if (aDOMEvent.type === "dragstart")
    {
        [[CPRunLoop currentRunLoop] limitDateForMode:CPDefaultRunLoopMode];

        [pasteboard _setPasteboard:[dragServer draggingPasteboard]];

        var draggedWindow = [dragServer draggedWindow],
            draggedWindowFrame = [draggedWindow frame],
            DOMDragElement = draggedWindow._DOMElement;

        DOMDragElement.style.left = -_CGRectGetWidth(draggedWindowFrame) + "px";
        DOMDragElement.style.top = -_CGRectGetHeight(draggedWindowFrame) + "px";

        var parentNode = DOMDragElement.parentNode;

        if (parentNode)
            parentNode.removeChild(DOMDragElement);

        _DOMBodyElement.appendChild(DOMDragElement);

        var draggingOffset = [dragServer draggingOffset];

        aDOMEvent.dataTransfer.setDragImage(DOMDragElement, draggingOffset.width, draggingOffset.height);
        aDOMEvent.dataTransfer.effectAllowed = "all";

        [dragServer draggingStartedInPlatformWindow:self globalLocation:[CPPlatform isBrowser] ? location : _CGPointMake(aDOMEvent.screenX, aDOMEvent.screenY)];
    }

    else if (type === "drag")
    {
        var y = aDOMEvent.screenY;

        if (CPFeatureIsCompatible(CPHTML5DragAndDropSourceYOffBy1))
            y -= 1;

        [dragServer draggingSourceUpdatedWithGlobalLocation:[CPPlatform isBrowser] ? location : _CGPointMake(aDOMEvent.screenX, y)];
    }

    else if (type === "dragover" || type === "dragleave")
    {
        if (aDOMEvent.preventDefault)
            aDOMEvent.preventDefault();

        var dropEffect = "none",
            dragOperation = [dragServer draggingUpdatedInPlatformWindow:self location:location];

        if (dragOperation === CPDragOperationMove || dragOperation === CPDragOperationGeneric || dragOperation === CPDragOperationPrivate)
            dropEffect = "move";

        else if (dragOperation === CPDragOperationCopy)
            dropEffect = "copy";

        else if (dragOperation === CPDragOperationLink)
            dropEffect = "link";

        aDOMEvent.dataTransfer.dropEffect = dropEffect;
    }

    else if (type === "dragend")
    {
        var dropEffect = aDOMEvent.dataTransfer.dropEffect;

        if (dropEffect === "move")
            dragOperation = CPDragOperationMove;
        else if (dropEffect === "copy")
            dragOperation = CPDragOperationCopy;
        else if (dropEffect === "link")
            dragOperation = CPDragOperationLink;
        else
            dragOperation = CPDragOperationNone;

        [dragServer draggingEndedInPlatformWindow:self globalLocation:[CPPlatform isBrowser] ? location : _CGPointMake(aDOMEvent.screenX, aDOMEvent.screenY) operation:dragOperation];
    }

    else //if (type === "drop")
    {
        [dragServer performDragOperationInPlatformWindow:self];

        // W3C Model
        if (aDOMEvent.preventDefault)
            aDOMEvent.preventDefault();

        if (aDOMEvent.stopPropagation)
            aDOMEvent.stopPropagation();
    }

    [[CPRunLoop currentRunLoop] limitDateForMode:CPDefaultRunLoopMode];
}

- (void)keyEvent:(DOMEvent)aDOMEvent
{
    var event,
        timestamp = aDOMEvent.timeStamp || new Date(),
        sourceElement = aDOMEvent.target || aDOMEvent.srcElement,
        windowNumber = [[CPApp keyWindow] windowNumber],
        modifierFlags = (aDOMEvent.shiftKey ? CPShiftKeyMask : 0) |
                        (aDOMEvent.ctrlKey ? CPControlKeyMask : 0) |
                        (aDOMEvent.altKey ? CPAlternateKeyMask : 0) |
                        (aDOMEvent.metaKey ? CPCommandKeyMask : 0);

    // With a few exceptions, all key events are blocked from propagating to
    // the browser.  Here the following exceptions are being allowed:
    //
    //   - All keys pressed along with a ctrl or cmd key _unless_ they are in
    //     one of the two blacklists.
    //   - Any key listed in the whitelist.
    //
    // The ctrl/cmd keys are used for browser hotkeys as are the keys listed in
    // the whitelist (F1-F12 at the time of writing).
    //
    // If a key is listed in both the blacklist and whitelist, the blacklist is
    // checked first.  The key will be blocked from propagating in that case.

    StopDOMEventPropagation = YES;

    // Make sure it is not in the blacklists.
    if(! (CharacterKeysToPrevent[String.fromCharCode(aDOMEvent.keyCode || aDOMEvent.charCode).toLowerCase()] || KeyCodesToPrevent[aDOMEvent.keyCode]))
    {
        // It is not in the blacklist, let it through if the ctrl/cmd key is
        // also down or it's in the whitelist.
        if((modifierFlags & (CPControlKeyMask | CPCommandKeyMask)) || KeyCodesToAllow[aDOMEvent.keyCode])
        {
            StopDOMEventPropagation = NO;
        }
    }

    var isNativePasteEvent = NO,
        isNativeCopyOrCutEvent = NO,
        overrideCharacters = nil;

    switch (aDOMEvent.type)
    {
        case "keydown":     // Grab and store the keycode now since it is correct and consistent at this point.
                            if (aDOMEvent.keyCode in MozKeyCodeToKeyCodeMap)
                                _keyCode = MozKeyCodeToKeyCodeMap[aDOMEvent.keyCode];
                            else
                                _keyCode = aDOMEvent.keyCode;

                            var characters;

                            // Handle key codes for which String.fromCharCode won't work.
                            if (aDOMEvent.which === 0 || aDOMEvent.charCode === 0)
                                characters = KeyCodesToUnicodeMap[_keyCode];

                            if (!characters)
                                characters = String.fromCharCode(_keyCode).toLowerCase();

                            overrideCharacters = (modifierFlags & CPShiftKeyMask || _capsLockActive) ? characters.toUpperCase() : characters;

                            // check for caps lock state
                            if (_keyCode === CPKeyCodes.CAPS_LOCK)
                                _capsLockActive = YES;

                            if ([ModifierKeyCodes containsObject:_keyCode])
                            {
                                // A modifier key will never fire keypress. We don't need to do any other processing so we just fire it here and break.
                                event = [CPEvent keyEventWithType:CPFlagsChanged location:location modifierFlags:modifierFlags
                                            timestamp:timestamp windowNumber:windowNumber context:nil
                                            characters:nil charactersIgnoringModifiers:nil isARepeat:NO keyCode:_keyCode];

                                break;
                            }
                            else if (modifierFlags & (CPControlKeyMask | CPCommandKeyMask))
                            {
                                //we are simply going to skip all keypress events that use cmd/ctrl key
                                //this lets us be consistent in all browsers and send on the keydown
                                //which means we can cancel the event early enough, but only if sendEvent needs to

                                var eligibleForCopyPaste = [self _validateCopyCutOrPasteEvent:aDOMEvent flags:modifierFlags];

                                // If this could be a native PASTE event, then we need to further examine it before
                                // sending a CPEvent.  Select our element to see if anything gets pasted in it.
                                if (characters === "v" && eligibleForCopyPaste)
                                {
                                    if (!_ignoreNativePastePreparation)
                                    {
                                        _DOMPasteboardElement.select();
                                        _DOMPasteboardElement.value = "";
                                    }

                                    isNativePasteEvent = YES;
                                }

                                // However, of this could be a native COPY event, we need to let the normal event-process take place so it
                                // can capture our internal Cappuccino pasteboard.
                                else if ((characters == "c" || characters == "x") && eligibleForCopyPaste)
                                {
                                    isNativeCopyOrCutEvent = YES;

                                    if (_ignoreNativeCopyOrCutEvent)
                                        break;
                                }
                            }
                            else if (CPKeyCodes.firesKeyPressEvent(_keyCode, _lastKey, aDOMEvent.shiftKey, aDOMEvent.ctrlKey, aDOMEvent.altKey))
                            {
                                // this branch is taken by events which fire keydown, keypress, and keyup.
                                // this is the only time we'll ALLOW character keys to propagate (needed for text fields)
                                StopDOMEventPropagation = NO;
                                break;
                            }
                            else
                            {
                                //this branch is taken by "remedial" key events
                                // In this state we continue to keypress and send the CPEvent
                            }

        case "keypress":
                            // we unconditionally break on keypress events with modifiers,
                            // because we forced the event to be sent on the keydown
                            if (aDOMEvent.type === "keypress" && (modifierFlags & (CPControlKeyMask | CPCommandKeyMask)))
                                break;

                            var keyCode = _keyCode,
                                charCode = aDOMEvent.keyCode || aDOMEvent.charCode,
                                isARepeat = (_charCodes[keyCode] != nil);

                            _lastKey = keyCode;
                            _charCodes[keyCode] = charCode;

                            var characters = overrideCharacters;
                            // Is this a special key?
                            if (!characters && (aDOMEvent.which === 0 || aDOMEvent.charCode === 0))
                                characters = KeyCodesToUnicodeMap[charCode];

                            if (!characters)
                                characters = String.fromCharCode(charCode);

                            charactersIgnoringModifiers = characters.toLowerCase(); // FIXME: This isn't correct. It SHOULD include Shift.

                            // Safari won't send proper capitalization during cmd-key events
                            if (!overrideCharacters && (modifierFlags & CPCommandKeyMask) && ((modifierFlags & CPShiftKeyMask) || _capsLockActive))
                                characters = characters.toUpperCase();

                            event = [CPEvent keyEventWithType:CPKeyDown location:location modifierFlags:modifierFlags
                                        timestamp:timestamp windowNumber:windowNumber context:nil
                                        characters:characters charactersIgnoringModifiers:charactersIgnoringModifiers isARepeat:isARepeat keyCode:charCode];

                            if (isNativePasteEvent)
                            {
                                _pasteboardKeyDownEvent = event;
                                window.setNativeTimeout(function () { [self _checkPasteboardElement] }, 0);
                            }

                            break;

        case "keyup":       var keyCode = aDOMEvent.keyCode,
                                charCode = _charCodes[keyCode];

                            _keyCode = -1;
                            _lastKey = -1;
                            _charCodes[keyCode] = nil;
                            _ignoreNativeCopyOrCutEvent = NO;
                            _ignoreNativePastePreparation = NO;

                            // check for caps lock state
                            if (keyCode === CPKeyCodes.CAPS_LOCK)
                                _capsLockActive = NO;

                            if ([ModifierKeyCodes containsObject:keyCode])
                                break;

                            var characters = KeyCodesToUnicodeMap[charCode] || String.fromCharCode(charCode),
                                charactersIgnoringModifiers = characters.toLowerCase();

                            if (!(modifierFlags & CPShiftKeyMask) && (modifierFlags & CPCommandKeyMask) && !_capsLockActive)
                                characters = charactersIgnoringModifiers;

                            event = [CPEvent keyEventWithType:CPKeyUp location:location modifierFlags:modifierFlags
                                        timestamp: timestamp windowNumber:windowNumber context:nil
                                        characters:characters charactersIgnoringModifiers:charactersIgnoringModifiers isARepeat:NO keyCode:keyCode];
                            break;
    }

    if (event && !isNativePasteEvent)
    {
        event._DOMEvent = aDOMEvent;

        [CPApp sendEvent:event];

        if (isNativeCopyOrCutEvent)
        {
            // If this is a native copy event, then check if the pasteboard has anything in it.
            [self _primePasteboardElement];
        }
    }

    if (StopDOMEventPropagation)
        CPDOMEventStop(aDOMEvent, self);

    [[CPRunLoop currentRunLoop] limitDateForMode:CPDefaultRunLoopMode];
}

- (void)copyEvent:(DOMEvent)aDOMEvent
{
    if ([self _validateCopyCutOrPasteEvent:aDOMEvent flags:CPPlatformActionKeyMask] && !_ignoreNativeCopyOrCutEvent)
    {
        //we have to send out a fake copy or cut event so that we can force the copy/cut mechanisms to take place
        var cut = aDOMEvent.type === "beforecut",
            keyCode = cut ? CPKeyCodes.X : CPKeyCodes.C,
            characters = cut ? "x" : "c",
            timestamp = aDOMEvent.timeStamp ? aDOMEvent.timeStamp : new Date(),
            windowNumber = [[CPApp keyWindow] windowNumber],
            modifierFlags = CPPlatformActionKeyMask;

        event = [CPEvent keyEventWithType:CPKeyDown location:location modifierFlags:modifierFlags
                    timestamp:timestamp windowNumber:windowNumber context:nil
                    characters:characters charactersIgnoringModifiers:characters isARepeat:NO keyCode:keyCode];

        event._DOMEvent = aDOMEvent;
        [CPApp sendEvent:event];

        [self _primePasteboardElement];

        //then we have to IGNORE the real keyboard event to prevent a double copy
        //safari also sends the beforecopy event twice, so we additionally check here and prevent two events
        _ignoreNativeCopyOrCutEvent = YES;
    }

    [[CPRunLoop currentRunLoop] limitDateForMode:CPDefaultRunLoopMode];
}

- (void)pasteEvent:(DOMEvent)aDOMEvent
{
    if ([self _validateCopyCutOrPasteEvent:aDOMEvent flags:CPPlatformActionKeyMask])
    {
        _DOMPasteboardElement.focus();
        _DOMPasteboardElement.select();
        _DOMPasteboardElement.value = "";
        _ignoreNativePastePreparation = YES;
    }

    [[CPRunLoop currentRunLoop] limitDateForMode:CPDefaultRunLoopMode];
}

- (void)_validateCopyCutOrPasteEvent:(DOMEvent)aDOMEvent flags:(unsigned)modifierFlags
{
    return (
            ((aDOMEvent.target || aDOMEvent.srcElement).nodeName.toUpperCase() !== "INPUT" &&
             (aDOMEvent.target || aDOMEvent.srcElement).nodeName.toUpperCase() !== "TEXTAREA"
            ) || aDOMEvent.target === _DOMPasteboardElement
           ) &&
            (modifierFlags & CPPlatformActionKeyMask);
}

- (void)_primePasteboardElement
{
    var pasteboard = [CPPasteboard generalPasteboard],
        types = [pasteboard types];

    if (types.length)
    {
        if ([types indexOfObjectIdenticalTo:CPStringPboardType] != CPNotFound)
            _DOMPasteboardElement.value = [pasteboard stringForType:CPStringPboardType];
        else
            _DOMPasteboardElement.value = [pasteboard _generateStateUID];

        _DOMPasteboardElement.focus();
        _DOMPasteboardElement.select();

        window.setNativeTimeout(function() { [self _clearPasteboardElement]; }, 0);
    }
}

- (void)_checkPasteboardElement
{
    var value = _DOMPasteboardElement.value;

    if ([value length])
    {
        var pasteboard = [CPPasteboard generalPasteboard];

        if ([pasteboard _stateUID] != value)
        {
            [pasteboard declareTypes:[CPStringPboardType] owner:self];
            [pasteboard setString:value forType:CPStringPboardType];
        }
    }

    [self _clearPasteboardElement];

    [CPApp sendEvent:_pasteboardKeyDownEvent];

    _pasteboardKeyDownEvent = nil;

    [[CPRunLoop currentRunLoop] limitDateForMode:CPDefaultRunLoopMode];
}

- (void)_clearPasteboardElement
{
    _DOMPasteboardElement.value = "";
    _DOMPasteboardElement.blur();
}

- (void)scrollEvent:(DOMEvent)aDOMEvent
{
    if (_hideDOMScrollingElementTimeout)
    {
        clearTimeout(_hideDOMScrollingElementTimeout);
        _hideDOMScrollingElementTimeout = nil;
    }
    
    if(!aDOMEvent)
        aDOMEvent = window.event;

    var location = nil;
    if (CPFeatureIsCompatible(CPJavaScriptMouseWheelValues_8_15))
    {
        var x = aDOMEvent._offsetX || 0.0,
            y = aDOMEvent._offsetY || 0.0,
            element = aDOMEvent.target;

        while (element.nodeType !== 1)
            element = element.parentNode;

        if (element.offsetParent)
        {
            do
            {
                x += element.offsetLeft;
                y += element.offsetTop;

            } while (element = element.offsetParent);
        }

        location = _CGPointMake((x + ((aDOMEvent.clientX - 8) / 15)), (y + ((aDOMEvent.clientY - 8) / 15)));
    }
    else if (aDOMEvent._overrideLocation)
        location = aDOMEvent._overrideLocation;
    else
        location = _CGPointMake(aDOMEvent.clientX, aDOMEvent.clientY);

    var deltaX = 0.0,
        deltaY = 0.0,
        windowNumber = 0,
        timestamp = aDOMEvent.timeStamp ? aDOMEvent.timeStamp : new Date(),
        modifierFlags = (aDOMEvent.shiftKey ? CPShiftKeyMask : 0) |
                        (aDOMEvent.ctrlKey ? CPControlKeyMask : 0) |
                        (aDOMEvent.altKey ? CPAlternateKeyMask : 0) |
                        (aDOMEvent.metaKey ? CPCommandKeyMask : 0);

    // Show the dom element
    _DOMScrollingElement.style.visibility = "visible";
    _DOMScrollingElement.style.top = (location.y - 15) + @"px";
    _DOMScrollingElement.style.left = (location.x - 15) + @"px";

    // We let the browser handle the scrolling
    StopDOMEventPropagation = NO;

    var theWindow = [self hitTest:location];

    if (!theWindow)
        return;

    var windowNumber = [theWindow windowNumber];

    location = [theWindow convertBridgeToBase:location];

    var event = [CPEvent mouseEventWithType:CPScrollWheel location:location modifierFlags:modifierFlags
                                  timestamp:timestamp windowNumber:windowNumber context:nil eventNumber:-1 clickCount:1 pressure:0];
    event._DOMEvent = aDOMEvent;
    
    // We lag 1 event behind without this timeout.
    setTimeout(function(){     
           
        // Find the scroll delta
        var deltaX = _DOMScrollingElement.scrollLeft - 150,
            deltaY = _DOMScrollingElement.scrollTop - 150;
        
        // If we scroll super with momentum,
        // there are so many events going off that
        // a tiny percent don't actually have any deltas.
        //
        // This does *not* make scrolling appear sluggish,
        // it just seems like that is something that happens.
        //
        // We get free performance boost if we skip sending these events,
        // as sending a scroll event with no deltas doesn't do anything.
        if (deltaX || deltaY)
        {
            event._deltaX = deltaX;
            event._deltaY = deltaY;
            
            [CPApp sendEvent:event];
        }

        // We set StopDOMEventPropagation = NO on line 1008
        //if (StopDOMEventPropagation)
        //    CPDOMEventStop(aDOMEvent, self);

        // Reset the DOM elements scroll offset
        _DOMScrollingElement.scrollLeft = 150;
        _DOMScrollingElement.scrollTop = 150;

        // Is this needed?
        //[[CPRunLoop currentRunLoop] limitDateForMode:CPDefaultRunLoopMode];
        
    }, 0);
    
    // We hide the dom element after a little bit
    // so that other DOM elements such as inputs
    // can receive events.
    _hideDOMScrollingElementTimeout = setTimeout(function(){
        _DOMScrollingElement.style.visibility = "hidden";
    }, 300);
}

- (void)resizeEvent:(DOMEvent)aDOMEvent
{
    // FIXME: This is not the right way to do this.
    // We should pay attention to mouse down and mouse up in conjunction with this.
    //window.liveResize = YES;

    if ([CPPlatform isBrowser])
        [CPApp._activeMenu cancelTracking];

    var oldSize = [self contentRect].size;

    [self updateFromNativeContentRect];

    var levels = _windowLevels,
        layers = _windowLayers,
        levelCount = levels.length;

    while (levelCount--)
    {
        var windows = [layers objectForKey:levels[levelCount]]._windows,
            windowCount = windows.length;

        while (windowCount--)
            [windows[windowCount] resizeWithOldPlatformWindowSize:oldSize];
    }

{'fraction_non_alphanumeric': 0.08811665430267063, 'fraction_numerical': 0.007337258902077151, 'mean_word_length': 2.4487128237927727, 'pattern_counts': {'":': 6, '<': 23, '<?xml version=': 0, '>': 23, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 56, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '1072031', 'n_tokens_mistral': 23901, 'n_tokens_neox': 22765, 'n_words': 5375}