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            THROWS.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 (this == other)                       return true;
158            if (other == null)                       return false;
159            if (other.getClass() != this.getClass()) return false;
160        
161            ScreenshotParams o = (ScreenshotParams) other;
162        
163            return
164                    Objects.equals(this.format, o.format)
165                &&  Objects.equals(this.quality, o.quality);
166        }
167        
168        /** Generates a Hash-Code for {@code 'this'} instance */
169        public int hashCode()
170        {
171            return
172                    Objects.hashCode(this.format)
173                +   Objects.hashCode(this.quality);
174        }
175    }
176    
177    /**
178     * Issued when the target starts or stops needing BeginFrames.
179     * Deprecated. Issue beginFrame unconditionally instead and use result from
180     * beginFrame to detect whether the frames were suppressed.
181     * <BR />
182     * <BR /><B>DEPRECATED</B>
183     */
184    public static class needsBeginFramesChanged
185        extends BrowserEvent
186        implements java.io.Serializable
187    {
188        /** For Object Serialization.  java.io.Serializable */
189        protected static final long serialVersionUID = 1;
190        
191        public boolean[] optionals()
192        { return new boolean[] { false, }; }
193        
194        /** True if BeginFrames are needed, false otherwise. */
195        public final boolean needsBeginFrames;
196        
197        /**
198         * Constructor
199         *
200         * @param needsBeginFrames True if BeginFrames are needed, false otherwise.
201         */
202        public needsBeginFramesChanged(boolean needsBeginFrames)
203        {
204            super("HeadlessExperimental", "needsBeginFramesChanged", 1);
205            
206            this.needsBeginFrames  = needsBeginFrames;
207        }
208        
209        /**
210         * JSON Object Constructor
211         * @param jo A Json-Object having data about an instance of {@code 'needsBeginFramesChanged'}.
212         */
213        public needsBeginFramesChanged (JsonObject jo)
214        {
215            super("HeadlessExperimental", "needsBeginFramesChanged", 1);
216        
217            this.needsBeginFrames  = ReadPrimJSON.getBoolean(jo, "needsBeginFrames");
218        }
219        
220        
221        /** Checks whether {@code 'this'} equals an input Java-{@code Object} */
222        public boolean equals(Object other)
223        {
224            if (this == other)                       return true;
225            if (other == null)                       return false;
226            if (other.getClass() != this.getClass()) return false;
227        
228            needsBeginFramesChanged o = (needsBeginFramesChanged) other;
229        
230            return
231                    (this.needsBeginFrames == o.needsBeginFrames);
232        }
233        
234        /** Generates a Hash-Code for {@code 'this'} instance */
235        public int hashCode()
236        {
237            return
238                    (this.needsBeginFrames ? 1 : 0);
239        }
240    }
241    
242    
243    // Counter for keeping the WebSocket Request ID's distinct.
244    private static int counter = 1;
245    
246    /**
247     * Sends a BeginFrame to the target and returns when the frame was completed. Optionally captures a
248     * screenshot from the resulting frame. Requires that the target was created with enabled
249     * BeginFrameControl. Designed for use with --run-all-compositor-stages-before-draw, see also
250     * https://goo.gl/3zHXhB for more background.
251     * 
252     * @param frameTimeTicks 
253     * Timestamp of this BeginFrame in Renderer TimeTicks (milliseconds of uptime). If not set,
254     * the current time will be used.
255     * <BR /><B>OPTIONAL</B>
256     * 
257     * @param interval 
258     * The interval between BeginFrames that is reported to the compositor, in milliseconds.
259     * Defaults to a 60 frames/second interval, i.e. about 16.666 milliseconds.
260     * <BR /><B>OPTIONAL</B>
261     * 
262     * @param noDisplayUpdates 
263     * Whether updates should not be committed and drawn onto the display. False by default. If
264     * true, only side effects of the BeginFrame will be run, such as layout and animations, but
265     * any visual updates may not be visible on the display or in screenshots.
266     * <BR /><B>OPTIONAL</B>
267     * 
268     * @param screenshot 
269     * If set, a screenshot of the frame will be captured and returned in the response. Otherwise,
270     * no screenshot will be captured. Note that capturing a screenshot can fail, for example,
271     * during renderer initialization. In such a case, no screenshot data will be returned.
272     * <BR /><B>OPTIONAL</B>
273     * 
274     * @return An instance of <CODE>{@link Script}&lt;String, {@link JsonObject},
275     * {@link Ret2}&gt;</CODE>
276     *
277     * <BR /><BR />This {@link Script} may be <B STYLE='color:red'>executed</B> (using 
278     * {@link Script#exec()}), and a {@link Promise} returned.
279     *
280     * <BR /><BR />When the {@code Promise} is <B STYLE='color: red'>awaited</B>
281     * (using {@link Promise#await()}), the {@code Ret2} will subsequently
282     * be returned from that call.
283     * 
284     * <BR /><BR />The <B STYLE='color: red'>returned</B> values are encapsulated
285     * in an instance of <B>{@link Ret2}</B>
286     *
287     * <BR /><BR /><UL CLASS=JDUL>
288     * <LI><CODE><B>Ret2.a:</B> Boolean (<B>hasDamage</B>)</CODE>
289     *     <BR />Whether the BeginFrame resulted in damage and, thus, a new frame was committed to the
290     *     display. Reported for diagnostic uses, may be removed in the future.
291     *     <BR /><BR /></LI>
292     * <LI><CODE><B>Ret2.b:</B> String (<B>screenshotData</B>)</CODE>
293     *     <BR />Base64-encoded image data of the screenshot, if one was requested and successfully taken. (Encoded as a base64 string when passed over JSON)
294     *     </LI>
295     * </UL>
296     */
297    public static Script<String, JsonObject, Ret2<Boolean, String>> beginFrame(
298            Number frameTimeTicks, Number interval, Boolean noDisplayUpdates, 
299            HeadlessExperimental.ScreenshotParams screenshot
300        )
301    {
302        final int       webSocketID = 22000000 + counter++;
303        final boolean[] optionals   = { true, true, true, true, };
304        
305        // Convert Method Parameters into JSON.  Build the JSON Request-Object (as a String)
306        String requestJSON = WriteJSON.get(
307            parameterTypes.get("beginFrame"),
308            parameterNames.get("beginFrame"),
309            optionals, webSocketID,
310            "HeadlessExperimental.beginFrame",
311            frameTimeTicks, interval, noDisplayUpdates, screenshot
312        );
313        
314        // 'JSON Binding' ... Converts Browser Response-JSON into Java-Type 'Ret2'
315        Function<JsonObject, Ret2<Boolean, String>> 
316            responseProcessor = (JsonObject jo) -> new Ret2<>(
317                ReadBoxedJSON.getBoolean(jo, "hasDamage", true),
318                ReadJSON.getString(jo, "screenshotData", true, false)
319            );
320        
321        return new Script<>(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            (webSocketID, requestJSON, VOID_RETURN.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            (webSocketID, requestJSON, VOID_RETURN.NoReturnValues);
384    }
385    
386}