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