001package Torello.Browser;
002
003import java.util.*;
004import javax.json.*;
005import javax.json.stream.*;
006import java.io.*;
007
008import java.lang.reflect.Method;
009import java.lang.reflect.Parameter;
010import java.util.function.Function;
011
012import Torello.Java.Additional.*;
013import Torello.Java.JSON.*;
014
015import static Torello.Java.JSON.JFlag.*;
016
017import Torello.Java.StrCmpr;
018import Torello.JavaDoc.StaticFunctional;
019import Torello.JavaDoc.JDHeaderBackgroundImg;
020import Torello.JavaDoc.Excuse;
021
022/**
023 * <SPAN CLASS=COPIEDJDK><B>This domain provides experimental commands only supported in headless mode.</B></SPAN>
024 * 
025 * <EMBED CLASS='external-html' DATA-FILE-ID=CODE_GEN_NOTE>
026 */
027@StaticFunctional(Excused={"counter"}, Excuses={Excuse.CONFIGURATION})
028@JDHeaderBackgroundImg(EmbedTagFileID="WOOD_PLANK_NOTE")
029public class HeadlessExperimental
030{
031    // ********************************************************************************************
032    // ********************************************************************************************
033    // Class Header Stuff
034    // ********************************************************************************************
035    // ********************************************************************************************
036
037
038    // No Pubic Constructors
039    private HeadlessExperimental () { }
040
041    // These two Vector's are used by all the "Methods" exported by this class.  java.lang.reflect
042    // is used to generate the JSON String's.  It saves thousands of lines of Auto-Generated Code.
043    private static final Map<String, Vector<String>>    parameterNames = new HashMap<>();
044    private static final Map<String, Vector<Class<?>>>  parameterTypes = new HashMap<>();
045
046    // Some Methods do not take any parameters - for instance all the "enable()" and "disable()"
047    // I simply could not get ride of RAW-TYPES and UNCHECKED warnings... so there are now,
048    // offically, two empty-vectors.  One for String's, and the other for Classes.
049
050    private static final Vector<String>     EMPTY_VEC_STR = new Vector<>();
051    private static final Vector<Class<?>>   EMPTY_VEC_CLASS = new Vector<>();
052
053    static
054    {
055        for (Method m : HeadlessExperimental.class.getMethods())
056        {
057            // This doesn't work!  The parameter names are all "arg0" ... "argN"
058            // It works for java.lang.reflect.Field, BUT NOT java.lang.reflect.Parameter!
059            //
060            // Vector<String> parameterNamesList = new Vector<>(); -- NOPE!
061
062            Vector<Class<?>> parameterTypesList = new Vector<>();
063        
064            for (Parameter p : m.getParameters()) parameterTypesList.add(p.getType());
065
066            parameterTypes.put(
067                m.getName(),
068                (parameterTypesList.size() > 0) ? parameterTypesList : EMPTY_VEC_CLASS
069            );
070        }
071    }
072
073    static
074    {
075        Vector<String> v = null;
076
077        v = new Vector<String>(4);
078        parameterNames.put("beginFrame", v);
079        Collections.addAll(v, new String[]
080        { "frameTimeTicks", "interval", "noDisplayUpdates", "screenshot", });
081
082        parameterNames.put("disable", EMPTY_VEC_STR);
083
084        parameterNames.put("enable", EMPTY_VEC_STR);
085    }
086
087
088    // ********************************************************************************************
089    // ********************************************************************************************
090    // Types - Static Inner Classes
091    // ********************************************************************************************
092    // ********************************************************************************************
093
094    /** Encoding options for a screenshot. */
095    public static class ScreenshotParams
096        extends BaseType
097        implements java.io.Serializable
098    {
099        /** For Object Serialization.  java.io.Serializable */
100        protected static final long serialVersionUID = 1;
101        
102        public boolean[] optionals()
103        { return new boolean[] { true, true, }; }
104        
105        /**
106         * Image compression format (defaults to png).
107         * <BR />
108         * <BR /><B>OPTIONAL</B>
109         */
110        public final String format;
111        
112        /**
113         * Compression quality from range [0..100] (jpeg only).
114         * <BR />
115         * <BR /><B>OPTIONAL</B>
116         */
117        public final Integer quality;
118        
119        /**
120         * Constructor
121         *
122         * @param format Image compression format (defaults to png).
123         * <BR />Acceptable Values: ["jpeg", "png"]
124         * <BR /><B>OPTIONAL</B>
125         * 
126         * @param quality Compression quality from range [0..100] (jpeg only).
127         * <BR /><B>OPTIONAL</B>
128         */
129        public ScreenshotParams(String format, Integer quality)
130        {
131            // Exception-Check(s) to ensure that if any parameters which must adhere to a
132            // provided List of Enumerated Values, fails, then IllegalArgumentException shall throw.
133            
134            BRDPC.checkIAE(
135                "format", format,
136                "jpeg", "png"
137            );
138            
139            this.format   = format;
140            this.quality  = quality;
141        }
142        
143        /**
144         * JSON Object Constructor
145         * @param jo A Json-Object having data about an instance of {@code 'ScreenshotParams'}.
146         */
147        public ScreenshotParams (JsonObject jo)
148        {
149            this.format   = ReadJSON.getString(jo, "format", true, false);
150            this.quality  = ReadBoxedJSON.getInteger(jo, "quality", true);
151        }
152        
153        
154        /** Checks whether {@code 'this'} equals an input Java-{@code Object} */
155        public boolean equals(Object other)
156        {
157            if (other == null)                       return false;
158            if (other.getClass() != this.getClass()) return false;
159        
160            ScreenshotParams o = (ScreenshotParams) other;
161        
162            return
163                    Objects.equals(this.format, o.format)
164                &&  Objects.equals(this.quality, o.quality);
165        }
166        
167        /** Generates a Hash-Code for {@code 'this'} instance */
168        public int hashCode()
169        {
170            return
171                    Objects.hashCode(this.format)
172                +   Objects.hashCode(this.quality);
173        }
174    }
175    
176    /**
177     * Issued when the target starts or stops needing BeginFrames.
178     * Deprecated. Issue beginFrame unconditionally instead and use result from
179     * beginFrame to detect whether the frames were suppressed.
180     * <BR />
181     * <BR /><B>DEPRECATED</B>
182     */
183    public static class needsBeginFramesChanged
184        extends BrowserEvent
185        implements java.io.Serializable
186    {
187        /** For Object Serialization.  java.io.Serializable */
188        protected static final long serialVersionUID = 1;
189        
190        public boolean[] optionals()
191        { return new boolean[] { false, }; }
192        
193        /** True if BeginFrames are needed, false otherwise. */
194        public final boolean needsBeginFrames;
195        
196        /**
197         * Constructor
198         *
199         * @param needsBeginFrames True if BeginFrames are needed, false otherwise.
200         */
201        public needsBeginFramesChanged(boolean needsBeginFrames)
202        {
203            super("HeadlessExperimental", "needsBeginFramesChanged", 1);
204            
205            this.needsBeginFrames  = needsBeginFrames;
206        }
207        
208        /**
209         * JSON Object Constructor
210         * @param jo A Json-Object having data about an instance of {@code 'needsBeginFramesChanged'}.
211         */
212        public needsBeginFramesChanged (JsonObject jo)
213        {
214            super("HeadlessExperimental", "needsBeginFramesChanged", 1);
215        
216            this.needsBeginFrames  = ReadPrimJSON.getBoolean(jo, "needsBeginFrames");
217        }
218        
219        
220        /** Checks whether {@code 'this'} equals an input Java-{@code Object} */
221        public boolean equals(Object other)
222        {
223            if (other == null)                       return false;
224            if (other.getClass() != this.getClass()) return false;
225        
226            needsBeginFramesChanged o = (needsBeginFramesChanged) other;
227        
228            return
229                    (this.needsBeginFrames == o.needsBeginFrames);
230        }
231        
232        /** Generates a Hash-Code for {@code 'this'} instance */
233        public int hashCode()
234        {
235            return
236                    (this.needsBeginFrames ? 1 : 0);
237        }
238    }
239    
240    
241    // Counter for keeping the WebSocket Request ID's distinct.
242    private static int counter = 1;
243    
244    /**
245     * Sends a BeginFrame to the target and returns when the frame was completed. Optionally captures a
246     * screenshot from the resulting frame. Requires that the target was created with enabled
247     * BeginFrameControl. Designed for use with --run-all-compositor-stages-before-draw, see also
248     * https://goo.gl/3zHXhB for more background.
249     * 
250     * @param frameTimeTicks 
251     * Timestamp of this BeginFrame in Renderer TimeTicks (milliseconds of uptime). If not set,
252     * the current time will be used.
253     * <BR /><B>OPTIONAL</B>
254     * 
255     * @param interval 
256     * The interval between BeginFrames that is reported to the compositor, in milliseconds.
257     * Defaults to a 60 frames/second interval, i.e. about 16.666 milliseconds.
258     * <BR /><B>OPTIONAL</B>
259     * 
260     * @param noDisplayUpdates 
261     * Whether updates should not be committed and drawn onto the display. False by default. If
262     * true, only side effects of the BeginFrame will be run, such as layout and animations, but
263     * any visual updates may not be visible on the display or in screenshots.
264     * <BR /><B>OPTIONAL</B>
265     * 
266     * @param screenshot 
267     * If set, a screenshot of the frame will be captured and returned in the response. Otherwise,
268     * no screenshot will be captured. Note that capturing a screenshot can fail, for example,
269     * during renderer initialization. In such a case, no screenshot data will be returned.
270     * <BR /><B>OPTIONAL</B>
271     * 
272     * @return An instance of <CODE>{@link Script}&lt;String, {@link JsonObject},
273     * {@link Ret2}&gt;</CODE>
274     *
275     * <BR /><BR />This {@link Script} may be <B STYLE='color:red'>executed</B> (using 
276     * {@link Script#exec()}), and a {@link Promise} returned.
277     *
278     * <BR /><BR />When the {@code Promise} is <B STYLE='color: red'>awaited</B>
279     * (using {@link Promise#await()}), the {@code Ret2} will subsequently
280     * be returned from that call.
281     * 
282     * <BR /><BR />The <B STYLE='color: red'>returned</B> values are encapsulated
283     * in an instance of <B>{@link Ret2}</B>
284     *
285     * <BR /><BR /><UL CLASS=JDUL>
286     * <LI><CODE><B>Ret2.a:</B> Boolean (<B>hasDamage</B>)</CODE>
287     *     <BR />Whether the BeginFrame resulted in damage and, thus, a new frame was committed to the
288     *     display. Reported for diagnostic uses, may be removed in the future.
289     *     <BR /><BR /></LI>
290     * <LI><CODE><B>Ret2.b:</B> String (<B>screenshotData</B>)</CODE>
291     *     <BR />Base64-encoded image data of the screenshot, if one was requested and successfully taken. (Encoded as a base64 string when passed over JSON)
292     *     </LI>
293     * </UL>
294     */
295    public static Script<String, JsonObject, Ret2<Boolean, String>> beginFrame(
296            Number frameTimeTicks, Number interval, Boolean noDisplayUpdates, 
297            HeadlessExperimental.ScreenshotParams screenshot
298        )
299    {
300        final int       webSocketID = 22000000 + counter++;
301        final boolean[] optionals   = { true, true, true, true, };
302        
303        // Convert Method Parameters into JSON.  Build the JSON Request-Object (as a String)
304        String requestJSON = WriteJSON.get(
305            parameterTypes.get("beginFrame"),
306            parameterNames.get("beginFrame"),
307            optionals, webSocketID,
308            "HeadlessExperimental.beginFrame",
309            frameTimeTicks, interval, noDisplayUpdates, screenshot
310        );
311        
312        // 'JSON Binding' ... Converts Browser Response-JSON into Java-Type 'Ret2'
313        Function<JsonObject, Ret2<Boolean, String>> 
314            responseProcessor = (JsonObject jo) -> new Ret2<>(
315                ReadBoxedJSON.getBoolean(jo, "hasDamage", true),
316                ReadJSON.getString(jo, "screenshotData", true, false)
317            );
318        
319        // Pass the 'defaultSender' to Script-Constructor
320        // The sender that is used can be changed before executing script.
321        return new Script<>(BRDPC.defaultSender, webSocketID, requestJSON, responseProcessor);
322    }
323    
324    /**
325     * Disables headless events for the target.
326     * 
327     * @return An instance of <CODE>{@link Script}&lt;String, {@link JsonObject},
328     * {@link Ret0}&gt;</CODE>
329     *
330     * <BR /><BR />This {@code Script} instance must be <B STYLE='color:red'>executed</B> before the
331     * browser receives the invocation-request.
332     *
333     * <BR /><BR />This Browser-Function <I>does not have</I> a return-value.  You may choose to
334     * <B STYLE='color: red'>await</B> the {@link Promise}{@code <JsonObject,} {@link Ret0}
335     * {@code >} to ensure the Browser Function has run to completion.
336     */
337    public static Script<String, JsonObject, Ret0> disable()
338    {
339        final int          webSocketID = 22001000 + counter++;
340        final boolean[]    optionals   = new boolean[0];
341        
342        // Convert Method Parameters into JSON.  Build the JSON Request-Object (as a String)
343        String requestJSON = WriteJSON.get(
344            parameterTypes.get("disable"),
345            parameterNames.get("disable"),
346            optionals, webSocketID,
347            "HeadlessExperimental.disable"
348        );
349        
350        // This Remote Command does not have a Return-Value.
351        return new Script<>
352            (BRDPC.defaultSender, webSocketID, requestJSON, BRDPC.NoReturnValues);
353    }
354    
355    /**
356     * Enables headless events for the target.
357     * 
358     * @return An instance of <CODE>{@link Script}&lt;String, {@link JsonObject},
359     * {@link Ret0}&gt;</CODE>
360     *
361     * <BR /><BR />This {@code Script} instance must be <B STYLE='color:red'>executed</B> before the
362     * browser receives the invocation-request.
363     *
364     * <BR /><BR />This Browser-Function <I>does not have</I> a return-value.  You may choose to
365     * <B STYLE='color: red'>await</B> the {@link Promise}{@code <JsonObject,} {@link Ret0}
366     * {@code >} to ensure the Browser Function has run to completion.
367     */
368    public static Script<String, JsonObject, Ret0> enable()
369    {
370        final int          webSocketID = 22002000 + counter++;
371        final boolean[]    optionals   = new boolean[0];
372        
373        // Convert Method Parameters into JSON.  Build the JSON Request-Object (as a String)
374        String requestJSON = WriteJSON.get(
375            parameterTypes.get("enable"),
376            parameterNames.get("enable"),
377            optionals, webSocketID,
378            "HeadlessExperimental.enable"
379        );
380        
381        // This Remote Command does not have a Return-Value.
382        return new Script<>
383            (BRDPC.defaultSender, webSocketID, requestJSON, BRDPC.NoReturnValues);
384    }
385    
386}