/* GNU LESSER GENERAL PUBLIC LICENSE Copyright (C) 2015 Uproot Labs India Pvt 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 St, Fifth Floor, Boston, MA 02110-1301 USA */ package org.lobobrowser.main; import java.awt.Component; import java.awt.Graphics; import java.awt.image.BufferedImage; import java.io.BufferedReader; import java.io.ByteArrayOutputStream; import java.io.DataOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.OutputStream; import java.io.Reader; import java.lang.reflect.InvocationTargetException; import java.net.MalformedURLException; import java.net.ServerSocket; import java.net.Socket; import java.util.concurrent.ExecutionException; import javax.imageio.ImageIO; import javax.swing.SwingUtilities; import org.lobobrowser.clientlet.ComponentContent; import org.lobobrowser.ua.NavigatorFrame; import org.lobobrowser.ua.NavigatorProgressEvent; import org.lobobrowser.ua.ProgressType; class GrinderServer implements Runnable { private final NavigatorFrame frame; private final ServerSocket socket; public GrinderServer(final NavigatorFrame frame) throws IOException { this.frame = frame; socket = new ServerSocket(0); final Thread t = new Thread(this, "GrinderServer"); t.setDaemon(true); t.start(); } @Override public void run() { boolean done = false; while (!done) { try { final ServerSocket ss = socket; try ( final Socket s = ss.accept()) { s.setSoTimeout(10000); s.setTcpNoDelay(true); try ( final InputStream in = s.getInputStream()) { final Reader reader = new InputStreamReader(in); final BufferedReader br = new BufferedReader(reader); String commandLine = br.readLine(); if (commandLine != null) { final int blankIdx = commandLine.indexOf(' '); final String command = blankIdx == -1 ? commandLine : commandLine.substring(0, blankIdx).trim(); // System.out.println("Command: " + command); if ("TO".equals(command)) { if (blankIdx != -1) { final String path = commandLine.substring(blankIdx + 1).trim(); handleTo(s, br, path); } } else if ("SET_SIZE".equals(command)) { if (blankIdx != -1) { final String params = commandLine.substring(blankIdx + 1).trim(); handleResize(s, br, params); } } else if ("SCREENSHOT".equals(command)) { handleScreenShot(s, br); } else if ("CLOSE".equals(command)) { frame.closeWindow(); done = true; } else if ("QUIT".equals(command)) { frame.closeWindow(); PlatformInit.shutdown(); done = true; } } } } } catch (final Exception t) { t.printStackTrace(System.err); } } } private void handleResize(final Socket s, final BufferedReader br, final String params) throws IOException { final String[] parts = params.split("[,\\s]+"); final int w = Integer.parseInt(parts[0]); final int h = Integer.parseInt(parts[1]); try { SwingUtilities.invokeAndWait(() -> { frame.resizeWindowTo(w, h); }); } catch (InvocationTargetException | InterruptedException e) { e.printStackTrace(); } markDoneAndWaitForAck(s, br); } // TODO: Add way to mark error private static void markDoneAndWaitForAck(final Socket s, final BufferedReader br) throws IOException { final DataOutputStream dos = new DataOutputStream(s.getOutputStream()); dos.writeInt(0); dos.flush(); // Wait for ACK br.readLine(); } private class ReadyChecker implements Runnable { boolean isReady = false; @Override public void run() { isReady = frame.getComponentContent().isReadyToPaint(); } } private boolean isReadyToPaint() { try { ReadyChecker checker = new ReadyChecker(); SwingUtilities.invokeAndWait(checker); return checker.isReady; } catch (InvocationTargetException | InterruptedException e) { e.printStackTrace(); } return true; } private void handleScreenShot(final Socket s, final BufferedReader br) throws IOException { try { while (!isReadyToPaint()) { Thread.sleep(50); } } catch (InterruptedException e) { e.printStackTrace(); } final ComponentContent componentContent = frame.getComponentContent(); componentContent.disableRenderHints(); final Component component = componentContent.getComponent(); final BufferedImage img = new BufferedImage(component.getWidth(), component.getHeight(), BufferedImage.TYPE_INT_ARGB); Graphics g = img.getGraphics(); try { SwingUtilities.invokeAndWait(() -> { component.paint(g); }); } catch (InvocationTargetException | InterruptedException e) { e.printStackTrace(); } final ByteArrayOutputStream bos = new ByteArrayOutputStream(); ImageIO.write(img, "PNG", bos); final OutputStream os = s.getOutputStream(); { final DataOutputStream dos = new DataOutputStream(os); dos.writeInt(bos.size()); dos.flush(); } bos.writeTo(os); os.flush(); // Wait for ACK br.readLine(); } private void handleTo(final Socket s, final BufferedReader br, final String path) throws MalformedURLException, InterruptedException, ExecutionException, IOException { // System.out.println(" path: " + path); frame.setProgressEvent(null); frame.navigate(path); waitForNavigationCompletion(); // TODO: reload and wait for navigation after enabling all requests? frame.allowAllFirstPartyRequests(); markDoneAndWaitForAck(s, br); } private void waitForNavigationCompletion() throws InterruptedException, ExecutionException { { NavigatorProgressEvent progressEvent = frame.getProgressEvent(); while (progressEvent == null || progressEvent.getProgressType() != ProgressType.DONE) { Thread.sleep(10); progressEvent = frame.getProgressEvent(); } while (frame.getComponentContent() == null) { Thread.sleep(10); } final Component component = frame.getComponentContent().getComponent(); if (component instanceof DefferedLayoutSupport) { final DefferedLayoutSupport defSupport = (DefferedLayoutSupport) component; defSupport.layoutCompletion().get(); } } } public int getPort() { return socket.getLocalPort(); } }