Skip to content

Commit

Permalink
New: JavaScript clipboard API support for pasting.
Browse files Browse the repository at this point in the history
Without this feature, attempts to paste data into a Cappuccino app (outside of a text field) are handled by redirecting the paste into a hidden text field. Then a paste event is generated from whatever is captured in this field after a 0 timeout. This method is prone to timing related bugs where the paste event actually has an empty clipboard.

With this new feature we read the paste data directly from the browser's native paste event when the browser supports it (Chrome 10+, Safari 5+, Firefox 22+). This is much more dependable and may in the future also enable us to read other types of paste data such as images.
  • Loading branch information
aljungberg committed Jun 13, 2013
1 parent 72169e5 commit a9a0b1c
Show file tree
Hide file tree
Showing 2 changed files with 76 additions and 13 deletions.
21 changes: 17 additions & 4 deletions AppKit/CPCompatibility.j
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,11 @@ CPHTMLDragAndDropFeature = 8;

CPJavaScriptInnerTextFeature = 9;
CPJavaScriptTextContentFeature = 10;
// In onpaste, oncopy and oncut events, the event has an event.clipboardData from which the current pasteboard contents can be read with event.clipboardData.getData.
CPJavaScriptClipboardEventsFeature = 11;
// The paste event is only sent if an input or textarea has focus.
CPJavaScriptClipboardEventsRequireInput = 32;
// window.clipboardData exists and can be read and written to at any time using window.clipboardData.getData/setData.
CPJavaScriptClipboardAccessFeature = 12;
CPJavaScriptCanvasDrawFeature = 13;
CPJavaScriptCanvasTransformFeature = 14;
Expand Down Expand Up @@ -76,6 +80,8 @@ CPInputOnInputEventFeature = 30;

CPFileAPIFeature = 31;



/*
When an absolutely positioned div (CPView) with an absolutely positioned canvas in it (CPView with drawRect:) moves things on top of the canvas (subviews) don't redraw correctly. E.g. if you have a bunch of text fields in a CPBox in a sheet which animates in, some of the text fields might not be visible because the CPBox has a canvas at the bottom and the box moved form offscreen to onscreen. This bug is probably very related: https://bugs.webkit.org/show_bug.cgi?id=67203
*/
Expand Down Expand Up @@ -118,6 +124,9 @@ else if (typeof window !== "undefined" && window.attachEvent) // Must follow Ope

// Tested in Internet Explore 8 and 9.
PLATFORM_FEATURES[CPInputSetFontOutsideOfDOM] = NO;

// IE allows free clipboard access.
PLATFORM_FEATURES[CPJavaScriptClipboardAccessFeature] = YES;
}

// WebKit
Expand All @@ -129,11 +138,9 @@ else if (USER_AGENT.indexOf("AppleWebKit/") != -1)
PLATFORM_FEATURES[CPCSSRGBAFeature] = YES;
PLATFORM_FEATURES[CPHTMLContentEditableFeature] = YES;

if (USER_AGENT.indexOf("Chrome") === -1)
PLATFORM_FEATURES[CPHTMLDragAndDropFeature] = YES;

PLATFORM_FEATURES[CPJavaScriptClipboardEventsFeature] = YES;
PLATFORM_FEATURES[CPJavaScriptClipboardAccessFeature] = YES;
PLATFORM_FEATURES[CPJavaScriptClipboardAccessFeature] = NO;
PLATFORM_FEATURES[CPJavaScriptClipboardEventsRequireInput] = YES;
PLATFORM_FEATURES[CPJavaScriptShadowFeature] = YES;

var versionStart = USER_AGENT.indexOf("AppleWebKit/") + "AppleWebKit/".length,
Expand All @@ -159,7 +166,10 @@ else if (USER_AGENT.indexOf("AppleWebKit/") != -1)
PLATFORM_FEATURES[CPInput1PxLeftPadding] = YES;

if (USER_AGENT.indexOf("Chrome") === CPNotFound)
{
PLATFORM_FEATURES[CPSOPDisabledFromFileURLs] = YES;
PLATFORM_FEATURES[CPHTMLDragAndDropFeature] = YES;
}

// Assume this bug was introduced around Safari 5.1/Chrome 16. This could probably be tighter.
if (majorVersion > 533)
Expand Down Expand Up @@ -190,6 +200,9 @@ else if (USER_AGENT.indexOf("Gecko") !== -1) // Must follow KHTML check.

// Some day this might be fixed and should be version prefixed. No known fixed version yet.
PLATFORM_FEATURES[CPInput1PxLeftPadding] = YES;

if (version >= 22.0)
PLATFORM_FEATURES[CPJavaScriptClipboardEventsFeature] = YES;
}

// Feature-specific checks
Expand Down
68 changes: 59 additions & 9 deletions AppKit/Platform/DOM/CPPlatformWindow+DOM.j
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,9 @@ var ModifierKeyCodes = [
var resizeTimer = nil;

#if PLATFORM(DOM)

#define HAS_INPUT_OR_TEXTAREA_TARGET(aDOMEvent) (((aDOMEvent).target || (aDOMEvent).srcElement).nodeName.toUpperCase() == "INPUT" || ((aDOMEvent).target || (aDOMEvent).srcElement).nodeName.toUpperCase() == "TEXTAREA")

@implementation CPPlatformWindow (DOM)

- (id)_init
Expand Down Expand Up @@ -423,7 +426,14 @@ var resizeTimer = nil;

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

if (CPFeatureIsCompatible(CPJavaScriptClipboardEventsFeature))
{
_DOMWindow.addEventListener("beforepaste", function(anEvent) { console.log("beforepaste: ", anEvent); }, NO);
_DOMWindow.addEventListener("paste", pasteEventCallback, NO);
}
else
theDocument.addEventListener("beforepaste", pasteEventCallback, NO);

theDocument.addEventListener("keyup", keyEventCallback, NO);
theDocument.addEventListener("keydown", keyEventCallback, NO);
Expand Down Expand Up @@ -457,6 +467,13 @@ var resizeTimer = nil;
theDocument.removeEventListener("beforecopy", copyEventCallback, NO);
theDocument.removeEventListener("beforecut", copyEventCallback, NO);
theDocument.removeEventListener("beforepaste", pasteEventCallback, NO);
if (CPFeatureIsCompatible(CPJavaScriptClipboardEventsFeature))
{
_DOMWindow.removeEventListener("beforepaste", pasteEventCallback, NO);
_DOMWindow.removeEventListener("paste", pasteEventCallback, NO);
}
else
theDocument.removeEventListener("beforepaste", pasteEventCallback, NO);

theDocument.removeEventListener("touchstart", touchEventCallback, NO);
theDocument.removeEventListener("touchend", touchEventCallback, NO);
Expand Down Expand Up @@ -826,7 +843,15 @@ var resizeTimer = nil;
if (isNativePasteEvent)
{
_pasteboardKeyDownEvent = event;
window.setNativeTimeout(function () { [self _checkPasteboardElement] }, 0);

// If we are using the paste into field hack, we need to check it in a moment.
if (!(CPFeatureIsCompatible(CPJavaScriptClipboardEventsFeature) && !CPFeatureIsCompatible(CPJavaScriptClipboardAccessFeature)))
{
// Only stop the event if we're using the paste hack. Otherwise we'll handle it with
// our onpaste handler.
CPDOMEventStop(aDOMEvent);
window.setNativeTimeout(function () { [self _checkPasteboardElement]; }, 0);
}
}

break;
Expand Down Expand Up @@ -864,6 +889,7 @@ var resizeTimer = nil;
event = [CPEvent keyEventWithType:CPKeyUp location:location modifierFlags:modifierFlags
timestamp: timestamp windowNumber:windowNumber context:nil
characters:characters charactersIgnoringModifiers:charactersIgnoringModifiers isARepeat:NO keyCode:keyCode];

break;
}

Expand Down Expand Up @@ -915,13 +941,42 @@ var resizeTimer = nil;
[[CPRunLoop currentRunLoop] limitDateForMode:CPDefaultRunLoopMode];
}

- (void)pasteEvent:(DOMEvent)aDOMEvent
- (boolean)pasteEvent:(DOMEvent)aDOMEvent
{
if (CPFeatureIsCompatible(CPJavaScriptClipboardEventsFeature) || CPFeatureIsCompatible(CPJavaScriptClipboardAccessFeature))
{
var value = CPFeatureIsCompatible(CPJavaScriptClipboardEventsFeature) ? aDOMEvent.clipboardData.getData('text/plain') : 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];
}
}

[CPApp sendEvent:_pasteboardKeyDownEvent];

_pasteboardKeyDownEvent = nil;

[[CPRunLoop currentRunLoop] limitDateForMode:CPDefaultRunLoopMode];

if (!HAS_INPUT_OR_TEXTAREA_TARGET(aDOMEvent))
CPDOMEventStop(aDOMEvent, self);

return;
}

// Set up to capture the paste in a temporary input field. We'll send the event after capture.
if ([self _validateCopyCutOrPasteEvent:aDOMEvent flags:CPPlatformActionKeyMask])
{
_DOMPasteboardElement.focus();
_DOMPasteboardElement.select();
_DOMPasteboardElement.value = "";

_ignoreNativePastePreparation = YES;
}

Expand All @@ -930,12 +985,7 @@ var resizeTimer = nil;

- (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);
return (!HAS_INPUT_OR_TEXTAREA_TARGET(aDOMEvent) || aDOMEvent.target === _DOMPasteboardElement) && (modifierFlags & CPPlatformActionKeyMask);
}

- (void)_primePasteboardElement
Expand Down

0 comments on commit a9a0b1c

Please sign in to comment.