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}<String, {@link JsonObject}, 275 * {@link Ret2}></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}<String, {@link JsonObject}, 328 * {@link Ret0}></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}<String, {@link JsonObject}, 359 * {@link Ret0}></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}