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}<String, {@link JsonObject}, 225 * {@link Ret2}></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}<String, {@link JsonObject}, 279 * {@link Ret0}></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}<String, {@link JsonObject}, 311 * {@link Ret0}></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}