diff --git a/AppKit/Platform/CPPlatformWindow.j b/AppKit/Platform/CPPlatformWindow.j index d75858dca..628293117 100644 --- a/AppKit/Platform/CPPlatformWindow.j +++ b/AppKit/Platform/CPPlatformWindow.j @@ -26,6 +26,7 @@ @import "CPPlatform.j" @class CPMenu +@class CPPlatformPasteboard @global CPApp @@ -33,46 +34,42 @@ var PrimaryPlatformWindow = NULL; @implementation CPPlatformWindow : CPObject { - CGRect _contentRect; + CGRect _contentRect; - CPInteger _level; - BOOL _hasShadow; - unsigned _shadowStyle; - CPString _title; + CPInteger _level; + BOOL _hasShadow; + unsigned _shadowStyle; + CPString _title; #if PLATFORM(DOM) - DOMWindow _DOMWindow; + DOMWindow _DOMWindow; - DOMElement _DOMBodyElement; - DOMElement _DOMFocusElement; - DOMElement _DOMEventGuard; - DOMElement _DOMScrollingElement; - id _hideDOMScrollingElementTimeout; + DOMElement _DOMBodyElement; + DOMElement _DOMFocusElement; + DOMElement _DOMEventGuard; + DOMElement _DOMScrollingElement; + id _hideDOMScrollingElementTimeout; - CPArray _windowLevels; - CPDictionary _windowLayers; + CPArray _windowLevels; + CPDictionary _windowLayers; - BOOL _mouseIsDown; - BOOL _mouseDownIsRightClick; - CGPoint _lastMouseEventLocation; - CPWindow _mouseDownWindow; - CPTimeInterval _lastMouseUp; - CPTimeInterval _lastMouseDown; + BOOL _mouseIsDown; + BOOL _mouseDownIsRightClick; + CGPoint _lastMouseEventLocation; + CPWindow _mouseDownWindow; + CPTimeInterval _lastMouseUp; + CPTimeInterval _lastMouseDown; - Object _charCodes; - unsigned _keyCode; - unsigned _lastKey; - BOOL _capsLockActive; - BOOL _ignoreNativeCopyOrCutEvent; - BOOL _ignoreNativePastePreparation; + Object _charCodes; + unsigned _keyCode; + unsigned _lastKey; + BOOL _capsLockActive; - BOOL _DOMEventMode; + BOOL _DOMEventMode; - // Native Pasteboard Support - DOMElement _DOMPasteboardElement; - CPEvent _pasteboardKeyDownEvent; + CPPlatformPasteboard _platformPasteboard; - CPString _overriddenEventType; + CPString _overriddenEventType; #endif } diff --git a/AppKit/Platform/DOM/CPPlatformPasteboard.j b/AppKit/Platform/DOM/CPPlatformPasteboard.j new file mode 100644 index 000000000..69a1e667f --- /dev/null +++ b/AppKit/Platform/DOM/CPPlatformPasteboard.j @@ -0,0 +1,515 @@ +/* + * CPPlatformPasteboard.j + * AppKit + * + * Created by Alexander Ljungberg. + * Copyright 2013, SlevenBits Ltd. + * + * 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 + */ + +@import +@import + +@import "CPCompatibility.j" +@import "CPEvent.j" +@import "CPPasteboard.j" +@import "CPPlatform.j" +@import "CPPlatformWindow+DOMKeys.j" + +@global CPApp +@global CPPlatformWindow + +// From CPPlatformWindow+DOM.j +@global _CPDOMEventStop + +#if PLATFORM(DOM) + +var hasEditableTarget = function(aDOMEvent) +{ + var target = aDOMEvent.target || aDOMEvent.srcElement; + + if (!target) + return NO; + + if (target.contentEditable == "true") + return YES; + + var nodeName = target.nodeName.toUpperCase(); + + return nodeName === "INPUT" || nodeName == "TEXTAREA"; +} + +/* + * This class encapsulates copy and paste related functionality for the + * DOM environment. While originally all of this code was apread out in a + * dozen places in CPPlatformWindow+DOM.j, this class serves to collect + * and isolate that code for easier testing and maintenance. + */ +@implementation CPPlatformPasteboard : CPObject +{ + DOMWindow _DOMWindow; + + DOMElement _DOMPasteboardElement; + + BOOL supportsNativeCopyAndPaste; + BOOL hasBugWhichPreventsNonEditablePaste; + BOOL hasBugWhichPreventsNonEditablePasteRedirect; + + CPEvent _lastKeyDownEvent; + BOOL currentEventIsNativePasteEvent; + BOOL currentEventIsNativeCopyOrCutEvent; + BOOL currentEventShouldBeSuppressed; + BOOL currentEventShouldDefinitelyBubble; + BOOL currentEventShouldDefinitelyNotBubble; + + BOOL _ignoreNativeCopyOrCutEvent; + BOOL _ignoreNativePastePreparation; +} + +- (id)init +{ + if (self = [super init]) + { + supportsNativeCopyAndPaste = CPFeatureIsCompatible(CPJavaScriptClipboardEventsFeature); + hasBugWhichPreventsNonEditablePaste = CPPlatformHasBug(CPJavaScriptPasteRequiresEditableTarget); + hasBugWhichPreventsNonEditablePasteRedirect = CPPlatformHasBug(CPJavaScriptPasteCantRefocus); + } + + return self; +} + +- (void)setDOMWindow:(DOMWindow)aDOMWindow +{ + if (_DOMWindow === aDOMWindow) + return; + + if (_DOMWindow) + [self destroyDOMElements]; + + _DOMWindow = aDOMWindow; + + if (_DOMWindow) + [self createDOMElements]; +} + +- (void)createDOMElements +{ + var theDocument = _DOMWindow.document, + _DOMBodyElement = theDocument.getElementById("cappuccino-body") || theDocument.body; + + // 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); + + _DOMPasteboardElement.blur(); + + var copyEventCallback = function (anEvent) { return [self beforeCopyEvent:anEvent]; }, + nativeBeforeClipboardEventCallback = function (anEvent) { return [self nativeBeforeClipboardEvent:anEvent]; }, + nativeCopyOrCutEventCallback = function (anEvent) { return [self nativeCopyOrCutEvent:anEvent]; }, + pasteEventCallback = function (anEvent) { return [self beforePasteEvent:anEvent]; }, + nativePasteEventCallback = function (anEvent) { return [self nativePasteEvent:anEvent]; }; + + if (theDocument.addEventListener) + { + if (supportsNativeCopyAndPaste) + { + _DOMWindow.addEventListener("beforecopy", nativeBeforeClipboardEventCallback, NO); + _DOMWindow.addEventListener("beforecut", nativeBeforeClipboardEventCallback, NO); + _DOMWindow.addEventListener("beforepaste", nativeBeforeClipboardEventCallback, NO); + _DOMWindow.addEventListener("copy", nativeCopyOrCutEventCallback, NO); + _DOMWindow.addEventListener("cut", nativeCopyOrCutEventCallback, NO); + _DOMWindow.addEventListener("paste", nativePasteEventCallback, NO); + } + else + { + theDocument.addEventListener("beforepaste", pasteEventCallback, NO); + theDocument.addEventListener("beforecopy", copyEventCallback, NO); + theDocument.addEventListener("beforecut", copyEventCallback, NO); + } + + _DOMWindow.addEventListener("unload", function() + { + if (supportsNativeCopyAndPaste) + { + _DOMWindow.removeEventListener("beforecopy", nativeBeforeClipboardEventCallback, NO); + _DOMWindow.removeEventListener("beforecut", nativeBeforeClipboardEventCallback, NO); + _DOMWindow.removeEventListener("beforepaste", nativeBeforeClipboardEventCallback, NO); + _DOMWindow.removeEventListener("copy", nativeCopyOrCutEventCallback, NO); + _DOMWindow.removeEventListener("cut", nativeCopyOrCutEventCallback, NO); + _DOMWindow.removeEventListener("paste", nativePasteEventCallback, NO); + } + else + { + theDocument.removeEventListener("beforepaste", pasteEventCallback, NO); + theDocument.removeEventListener("beforecopy", copyEventCallback, NO); + theDocument.removeEventListener("beforecut", copyEventCallback, NO); + } + }, NO); + } + else + { + // TODO If we wanted IE 8 and lower copy and paste it'd go here. + } +} + +- (void)destroyDOMElements +{ + var theDocument = _DOMWindow.document, + _DOMBodyElement = theDocument.getElementById("cappuccino-body") || theDocument.body; + + _DOMBodyElement.removeChild(_DOMPasteboardElement); + _DOMPasteboardElement = nil; +} + +- (void)windowMaySendKeyEvent:(CPEvent)anEvent +{ + // Reset our opinions. + currentEventIsNativePasteEvent = NO; + currentEventIsNativeCopyOrCutEvent = NO; + currentEventShouldBeSuppressed = NO; + currentEventShouldDefinitelyNotBubble = NO; + currentEventShouldDefinitelyBubble = NO; + + if (!anEvent) + return; + + if ([anEvent type] !== CPKeyDown) + { + // Reset these flags on key up. + _ignoreNativePastePreparation = NO; + _ignoreNativeCopyOrCutEvent = NO; + return; + } + + var modifierFlags = [anEvent modifierFlags]; + + if (!(modifierFlags & (CPControlKeyMask | CPCommandKeyMask))) + return; + + _lastKeyDownEvent = anEvent; + + var aDOMEvent = anEvent._DOMEvent, + characters = [anEvent characters], + mayRequireDOMPasteboardElement = [self _mayRequireDOMPasteboardElementHack:aDOMEvent flags:modifierFlags]; + + if (characters === "v" && mayRequireDOMPasteboardElement) + { + if (supportsNativeCopyAndPaste && hasBugWhichPreventsNonEditablePaste && hasBugWhichPreventsNonEditablePasteRedirect && !hasEditableTarget(aDOMEvent)) + { + // You can't paste from the system clipboard into a non-editable area in Safari, neither using native + // copy and paste nor our _DOMPasteboardElement hack. We will paste from the Cappuccino pasteboard only + // and allow Safari to "beep" to indicate something went wrong. + + // The key down will not result in a paste event being sent by Safari, so it's not a paste event. + currentEventIsNativePasteEvent = NO; + // Yes to get the beep. + currentEventShouldDefinitelyBubble = YES; + } + else if (!(supportsNativeCopyAndPaste || hasBugWhichPreventsNonEditablePaste) && !_ignoreNativePastePreparation) + { + // We don't support native copy and paste so we must focus the _DOMPasteboardElement to receive the + // paste content. This needs to be done at keyDown time (or in the case of modern Safari, before the keyDown, + // which unfortunately isn't possible. See above.) + _DOMPasteboardElement.focus(); + _DOMPasteboardElement.select(); + _DOMPasteboardElement.value = ""; + _DOMWindow.setNativeTimeout(function () { [self _checkDOMPasteboardElement]; }, 0); + + currentEventIsNativePasteEvent = YES; + } + else if (supportsNativeCopyAndPaste) + currentEventIsNativePasteEvent = YES; + + if (currentEventIsNativePasteEvent) + { + // We need the event to propagate or nothing will be pasted. + currentEventShouldDefinitelyBubble = YES; + + // And we don't send the keydown because either our native paste envent handler will send it, + // or the _checkDOMPasteboardElement check will. + currentEventShouldBeSuppressed = YES; + } + } + else if ((characters == "c" || characters == "x") && mayRequireDOMPasteboardElement) + { + currentEventIsNativeCopyOrCutEvent = YES; + + // If _ignoreNativeCopyOrCutEvent, we already handled the copy/cut in beforeCopyEvent:. + // If supportsNativeCopyAndPaste, we will be handling it in the nativebeforeCopyEvent: handler. + // In both of those cases we don't want to send the event on keydown too, or we'll get 2X copy/cut operations. + if (supportsNativeCopyAndPaste || _ignoreNativeCopyOrCutEvent) + currentEventShouldBeSuppressed = YES; + + currentEventShouldDefinitelyBubble = YES; + } +} + +- (BOOL)windowShouldSuppressKeyEvent +{ + return currentEventShouldBeSuppressed; +} + +- (void)windowDidSendKeyEvent:(CPEvent)anEvent +{ + // Now that the copy event has been sent through the Cappuccino event stack, we can load any Cappuccino + // pasteboard string into our DOMPasteboardElement hack, if necessary. + if (!supportsNativeCopyAndPaste && currentEventIsNativeCopyOrCutEvent) + [self _primeDOMPasteboardElement]; +} + +- (BOOL)windowShouldStopPropagation +{ + return currentEventShouldDefinitelyNotBubble; +} + +- (BOOL)windowShouldNotStopPropagation +{ + return currentEventShouldDefinitelyBubble; +} + +- (CPEvent)_fakeClipboardEvent:(DOMEvent)aDOMEvent type:(CPString)aType +{ + var keyCode = aType === "x" ? CPKeyCodes.X : (aType === "c" ? CPKeyCodes.C : CPKeyCodes.V), + characters = aType, + timestamp = [CPEvent currentTimestamp], // fake event, might as well use current timestamp + windowNumber = [[CPApp keyWindow] windowNumber], + modifierFlags = CPPlatformActionKeyMask, + location = [_lastKeyDownEvent locationInWindow], + anEvent = [CPEvent keyEventWithType:CPKeyDown location:location modifierFlags:modifierFlags + timestamp:timestamp windowNumber:windowNumber context:nil + characters:characters charactersIgnoringModifiers:characters isARepeat:NO keyCode:keyCode]; + + anEvent._data1 = @{ "simulated": YES }; + anEvent._DOMEvent = aDOMEvent; + + return anEvent; +} + +- (void)beforeCopyEvent:(DOMEvent)aDOMEvent +{ + if ([self _mayRequireDOMPasteboardElementHack: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 anEvent = [self _fakeClipboardEvent:aDOMEvent type:(aDOMEvent.type === "beforecut" ? "x" : "c")]; + + [CPApp sendEvent:anEvent]; + + // Once we've sent it, we can load the copy information into the pasteboard hack. + [self _primeDOMPasteboardElement]; + + //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]; +} + +- (boolean)beforePasteEvent:(DOMEvent)aDOMEvent +{ + // Set up to capture the paste in a temporary input field. We'll send the event after capture. + if ([self _mayRequireDOMPasteboardElementHack:aDOMEvent flags:CPPlatformActionKeyMask]) + { + _DOMPasteboardElement.focus(); + _DOMPasteboardElement.select(); + _DOMPasteboardElement.value = ""; + + _ignoreNativePastePreparation = YES; + } + + [[CPRunLoop currentRunLoop] limitDateForMode:CPDefaultRunLoopMode]; +} + +/* +Return true if the event may be a copy and paste event, but the target is not an input or text area. +*/ +- (void)_mayRequireDOMPasteboardElementHack:(DOMEvent)aDOMEvent flags:(unsigned)modifierFlags +{ + return !hasEditableTarget(aDOMEvent) && (modifierFlags & CPPlatformActionKeyMask); +} + +- (void)_primeDOMPasteboardElement +{ + 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 _clearDOMPasteboardElement]; }, 0); + } +} + +- (void)_checkDOMPasteboardElement +{ + if (supportsNativeCopyAndPaste) + { + [self _clearDOMPasteboardElement]; + return; + } + + 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 _clearDOMPasteboardElement]; + + [CPApp sendEvent:[self _fakeClipboardEvent:nil type:@"v"]]; + + [[CPRunLoop currentRunLoop] limitDateForMode:CPDefaultRunLoopMode]; +} + +- (void)_clearDOMPasteboardElement +{ + _DOMPasteboardElement.value = ""; + _DOMPasteboardElement.blur(); +} + +- (boolean)nativeBeforeClipboardEvent:(DOMEvent)aDOMEvent +{ + // Our job here is to return "false" if the given clipboard operation should be enabled even in a situation where + // the browser might normally grey the option out. + + // Allow these fields to do their own thing. + if (hasEditableTarget(aDOMEvent)) + return true; + + var returnValue = YES; + + switch (aDOMEvent.type) + { + case "beforecopy": + returnValue = !([CPApp targetForAction:@selector(copy:)]); + break; + case "beforecut": + returnValue = !([CPApp targetForAction:@selector(cut:)]); + break; + case "beforepaste": + returnValue = !([CPApp targetForAction:@selector(paste:)]); + break; + } + + if (!returnValue) + _CPDOMEventStop(aDOMEvent, self); + + return returnValue; +} + +- (boolean)nativePasteEvent:(DOMEvent)aDOMEvent +{ + // This shouldn't happen. + if (!supportsNativeCopyAndPaste) + return; + + var value; + if (aDOMEvent.clipboardData && aDOMEvent.clipboardData.setData) + value = aDOMEvent.clipboardData.getData('text/plain'); + else + value = _DOMWindow.clipboardData.getData("Text"); + + if ([value length]) + { + var pasteboard = [CPPasteboard generalPasteboard]; + + if ([pasteboard _stateUID] != value) + { + [pasteboard declareTypes:[CPStringPboardType] owner:self]; + [pasteboard setString:value forType:CPStringPboardType]; + } + } + + var anEvent = [self _fakeClipboardEvent:aDOMEvent type:"v"], + platformWindow = [[anEvent window] platformWindow]; + + // By default we'll stop the native handling of the event since we're handling it ourselves. However, we need to + // stop it before we send the event so that the event can overrule our choice. CPTextField for instance wants the + // default handling when focused (which is to insert into the field). + [platformWindow _propagateCurrentDOMEvent:NO] + + [CPApp sendEvent:anEvent]; + + [[CPRunLoop currentRunLoop] limitDateForMode:CPDefaultRunLoopMode]; + + if (![platformWindow _willPropagateCurrentDOMEvent]) + _CPDOMEventStop(aDOMEvent, self); + + return false; +} + +- (boolean)nativeCopyOrCutEvent:(DOMEvent)aDOMEvent +{ + // This shouldn't happen. + if (!supportsNativeCopyAndPaste) + return; + + var anEvent = [self _fakeClipboardEvent:aDOMEvent type:(aDOMEvent.type.indexOf("cut") != CPNotFound ? "x" : "c")], + platformWindow = [[anEvent window] platformWindow]; + + [platformWindow _propagateCurrentDOMEvent:NO] + + // Let the app react through copy: and cut: actions. + [CPApp sendEvent:anEvent]; + [[CPRunLoop currentRunLoop] limitDateForMode:CPDefaultRunLoopMode]; + + // If StopDOMEventPropagation was set to NO, we don't try to write to the system clipboard. The control that did this + // wants to use the default copy/cut functionality. + if (![platformWindow _willPropagateCurrentDOMEvent]) + { + // Now the app should have written whatever it wants to have copied to the Cappuccino clipboard. So now we need + // to write it to the system board. + _CPDOMEventStop(aDOMEvent, self); + + var pasteboard = [CPPasteboard generalPasteboard]; + + if ([[pasteboard types] containsObject:CPStringPboardType]) + { + var stringValue = [pasteboard stringForType:CPStringPboardType]; + + if (aDOMEvent.clipboardData && aDOMEvent.clipboardData.setData) + aDOMEvent.clipboardData.setData('text/plain', stringValue); + else + _DOMWindow.clipboardData.setData('Text', stringValue); + } + } + + return ![platformWindow _willPropagateCurrentDOMEvent]; +} + +@end + +#endif diff --git a/AppKit/Platform/DOM/CPPlatformWindow+DOM.j b/AppKit/Platform/DOM/CPPlatformWindow+DOM.j index e72983def..82419d232 100644 --- a/AppKit/Platform/DOM/CPPlatformWindow+DOM.j +++ b/AppKit/Platform/DOM/CPPlatformWindow+DOM.j @@ -112,15 +112,16 @@ @import @import -@import "CPCursor.j" @import "CPCompatibility.j" +@import "CPCursor.j" @import "CPDOMWindowLayer.j" @import "CPDragServer_Constants.j" @import "CPEvent.j" @import "CPPasteboard.j" @import "CPPlatform.j" -@import "CPPlatformWindow.j" +@import "CPPlatformPasteboard.j" @import "CPPlatformWindow+DOMKeys.j" +@import "CPPlatformWindow.j" @import "CPText.j" @import "CPWindow_Constants.j" @@ -135,7 +136,6 @@ var PlatformWindows = [CPSet set]; // Define up here so compressor knows about them. var CPDOMEventGetClickCount, - CPDOMEventStop, StopDOMEventPropagation, StopContextMenuDOMEventPropagation; @@ -197,31 +197,12 @@ var ModifierKeyCodes = [ CPKeyCodes.SHIFT ], - supportsNativeDragAndDrop = [CPPlatform supportsDragAndDrop], - - supportsNativeCopyAndPaste = CPFeatureIsCompatible(CPJavaScriptClipboardEventsFeature), - hasBugWhichPreventsNonEditablePaste = CPPlatformHasBug(CPJavaScriptPasteRequiresEditableTarget), - hasBugWhichPreventsNonEditablePasteRedirect = CPPlatformHasBug(CPJavaScriptPasteCantRefocus); + supportsNativeDragAndDrop = [CPPlatform supportsDragAndDrop]; var resizeTimer = nil; #if PLATFORM(DOM) -var hasEditableTarget = function(aDOMEvent) -{ - var target = (aDOMEvent).target || (aDOMEvent).srcElement; - - if (!target) - return NO; - - if (target.contentEditable == "true") - return YES; - - var nodeName = target.nodeName.toUpperCase(); - - return nodeName === "INPUT" || nodeName == "TEXTAREA"; -} - @implementation CPPlatformWindow (DOM) - (id)_init @@ -236,6 +217,8 @@ var hasEditableTarget = function(aDOMEvent) _windowLevels = []; _windowLayers = @{}; + _platformPasteboard = [CPPlatformPasteboard new]; + [self registerDOMWindow]; [self updateFromNativeContentRect]; @@ -322,19 +305,6 @@ var hasEditableTarget = function(aDOMEvent) _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 @@ -373,13 +343,6 @@ var hasEditableTarget = function(aDOMEvent) _DOMScrollingElement.scrollLeft = 150; } -- (void)registerDOMElement:(DOMElement)anElement -{ - var nativeBeforeClipboardEventCallback = function (anEvent) { return [self nativeBeforeClipboardEvent:anEvent]; }; - - anElement.addEventListener("beforepaste", nativeBeforeClipboardEventCallback, NO); -} - - (void)registerDOMWindow { var theDocument = _DOMWindow.document; @@ -395,6 +358,8 @@ var hasEditableTarget = function(aDOMEvent) [self createDOMElements]; [self _addLayers]; + [_platformPasteboard setDOMWindow:_DOMWindow]; + var theClass = [self class], dragEventImplementation = class_getMethodImplementation(theClass, @selector(dragEvent:)), @@ -404,20 +369,6 @@ var hasEditableTarget = function(aDOMEvent) 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); }, - - nativeBeforeClipboardEventCallback = function (anEvent) { return [self nativeBeforeClipboardEvent:anEvent]; }, - - nativeCopyOrCutEventCallback = function (anEvent) { return [self nativeCopyOrCutEvent:anEvent]; }, - - pasteEventSelector = @selector(pasteEvent:), - pasteEventImplementation = class_getMethodImplementation(theClass, pasteEventSelector), - pasteEventCallback = function (anEvent) { return pasteEventImplementation(self, nil, anEvent); }, - - nativePasteEventCallback = function (anEvent) { return [self nativePasteEvent:anEvent]; }, - keyEventSelector = @selector(keyEvent:), keyEventImplementation = class_getMethodImplementation(theClass, keyEventSelector), keyEventCallback = function (anEvent) { return keyEventImplementation(self, nil, anEvent); }, @@ -455,22 +406,6 @@ var hasEditableTarget = function(aDOMEvent) theDocument.addEventListener("mousemove", mouseEventCallback, NO); theDocument.addEventListener("contextmenu", contextMenuEventCallback, NO); - if (supportsNativeCopyAndPaste) - { - _DOMWindow.addEventListener("beforecopy", nativeBeforeClipboardEventCallback, NO); - _DOMWindow.addEventListener("beforecut", nativeBeforeClipboardEventCallback, NO); - _DOMWindow.addEventListener("beforepaste", nativeBeforeClipboardEventCallback, NO); - _DOMWindow.addEventListener("copy", nativeCopyOrCutEventCallback, NO); - _DOMWindow.addEventListener("cut", nativeCopyOrCutEventCallback, NO); - _DOMWindow.addEventListener("paste", nativePasteEventCallback, NO); - } - else - { - theDocument.addEventListener("beforepaste", pasteEventCallback, NO); - theDocument.addEventListener("beforecopy", copyEventCallback, NO); - theDocument.addEventListener("beforecut", copyEventCallback, NO); - } - theDocument.addEventListener("keyup", keyEventCallback, NO); theDocument.addEventListener("keydown", keyEventCallback, NO); theDocument.addEventListener("keypress", keyEventCallback, NO); @@ -500,22 +435,6 @@ var hasEditableTarget = function(aDOMEvent) theDocument.removeEventListener("keydown", keyEventCallback, NO); theDocument.removeEventListener("keypress", keyEventCallback, NO); - if (supportsNativeCopyAndPaste) - { - _DOMWindow.removeEventListener("beforecopy", nativeBeforeClipboardEventCallback, NO); - _DOMWindow.removeEventListener("beforecut", nativeBeforeClipboardEventCallback, NO); - _DOMWindow.removeEventListener("beforepaste", nativeBeforeClipboardEventCallback, NO); - _DOMWindow.removeEventListener("copy", nativeCopyOrCutEventCallback, NO); - _DOMWindow.removeEventListener("cut", nativeCopyOrCutEventCallback, NO); - _DOMWindow.removeEventListener("paste", nativePasteEventCallback, NO); - } - else - { - theDocument.removeEventListener("beforepaste", pasteEventCallback, NO); - theDocument.removeEventListener("beforecopy", copyEventCallback, NO); - theDocument.removeEventListener("beforecut", copyEventCallback, NO); - } - theDocument.removeEventListener("touchstart", touchEventCallback, NO); theDocument.removeEventListener("touchend", touchEventCallback, NO); theDocument.removeEventListener("touchmove", touchEventCallback, NO); @@ -531,6 +450,8 @@ var hasEditableTarget = function(aDOMEvent) [PlatformWindows removeObject:self]; + [_platformPasteboard setDOMWindow:nil]; + self._DOMWindow = nil; }, NO); } @@ -552,7 +473,7 @@ var hasEditableTarget = function(aDOMEvent) theDocument.onmousewheel = scrollEventCallback; _DOMBodyElement.ondrag = function () { return NO; }; - _DOMBodyElement.onselectstart = function () { return _DOMWindow.event.srcElement === _DOMPasteboardElement; }; + _DOMBodyElement.onselectstart = function () { return _DOMWindow.event.srcElement === _platformPasteboard._DOMPasteboardElement; }; _DOMWindow.attachEvent("onunload", function() { @@ -581,6 +502,8 @@ var hasEditableTarget = function(aDOMEvent) [PlatformWindows removeObject:self]; + [_platformPasteboard setDOMWindow:nil]; + self._DOMWindow = nil; }, NO); } @@ -767,9 +690,7 @@ var hasEditableTarget = function(aDOMEvent) StopDOMEventPropagation = NO; } - var isNativePasteEvent = NO, - isNativeCopyOrCutEvent = NO, - overrideCharacters = nil, + var overrideCharacters = nil, charactersIgnoringModifiers = @""; switch (aDOMEvent.type) @@ -811,47 +732,6 @@ var hasEditableTarget = function(aDOMEvent) //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 mayRequireDOMPasteboardElement = [self _requiresDOMPasteboardElement: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" && mayRequireDOMPasteboardElement) - { - StopDOMEventPropagation = NO; - - if (supportsNativeCopyAndPaste && hasBugWhichPreventsNonEditablePaste && hasBugWhichPreventsNonEditablePasteRedirect && !hasEditableTarget(aDOMEvent)) - { - // You can't paste from the system clipboard into a non-editable area in Safari, neither using native - // copy and paste nor our _DOMPasteboardElement hack. We will paste from the Cappuccino pasteboard only - // and allow Safari to "beep" to indicate something went wrong. - - isNativePasteEvent = NO; - } - else if (!(supportsNativeCopyAndPaste || hasBugWhichPreventsNonEditablePaste) && !_ignoreNativePastePreparation) - { - // We don't support native copy and paste so we must focus the _DOMPasteboardElement to receive the - // paste content. - _DOMPasteboardElement.focus(); - _DOMPasteboardElement.select(); - _DOMPasteboardElement.value = ""; - - isNativePasteEvent = YES; - } - else if (supportsNativeCopyAndPaste) - 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") && mayRequireDOMPasteboardElement) - { - StopDOMEventPropagation = NO; - isNativeCopyOrCutEvent = YES; - - if (!supportsNativeCopyAndPaste && _ignoreNativeCopyOrCutEvent) - break; - } } else if (CPKeyCodes.firesKeyPressEvent(_keyCode, _lastKey, aDOMEvent.shiftKey, aDOMEvent.ctrlKey, aDOMEvent.altKey)) { @@ -897,18 +777,6 @@ var hasEditableTarget = function(aDOMEvent) timestamp:timestamp windowNumber:windowNumber context:nil characters:characters charactersIgnoringModifiers:charactersIgnoringModifiers isARepeat:isARepeat keyCode:charCode]; - if (isNativePasteEvent) - { - _pasteboardKeyDownEvent = event; - - // If we are using the paste into field hack, we need to check it in a moment. - if (!supportsNativeCopyAndPaste) - window.setNativeTimeout(function () { [self _checkPasteboardElement]; }, 0); - - // We need the event to propagate or nothing will be pasted. - StopDOMEventPropagation = NO; - } - break; case "keyup": @@ -918,8 +786,6 @@ var hasEditableTarget = function(aDOMEvent) _keyCode = -1; _lastKey = -1; _charCodes[keyCode] = nil; - _ignoreNativeCopyOrCutEvent = NO; - _ignoreNativePastePreparation = NO; // check for caps lock state if (keyCode === CPKeyCodes.CAPS_LOCK) @@ -948,249 +814,29 @@ var hasEditableTarget = function(aDOMEvent) break; } - // If we are going to use our native cut and paste handlers, they will fake the keydown later. So we need to - // not send it here too, or a single cut turns into 2 cuts, etc. - if (event && !isNativePasteEvent && (!supportsNativeCopyAndPaste || !isNativeCopyOrCutEvent)) - { + if (event) event._DOMEvent = aDOMEvent; + [_platformPasteboard windowMaySendKeyEvent:event]; + + if (event && ![_platformPasteboard windowShouldSuppressKeyEvent]) + { [CPApp sendEvent:event]; - if (!supportsNativeCopyAndPaste && isNativeCopyOrCutEvent) - { - // If this is a native copy event, then check if the pasteboard has anything in it. - [self _primeDOMPasteboardElement]; - } + [_platformPasteboard windowDidSendKeyEvent:event]; + } - if (StopDOMEventPropagation) - CPDOMEventStop(aDOMEvent, self); + var didStop = NO; + // Platform pasteboard can overrule the decision to stop propagation either way, or it might have no opinion. + if ([_platformPasteboard windowShouldStopPropagation] || (StopDOMEventPropagation && ![_platformPasteboard windowShouldNotStopPropagation])) + { + didStop = YES; + _CPDOMEventStop(aDOMEvent, self); } [[CPRunLoop currentRunLoop] limitDateForMode:CPDefaultRunLoopMode]; - return !StopDOMEventPropagation -} - -- (CPEvent)_fakeClipboardEvent:(DOMEvent)aDOMEvent type:(CPString)aType -{ - var keyCode = aType === "x" ? CPKeyCodes.X : (aType === "c" ? CPKeyCodes.C : CPKeyCodes.V), - characters = aType, - timestamp = [CPEvent currentTimestamp], // fake event, might as well use current timestamp - windowNumber = [[CPApp keyWindow] windowNumber], - modifierFlags = CPPlatformActionKeyMask, - location = _lastMouseEventLocation || CGPointMakeZero(), - event = [CPEvent keyEventWithType:CPKeyDown location:location modifierFlags:modifierFlags - timestamp:timestamp windowNumber:windowNumber context:nil - characters:characters charactersIgnoringModifiers:characters isARepeat:NO keyCode:keyCode]; - - event._data1 = @{ "simulated": YES }; - event._DOMEvent = aDOMEvent; - - return event; -} - -- (void)copyEvent:(DOMEvent)aDOMEvent -{ - if ([self _requiresDOMPasteboardElement: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 event = [self _fakeClipboardEvent:aDOMEvent type:(aDOMEvent.type === "beforecut" ? "x" : "c")]; - - [CPApp sendEvent:event]; - - [self _primeDOMPasteboardElement]; - - //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]; -} - -- (boolean)nativeBeforeClipboardEvent:(DOMEvent)aDOMEvent -{ - // Our job here is to return "false" if the given clipboard operation should be enabled even in a situation where - // the browser might normally grey the option out. - - // Allow these fields to do their own thing. - if (hasEditableTarget(aDOMEvent)) - return true; - - var returnValue = YES; - - switch (aDOMEvent.type) - { - case "beforecopy": - returnValue = !([CPApp targetForAction:@selector(copy:)]); - break; - case "beforecut": - returnValue = !([CPApp targetForAction:@selector(cut:)]); - break; - case "beforepaste": - returnValue = !([CPApp targetForAction:@selector(paste:)]); - break; - } - - if (!returnValue) - CPDOMEventStop(aDOMEvent, self); - - return returnValue; -} - -- (boolean)nativePasteEvent:(DOMEvent)aDOMEvent -{ - // This shouldn't happen. - if (!supportsNativeCopyAndPaste) - return; - - var value; - if (aDOMEvent.clipboardData && aDOMEvent.clipboardData.setData) - value = aDOMEvent.clipboardData.getData('text/plain'); - else - value = window.clipboardData.getData("Text"); - - if ([value length]) - { - var pasteboard = [CPPasteboard generalPasteboard]; - - if ([pasteboard _stateUID] != value) - { - [pasteboard declareTypes:[CPStringPboardType] owner:self]; - [pasteboard setString:value forType:CPStringPboardType]; - } - } - - // By default we'll stop the native handling of the event since we're handling it ourselves. However, we need to - // stop it before we send the event so that the event can overrule our choice. CPTextField for instance wants the - // default handling when focused (which is to insert into the field). - StopDOMEventPropagation = YES; - - [CPApp sendEvent:[self _fakeClipboardEvent:aDOMEvent type:"v"]]; - - [[CPRunLoop currentRunLoop] limitDateForMode:CPDefaultRunLoopMode]; - - if (StopDOMEventPropagation) - CPDOMEventStop(aDOMEvent, self); - - return false; -} - -- (boolean)nativeCopyOrCutEvent:(DOMEvent)aDOMEvent -{ - // This shouldn't happen. - if (!supportsNativeCopyAndPaste) - return; - - StopDOMEventPropagation = YES; - - // Let the app react through copy: and cut: actions. - [CPApp sendEvent:[self _fakeClipboardEvent:aDOMEvent type:(aDOMEvent.type.indexOf("cut") != CPNotFound ? "x" : "c")]]; - [[CPRunLoop currentRunLoop] limitDateForMode:CPDefaultRunLoopMode]; - - // If StopDOMEventPropagation was set to NO, we don't try to write to the system clipboard. The control that did this - // wants to use the default copy/cut functionality. - if (StopDOMEventPropagation) - { - // Now the app should have written whatever it wants to have copied to the Cappuccino clipboard. So now we need - // to write it to the system board. - CPDOMEventStop(aDOMEvent, self); - - var pasteboard = [CPPasteboard generalPasteboard]; - - if ([[pasteboard types] containsObject:CPStringPboardType]) - { - var stringValue = [pasteboard stringForType:CPStringPboardType]; - - if (aDOMEvent.clipboardData && aDOMEvent.clipboardData.setData) - aDOMEvent.clipboardData.setData('text/plain', stringValue); - else - window.clipboardData.setData('Text', stringValue); - } - } - - return StopDOMEventPropagation; -} - -- (boolean)pasteEvent:(DOMEvent)aDOMEvent -{ - // Set up to capture the paste in a temporary input field. We'll send the event after capture. - if ([self _requiresDOMPasteboardElement:aDOMEvent flags:CPPlatformActionKeyMask]) - { - _DOMPasteboardElement.focus(); - _DOMPasteboardElement.select(); - _DOMPasteboardElement.value = ""; - - _ignoreNativePastePreparation = YES; - } - - [[CPRunLoop currentRunLoop] limitDateForMode:CPDefaultRunLoopMode]; -} - -/* -Return true if the event may be a copy and paste event, but the target is not an input or text area. -*/ -- (void)_requiresDOMPasteboardElement:(DOMEvent)aDOMEvent flags:(unsigned)modifierFlags -{ - return !hasEditableTarget(aDOMEvent) && (modifierFlags & CPPlatformActionKeyMask); -} - -- (void)_primeDOMPasteboardElement -{ - if (supportsNativeCopyAndPaste) - return; - - 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 -{ - if (supportsNativeCopyAndPaste) - { - [self _clearPasteboardElement]; - return; - } - - 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(); + return !didStop; } - (void)scrollEvent:(DOMEvent)aDOMEvent @@ -1287,7 +933,7 @@ Return true if the event may be a copy and paste event, but the target is not an // We set StopDOMEventPropagation = NO on line 1008 //if (StopDOMEventPropagation) - // CPDOMEventStop(aDOMEvent, self); + // _CPDOMEventStop(aDOMEvent, self); // Reset the DOM elements scroll offset _DOMScrollingElement.scrollLeft = 150; @@ -1529,7 +1175,7 @@ Return true if the event may be a copy and paste event, but the target is not an } if (StopDOMEventPropagation && (!supportsNativeDragAndDrop || type !== "mousedown" && !isDragging)) - CPDOMEventStop(aDOMEvent, self); + _CPDOMEventStop(aDOMEvent, self); // If there are any tracking event listeners (listening for CPLeftMouseDraggedMask) // then show the event guard so we don't lose events to iframes @@ -1556,7 +1202,7 @@ Return true if the event may be a copy and paste event, but the target is not an - (void)contextMenuEvent:(DOMEvent)aDOMEvent { if (StopContextMenuDOMEventPropagation) - CPDOMEventStop(aDOMEvent, self); + _CPDOMEventStop(aDOMEvent, self); return !StopContextMenuDOMEventPropagation; } @@ -1957,7 +1603,8 @@ var CPDOMEventGetClickCount = function(aComparisonEvent, aTimestamp, aLocation) ABS(comparisonLocation.y - aLocation.y) < CLICK_SPACE_DELTA) ? [aComparisonEvent clickCount] + 1 : 1; }; -var CPDOMEventStop = function(aDOMEvent, aPlatformWindow) +// Global. +_CPDOMEventStop = function(aDOMEvent, aPlatformWindow) { // IE Model aDOMEvent.cancelBubble = true; diff --git a/Tests/Manual/CopyAndPaste/AppController.j b/Tests/Manual/CopyAndPaste/AppController.j index 2d67f4045..937d5ecc9 100644 --- a/Tests/Manual/CopyAndPaste/AppController.j +++ b/Tests/Manual/CopyAndPaste/AppController.j @@ -9,7 +9,6 @@ @import @import -@import "../CPTrace.j" @import "CollectionViewItem.j" @implementation AppController : CPObject