001package Torello.Browser; 002 003import Torello.Java.Additional.AppendableSafe; 004import Torello.Java.UnreachableError; 005 006import java.io.IOException; 007import java.util.List; 008import java.util.Vector; 009import java.util.function.Consumer; 010 011 012/** 013 * Central record, log collector, and user-callback router for a CDP browser session. 014 * 015 * <BR /><BR /> 016 * A {@code ConnRecord} is the single place where the Browser Tool stores the visible products of a 017 * Chrome DevTools Protocol connection. A CDP session emits both ordinary text and strongly typed 018 * Java objects. Some of that information comes from the low-level WebSocket driver, some comes 019 * from the Browser Tool's command/response dispatcher, and some comes from decoded browser events. 020 * Without a central record, the output quickly becomes scattered across the connection classes, 021 * handler classes, promises, and user examples. 022 * 023 * <BR /><BR /> 024 * <EMBED CLASS='external-html' DATA-FILE-ID=ConnRecord> 025 */ 026public class ConnRecord 027{ 028 /** 029 * Creates an empty connection record. 030 * 031 * <BR /><BR /> 032 * All object-saving and text-saving switches are enabled by default. The public convenience 033 * references for browser/page connections and browser/page WebSocket senders are initialized 034 * to {@code null}, and may be filled in by example code or connection-building helpers. 035 */ 036 public ConnRecord() { } 037 038 039 // ******************************************************************************************** 040 // ******************************************************************************************** 041 // User Convenience Reference Holders. 042 // ******************************************************************************************** 043 // ******************************************************************************************** 044 045 046 /** 047 * Optional reference to the browser-level connection descriptor used by this session. 048 * 049 * <BR /><BR /><DIV CLASS=JDHint> 050 * This field is provided strictly as a convenience holder for the example classes, and for 051 * user code. Neither the methods in this class, nor others in this package read or modify it. 052 * 053 * <BR /><BR /> 054 * 🧠This field will only be set by the programmer, and only for convenience, storage purposes 055 * </DIV> 056 */ 057 public BrowserConn browserConn = null; 058 059 /** 060 * Optional reference to the page-level connection descriptor used by this session. 061 * 062 * <BR /><BR /><DIV CLASS=JDHint> 063 * This field is provided strictly as a convenience holder for the example classes, and for 064 * user code. Neither the methods in this class, nor others in this package read or modify it. 065 * 066 * <BR /><BR /> 067 * 🧠This field will only be set by the programmer, and only for convenience, storage purposes 068 * </DIV> 069 */ 070 public PageConn pageConn = null; 071 072 /** 073 * Optional reference to the browser-level {@link WebSocketSender} used by this session. 074 * 075 * <BR /><BR /><DIV CLASS=JDHint> 076 * Example code often needs to keep both the browser socket and the page socket close at hand. 077 * This field provides a conventional place to store the browser socket. 078 * 079 * <BR /><BR /> 080 * 🧠This field will only be set by the programmer, and only for convenience, storage purposes 081 * </DIV> 082 */ 083 public WebSocketSender bws = null; 084 085 /** 086 * Optional reference to the page-level {@link WebSocketSender} used by this session. 087 * 088 * <BR /><BR /><DIV CLASS=JDHint> 089 * Example code often needs to keep both the browser socket and the page socket close at hand. 090 * This field provides a conventional place to store the page socket. 091 * 092 * <BR /><BR /> 093 * 🧠This field will only be set by the programmer, and only for convenience, storage purposes 094 * </DIV> 095 */ 096 public WebSocketSender pws = null; 097 098 099 // ******************************************************************************************** 100 // ******************************************************************************************** 101 // Events, Errors 102 // ******************************************************************************************** 103 // ******************************************************************************************** 104 105 106 /** 107 * When {@code true}, decoded {@link BrowserEvent} objects are saved to {@link #events}. 108 * 109 * <BR /><BR /> 110 * Disabling this flag prevents the internal event list from growing, but it does not disable 111 * the user-provided event handler installed by {@link #setEventHandler(Consumer)}. 112 * 113 * @see BrowserEvent 114 * @see #acceptEvent(BrowserEvent) 115 * @see #events 116 */ 117 public boolean saveEvents = true; 118 119 /** 120 * When {@code true}, browser-returned command errors are saved to {@link #browserErrors}. 121 * 122 * <BR /><BR /> 123 * A {@link BrowserError} represents an error reported by Chrome itself in response to a command 124 * that was sent through the CDP WebSocket connection. 125 * 126 * @see BrowserError 127 * @see #acceptBrowserError(BrowserError) 128 * @see #browserErrors 129 */ 130 public boolean saveBrowserErrors = true; 131 132 /** 133 * When {@code true}, internal Browser Tool / RDP errors are saved to {@link #rdpErrors}. 134 * 135 * <BR /><BR /> 136 * An {@link RDPError} generally means that the Browser Tool could not parse, classify, load, 137 * decode, or otherwise handle a message that arrived from the Remote Debugging Protocol layer. 138 * 139 * @see RDPError 140 * @see #acceptRDPError(RDPError) 141 * @see #rdpErrors 142 */ 143 public boolean saveRDPErrors = true; 144 145 // Receives decoded browser events after they have been accepted by this record. 146 private Consumer<? super BrowserEvent<?>> userProvidedEventHandler = null; 147 148 // Receives browser-returned command errors after they have been accepted by this record. 149 private Consumer<? super BrowserError> userProvidedBrowserErrorHandler = null; 150 151 // Receives internal Browser Tool / RDP errors after they have been accepted by this record. 152 private Consumer<? super RDPError> userProvidedRDPErrorHandler = null; 153 154 /** 155 * Installs a callback that receives each decoded browser event. 156 * 157 * <BR /><BR /> 158 * Passing {@code null} clears the current callback. The callback is invoked even when 159 * {@link #saveEvents} is {@code false}; the save flag controls only whether the event is stored 160 * in {@link #events}. 161 * 162 * <BR /><BR /><DIV CLASS=JDHint> 163 * If this handler is non-null, this is invoked <B STYLE='color:red;'>in addition to</B> the 164 * handler used to store and save {@code BrowserEvent} objects in a storage list. This method 165 * assigns the programmer's handler to the internal {@code 'userProvidedEventHandler'} field, 166 * which is a <B STYLE='color:red;'>second</B> internal handler for events! 167 * </DIV> 168 * 169 * @param handler A callback for decoded browser events, or {@code null} to clear it. 170 */ 171 public synchronized void setEventHandler(final Consumer<? super BrowserEvent<?>> handler) 172 { this.userProvidedEventHandler = handler; } 173 174 /** 175 * Installs a callback that receives browser-returned command errors. 176 * 177 * <BR /><BR /> 178 * Passing {@code null} clears the current callback. The callback is invoked even when 179 * {@link #saveBrowserErrors} is {@code false}; the save flag controls only whether the error is 180 * stored in {@link #browserErrors}. 181 * 182 * <BR /><BR /><DIV CLASS=JDHint> 183 * If this handler is non-null, this is invoked <B STYLE='color:red;'>in addition to</B> the 184 * handler used to store and save {@code BrowserError} objects in a storage list. This assigns 185 * the programmer's handler to the internal {@code 'userProvidedBrowserErrorHandler'} field, 186 * which is a <B STYLE='color:red;'>second</B> internal handler for browser errors! 187 * </DIV> 188 * 189 * @param handler A callback for browser-returned command errors, or {@code null} to clear it. 190 */ 191 public synchronized void setBrowserErrorHandler(final Consumer<? super BrowserError> handler) 192 { this.userProvidedBrowserErrorHandler = handler; } 193 194 /** 195 * Installs a callback that receives internal Browser Tool / RDP errors. 196 * 197 * <BR /><BR /> 198 * Passing {@code null} clears the current callback. The callback is invoked even when 199 * {@link #saveRDPErrors} is {@code false}; the save flag controls only whether the error is 200 * stored in {@link #rdpErrors}. 201 * 202 * <BR /><BR /><DIV CLASS=JDHint> 203 * If this handler is non-null, this is invoked <B STYLE='color:red;'>in addition to</B> the 204 * handler used to store and save {@code RDPError} objects in a storage list. This assigns the 205 * programmer's handler to the internal {@code 'userProvidedRDPErrorHandler'} field, which is a 206 * <B STYLE='color:red;'>second</B> internal handler for transport layer errors. 207 * </DIV> 208 * 209 * @param handler A callback for internal RDP errors, or {@code null} to clear it. 210 */ 211 public synchronized void setRDPErrorHandler(final Consumer<? super RDPError> handler) 212 { this.userProvidedRDPErrorHandler = handler; } 213 214 /** 215 * List of decoded browser events received during this session. 216 * 217 * <BR /><BR /><DIV CLASS=JDHint> 218 * 📌 Events are added by the package-private event-handling pipeline after a raw CDP event 219 * message has been converted into a {@link BrowserEvent} instance. 220 * </DIV> 221 * 222 * @see BrowserEvent 223 * @see #saveEvents 224 */ 225 public final List<BrowserEvent<?>> events = new Vector<>(); 226 227 /** 228 * List of browser-returned command errors received during this session. 229 * 230 * <BR /><BR /><DIV CLASS=JDHint> 231 * 📌 These errors are emitted by Chrome in response to a specific command request. They are 232 * distinct from {@link #rdpErrors}, which are produced by the Java-side Browser Tool when it 233 * cannot correctly handle protocol traffic. 234 * </DIV> 235 * 236 * @see BrowserError 237 * @see #saveBrowserErrors 238 */ 239 public final List<BrowserError> browserErrors = new Vector<>(); 240 241 /** 242 * List of internal Browser Tool / Remote Debugging Protocol errors received during this 243 * session. 244 * 245 * <BR /><BR /><DIV CLASS=JDHint> 246 * 📌 These errors usually indicate malformed, unexpected, unrecognized, or unhandled protocol 247 * traffic, or an exception thrown while the Java-side dispatch logic is decoding browser data. 248 * </DIV> 249 * 250 * @see RDPError 251 * @see #saveRDPErrors 252 */ 253 public final List<RDPError> rdpErrors = new Vector<>(); 254 255 /** 256 * Accepts a decoded browser event and routes it to storage and/or the user callback. 257 * 258 * <BR /><BR /> 259 * This method is protected because it is intended for the Browser Tool's internal handler 260 * classes, not for direct end-user invocation. 261 * 262 * @param be The decoded browser event that has been received from Chrome. 263 */ 264 protected synchronized void acceptEvent(BrowserEvent<?> be) 265 { 266 if (this.saveEvents) 267 this.events.add(be); 268 269 if (this.userProvidedEventHandler != null) 270 this.userProvidedEventHandler.accept(be); 271 } 272 273 /** 274 * Accepts a browser-returned command error and routes it to storage and/or the user callback. 275 * 276 * <BR /><BR /> 277 * This method is protected because it is intended for the Browser Tool's internal command 278 * response handler, not for direct end-user invocation. 279 * 280 * @param bErr The browser-returned command error that has been received from Chrome. 281 */ 282 protected synchronized void acceptBrowserError(BrowserError bErr) 283 { 284 if (this.saveBrowserErrors) 285 this.browserErrors.add(bErr); 286 287 if (this.userProvidedBrowserErrorHandler != null) 288 this.userProvidedBrowserErrorHandler.accept(bErr); 289 } 290 291 /** 292 * Accepts an internal Browser Tool / RDP error and routes it to storage and/or the user 293 * callback. 294 * 295 * <BR /><BR /> 296 * This method is protected because it is intended for the Browser Tool's internal 297 * WebSocket and dispatch handlers, not for direct end-user invocation. 298 * 299 * @param rdpErr The internal RDP error that has been produced by the Browser Tool. 300 */ 301 protected synchronized void acceptRDPError(RDPError rdpErr) 302 { 303 if (this.saveRDPErrors) 304 this.rdpErrors.add(rdpErr); 305 306 if (this.userProvidedRDPErrorHandler != null) 307 this.userProvidedRDPErrorHandler.accept(rdpErr); 308 } 309 310 311 // ******************************************************************************************** 312 // ******************************************************************************************** 313 // Output Log Text Fields 314 // ******************************************************************************************** 315 // ******************************************************************************************** 316 317 318 /** 319 * Saved raw WebSocket / NeoVisionaries transport text. 320 * 321 * <BR /><BR /> 322 * This stream receives low-level socket-frame, thread, ping, pong, close, disconnect, and other 323 * driver-layer messages. It is the closest stream to the WebSocket transport itself. 324 * 325 * @see #saveRawText 326 */ 327 public final StringBuffer rawSB = new StringBuffer(); 328 329 /** 330 * Saved application-level CDP interpretation text. 331 * 332 * <BR /><BR /> 333 * This stream receives text describing outgoing JSON requests, incoming JSON datagrams, 334 * command-response handling, browser-event decoding, and promise cancellation. 335 * 336 * @see #saveAppText 337 */ 338 public final StringBuffer appSB = new StringBuffer(); 339 340 /** 341 * Saved error and diagnostic text. 342 * 343 * <BR /><BR /> 344 * This stream receives text describing browser-command errors, unrecognized protocol messages, 345 * WebSocket exceptions, malformed responses, and other failures detected while handling CDP 346 * traffic. 347 * 348 * @see #saveErrText 349 */ 350 public final StringBuffer errSB = new StringBuffer(); 351 352 /** 353 * When {@code true}, text written through {@link #raw(String)} is saved to {@link #rawSB}. 354 * 355 * <BR /><BR /> 356 * Disabling this flag prevents the raw-text buffer from growing, but does not disable the 357 * user-provided raw-text receiver installed by {@link #setRawTextReceiver(Appendable)}. 358 * 359 * @see #rawSB 360 */ 361 public boolean saveRawText = true; 362 363 /** 364 * When {@code true}, text written through {@link #app(String)} is saved to {@link #appSB}. 365 * 366 * <BR /><BR /> 367 * Disabling this flag prevents the application-text buffer from growing, but does not disable 368 * the user-provided application-text receiver installed by 369 * {@link #setAppTextReceiver(Appendable)}. 370 * 371 * @see #appSB 372 */ 373 public boolean saveAppText = true; 374 375 /** 376 * When {@code true}, text written through {@link #err(String)} is saved to {@link #errSB}. 377 * 378 * <BR /><BR /> 379 * Disabling this flag prevents the error-text buffer from growing, but does not disable the 380 * user-provided error-text receiver installed by {@link #setErrTextReceiver(Appendable)}. 381 * 382 * @see #errSB 383 */ 384 public boolean saveErrText = true; 385 386 /** Receives raw WebSocket text after it has been accepted by this record. */ 387 private AppendableSafe userProvidedRaw = null; 388 389 /** Receives application-level CDP text after it has been accepted by this record. */ 390 private AppendableSafe userProvidedApp = null; 391 392 /** Receives error and diagnostic text after it has been accepted by this record. */ 393 private AppendableSafe userProvidedErr = null; 394 395 /** 396 * Installs an {@link Appendable} that receives raw WebSocket / transport text. 397 * 398 * <BR /><BR /> 399 * Typical values are {@code System.out}, a {@code StringBuilder}, a {@code Writer}, or an 400 * {@link AppendableSafe}. Passing {@code null} clears the current receiver. 401 * 402 * @param a The destination for raw WebSocket text, or {@code null} to clear it. 403 * 404 * @see AppendableSafe 405 * @see #checkIsInstance(Appendable) 406 */ 407 public synchronized void setRawTextReceiver(final Appendable a) 408 { this.userProvidedRaw = checkIsInstance(a); } 409 410 /** 411 * Installs an {@link Appendable} that receives application-level CDP interpretation text. 412 * 413 * <BR /><BR /> 414 * Typical values are {@code System.out}, a {@code StringBuilder}, a {@code Writer}, or an 415 * {@link AppendableSafe}. Passing {@code null} clears the current receiver. 416 * 417 * @param a The destination for application-level CDP text, or {@code null} to clear it. 418 * 419 * @see AppendableSafe 420 * @see #checkIsInstance(Appendable) 421 */ 422 public synchronized void setAppTextReceiver(final Appendable a) 423 { this.userProvidedApp = checkIsInstance(a); } 424 425 /** 426 * Installs an {@link Appendable} that receives error and diagnostic text. 427 * 428 * <BR /><BR /> 429 * Typical values are {@code System.out}, a {@code StringBuilder}, a {@code Writer}, or an 430 * {@link AppendableSafe}. Passing {@code null} clears the current receiver. 431 * 432 * @param a The destination for error and diagnostic text, or {@code null} to clear it. 433 * 434 * @see AppendableSafe 435 * @see #checkIsInstance(Appendable) 436 */ 437 public synchronized void setErrTextReceiver(final Appendable a) 438 { this.userProvidedErr = checkIsInstance(a); } 439 440 /** 441 * Accepts raw WebSocket / transport text and routes it to storage and/or the raw receiver. 442 * 443 * <BR /><BR /> 444 * This method is protected because raw transport text is produced by the internal 445 * WebSocket adapter, not by ordinary end-user code. 446 * 447 * @param text The raw WebSocket / transport text to record. 448 * 449 * @see #saveRawText 450 * @see #rawSB 451 */ 452 protected synchronized void raw(final String text) 453 { 454 if (this.saveRawText) this.rawSB.append(text); 455 if (this.userProvidedRaw != null) this.userProvidedRaw.append(text); 456 } 457 458 /** 459 * Accepts application-level CDP text and routes it to storage and/or the application receiver. 460 * 461 * <BR /><BR /> 462 * This method is protected because application-level CDP text is produced by the internal 463 * sender, dispatcher, event handler, and command-response handler. 464 * 465 * @param text The application-level CDP text to record. 466 * 467 * @see #saveAppText 468 * @see #appSB 469 */ 470 protected synchronized void app(final String text) 471 { 472 if (this.saveAppText) this.appSB.append(text); 473 if (this.userProvidedApp != null) this.userProvidedApp.append(text); 474 } 475 476 /** 477 * Accepts error and diagnostic text and routes it to storage and/or the error receiver. 478 * 479 * <BR /><BR /> 480 * This method is protected because error text is produced by the internal WebSocket, 481 * dispatch, event, and command-response handlers. 482 * 483 * @param text The error or diagnostic text to record. 484 * 485 * @see #saveErrText 486 * @see #errSB 487 */ 488 protected synchronized void err(final String text) 489 { 490 if (this.saveErrText) this.errSB.append(text); 491 if (this.userProvidedErr != null) this.userProvidedErr.append(text); 492 } 493 494 /** 495 * Ensures that a user-provided {@link Appendable} is safe for unchecked logging calls. 496 * 497 * <BR /><BR /> 498 * If the appendable is already an {@link AppendableSafe}, it is returned directly. Otherwise, 499 * it is wrapped so that {@link IOException} may be handled by 500 * {@link #handleLogIOE(IOException)}. 501 * A {@code null} value is preserved as {@code null}. 502 * 503 * @param a The appendable to check or wrap. 504 * @return An {@link AppendableSafe} wrapper, the original wrapper, or {@code null}. 505 */ 506 protected static AppendableSafe checkIsInstance(final Appendable a) 507 { 508 return (a == null) 509 ? null 510 : (a instanceof AppendableSafe) 511 ? (AppendableSafe) a 512 : new AppendableSafe(a, ConnRecord::handleLogIOE); 513 } 514 515 /** 516 * Handles an {@link IOException} thrown while writing diagnostic text. 517 * 518 * <BR /><BR /> 519 * Logging failures are treated as unreachable Browser Tool failures. The Browser Tool expects 520 * its own diagnostic stream plumbing to be reliable once it has been installed. 521 * 522 * @param ioe The exception thrown by the underlying appendable. 523 */ 524 private static void handleLogIOE(final IOException ioe) 525 { 526 System.err.println( 527 "There has been an exception throw while attempting to write to an output log.\n" + 528 "Cannot Proceed" 529 ); 530 531 throw new UnreachableError(ioe); 532 } 533 534}