8071668: [macosx] Clipboard does not work with 3rd parties Clipboard Managers
authoranashaty
Mon, 30 Mar 2015 18:41:51 +0300
changeset 29885 6eb60fac3383
parent 29884 013dd351eb8a
child 29886 545c0c3809b8
8071668: [macosx] Clipboard does not work with 3rd parties Clipboard Managers Reviewed-by: ant, serb
jdk/src/java.desktop/macosx/classes/sun/lwawt/macosx/CClipboard.java
jdk/src/java.desktop/macosx/classes/sun/lwawt/macosx/CEmbeddedFrame.java
jdk/src/java.desktop/macosx/native/libawt_lwawt/awt/CClipboard.m
jdk/src/java.desktop/share/classes/sun/awt/datatransfer/SunClipboard.java
jdk/test/java/awt/datatransfer/ClipboardInterVMTest/ClipboardInterVMTest.java
--- a/jdk/src/java.desktop/macosx/classes/sun/lwawt/macosx/CClipboard.java	Mon Mar 30 17:46:58 2015 +0300
+++ b/jdk/src/java.desktop/macosx/classes/sun/lwawt/macosx/CClipboard.java	Mon Mar 30 18:41:51 2015 +0300
@@ -57,6 +57,18 @@
     }
 
     @Override
+    public synchronized Transferable getContents(Object requestor) {
+        checkPasteboardAndNotify();
+        return super.getContents(requestor);
+    }
+
+    @Override
+    protected synchronized Transferable getContextContents() {
+        checkPasteboardAndNotify();
+        return super.getContextContents();
+    }
+
+    @Override
     protected void setContentsNative(Transferable contents) {
         FlavorTable flavorMap = getDefaultFlavorTable();
         // Don't use delayed Clipboard rendering for the Transferable's data.
@@ -116,13 +128,20 @@
     private native void declareTypes(long[] formats, SunClipboard newOwner);
     private native void setData(byte[] data, long format);
 
+    void checkPasteboardAndNotify() {
+        if (checkPasteboardWithoutNotification()) {
+            notifyChanged();
+            lostOwnershipNow(null);
+        }
+    }
+
     /**
      * Invokes native check whether a change count on the general pasteboard is different
      * than when we set it. The different count value means the current owner lost
      * pasteboard ownership and someone else put data on the clipboard.
      * @since 1.7
      */
-    native void checkPasteboard();
+    native boolean checkPasteboardWithoutNotification();
 
     /*** Native Callbacks ***/
     private void notifyLostOwnership() {
--- a/jdk/src/java.desktop/macosx/classes/sun/lwawt/macosx/CEmbeddedFrame.java	Mon Mar 30 17:46:58 2015 +0300
+++ b/jdk/src/java.desktop/macosx/classes/sun/lwawt/macosx/CEmbeddedFrame.java	Mon Mar 30 18:41:51 2015 +0300
@@ -123,7 +123,7 @@
             // it won't be invoced if focuse is moved to a html element
             // on the same page.
             CClipboard clipboard = (CClipboard) Toolkit.getDefaultToolkit().getSystemClipboard();
-            clipboard.checkPasteboard();
+            clipboard.checkPasteboardAndNotify();
         }
         if (parentWindowActive) {
             responder.handleWindowFocusEvent(focused, null);
--- a/jdk/src/java.desktop/macosx/native/libawt_lwawt/awt/CClipboard.m	Mon Mar 30 17:46:58 2015 +0300
+++ b/jdk/src/java.desktop/macosx/native/libawt_lwawt/awt/CClipboard.m	Mon Mar 30 18:41:51 2015 +0300
@@ -107,6 +107,19 @@
     }
 }
 
+- (BOOL) checkPasteboardWithoutNotification:(id)application {
+    AWT_ASSERT_APPKIT_THREAD;
+    
+    NSInteger newChangeCount = [[NSPasteboard generalPasteboard] changeCount];
+    
+    if (self.changeCount != newChangeCount) {
+        self.changeCount = newChangeCount;    
+        return YES;
+    } else {
+        return NO;
+    }
+}
+
 @end
 
 /*
@@ -260,21 +273,20 @@
     return returnValue;
 }
 
-/*
- * Class:     sun_lwawt_macosx_CClipboard
- * Method:    checkPasteboard
- * Signature: ()V
- */
-JNIEXPORT void JNICALL Java_sun_lwawt_macosx_CClipboard_checkPasteboard
-(JNIEnv *env, jobject inObject )
-{
-JNF_COCOA_ENTER(env);
-
-    [ThreadUtilities performOnMainThreadWaiting:YES block:^(){
-        [[CClipboard sharedClipboard] checkPasteboard:nil];
-    }];
-        
-JNF_COCOA_EXIT(env);
-}
-
-
+/*                                                                                            
+ * Class:     sun_lwawt_macosx_CClipboard                                                     
+ * Method:    checkPasteboard                                                                 
+ * Signature: ()V                                                                             
+ */                                                                                           
+JNIEXPORT jboolean JNICALL Java_sun_lwawt_macosx_CClipboard_checkPasteboardWithoutNotification
+(JNIEnv *env, jobject inObject)                                                               
+{                                                                                             
+    __block BOOL ret = NO;                                                                    
+    JNF_COCOA_ENTER(env);                                                                     
+    [ThreadUtilities performOnMainThreadWaiting:YES block:^(){                                
+        ret = [[CClipboard sharedClipboard] checkPasteboardWithoutNotification:nil];          
+    }];                                                                                       
+                                                                                              
+    JNF_COCOA_EXIT(env);                                                                      
+    return ret;                                                                               
+}                                                                                             
\ No newline at end of file
--- a/jdk/src/java.desktop/share/classes/sun/awt/datatransfer/SunClipboard.java	Mon Mar 30 17:46:58 2015 +0300
+++ b/jdk/src/java.desktop/share/classes/sun/awt/datatransfer/SunClipboard.java	Mon Mar 30 18:41:51 2015 +0300
@@ -144,7 +144,7 @@
      *         AppContext as it is currently retrieved or null otherwise
      * @since 1.5
      */
-    private synchronized Transferable getContextContents() {
+    protected synchronized Transferable getContextContents() {
         AppContext context = AppContext.getAppContext();
         return (context == contentsContext) ? contents : null;
     }
@@ -275,42 +275,41 @@
             return;
         }
 
-        final Runnable runnable = new Runnable() {
-                public void run() {
-                    final SunClipboard sunClipboard = SunClipboard.this;
-                    ClipboardOwner owner = null;
-                    Transferable contents = null;
-
-                    synchronized (sunClipboard) {
-                        final AppContext context = sunClipboard.contentsContext;
-
-                        if (context == null) {
-                            return;
-                        }
-
-                        if (disposedContext == null || context == disposedContext) {
-                            owner = sunClipboard.owner;
-                            contents = sunClipboard.contents;
-                            sunClipboard.contentsContext = null;
-                            sunClipboard.owner = null;
-                            sunClipboard.contents = null;
-                            sunClipboard.clearNativeContext();
-                            context.removePropertyChangeListener
-                                (AppContext.DISPOSED_PROPERTY_NAME, sunClipboard);
-                        } else {
-                            return;
-                        }
-                    }
-                    if (owner != null) {
-                        owner.lostOwnership(sunClipboard, contents);
-                    }
-                }
-            };
-
-        SunToolkit.postEvent(context, new PeerEvent(this, runnable,
+        SunToolkit.postEvent(context, new PeerEvent(this, () -> lostOwnershipNow(disposedContext),
                                                     PeerEvent.PRIORITY_EVENT));
     }
 
+    protected void lostOwnershipNow(final AppContext disposedContext) {
+        final SunClipboard sunClipboard = SunClipboard.this;
+        ClipboardOwner owner = null;
+        Transferable contents = null;
+
+        synchronized (sunClipboard) {
+            final AppContext context = sunClipboard.contentsContext;
+
+            if (context == null) {
+                return;
+            }
+
+            if (disposedContext == null || context == disposedContext) {
+                owner = sunClipboard.owner;
+                contents = sunClipboard.contents;
+                sunClipboard.contentsContext = null;
+                sunClipboard.owner = null;
+                sunClipboard.contents = null;
+                sunClipboard.clearNativeContext();
+                context.removePropertyChangeListener
+                        (AppContext.DISPOSED_PROPERTY_NAME, sunClipboard);
+            } else {
+                return;
+            }
+        }
+        if (owner != null) {
+            owner.lostOwnership(sunClipboard, contents);
+        }
+    }
+
+
     protected abstract void clearNativeContext();
 
     protected abstract void setContentsNative(Transferable contents);
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/java/awt/datatransfer/ClipboardInterVMTest/ClipboardInterVMTest.java	Mon Mar 30 18:41:51 2015 +0300
@@ -0,0 +1,171 @@
+/*
+ * Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code 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 General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+/*
+  @test
+  @bug 8071668
+  @summary Check whether clipboard see changes from external process after taking ownership
+  @author Anton Nashatyrev: area=datatransfer
+  @library /lib/testlibrary
+  @build jdk.testlibrary.Utils
+  @run main ClipboardInterVMTest
+*/
+
+import jdk.testlibrary.Utils;
+
+import java.awt.*;
+import java.awt.datatransfer.*;
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.IOException;
+import java.io.Reader;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+public class ClipboardInterVMTest {
+
+    static CountDownLatch lostOwnershipMonitor = new CountDownLatch(1);
+    static CountDownLatch flavorChangedMonitor = new CountDownLatch(1);
+    static Process process;
+
+    public static void main(String[] args) throws Throwable {
+        Clipboard clip = Toolkit.getDefaultToolkit().getSystemClipboard();
+
+        if (args.length > 0) {
+            System.out.println("Changing clip...");
+            clip.setContents(new StringSelection("pong"), null);
+            System.out.println("done");
+            // keeping this process running for a while since on Mac the clipboard
+            // will be invalidated via NSApplicationDidBecomeActiveNotification
+            // callback in the main process after this child process finishes
+            Thread.sleep(60 * 1000);
+            return;
+        };
+
+
+        clip.setContents(new CustomSelection(), new ClipboardOwner() {
+            @Override
+            public void lostOwnership(Clipboard clipboard, Transferable contents) {
+                System.out.println("ClipboardInterVMTest.lostOwnership");
+                lostOwnershipMonitor.countDown();
+            }
+        });
+
+        clip.addFlavorListener(new FlavorListener() {
+            @Override
+            public void flavorsChanged(FlavorEvent e) {
+                System.out.println("ClipboardInterVMTest.flavorsChanged");
+                flavorChangedMonitor.countDown();
+            }
+        });
+
+        System.out.println("Starting external clipborad modifier...");
+        new Thread(() -> runTest(ClipboardInterVMTest.class.getCanonicalName(), "pong")).start();
+
+        String content = "";
+        long startTime = System.currentTimeMillis();
+        while (System.currentTimeMillis() - startTime < 30 * 1000) {
+            Transferable c = clip.getContents(null);
+            if (c.isDataFlavorSupported(DataFlavor.plainTextFlavor)) {
+                Reader reader = DataFlavor.plainTextFlavor.getReaderForText(c);
+                content = new BufferedReader(reader).readLine();
+                System.out.println(content);
+                if (content.equals("pong")) {
+                    break;
+                }
+            }
+            Thread.sleep(200);
+        }
+
+        if (!lostOwnershipMonitor.await(10, TimeUnit.SECONDS)) {
+            throw new RuntimeException("No LostOwnership event received.");
+        };
+
+        if (!flavorChangedMonitor.await(10, TimeUnit.SECONDS)) {
+            throw new RuntimeException("No LostOwnership event received.");
+        };
+
+        if (!content.equals("pong")) {
+            throw new RuntimeException("Content was not passed.");
+        }
+
+        process.destroy();
+
+        System.out.println("Passed.");
+    }
+
+    private static void runTest(String main, String... args)  {
+
+        try {
+            List<String> opts = new ArrayList<>();
+            opts.add(getJavaExe());
+            opts.addAll(Arrays.asList(Utils.getTestJavaOpts()));
+            opts.add("-cp");
+            opts.add(System.getProperty("test.class.path", System.getProperty("java.class.path")));
+
+            opts.add(main);
+            opts.addAll(Arrays.asList(args));
+
+            ProcessBuilder pb = new ProcessBuilder(opts.toArray(new String[0]));
+            process = pb.start();
+        } catch (Throwable throwable) {
+            throw new RuntimeException(throwable);
+        }
+    }
+
+    private static String getJavaExe() throws IOException {
+        File p  = new File(System.getProperty("java.home"), "bin");
+        File j = new File(p, "java");
+        if (!j.canRead()) {
+            j = new File(p, "java.exe");
+        }
+        if (!j.canRead()) {
+            throw new RuntimeException("Can't find java executable in " + p);
+        }
+        return j.getCanonicalPath();
+    }
+
+    static class CustomSelection implements Transferable {
+        private static final DataFlavor[] flavors = { DataFlavor.allHtmlFlavor };
+
+        public DataFlavor[] getTransferDataFlavors() {
+            return flavors;
+        }
+
+        public boolean isDataFlavorSupported(DataFlavor flavor) {
+            return flavors[0].equals(flavor);
+        }
+
+        public Object getTransferData(DataFlavor flavor)
+                throws UnsupportedFlavorException, java.io.IOException {
+            if (isDataFlavorSupported(flavor)) {
+                return "ping";
+            } else {
+                throw new UnsupportedFlavorException(flavor);
+            }
+        }
+    }
+}
\ No newline at end of file