rebrowser-playwright-core 1.47.100 → 1.49.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (156) hide show
  1. package/README.md +1 -1
  2. package/ThirdPartyNotices.txt +70 -384
  3. package/bin/reinstall_chrome_beta_linux.sh +10 -8
  4. package/bin/reinstall_chrome_stable_linux.sh +10 -8
  5. package/bin/reinstall_msedge_beta_linux.sh +11 -9
  6. package/bin/reinstall_msedge_dev_linux.sh +11 -9
  7. package/bin/reinstall_msedge_stable_linux.sh +11 -9
  8. package/browsers.json +24 -12
  9. package/lib/cli/program.js +49 -19
  10. package/lib/client/api.js +6 -0
  11. package/lib/client/browserContext.js +20 -2
  12. package/lib/client/channelOwner.js +6 -2
  13. package/lib/client/connection.js +3 -0
  14. package/lib/client/fetch.js +16 -21
  15. package/lib/client/jsHandle.js +0 -8
  16. package/lib/client/localUtils.js +1 -0
  17. package/lib/client/locator.js +7 -0
  18. package/lib/client/network.js +180 -17
  19. package/lib/client/page.js +25 -2
  20. package/lib/client/playwright.js +6 -3
  21. package/lib/client/tracing.js +29 -20
  22. package/lib/client/waiter.js +15 -11
  23. package/lib/generated/consoleApiSource.js +1 -1
  24. package/lib/generated/injectedScriptSource.js +1 -1
  25. package/lib/generated/pollingRecorderSource.js +7 -0
  26. package/lib/generated/webSocketMockSource.js +7 -0
  27. package/lib/protocol/validator.js +103 -17
  28. package/lib/server/ariaSnapshot.js +33 -0
  29. package/lib/server/bidi/bidiBrowser.js +23 -8
  30. package/lib/server/bidi/bidiChromium.js +124 -0
  31. package/lib/server/bidi/bidiConnection.js +1 -3
  32. package/lib/server/bidi/bidiExecutionContext.js +0 -6
  33. package/lib/server/bidi/bidiFirefox.js +15 -21
  34. package/lib/server/bidi/bidiInput.js +16 -32
  35. package/lib/server/bidi/bidiNetworkManager.js +39 -5
  36. package/lib/server/bidi/bidiOverCdp.js +103 -0
  37. package/lib/server/bidi/bidiPage.js +98 -25
  38. package/lib/server/bidi/bidiPdf.js +140 -0
  39. package/lib/server/bidi/third_party/firefoxPrefs.js +221 -0
  40. package/lib/server/browser.js +13 -2
  41. package/lib/server/browserContext.js +6 -23
  42. package/lib/server/browserType.js +43 -12
  43. package/lib/server/chromium/chromium.js +9 -17
  44. package/lib/server/chromium/chromiumSwitches.js +3 -1
  45. package/lib/server/chromium/crBrowser.js +4 -3
  46. package/lib/server/chromium/crConnection.js +153 -0
  47. package/lib/server/chromium/crExecutionContext.js +0 -20
  48. package/lib/server/chromium/crPage.js +13 -7
  49. package/lib/server/chromium/videoRecorder.js +1 -1
  50. package/lib/server/codegen/csharp.js +12 -17
  51. package/lib/server/codegen/java.js +5 -8
  52. package/lib/server/codegen/javascript.js +29 -7
  53. package/lib/server/codegen/language.js +14 -2
  54. package/lib/server/codegen/python.js +13 -17
  55. package/lib/server/cookieStore.js +73 -0
  56. package/lib/server/debugController.js +10 -7
  57. package/lib/server/deviceDescriptorsSource.json +127 -127
  58. package/lib/server/dialog.js +1 -0
  59. package/lib/server/dispatchers/androidDispatcher.js +13 -2
  60. package/lib/server/dispatchers/browserContextDispatcher.js +19 -2
  61. package/lib/server/dispatchers/debugControllerDispatcher.js +1 -1
  62. package/lib/server/dispatchers/dispatcher.js +1 -1
  63. package/lib/server/dispatchers/frameDispatcher.js +8 -1
  64. package/lib/server/dispatchers/jsHandleDispatcher.js +0 -5
  65. package/lib/server/dispatchers/pageDispatcher.js +9 -0
  66. package/lib/server/dispatchers/playwrightDispatcher.js +2 -1
  67. package/lib/server/dispatchers/tracingDispatcher.js +10 -0
  68. package/lib/server/dispatchers/webSocketRouteDispatcher.js +189 -0
  69. package/lib/server/dom.js +79 -36
  70. package/lib/server/download.js +9 -2
  71. package/lib/server/fetch.js +105 -96
  72. package/lib/server/firefox/ffBrowser.js +6 -4
  73. package/lib/server/firefox/ffExecutionContext.js +0 -15
  74. package/lib/server/firefox/ffNetworkManager.js +1 -1
  75. package/lib/server/firefox/ffPage.js +6 -3
  76. package/lib/server/firefox/firefox.js +2 -13
  77. package/lib/server/frameSelectors.js +1 -1
  78. package/lib/server/frames.js +68 -67
  79. package/lib/server/har/harTracer.js +11 -0
  80. package/lib/server/index.js +0 -6
  81. package/lib/server/input.js +0 -1
  82. package/lib/server/javascript.js +0 -13
  83. package/lib/server/page.js +53 -9
  84. package/lib/server/playwright.js +5 -2
  85. package/lib/server/progress.js +0 -3
  86. package/lib/server/recorder/contextRecorder.js +33 -50
  87. package/lib/server/recorder/recorderApp.js +22 -25
  88. package/lib/server/recorder/recorderCollection.js +68 -79
  89. package/lib/server/recorder/recorderInTraceViewer.js +144 -0
  90. package/lib/server/recorder/recorderRunner.js +75 -97
  91. package/lib/server/recorder/recorderUtils.js +48 -6
  92. package/lib/server/recorder.js +87 -47
  93. package/lib/server/registry/index.js +154 -26
  94. package/lib/server/registry/nativeDeps.js +6 -3
  95. package/lib/server/socksClientCertificatesInterceptor.js +15 -3
  96. package/lib/server/trace/recorder/snapshotter.js +2 -12
  97. package/lib/server/trace/recorder/snapshotterInjected.js +21 -3
  98. package/lib/server/trace/recorder/tracing.js +127 -17
  99. package/lib/server/trace/test/inMemorySnapshotter.js +4 -4
  100. package/lib/server/trace/viewer/traceViewer.js +4 -6
  101. package/lib/server/webkit/webkit.js +1 -1
  102. package/lib/server/webkit/wkBrowser.js +8 -7
  103. package/lib/server/webkit/wkExecutionContext.js +0 -16
  104. package/lib/server/webkit/wkPage.js +11 -4
  105. package/lib/utils/comparators.js +12 -30
  106. package/lib/utils/crypto.js +1 -4
  107. package/lib/utils/happy-eyeballs.js +13 -0
  108. package/lib/utils/hostPlatform.js +8 -2
  109. package/lib/utils/httpServer.js +1 -0
  110. package/lib/utils/isomorphic/ariaSnapshot.js +267 -0
  111. package/lib/utils/isomorphic/locatorGenerators.js +30 -21
  112. package/lib/utils/isomorphic/locatorParser.js +2 -2
  113. package/lib/utils/isomorphic/recorderUtils.js +227 -0
  114. package/lib/utils/isomorphic/stringUtils.js +30 -0
  115. package/lib/utils/isomorphic/urlMatch.js +5 -1
  116. package/lib/utils/network.js +1 -1
  117. package/lib/utils/sequence.js +64 -0
  118. package/lib/utils/stackTrace.js +16 -3
  119. package/lib/utils/zones.js +32 -23
  120. package/lib/utilsBundle.js +4 -5
  121. package/lib/utilsBundleImpl/index.js +190 -33
  122. package/lib/vite/htmlReport/index.html +19 -16
  123. package/lib/vite/recorder/assets/codeMirrorModule-AFvV6hAs.js +24 -0
  124. package/lib/vite/recorder/assets/index-_cTWgVuJ.js +184 -0
  125. package/lib/vite/recorder/assets/{index-B-MT5gKo.css → index-iA1aAGZg.css} +1 -1
  126. package/lib/vite/recorder/index.html +2 -2
  127. package/lib/vite/traceViewer/assets/codeMirrorModule-KatbITLF.js +24 -0
  128. package/lib/vite/traceViewer/assets/inspectorTab-DdpLd2bb.js +68 -0
  129. package/lib/vite/traceViewer/assets/workbench-CdYbzWFD.js +9 -0
  130. package/lib/vite/traceViewer/embedded.6m3UZh7r.js +2 -0
  131. package/lib/vite/traceViewer/embedded.html +5 -3
  132. package/lib/vite/traceViewer/index.WUV-8boJ.js +2 -0
  133. package/lib/vite/traceViewer/index.html +24 -5
  134. package/lib/vite/traceViewer/inspectorTab.DEOUW62d.css +1 -0
  135. package/lib/vite/traceViewer/recorder.B_SY1GJM.css +0 -0
  136. package/lib/vite/traceViewer/recorder.OT2tVHgn.js +2 -0
  137. package/lib/vite/traceViewer/recorder.html +17 -0
  138. package/lib/vite/traceViewer/sw.bundle.js +3 -3
  139. package/lib/vite/traceViewer/uiMode.BZBTyvGn.js +5 -0
  140. package/lib/vite/traceViewer/uiMode.html +6 -4
  141. package/lib/vite/traceViewer/{uiMode.D3cNFP6u.css → uiMode.voC1ZiOQ.css} +1 -1
  142. package/lib/vite/traceViewer/workbench.C-zR9ysA.css +1 -0
  143. package/package.json +1 -1
  144. package/types/protocol.d.ts +135 -16
  145. package/types/types.d.ts +2149 -965
  146. package/lib/generated/recorderSource.js +0 -7
  147. package/lib/third_party/diff_match_patch.js +0 -2222
  148. package/lib/vite/recorder/assets/codeMirrorModule-aW6Vz3gf.js +0 -24
  149. package/lib/vite/recorder/assets/index-DCpXOVIh.js +0 -47
  150. package/lib/vite/traceViewer/assets/codeMirrorModule-eFKCUIu2.js +0 -24
  151. package/lib/vite/traceViewer/assets/workbench-D0cirtfA.js +0 -72
  152. package/lib/vite/traceViewer/embedded.BkuvsLJp.js +0 -2
  153. package/lib/vite/traceViewer/index.C968kAiC.js +0 -2
  154. package/lib/vite/traceViewer/uiMode.BkFnr4V0.js +0 -10
  155. package/lib/vite/traceViewer/workbench.DjbIuxix.css +0 -1
  156. /package/lib/server/recorder/{recorderActions.js → recorderFrontend.js} +0 -0
package/lib/server/dom.js CHANGED
@@ -225,17 +225,21 @@ class ElementHandle extends js.JSHandle {
225
225
  // Allow 1x1 elements. Compensate for rounding errors by comparing with 0.99 instead.
226
226
  const filtered = quads.map(quad => intersectQuadWithViewport(quad)).filter(quad => computeQuadArea(quad) > 0.99);
227
227
  if (!filtered.length) return 'error:notinviewport';
228
- // Return the middle point of the first quad.
229
- const result = {
230
- x: 0,
231
- y: 0
232
- };
233
- for (const point of filtered[0]) {
234
- result.x += point.x / 4;
235
- result.y += point.y / 4;
228
+ if (this._page._browserContext._browser.options.name === 'firefox') {
229
+ // Firefox internally uses integer coordinates, so 8.x is converted to 8 or 9 when clicking.
230
+ //
231
+ // This does not work nicely for small elements. For example, 1x1 square with corners
232
+ // (8;8) and (9;9) is targeted when clicking at (8;8) but not when clicking at (9;9).
233
+ // So, clicking at (8.x;8.y) will sometimes click at (9;9) and miss the target.
234
+ //
235
+ // Therefore, we try to find an integer point within a quad to make sure we click inside the element.
236
+ for (const quad of filtered) {
237
+ const integerPoint = findIntegerPointInsideQuad(quad);
238
+ if (integerPoint) return integerPoint;
239
+ }
236
240
  }
237
- compensateHalfIntegerRoundingError(result);
238
- return result;
241
+ // Return the middle point of the first quad.
242
+ return quadMiddlePoint(filtered[0]);
239
243
  }
240
244
  async _offsetPoint(offset) {
241
245
  const [box, border] = await Promise.all([this.boundingBox(), this.evaluateInUtility(([injected, node]) => injected.getElementBorderWidth(node), {}).catch(e => {})]);
@@ -253,7 +257,7 @@ class ElementHandle extends js.JSHandle {
253
257
  const waitTime = [0, 20, 100, 100, 500];
254
258
  while (progress.isRunning()) {
255
259
  if (retry) {
256
- progress.log(`retrying ${actionName} action${options.trial ? ' (trial run)' : ''}, attempt #${retry}`);
260
+ progress.log(`retrying ${actionName} action${options.trial ? ' (trial run)' : ''}`);
257
261
  const timeout = waitTime[Math.min(retry - 1, waitTime.length - 1)];
258
262
  if (timeout) {
259
263
  progress.log(` waiting ${timeout}ms`);
@@ -263,7 +267,7 @@ class ElementHandle extends js.JSHandle {
263
267
  } else {
264
268
  progress.log(`attempting ${actionName} action${options.trial ? ' (trial run)' : ''}`);
265
269
  }
266
- if (!options.skipLocatorHandlersCheckpoint && !options.force) await this._frame._page.performLocatorHandlersCheckpoint(progress);
270
+ if (!options.skipActionPreChecks && !options.force) await this._frame._page.performActionPreChecks(progress);
267
271
  const result = await action(retry);
268
272
  ++retry;
269
273
  if (result === 'error:notvisible') {
@@ -294,7 +298,7 @@ class ElementHandle extends js.JSHandle {
294
298
  }
295
299
  async _retryPointerAction(progress, actionName, waitForEnabled, action, options) {
296
300
  // Note: do not perform locator handlers checkpoint to avoid moving the mouse in the middle of a drag operation.
297
- const skipLocatorHandlersCheckpoint = actionName === 'move and up';
301
+ const skipActionPreChecks = actionName === 'move and up';
298
302
  return await this._retryAction(progress, actionName, async retry => {
299
303
  // By default, we scroll with protocol method to reveal the action point.
300
304
  // However, that might not work to scroll from under position:sticky elements
@@ -314,7 +318,7 @@ class ElementHandle extends js.JSHandle {
314
318
  return await this._performPointerAction(progress, actionName, waitForEnabled, action, forceScrollOptions, options);
315
319
  }, {
316
320
  ...options,
317
- skipLocatorHandlersCheckpoint
321
+ skipActionPreChecks
318
322
  });
319
323
  }
320
324
  async _performPointerAction(progress, actionName, waitForEnabled, action, forceScrollOptions, options) {
@@ -367,7 +371,7 @@ class ElementHandle extends js.JSHandle {
367
371
  if (typeof maybePoint === 'string') return maybePoint;
368
372
  const point = roundPoint(maybePoint);
369
373
  progress.metadata.point = point;
370
- await progress.beforeInputAction(this);
374
+ await this.instrumentation.onBeforeInputAction(this, progress.metadata);
371
375
  let hitTargetInterceptionHandle;
372
376
  if (force) {
373
377
  progress.log(` forcing action`);
@@ -433,9 +437,16 @@ class ElementHandle extends js.JSHandle {
433
437
  progress.log(' navigations have finished');
434
438
  return 'done';
435
439
  }
440
+ async _markAsTargetElement(metadata) {
441
+ if (!metadata.id) return;
442
+ await this.evaluateInUtility(([injected, node, callId]) => {
443
+ if (node.nodeType === 1 /* Node.ELEMENT_NODE */) injected.markTargetElements(new Set([node]), callId);
444
+ }, metadata.id);
445
+ }
436
446
  async hover(metadata, options) {
437
447
  const controller = new _progress.ProgressController(metadata, this);
438
448
  return controller.run(async progress => {
449
+ await this._markAsTargetElement(metadata);
439
450
  const result = await this._hover(progress, options);
440
451
  return assertDone(throwRetargetableDOMError(result));
441
452
  }, this._page._timeoutSettings.timeout(options));
@@ -449,6 +460,7 @@ class ElementHandle extends js.JSHandle {
449
460
  async click(metadata, options = {}) {
450
461
  const controller = new _progress.ProgressController(metadata, this);
451
462
  return controller.run(async progress => {
463
+ await this._markAsTargetElement(metadata);
452
464
  const result = await this._click(progress, {
453
465
  ...options,
454
466
  waitAfter: !options.noWaitAfter
@@ -462,6 +474,7 @@ class ElementHandle extends js.JSHandle {
462
474
  async dblclick(metadata, options) {
463
475
  const controller = new _progress.ProgressController(metadata, this);
464
476
  return controller.run(async progress => {
477
+ await this._markAsTargetElement(metadata);
465
478
  const result = await this._dblclick(progress, options);
466
479
  return assertDone(throwRetargetableDOMError(result));
467
480
  }, this._page._timeoutSettings.timeout(options));
@@ -475,6 +488,7 @@ class ElementHandle extends js.JSHandle {
475
488
  async tap(metadata, options = {}) {
476
489
  const controller = new _progress.ProgressController(metadata, this);
477
490
  return controller.run(async progress => {
491
+ await this._markAsTargetElement(metadata);
478
492
  const result = await this._tap(progress, options);
479
493
  return assertDone(throwRetargetableDOMError(result));
480
494
  }, this._page._timeoutSettings.timeout(options));
@@ -488,6 +502,7 @@ class ElementHandle extends js.JSHandle {
488
502
  async selectOption(metadata, elements, values, options) {
489
503
  const controller = new _progress.ProgressController(metadata, this);
490
504
  return controller.run(async progress => {
505
+ await this._markAsTargetElement(metadata);
491
506
  const result = await this._selectOption(progress, elements, values, options);
492
507
  return throwRetargetableDOMError(result);
493
508
  }, this._page._timeoutSettings.timeout(options));
@@ -495,7 +510,7 @@ class ElementHandle extends js.JSHandle {
495
510
  async _selectOption(progress, elements, values, options) {
496
511
  let resultingOptions = [];
497
512
  await this._retryAction(progress, 'select option', async () => {
498
- await progress.beforeInputAction(this);
513
+ await this.instrumentation.onBeforeInputAction(this, progress.metadata);
499
514
  if (!options.force) progress.log(` waiting for element to be visible and enabled`);
500
515
  const optionsToSelect = [...elements, ...values];
501
516
  const result = await this.evaluateInUtility(async ([injected, node, {
@@ -523,6 +538,7 @@ class ElementHandle extends js.JSHandle {
523
538
  async fill(metadata, value, options = {}) {
524
539
  const controller = new _progress.ProgressController(metadata, this);
525
540
  return controller.run(async progress => {
541
+ await this._markAsTargetElement(metadata);
526
542
  const result = await this._fill(progress, value, options);
527
543
  assertDone(throwRetargetableDOMError(result));
528
544
  }, this._page._timeoutSettings.timeout(options));
@@ -530,7 +546,7 @@ class ElementHandle extends js.JSHandle {
530
546
  async _fill(progress, value, options) {
531
547
  progress.log(` fill("${value}")`);
532
548
  return await this._retryAction(progress, 'fill', async () => {
533
- await progress.beforeInputAction(this);
549
+ await this.instrumentation.onBeforeInputAction(this, progress.metadata);
534
550
  if (!options.force) progress.log(' waiting for element to be visible, enabled and editable');
535
551
  const result = await this.evaluateInUtility(async ([injected, node, {
536
552
  value,
@@ -578,6 +594,7 @@ class ElementHandle extends js.JSHandle {
578
594
  const inputFileItems = await (0, _fileUploadUtils.prepareFilesForUpload)(this._frame, params);
579
595
  const controller = new _progress.ProgressController(metadata, this);
580
596
  return controller.run(async progress => {
597
+ await this._markAsTargetElement(metadata);
581
598
  const result = await this._setInputFiles(progress, inputFileItems);
582
599
  return assertDone(throwRetargetableDOMError(result));
583
600
  }, this._page._timeoutSettings.timeout(params));
@@ -607,7 +624,7 @@ class ElementHandle extends js.JSHandle {
607
624
  });
608
625
  if (result === 'error:notconnected' || !result.asElement()) return 'error:notconnected';
609
626
  const retargeted = result.asElement();
610
- await progress.beforeInputAction(this);
627
+ await this.instrumentation.onBeforeInputAction(this, progress.metadata);
611
628
  progress.throwIfAborted(); // Avoid action that has side-effects.
612
629
  if (localPaths || localDirectory) {
613
630
  const localPathsOrDirectory = localDirectory ? [localDirectory] : localPaths;
@@ -628,6 +645,7 @@ class ElementHandle extends js.JSHandle {
628
645
  async focus(metadata) {
629
646
  const controller = new _progress.ProgressController(metadata, this);
630
647
  await controller.run(async progress => {
648
+ await this._markAsTargetElement(metadata);
631
649
  const result = await this._focus(progress);
632
650
  return assertDone(throwRetargetableDOMError(result));
633
651
  }, 0);
@@ -643,13 +661,14 @@ class ElementHandle extends js.JSHandle {
643
661
  async type(metadata, text, options) {
644
662
  const controller = new _progress.ProgressController(metadata, this);
645
663
  return controller.run(async progress => {
664
+ await this._markAsTargetElement(metadata);
646
665
  const result = await this._type(progress, text, options);
647
666
  return assertDone(throwRetargetableDOMError(result));
648
667
  }, this._page._timeoutSettings.timeout(options));
649
668
  }
650
669
  async _type(progress, text, options) {
651
670
  progress.log(`elementHandle.type("${text}")`);
652
- await progress.beforeInputAction(this);
671
+ await this.instrumentation.onBeforeInputAction(this, progress.metadata);
653
672
  const result = await this._focus(progress, true /* resetSelectionIfNotFocused */);
654
673
  if (result !== 'done') return result;
655
674
  progress.throwIfAborted(); // Avoid action that has side-effects.
@@ -659,13 +678,14 @@ class ElementHandle extends js.JSHandle {
659
678
  async press(metadata, key, options) {
660
679
  const controller = new _progress.ProgressController(metadata, this);
661
680
  return controller.run(async progress => {
681
+ await this._markAsTargetElement(metadata);
662
682
  const result = await this._press(progress, key, options);
663
683
  return assertDone(throwRetargetableDOMError(result));
664
684
  }, this._page._timeoutSettings.timeout(options));
665
685
  }
666
686
  async _press(progress, key, options) {
667
687
  progress.log(`elementHandle.press("${key}")`);
668
- await progress.beforeInputAction(this);
688
+ await this.instrumentation.onBeforeInputAction(this, progress.metadata);
669
689
  return this._page._frameManager.waitForSignalsCreatedBy(progress, !options.noWaitAfter, async () => {
670
690
  const result = await this._focus(progress, true /* resetSelectionIfNotFocused */);
671
691
  if (result !== 'done') return result;
@@ -693,6 +713,7 @@ class ElementHandle extends js.JSHandle {
693
713
  const result = await this.evaluateInUtility(([injected, node]) => injected.elementState(node, 'checked'), {});
694
714
  return throwRetargetableDOMError(result);
695
715
  };
716
+ await this._markAsTargetElement(progress.metadata);
696
717
  if ((await isChecked()) === state) return 'done';
697
718
  const result = await this._click(progress, {
698
719
  ...options,
@@ -706,6 +727,9 @@ class ElementHandle extends js.JSHandle {
706
727
  async boundingBox() {
707
728
  return this._page._delegate.getBoundingBox(this);
708
729
  }
730
+ async ariaSnapshot() {
731
+ return await this.evaluateInUtility(([injected, element]) => injected.ariaSnapshot(element), {});
732
+ }
709
733
  async screenshot(metadata, options = {}) {
710
734
  const controller = new _progress.ProgressController(metadata, this);
711
735
  return controller.run(progress => this._page._screenshotter.screenshotElement(progress, this, options), this._page._timeoutSettings.timeout(options));
@@ -825,21 +849,40 @@ function roundPoint(point) {
825
849
  y: (point.y * 100 | 0) / 100
826
850
  };
827
851
  }
828
- function compensateHalfIntegerRoundingError(point) {
829
- // Firefox internally uses integer coordinates, so 8.5 is converted to 9 when clicking.
830
- //
831
- // This does not work nicely for small elements. For example, 1x1 square with corners
832
- // (8;8) and (9;9) is targeted when clicking at (8;8) but not when clicking at (9;9).
833
- // So, clicking at (8.5;8.5) will effectively click at (9;9) and miss the target.
834
- //
835
- // Therefore, we skew half-integer values from the interval (8.49, 8.51) towards
836
- // (8.47, 8.49) that is rounded towards 8. This means clicking at (8.5;8.5) will
837
- // be replaced with (8.48;8.48) and will effectively click at (8;8).
838
- //
839
- // Other browsers use float coordinates, so this change should not matter.
840
- const remainderX = point.x - Math.floor(point.x);
841
- if (remainderX > 0.49 && remainderX < 0.51) point.x -= 0.02;
842
- const remainderY = point.y - Math.floor(point.y);
843
- if (remainderY > 0.49 && remainderY < 0.51) point.y -= 0.02;
852
+ function quadMiddlePoint(quad) {
853
+ const result = {
854
+ x: 0,
855
+ y: 0
856
+ };
857
+ for (const point of quad) {
858
+ result.x += point.x / 4;
859
+ result.y += point.y / 4;
860
+ }
861
+ return result;
862
+ }
863
+ function triangleArea(p1, p2, p3) {
864
+ return Math.abs(p1.x * (p2.y - p3.y) + p2.x * (p3.y - p1.y) + p3.x * (p1.y - p2.y)) / 2;
865
+ }
866
+ function isPointInsideQuad(point, quad) {
867
+ const area1 = triangleArea(point, quad[0], quad[1]) + triangleArea(point, quad[1], quad[2]) + triangleArea(point, quad[2], quad[3]) + triangleArea(point, quad[3], quad[0]);
868
+ const area2 = triangleArea(quad[0], quad[1], quad[2]) + triangleArea(quad[1], quad[2], quad[3]);
869
+ // Check that point is inside the quad.
870
+ if (Math.abs(area1 - area2) > 0.1) return false;
871
+ // Check that point is not on the right/bottom edge, because clicking
872
+ // there does not actually click the element.
873
+ return point.x < Math.max(quad[0].x, quad[1].x, quad[2].x, quad[3].x) && point.y < Math.max(quad[0].y, quad[1].y, quad[2].y, quad[3].y);
874
+ }
875
+ function findIntegerPointInsideQuad(quad) {
876
+ // Try all four rounding directions of the middle point.
877
+ const point = quadMiddlePoint(quad);
878
+ point.x = Math.floor(point.x);
879
+ point.y = Math.floor(point.y);
880
+ if (isPointInsideQuad(point, quad)) return point;
881
+ point.x += 1;
882
+ if (isPointInsideQuad(point, quad)) return point;
883
+ point.y += 1;
884
+ if (isPointInsideQuad(point, quad)) return point;
885
+ point.x -= 1;
886
+ if (isPointInsideQuad(point, quad)) return point;
844
887
  }
845
888
  const kUnableToAdoptErrorMessage = exports.kUnableToAdoptErrorMessage = 'Unable to adopt element handle from a different document';
@@ -39,15 +39,22 @@ class Download {
39
39
  this.url = url;
40
40
  this._suggestedFilename = suggestedFilename;
41
41
  page._browserContext._downloads.add(this);
42
- if (suggestedFilename !== undefined) this._page.emit(_page.Page.Events.Download, this);
42
+ if (suggestedFilename !== undefined) this._fireDownloadEvent();
43
+ }
44
+ page() {
45
+ return this._page;
43
46
  }
44
47
  _filenameSuggested(suggestedFilename) {
45
48
  (0, _utils.assert)(this._suggestedFilename === undefined);
46
49
  this._suggestedFilename = suggestedFilename;
47
- this._page.emit(_page.Page.Events.Download, this);
50
+ this._fireDownloadEvent();
48
51
  }
49
52
  suggestedFilename() {
50
53
  return this._suggestedFilename;
51
54
  }
55
+ _fireDownloadEvent() {
56
+ this._page.instrumentation.onDownload(this._page, this);
57
+ this._page.emit(_page.Page.Events.Download, this);
58
+ }
52
59
  }
53
60
  exports.Download = Download;
@@ -4,10 +4,10 @@ Object.defineProperty(exports, "__esModule", {
4
4
  value: true
5
5
  });
6
6
  exports.GlobalAPIRequestContext = exports.BrowserContextAPIRequestContext = exports.APIRequestContext = void 0;
7
+ exports.createProxyAgent = createProxyAgent;
7
8
  var _http = _interopRequireDefault(require("http"));
8
9
  var _https = _interopRequireDefault(require("https"));
9
10
  var _stream = require("stream");
10
- var _url = _interopRequireDefault(require("url"));
11
11
  var _zlib = _interopRequireDefault(require("zlib"));
12
12
  var _timeoutSettings = require("../common/timeoutSettings");
13
13
  var _userAgent = require("../utils/userAgent");
@@ -20,8 +20,8 @@ var _happyEyeballs = require("../utils/happy-eyeballs");
20
20
  var _instrumentation = require("./instrumentation");
21
21
  var _progress = require("./progress");
22
22
  var _tracing = require("./trace/recorder/tracing");
23
- var _network = require("./network");
24
23
  var _socksClientCertificatesInterceptor = require("./socksClientCertificatesInterceptor");
24
+ var _tls = require("tls");
25
25
  function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
26
26
  /**
27
27
  * Copyright (c) Microsoft Corporation.
@@ -71,7 +71,7 @@ class APIRequestContext extends _instrumentation.SdkObject {
71
71
  return uid;
72
72
  }
73
73
  async fetch(params, metadata) {
74
- var _params$method, _defaults$clientCerti;
74
+ var _params$method;
75
75
  const defaults = this._defaultOptions();
76
76
  const headers = {
77
77
  'user-agent': defaults.userAgent,
@@ -90,8 +90,10 @@ class APIRequestContext extends _instrumentation.SdkObject {
90
90
  value
91
91
  } of params.headers) setHeader(headers, name, value);
92
92
  }
93
- const requestUrl = new URL(params.url, defaults.baseURL);
94
- if (params.params) {
93
+ const requestUrl = new URL((0, _utils.constructURLBasedOnBaseURL)(defaults.baseURL, params.url));
94
+ if (params.encodedParams) {
95
+ requestUrl.search = params.encodedParams;
96
+ } else if (params.params) {
95
97
  for (const {
96
98
  name,
97
99
  value
@@ -102,23 +104,9 @@ class APIRequestContext extends _instrumentation.SdkObject {
102
104
  const method = ((_params$method = params.method) === null || _params$method === void 0 ? void 0 : _params$method.toUpperCase()) || 'GET';
103
105
  const proxy = defaults.proxy;
104
106
  let agent;
105
- // When `clientCertificates` is present, we set the `proxy` property to our own socks proxy
106
- // for the browser to use. However, we don't need it here, because we already respect
107
- // `clientCertificates` when fetching from Node.js.
108
- if (proxy && !((_defaults$clientCerti = defaults.clientCertificates) !== null && _defaults$clientCerti !== void 0 && _defaults$clientCerti.length) && proxy.server !== 'per-context' && !shouldBypassProxy(requestUrl, proxy.bypass)) {
109
- var _proxyOpts$protocol;
110
- const proxyOpts = _url.default.parse(proxy.server);
111
- if ((_proxyOpts$protocol = proxyOpts.protocol) !== null && _proxyOpts$protocol !== void 0 && _proxyOpts$protocol.startsWith('socks')) {
112
- agent = new _utilsBundle.SocksProxyAgent({
113
- host: proxyOpts.hostname,
114
- port: proxyOpts.port || undefined
115
- });
116
- } else {
117
- if (proxy.username) proxyOpts.auth = `${proxy.username}:${proxy.password || ''}`;
118
- // TODO: We should use HttpProxyAgent conditional on proxyOpts.protocol instead of always using CONNECT method.
119
- agent = new _utilsBundle.HttpsProxyAgent(proxyOpts);
120
- }
121
- }
107
+ // We skip 'per-context' in order to not break existing users. 'per-context' was previously used to
108
+ // workaround an upstream Chromium bug. Can be removed in the future.
109
+ if (proxy && proxy.server !== 'per-context' && !shouldBypassProxy(requestUrl, proxy.bypass)) agent = createProxyAgent(proxy);
122
110
  const timeout = defaults.timeoutSettings.timeout(params);
123
111
  const deadline = timeout && (0, _utils.monotonicTime)() + timeout;
124
112
  const options = {
@@ -190,6 +178,7 @@ class APIRequestContext extends _instrumentation.SdkObject {
190
178
  try {
191
179
  return await this._sendRequest(progress, url, options, postData);
192
180
  } catch (e) {
181
+ e = (0, _socksClientCertificatesInterceptor.rewriteOpenSSLErrorIfNeeded)(e);
193
182
  if (maxRetries === 0) throw e;
194
183
  if (i === maxRetries || options.deadline && (0, _utils.monotonicTime)() + backoff > options.deadline) throw new Error(`Failed after ${i + 1} attempt(s): ${e}`);
195
184
  // Retry on connection reset only.
@@ -227,8 +216,33 @@ class APIRequestContext extends _instrumentation.SdkObject {
227
216
  ...options,
228
217
  agent
229
218
  };
219
+ const startAt = (0, _utils.monotonicTime)();
220
+ let reusedSocketAt;
221
+ let dnsLookupAt;
222
+ let tcpConnectionAt;
223
+ let tlsHandshakeAt;
224
+ let requestFinishAt;
225
+ let serverIPAddress;
226
+ let serverPort;
227
+ let securityDetails;
228
+ const listeners = [];
230
229
  const request = requestConstructor(url, requestOptions, async response => {
230
+ const responseAt = (0, _utils.monotonicTime)();
231
231
  const notifyRequestFinished = body => {
232
+ var _tlsHandshakeAt;
233
+ const endAt = (0, _utils.monotonicTime)();
234
+ // spec: http://www.softwareishard.com/blog/har-12-spec/#timings
235
+ const connectEnd = (_tlsHandshakeAt = tlsHandshakeAt) !== null && _tlsHandshakeAt !== void 0 ? _tlsHandshakeAt : tcpConnectionAt;
236
+ const timings = {
237
+ send: requestFinishAt - startAt,
238
+ wait: responseAt - requestFinishAt,
239
+ receive: endAt - responseAt,
240
+ dns: dnsLookupAt ? dnsLookupAt - startAt : -1,
241
+ connect: connectEnd ? connectEnd - startAt : -1,
242
+ // "If [ssl] is defined then the time is also included in the connect field "
243
+ ssl: tlsHandshakeAt ? tlsHandshakeAt - tcpConnectionAt : -1,
244
+ blocked: reusedSocketAt ? reusedSocketAt - startAt : -1
245
+ };
232
246
  const requestFinishedEvent = {
233
247
  requestEvent,
234
248
  httpVersion: response.httpVersion,
@@ -237,7 +251,11 @@ class APIRequestContext extends _instrumentation.SdkObject {
237
251
  headers: response.headers,
238
252
  rawHeaders: response.rawHeaders,
239
253
  cookies,
240
- body
254
+ body,
255
+ timings,
256
+ serverIPAddress,
257
+ serverPort,
258
+ securityDetails
241
259
  };
242
260
  this.emit(APIRequestContext.Events.RequestFinished, requestFinishedEvent);
243
261
  };
@@ -363,13 +381,59 @@ class APIRequestContext extends _instrumentation.SdkObject {
363
381
  body.on('data', chunk => chunks.push(chunk));
364
382
  body.on('end', notifyBodyFinished);
365
383
  });
366
- request.on('error', error => reject((0, _socksClientCertificatesInterceptor.rewriteOpenSSLErrorIfNeeded)(error)));
367
- const disposeListener = () => {
384
+ request.on('error', reject);
385
+ listeners.push(_utils.eventsHelper.addEventListener(this, APIRequestContext.Events.Dispose, () => {
368
386
  reject(new Error('Request context disposed.'));
369
387
  request.destroy();
370
- };
371
- this.on(APIRequestContext.Events.Dispose, disposeListener);
372
- request.on('close', () => this.off(APIRequestContext.Events.Dispose, disposeListener));
388
+ }));
389
+ request.on('close', () => _utils.eventsHelper.removeEventListeners(listeners));
390
+ request.on('socket', socket => {
391
+ var _tcpConnectionAt, _tcpConnectionAt3;
392
+ if (request.reusedSocket) {
393
+ reusedSocketAt = (0, _utils.monotonicTime)();
394
+ return;
395
+ }
396
+
397
+ // happy eyeballs don't emit lookup and connect events, so we use our custom ones
398
+ const happyEyeBallsTimings = (0, _happyEyeballs.timingForSocket)(socket);
399
+ dnsLookupAt = happyEyeBallsTimings.dnsLookupAt;
400
+ (_tcpConnectionAt = tcpConnectionAt) !== null && _tcpConnectionAt !== void 0 ? _tcpConnectionAt : tcpConnectionAt = happyEyeBallsTimings.tcpConnectionAt;
401
+
402
+ // non-happy-eyeballs sockets
403
+ listeners.push(_utils.eventsHelper.addEventListener(socket, 'lookup', () => {
404
+ dnsLookupAt = (0, _utils.monotonicTime)();
405
+ }), _utils.eventsHelper.addEventListener(socket, 'connect', () => {
406
+ var _tcpConnectionAt2;
407
+ (_tcpConnectionAt2 = tcpConnectionAt) !== null && _tcpConnectionAt2 !== void 0 ? _tcpConnectionAt2 : tcpConnectionAt = (0, _utils.monotonicTime)();
408
+ }), _utils.eventsHelper.addEventListener(socket, 'secureConnect', () => {
409
+ tlsHandshakeAt = (0, _utils.monotonicTime)();
410
+ if (socket instanceof _tls.TLSSocket) {
411
+ var _socket$getProtocol;
412
+ const peerCertificate = socket.getPeerCertificate();
413
+ securityDetails = {
414
+ protocol: (_socket$getProtocol = socket.getProtocol()) !== null && _socket$getProtocol !== void 0 ? _socket$getProtocol : undefined,
415
+ subjectName: peerCertificate.subject.CN,
416
+ validFrom: new Date(peerCertificate.valid_from).getTime() / 1000,
417
+ validTo: new Date(peerCertificate.valid_to).getTime() / 1000,
418
+ issuer: peerCertificate.issuer.CN
419
+ };
420
+ }
421
+ }));
422
+
423
+ // when using socks proxy, having the socket means the connection got established
424
+ if (agent instanceof _utilsBundle.SocksProxyAgent) (_tcpConnectionAt3 = tcpConnectionAt) !== null && _tcpConnectionAt3 !== void 0 ? _tcpConnectionAt3 : tcpConnectionAt = (0, _utils.monotonicTime)();
425
+ serverIPAddress = socket.remoteAddress;
426
+ serverPort = socket.remotePort;
427
+ });
428
+ request.on('finish', () => {
429
+ requestFinishAt = (0, _utils.monotonicTime)();
430
+ });
431
+
432
+ // http proxy
433
+ request.on('proxyConnect', () => {
434
+ var _tcpConnectionAt4;
435
+ (_tcpConnectionAt4 = tcpConnectionAt) !== null && _tcpConnectionAt4 !== void 0 ? _tcpConnectionAt4 : tcpConnectionAt = (0, _utils.monotonicTime)();
436
+ });
373
437
  progress.log(`→ ${options.method} ${url.toString()}`);
374
438
  if (options.headers) {
375
439
  for (const [name, value] of Object.entries(options.headers)) progress.log(` ${name}: ${value}`);
@@ -470,7 +534,6 @@ class GlobalAPIRequestContext extends APIRequestContext {
470
534
  let url = proxy === null || proxy === void 0 ? void 0 : proxy.server.trim();
471
535
  if (!/^\w+:\/\//.test(url)) url = 'http://' + url;
472
536
  proxy.server = url;
473
- if (options.clientCertificates) throw new Error('Cannot specify both proxy and clientCertificates');
474
537
  }
475
538
  if (options.storageState) {
476
539
  this._origins = options.storageState.origins;
@@ -515,6 +578,15 @@ class GlobalAPIRequestContext extends APIRequestContext {
515
578
  }
516
579
  }
517
580
  exports.GlobalAPIRequestContext = GlobalAPIRequestContext;
581
+ function createProxyAgent(proxy) {
582
+ var _proxyURL$protocol;
583
+ const proxyURL = new URL(proxy.server);
584
+ if ((_proxyURL$protocol = proxyURL.protocol) !== null && _proxyURL$protocol !== void 0 && _proxyURL$protocol.startsWith('socks')) return new _utilsBundle.SocksProxyAgent(proxyURL);
585
+ if (proxy.username) proxyURL.username = proxy.username;
586
+ if (proxy.password) proxyURL.password = proxy.password;
587
+ // TODO: We should use HttpProxyAgent conditional on proxyURL.protocol instead of always using CONNECT method.
588
+ return new _utilsBundle.HttpsProxyAgent(proxyURL);
589
+ }
518
590
  function toHeadersArray(rawHeaders) {
519
591
  const result = [];
520
592
  for (let i = 0; i < rawHeaders.length; i += 2) result.push({
@@ -525,26 +597,9 @@ function toHeadersArray(rawHeaders) {
525
597
  }
526
598
  const redirectStatus = [301, 302, 303, 307, 308];
527
599
  function parseCookie(header) {
528
- const pairs = header.split(';').filter(s => s.trim().length > 0).map(p => {
529
- let key = '';
530
- let value = '';
531
- const separatorPos = p.indexOf('=');
532
- if (separatorPos === -1) {
533
- // If only a key is specified, the value is left undefined.
534
- key = p.trim();
535
- } else {
536
- // Otherwise we assume that the key is the element before the first `=`
537
- key = p.slice(0, separatorPos).trim();
538
- // And the value is the rest of the string.
539
- value = p.slice(separatorPos + 1).trim();
540
- }
541
- return [key, value];
542
- });
543
- if (!pairs.length) return null;
544
- const [name, value] = pairs[0];
600
+ const raw = (0, _cookieStore.parseRawCookie)(header);
601
+ if (!raw) return null;
545
602
  const cookie = {
546
- name,
547
- value,
548
603
  domain: '',
549
604
  path: '',
550
605
  expires: -1,
@@ -552,55 +607,9 @@ function parseCookie(header) {
552
607
  secure: false,
553
608
  // From https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie/SameSite
554
609
  // The cookie-sending behavior if SameSite is not specified is SameSite=Lax.
555
- sameSite: 'Lax'
610
+ sameSite: 'Lax',
611
+ ...raw
556
612
  };
557
- for (let i = 1; i < pairs.length; i++) {
558
- const [name, value] = pairs[i];
559
- switch (name.toLowerCase()) {
560
- case 'expires':
561
- const expiresMs = +new Date(value);
562
- // https://datatracker.ietf.org/doc/html/rfc6265#section-5.2.1
563
- if (isFinite(expiresMs)) {
564
- if (expiresMs <= 0) cookie.expires = 0;else cookie.expires = Math.min(expiresMs / 1000, _network.kMaxCookieExpiresDateInSeconds);
565
- }
566
- break;
567
- case 'max-age':
568
- const maxAgeSec = parseInt(value, 10);
569
- if (isFinite(maxAgeSec)) {
570
- // From https://datatracker.ietf.org/doc/html/rfc6265#section-5.2.2
571
- // If delta-seconds is less than or equal to zero (0), let expiry-time
572
- // be the earliest representable date and time.
573
- if (maxAgeSec <= 0) cookie.expires = 0;else cookie.expires = Math.min(Date.now() / 1000 + maxAgeSec, _network.kMaxCookieExpiresDateInSeconds);
574
- }
575
- break;
576
- case 'domain':
577
- cookie.domain = value.toLocaleLowerCase() || '';
578
- if (cookie.domain && !cookie.domain.startsWith('.') && cookie.domain.includes('.')) cookie.domain = '.' + cookie.domain;
579
- break;
580
- case 'path':
581
- cookie.path = value || '';
582
- break;
583
- case 'secure':
584
- cookie.secure = true;
585
- break;
586
- case 'httponly':
587
- cookie.httpOnly = true;
588
- break;
589
- case 'samesite':
590
- switch (value.toLowerCase()) {
591
- case 'none':
592
- cookie.sameSite = 'None';
593
- break;
594
- case 'lax':
595
- cookie.sameSite = 'Lax';
596
- break;
597
- case 'strict':
598
- cookie.sameSite = 'Strict';
599
- break;
600
- }
601
- break;
602
- }
603
- }
604
613
  return cookie;
605
614
  }
606
615
  function serializePostData(params, headers) {
@@ -53,7 +53,8 @@ class FFBrowser extends _browser.Browser {
53
53
  browser._defaultContext = new FFBrowserContext(browser, undefined, options.persistent);
54
54
  promises.push(browser._defaultContext._initialize());
55
55
  }
56
- if (options.proxy) promises.push(browser.session.send('Browser.setBrowserProxy', toJugglerProxyOptions(options.proxy)));
56
+ const proxy = options.originalLaunchOptions.proxyOverride || options.proxy;
57
+ if (proxy) promises.push(browser.session.send('Browser.setBrowserProxy', toJugglerProxyOptions(proxy)));
57
58
  await Promise.all(promises);
58
59
  return browser;
59
60
  }
@@ -205,7 +206,7 @@ class FFBrowserContext extends _browserContext.BrowserContext {
205
206
  browserContextId,
206
207
  bypassCSP: true
207
208
  }));
208
- if (this._options.ignoreHTTPSErrors) promises.push(this._browser.session.send('Browser.setIgnoreHTTPSErrors', {
209
+ if (this._options.ignoreHTTPSErrors || this._options.internalIgnoreHTTPSErrors) promises.push(this._browser.session.send('Browser.setIgnoreHTTPSErrors', {
209
210
  browserContextId,
210
211
  ignoreHTTPSErrors: true
211
212
  }));
@@ -255,10 +256,11 @@ class FFBrowserContext extends _browserContext.BrowserContext {
255
256
  });
256
257
  }));
257
258
  }
258
- if (this._options.proxy) {
259
+ const proxy = this._options.proxyOverride || this._options.proxy;
260
+ if (proxy) {
259
261
  promises.push(this._browser.session.send('Browser.setContextProxy', {
260
262
  browserContextId: this._browserContextId,
261
- ...toJugglerProxyOptions(this._options.proxy)
263
+ ...toJugglerProxyOptions(proxy)
262
264
  }));
263
265
  }
264
266
  await Promise.all(promises);
@@ -52,18 +52,6 @@ class FFExecutionContext {
52
52
  checkException(payload.exceptionDetails);
53
53
  return payload.result.objectId;
54
54
  }
55
- rawCallFunctionNoReply(func, ...args) {
56
- this._session.send('Runtime.callFunction', {
57
- functionDeclaration: func.toString(),
58
- args: args.map(a => a instanceof js.JSHandle ? {
59
- objectId: a._objectId
60
- } : {
61
- value: a
62
- }),
63
- returnByValue: true,
64
- executionContextId: this._executionContextId
65
- }).catch(() => {});
66
- }
67
55
  async evaluateWithArguments(expression, returnByValue, utilityScript, values, objectIds) {
68
56
  const payload = await this._session.send('Runtime.callFunction', {
69
57
  functionDeclaration: expression,
@@ -101,9 +89,6 @@ class FFExecutionContext {
101
89
  objectId
102
90
  });
103
91
  }
104
- objectCount(objectId) {
105
- throw new Error('Method not implemented in Firefox.');
106
- }
107
92
  }
108
93
  exports.FFExecutionContext = FFExecutionContext;
109
94
  function checkException(exceptionDetails) {