1 /* 2 * Copyright (c) 2011, 2016, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26 package com.sun.javafx.webkit; 27 28 import static com.sun.glass.ui.Clipboard.DRAG_IMAGE; 29 import static com.sun.glass.ui.Clipboard.DRAG_IMAGE_OFFSET; 30 import static com.sun.glass.ui.Clipboard.IE_URL_SHORTCUT_FILENAME; 31 import static javafx.scene.web.WebEvent.ALERT; 32 import static javafx.scene.web.WebEvent.RESIZED; 33 import static javafx.scene.web.WebEvent.STATUS_CHANGED; 34 import static javafx.scene.web.WebEvent.VISIBILITY_CHANGED; 35 36 import com.sun.javafx.tk.Toolkit; 37 import com.sun.webkit.UIClient; 38 import com.sun.webkit.WebPage; 39 import com.sun.webkit.graphics.WCImage; 40 import com.sun.webkit.graphics.WCRectangle; 41 import java.awt.image.BufferedImage; 42 import java.io.File; 43 import java.io.IOException; 44 import java.io.UnsupportedEncodingException; 45 import java.lang.reflect.Method; 46 import java.nio.ByteBuffer; 47 import java.security.AccessControlContext; 48 import java.security.AccessController; 49 import java.security.PrivilegedAction; 50 import java.util.Arrays; 51 import java.util.List; 52 import javafx.event.EventHandler; 53 import javafx.geometry.Rectangle2D; 54 import javafx.scene.image.Image; 55 import javafx.scene.input.ClipboardContent; 56 import javafx.scene.input.DataFormat; 57 import javafx.scene.input.Dragboard; 58 import javafx.scene.input.TransferMode; 59 import javafx.scene.web.PopupFeatures; 60 import javafx.scene.web.PromptData; 61 import javafx.scene.web.WebEngine; 62 import javafx.scene.web.WebEvent; 63 import javafx.scene.web.WebView; 64 import javafx.stage.FileChooser; 65 import javafx.stage.Window; 66 import javax.imageio.ImageIO; 67 68 public final class UIClientImpl implements UIClient { 69 private final Accessor accessor; 70 private FileChooser chooser; 71 72 public UIClientImpl(Accessor accessor) { 73 this.accessor = accessor; 74 } 75 76 private WebEngine getWebEngine() { 77 return accessor.getEngine(); 78 } 79 80 private AccessControlContext getAccessContext() { 81 return accessor.getPage().getAccessControlContext(); 82 } 83 84 @Override public WebPage createPage( 85 boolean menu, boolean status, boolean toolbar, boolean resizable) { 86 final WebEngine w = getWebEngine(); 87 if (w != null && w.getCreatePopupHandler() != null) { 88 final PopupFeatures pf = 89 new PopupFeatures(menu, status, toolbar, resizable); 90 WebEngine popup = AccessController.doPrivileged( 91 (PrivilegedAction<WebEngine>) () -> w.getCreatePopupHandler().call(pf), getAccessContext()); 92 return Accessor.getPageFor(popup); 93 } 94 return null; 95 } 96 97 private void dispatchWebEvent(final EventHandler handler, final WebEvent ev) { 98 AccessController.doPrivileged((PrivilegedAction<Void>) () -> { 99 handler.handle(ev); 100 return null; 101 }, getAccessContext()); 102 } 103 104 private void notifyVisibilityChanged(boolean visible) { 105 WebEngine w = getWebEngine(); 106 if (w != null && w.getOnVisibilityChanged() != null) { 107 dispatchWebEvent( 108 w.getOnVisibilityChanged(), 109 new WebEvent<Boolean>(w, VISIBILITY_CHANGED, visible)); 110 } 111 } 112 113 @Override public void closePage() { 114 notifyVisibilityChanged(false); 115 } 116 117 @Override public void showView() { 118 notifyVisibilityChanged(true); 119 } 120 121 @Override public WCRectangle getViewBounds() { 122 WebView view = accessor.getView(); 123 Window win = null; 124 if (view != null && 125 view.getScene() != null && 126 (win = view.getScene().getWindow()) != null) 127 { 128 return new WCRectangle( 129 (float) win.getX(), (float) win.getY(), 130 (float) win.getWidth(), (float) win.getHeight()); 131 } 132 return null; 133 } 134 135 @Override public void setViewBounds(WCRectangle r) { 136 WebEngine w = getWebEngine(); 137 if (w != null && w.getOnResized() != null) { 138 dispatchWebEvent( 139 w.getOnResized(), 140 new WebEvent<Rectangle2D>(w, RESIZED, 141 new Rectangle2D(r.getX(), r.getY(), r.getWidth(), r.getHeight()))); 142 } 143 } 144 145 @Override public void setStatusbarText(String text) { 146 WebEngine w = getWebEngine(); 147 if (w != null && w.getOnStatusChanged() != null) { 148 dispatchWebEvent( 149 w.getOnStatusChanged(), 150 new WebEvent<String>(w, STATUS_CHANGED, text)); 151 } 152 } 153 154 @Override public void alert(String text) { 155 WebEngine w = getWebEngine(); 156 if (w != null && w.getOnAlert() != null) { 157 dispatchWebEvent( 158 w.getOnAlert(), 159 new WebEvent<String>(w, ALERT, text)); 160 } 161 } 162 163 @Override public boolean confirm(final String text) { 164 final WebEngine w = getWebEngine(); 165 if (w != null && w.getConfirmHandler() != null) { 166 return AccessController.doPrivileged( 167 (PrivilegedAction<Boolean>) () -> w.getConfirmHandler().call(text), getAccessContext()); 168 } 169 return false; 170 } 171 172 @Override public String prompt(String text, String defaultValue) { 173 final WebEngine w = getWebEngine(); 174 if (w != null && w.getPromptHandler() != null) { 175 final PromptData data = new PromptData(text, defaultValue); 176 return AccessController.doPrivileged( 177 (PrivilegedAction<String>) () -> w.getPromptHandler().call(data), getAccessContext()); 178 } 179 return ""; 180 } 181 182 @Override public String[] chooseFile(String initialFileName, boolean multiple) { 183 // get the toplevel window 184 Window win = null; 185 WebView view = accessor.getView(); 186 if (view != null && view.getScene() != null) { 187 win = view.getScene().getWindow(); 188 } 189 190 if (chooser == null) { 191 chooser = new FileChooser(); 192 } 193 194 // set initial directory 195 if (initialFileName != null) { 196 File dir = new File(initialFileName); 197 while (dir != null && !dir.isDirectory()) { 198 dir = dir.getParentFile(); 199 } 200 chooser.setInitialDirectory(dir); 201 } 202 203 if (multiple) { 204 List<File> files = chooser.showOpenMultipleDialog(win); 205 if (files != null) { 206 int n = files.size(); 207 String[] result = new String[n]; 208 for (int i = 0; i < n; i++) { 209 result[i] = files.get(i).getAbsolutePath(); 210 } 211 return result; 212 } 213 return null; 214 } else { 215 File f = chooser.showOpenDialog(win); 216 return f != null 217 ? new String[] { f.getAbsolutePath() } 218 : null; 219 } 220 } 221 222 @Override public void print() { 223 } 224 225 private ClipboardContent content; 226 private static DataFormat getDataFormat(String mimeType) { 227 synchronized (DataFormat.class) { 228 DataFormat ret = DataFormat.lookupMimeType(mimeType); 229 if (ret == null) { 230 ret = new DataFormat(mimeType); 231 } 232 return ret; 233 } 234 } 235 236 //copy from com.sun.glass.ui.Clipboard 237 private final static DataFormat DF_DRAG_IMAGE = getDataFormat(DRAG_IMAGE); 238 private final static DataFormat DF_DRAG_IMAGE_OFFSET = getDataFormat(DRAG_IMAGE_OFFSET); 239 240 @Override public void startDrag(WCImage image, 241 int imageOffsetX, int imageOffsetY, 242 int eventPosX, int eventPosY, 243 String[] mimeTypes, Object[] values 244 ){ 245 content = new ClipboardContent(); 246 for (int i = 0; i < mimeTypes.length; ++i) if (values[i] != null) { 247 try { 248 content.put(getDataFormat(mimeTypes[i]), 249 IE_URL_SHORTCUT_FILENAME.equals(mimeTypes[i]) 250 ? (Object)ByteBuffer.wrap(((String)values[i]).getBytes("UTF-16LE")) 251 : (Object)values[i]); 252 } catch (UnsupportedEncodingException ex) { 253 //never happens 254 } 255 } 256 if (image != null) { 257 ByteBuffer dragImageOffset = ByteBuffer.allocate(8); 258 dragImageOffset.rewind(); 259 dragImageOffset.putInt(imageOffsetX); 260 dragImageOffset.putInt(imageOffsetY); 261 content.put(DF_DRAG_IMAGE_OFFSET, dragImageOffset); 262 263 int w = image.getWidth(); 264 int h = image.getHeight(); 265 ByteBuffer pixels = image.getPixelBuffer(); 266 267 ByteBuffer dragImage = ByteBuffer.allocate(8 + w*h*4); 268 dragImage.putInt(w); 269 dragImage.putInt(h); 270 dragImage.put(pixels); 271 content.put(DF_DRAG_IMAGE, dragImage); 272 273 //The image is prepared synchronously, that is sad. 274 //Image need to be created by target request only. 275 //QuantumClipboard.putContent have to be rewritten in Glass manner 276 //with postponed data requests (DelayedCallback data object). 277 Object platformImage = image.getWidth() > 0 && image.getHeight() > 0 ? 278 image.getPlatformImage() : null; 279 if (platformImage != null) { 280 try { 281 File temp = File.createTempFile("jfx", ".png"); 282 temp.deleteOnExit(); 283 ImageIO.write( 284 toBufferedImage(Toolkit.getImageAccessor().fromPlatformImage( 285 Toolkit.getToolkit().loadPlatformImage( 286 platformImage 287 ) 288 )), 289 "png", 290 temp); 291 content.put(DataFormat.FILES, Arrays.asList(temp)); 292 } catch (IOException | SecurityException e) { 293 //That is ok. It was just an attempt. 294 //e.printStackTrace(); 295 } 296 } 297 } 298 } 299 300 @Override public void confirmStartDrag() { 301 WebView view = accessor.getView(); 302 if (view != null && content != null) { 303 //TODO: implement native support for Drag Source actions. 304 Dragboard db = view.startDragAndDrop(TransferMode.ANY); 305 db.setContent(content); 306 } 307 content = null; 308 } 309 310 @Override public boolean isDragConfirmed() { 311 return accessor.getView() != null && content != null; 312 } 313 314 // Method to implement the following via reflection: 315 // SwingFXUtils.fromFXImage(img, null) 316 public static BufferedImage toBufferedImage(Image img) { 317 try { 318 Class swingFXUtilsCls = Class.forName("javafx.embed.swing.SwingFXUtils"); 319 Method m_fromFXImage = swingFXUtilsCls.getMethod("fromFXImage", 320 Image.class, BufferedImage.class); 321 Object bimg = m_fromFXImage.invoke(null, img, null); 322 return (BufferedImage)bimg; 323 } catch (Exception ex) { 324 ex.printStackTrace(System.err); 325 } 326 327 // return null upon any exception 328 return null; 329 } 330 331 }