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}<String, {@link JsonObject}, 273 * {@link Ret2}></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}<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 (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}<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 (BRDPC.defaultSender, webSocketID, requestJSON, BRDPC.NoReturnValues); 384 } 385 386}