From 26a029d407be480d791972afb5975cf62c9360a6 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Fri, 19 Apr 2024 02:47:55 +0200 Subject: Adding upstream version 124.0.1. Signed-off-by: Daniel Baumann --- .../server/actors/accessibility/accessibility.js | 130 + devtools/server/actors/accessibility/accessible.js | 675 ++++ .../server/actors/accessibility/audit/contrast.js | 306 ++ .../server/actors/accessibility/audit/keyboard.js | 514 +++ .../server/actors/accessibility/audit/moz.build | 12 + .../actors/accessibility/audit/text-label.js | 438 +++ devtools/server/actors/accessibility/constants.js | 59 + devtools/server/actors/accessibility/moz.build | 20 + .../actors/accessibility/parent-accessibility.js | 154 + devtools/server/actors/accessibility/simulator.js | 81 + devtools/server/actors/accessibility/walker.js | 1315 ++++++++ devtools/server/actors/accessibility/worker.js | 103 + devtools/server/actors/addon/addons.js | 83 + devtools/server/actors/addon/moz.build | 10 + .../actors/addon/webextension-inspected-window.js | 680 ++++ devtools/server/actors/animation-type-longhand.js | 442 +++ devtools/server/actors/animation.js | 906 ++++++ devtools/server/actors/array-buffer.js | 69 + devtools/server/actors/blackboxing.js | 93 + devtools/server/actors/breakpoint-list.js | 92 + devtools/server/actors/breakpoint.js | 232 ++ devtools/server/actors/changes.js | 125 + devtools/server/actors/common.js | 110 + .../server/actors/compatibility/compatibility.js | 162 + .../actors/compatibility/lib/MDNCompatibility.js | 327 ++ devtools/server/actors/compatibility/lib/moz.build | 11 + .../compatibility/lib/test/xpcshell/.eslintrc.js | 6 + .../actors/compatibility/lib/test/xpcshell/head.js | 10 + .../lib/test/xpcshell/test_mdn-compatibility.js | 193 ++ .../compatibility/lib/test/xpcshell/xpcshell.toml | 7 + devtools/server/actors/compatibility/moz.build | 16 + devtools/server/actors/css-properties.js | 105 + devtools/server/actors/descriptors/moz.build | 12 + devtools/server/actors/descriptors/process.js | 246 ++ devtools/server/actors/descriptors/tab.js | 253 ++ devtools/server/actors/descriptors/webextension.js | 336 ++ devtools/server/actors/descriptors/worker.js | 182 ++ devtools/server/actors/device.js | 74 + devtools/server/actors/emulation/moz.build | 10 + devtools/server/actors/emulation/responsive.js | 83 + .../server/actors/emulation/touch-simulator.js | 309 ++ devtools/server/actors/environment.js | 206 ++ devtools/server/actors/errordocs.js | 222 ++ devtools/server/actors/frame.js | 225 ++ devtools/server/actors/heap-snapshot-file.js | 72 + devtools/server/actors/highlighters.js | 379 +++ devtools/server/actors/highlighters/accessible.js | 395 +++ .../server/actors/highlighters/auto-refresh.js | 368 +++ devtools/server/actors/highlighters/box-model.js | 892 ++++++ devtools/server/actors/highlighters/css-grid.js | 1962 ++++++++++++ .../server/actors/highlighters/css-transform.js | 265 ++ .../actors/highlighters/css/highlighters.css | 1059 +++++++ devtools/server/actors/highlighters/css/moz.build | 9 + devtools/server/actors/highlighters/eye-dropper.js | 608 ++++ devtools/server/actors/highlighters/flexbox.js | 1033 +++++++ devtools/server/actors/highlighters/fonts.js | 121 + .../server/actors/highlighters/geometry-editor.js | 808 +++++ .../server/actors/highlighters/measuring-tool.js | 853 +++++ devtools/server/actors/highlighters/moz.build | 31 + .../actors/highlighters/node-tabbing-order.js | 399 +++ .../server/actors/highlighters/paused-debugger.js | 260 ++ .../highlighters/remote-node-picker-notice.js | 188 ++ devtools/server/actors/highlighters/rulers.js | 312 ++ devtools/server/actors/highlighters/selector.js | 97 + devtools/server/actors/highlighters/shapes.js | 3263 ++++++++++++++++++++ .../server/actors/highlighters/tabbing-order.js | 247 ++ .../actors/highlighters/utils/accessibility.js | 774 +++++ .../server/actors/highlighters/utils/canvas.js | 596 ++++ .../server/actors/highlighters/utils/markup.js | 787 +++++ .../server/actors/highlighters/utils/moz.build | 7 + .../server/actors/highlighters/viewport-size.js | 129 + devtools/server/actors/inspector/constants.js | 17 + devtools/server/actors/inspector/css-logic.js | 1604 ++++++++++ .../actors/inspector/custom-element-watcher.js | 144 + .../server/actors/inspector/document-walker.js | 196 ++ .../server/actors/inspector/event-collector.js | 1069 +++++++ devtools/server/actors/inspector/inspector.js | 355 +++ devtools/server/actors/inspector/moz.build | 21 + devtools/server/actors/inspector/node-picker.js | 435 +++ devtools/server/actors/inspector/node.js | 861 ++++++ devtools/server/actors/inspector/utils.js | 570 ++++ devtools/server/actors/inspector/walker.js | 2764 +++++++++++++++++ devtools/server/actors/layout.js | 518 ++++ devtools/server/actors/manifest.js | 40 + devtools/server/actors/memory.js | 90 + devtools/server/actors/moz.build | 90 + .../actors/network-monitor/channel-event-sink.js | 99 + devtools/server/actors/network-monitor/moz.build | 12 + .../actors/network-monitor/network-content.js | 140 + .../actors/network-monitor/network-event-actor.js | 684 ++++ .../actors/network-monitor/network-parent.js | 175 ++ devtools/server/actors/object.js | 847 +++++ devtools/server/actors/object/moz.build | 12 + devtools/server/actors/object/previewers.js | 1142 +++++++ .../actors/object/private-properties-iterator.js | 70 + devtools/server/actors/object/property-iterator.js | 685 ++++ devtools/server/actors/object/symbol-iterator.js | 67 + devtools/server/actors/object/symbol.js | 109 + devtools/server/actors/object/utils.js | 615 ++++ devtools/server/actors/objects-manager.js | 39 + devtools/server/actors/page-style.js | 1297 ++++++++ devtools/server/actors/pause-scoped.js | 80 + devtools/server/actors/perf.js | 187 ++ devtools/server/actors/preference.js | 108 + devtools/server/actors/process.js | 76 + devtools/server/actors/reflow.js | 516 ++++ .../server/actors/resources/console-messages.js | 302 ++ devtools/server/actors/resources/css-changes.js | 42 + devtools/server/actors/resources/css-messages.js | 202 ++ .../actors/resources/css-registered-properties.js | 270 ++ devtools/server/actors/resources/document-event.js | 112 + devtools/server/actors/resources/error-messages.js | 192 ++ .../extensions-backgroundscript-status.js | 68 + devtools/server/actors/resources/index.js | 471 +++ devtools/server/actors/resources/jstracer-state.js | 96 + devtools/server/actors/resources/jstracer-trace.js | 43 + .../actors/resources/last-private-context-exit.js | 46 + devtools/server/actors/resources/moz.build | 44 + .../actors/resources/network-events-content.js | 267 ++ .../actors/resources/network-events-stacktraces.js | 214 ++ devtools/server/actors/resources/network-events.js | 420 +++ .../resources/parent-process-document-event.js | 174 ++ .../server/actors/resources/platform-messages.js | 60 + devtools/server/actors/resources/reflow.js | 63 + .../server/actors/resources/server-sent-events.js | 135 + devtools/server/actors/resources/sources.js | 100 + devtools/server/actors/resources/storage-cache.js | 22 + devtools/server/actors/resources/storage-cookie.js | 22 + .../server/actors/resources/storage-extension.js | 30 + .../server/actors/resources/storage-indexed-db.js | 22 + .../actors/resources/storage-local-storage.js | 22 + .../actors/resources/storage-session-storage.js | 22 + devtools/server/actors/resources/storage/cache.js | 195 ++ .../server/actors/resources/storage/cookies.js | 559 ++++ .../actors/resources/storage/extension-storage.js | 491 +++ devtools/server/actors/resources/storage/index.js | 404 +++ .../server/actors/resources/storage/indexed-db.js | 984 ++++++ .../resources/storage/local-and-session-storage.js | 200 ++ devtools/server/actors/resources/storage/moz.build | 17 + devtools/server/actors/resources/stylesheets.js | 145 + devtools/server/actors/resources/thread-states.js | 136 + .../resources/utils/content-process-storage.js | 453 +++ devtools/server/actors/resources/utils/moz.build | 14 + .../utils/nsi-console-listener-watcher.js | 192 ++ .../resources/utils/parent-process-storage.js | 580 ++++ devtools/server/actors/resources/websockets.js | 196 ++ devtools/server/actors/root.js | 606 ++++ devtools/server/actors/screenshot-content.js | 144 + devtools/server/actors/screenshot.js | 25 + devtools/server/actors/source.js | 694 +++++ devtools/server/actors/string.js | 45 + devtools/server/actors/style-rule.js | 1328 ++++++++ devtools/server/actors/style-sheets.js | 105 + devtools/server/actors/target-configuration.js | 493 +++ .../server/actors/targets/base-target-actor.js | 214 ++ devtools/server/actors/targets/content-process.js | 265 ++ devtools/server/actors/targets/index.js | 14 + devtools/server/actors/targets/moz.build | 20 + devtools/server/actors/targets/parent-process.js | 167 + .../targets/session-data-processors/blackboxing.js | 28 + .../targets/session-data-processors/breakpoints.js | 45 + .../session-data-processors/event-breakpoints.js | 36 + .../targets/session-data-processors/index.js | 50 + .../targets/session-data-processors/moz.build | 16 + .../targets/session-data-processors/resources.js | 25 + .../target-configuration.js | 32 + .../thread-configuration.js | 41 + .../session-data-processors/xhr-breakpoints.js | 44 + .../actors/targets/target-actor-registry.sys.mjs | 82 + devtools/server/actors/targets/webextension.js | 374 +++ devtools/server/actors/targets/window-global.js | 1935 ++++++++++++ devtools/server/actors/targets/worker.js | 149 + devtools/server/actors/thread-configuration.js | 80 + devtools/server/actors/thread.js | 2385 ++++++++++++++ devtools/server/actors/tracer.js | 502 +++ devtools/server/actors/utils/accessibility.js | 103 + devtools/server/actors/utils/actor-registry.js | 418 +++ .../server/actors/utils/breakpoint-actor-map.js | 84 + devtools/server/actors/utils/capture-screenshot.js | 200 ++ devtools/server/actors/utils/css-grid-utils.js | 60 + devtools/server/actors/utils/custom-formatters.js | 499 +++ devtools/server/actors/utils/dbg-source.js | 97 + devtools/server/actors/utils/event-breakpoints.js | 508 +++ devtools/server/actors/utils/event-loop.js | 221 ++ .../server/actors/utils/gecko-profile-collector.js | 285 ++ .../actors/utils/inactive-property-helper.js | 1443 +++++++++ devtools/server/actors/utils/logEvent.js | 112 + devtools/server/actors/utils/make-debugger.js | 122 + devtools/server/actors/utils/moz.build | 32 + devtools/server/actors/utils/shapes-utils.js | 149 + devtools/server/actors/utils/source-map-utils.js | 42 + devtools/server/actors/utils/source-url.js | 44 + devtools/server/actors/utils/sources-manager.js | 515 +++ devtools/server/actors/utils/stack.js | 183 ++ devtools/server/actors/utils/style-utils.js | 211 ++ devtools/server/actors/utils/stylesheet-utils.js | 155 + .../server/actors/utils/stylesheets-manager.js | 1031 +++++++ .../server/actors/utils/track-change-emitter.js | 19 + devtools/server/actors/utils/walker-search.js | 320 ++ devtools/server/actors/utils/watchpoint-map.js | 163 + devtools/server/actors/watcher.js | 864 ++++++ .../server/actors/watcher/SessionDataHelpers.jsm | 244 ++ .../server/actors/watcher/WatcherRegistry.sys.mjs | 397 +++ .../watcher/browsing-context-helpers.sys.mjs | 428 +++ devtools/server/actors/watcher/moz.build | 16 + devtools/server/actors/watcher/session-context.js | 219 ++ .../actors/watcher/target-helpers/frame-helper.js | 331 ++ .../server/actors/watcher/target-helpers/moz.build | 13 + .../watcher/target-helpers/process-helper.js | 389 +++ .../target-helpers/service-worker-helper.js | 220 ++ .../service-worker-jsprocessactor-startup.js | 26 + .../actors/watcher/target-helpers/worker-helper.js | 137 + devtools/server/actors/webbrowser.js | 776 +++++ devtools/server/actors/webconsole.js | 1736 +++++++++++ .../webconsole/commands/experimental-commands.ftl | 21 + .../server/actors/webconsole/commands/manager.js | 901 ++++++ .../server/actors/webconsole/commands/moz.build | 10 + .../server/actors/webconsole/commands/parser.js | 249 ++ .../actors/webconsole/eager-ecma-allowlist.js | 249 ++ .../actors/webconsole/eager-function-allowlist.js | 52 + .../server/actors/webconsole/eval-with-debugger.js | 710 +++++ .../actors/webconsole/listeners/console-api.js | 255 ++ .../webconsole/listeners/console-file-activity.js | 126 + .../actors/webconsole/listeners/console-reflow.js | 90 + .../actors/webconsole/listeners/console-service.js | 193 ++ .../actors/webconsole/listeners/document-events.js | 247 ++ .../server/actors/webconsole/listeners/moz.build | 13 + devtools/server/actors/webconsole/moz.build | 20 + devtools/server/actors/webconsole/utils.js | 160 + .../actors/webconsole/webidl-pure-allowlist.js | 87 + .../webconsole/webidl-unsafe-getters-names.js | 20 + .../server/actors/webconsole/worker-listeners.js | 35 + devtools/server/actors/worker/moz.build | 13 + devtools/server/actors/worker/push-subscription.js | 38 + .../worker/service-worker-registration-list.js | 114 + .../actors/worker/service-worker-registration.js | 264 ++ devtools/server/actors/worker/service-worker.js | 44 + .../actors/worker/worker-descriptor-actor-list.js | 213 ++ .../server/connectors/content-process-connector.js | 125 + devtools/server/connectors/frame-connector.js | 171 + .../js-window-actor/DevToolsFrameChild.sys.mjs | 706 +++++ .../js-window-actor/DevToolsFrameParent.sys.mjs | 279 ++ .../js-window-actor/DevToolsWorkerChild.sys.mjs | 571 ++++ .../js-window-actor/DevToolsWorkerParent.sys.mjs | 300 ++ .../js-window-actor/WindowGlobalLogger.sys.mjs | 76 + .../server/connectors/js-window-actor/moz.build | 13 + devtools/server/connectors/moz.build | 16 + .../DevToolsServiceWorkerChild.sys.mjs | 741 +++++ .../DevToolsServiceWorkerParent.sys.mjs | 314 ++ devtools/server/connectors/process-actor/moz.build | 10 + devtools/server/connectors/worker-connector.js | 208 ++ devtools/server/devtools-server-connection.js | 543 ++++ devtools/server/devtools-server.js | 513 +++ devtools/server/jar.mn | 8 + devtools/server/moz.build | 32 + devtools/server/performance/memory.js | 502 +++ devtools/server/performance/moz.build | 12 + devtools/server/socket/moz.build | 11 + devtools/server/socket/tests/chrome/chrome.toml | 4 + .../socket/tests/chrome/test_websocket-server.html | 88 + devtools/server/socket/websocket-server.js | 250 ++ devtools/server/startup/content-process-script.js | 282 ++ devtools/server/startup/content-process.js | 33 + devtools/server/startup/content-process.sys.mjs | 104 + devtools/server/startup/frame.js | 193 ++ devtools/server/startup/moz.build | 13 + devtools/server/startup/worker.js | 159 + devtools/server/tests/browser/animation-data.html | 115 + devtools/server/tests/browser/animation.html | 170 + .../browser/application-manifest-404-manifest.html | 10 + .../tests/browser/application-manifest-basic.html | 10 + .../browser/application-manifest-invalid-json.html | 11 + .../browser/application-manifest-no-manifest.html | 9 + .../browser/application-manifest-warnings.html | 10 + devtools/server/tests/browser/browser.toml | 213 ++ .../browser_accessibility_highlighter_infobar.js | 73 + ...browser_accessibility_infobar_audit_keyboard.js | 157 + ...owser_accessibility_infobar_audit_text_label.js | 164 + .../browser/browser_accessibility_infobar_show.js | 181 ++ .../browser_accessibility_keyboard_audit.js | 367 +++ .../tests/browser/browser_accessibility_node.js | 166 + .../browser/browser_accessibility_node_audit.js | 116 + .../browser/browser_accessibility_node_events.js | 197 ++ ...accessibility_node_tabbing_order_highlighter.js | 92 + .../tests/browser/browser_accessibility_simple.js | 106 + .../browser/browser_accessibility_simulator.js | 88 + ...wser_accessibility_tabbing_order_highlighter.js | 101 + .../browser_accessibility_text_label_audit.js | 1134 +++++++ ...browser_accessibility_text_label_audit_frame.js | 48 + .../tests/browser/browser_accessibility_walker.js | 170 + .../browser/browser_accessibility_walker_audit.js | 155 + .../server/tests/browser/browser_actor_error.js | 94 + .../browser/browser_animation_actor-lifetime.js | 80 + .../browser/browser_animation_emitMutations.js | 72 + .../browser/browser_animation_getMultipleStates.js | 63 + .../tests/browser/browser_animation_getPlayers.js | 39 + .../browser_animation_getStateAfterFinished.js | 76 + .../browser_animation_getSubTreeAnimations.js | 50 + .../browser/browser_animation_keepFinished.js | 55 + .../browser/browser_animation_playPauseIframe.js | 70 + .../browser/browser_animation_playPauseSeveral.js | 67 + .../tests/browser/browser_animation_playerState.js | 159 + .../browser/browser_animation_reconstructState.js | 40 + .../browser_animation_refreshTransitions.js | 97 + .../browser/browser_animation_setCurrentTime.js | 47 + .../browser/browser_animation_setPlaybackRate.js | 49 + .../tests/browser/browser_animation_simple.js | 39 + .../browser/browser_animation_updatedState.js | 66 + .../tests/browser/browser_application_manifest.js | 87 + .../tests/browser/browser_canvasframe_helper_01.js | 170 + .../tests/browser/browser_canvasframe_helper_02.js | 53 + .../tests/browser/browser_canvasframe_helper_03.js | 129 + .../tests/browser/browser_canvasframe_helper_04.js | 142 + .../tests/browser/browser_canvasframe_helper_05.js | 134 + .../tests/browser/browser_canvasframe_helper_06.js | 116 + .../browser/browser_compatibility_cssIssues.js | 137 + .../server/tests/browser/browser_connectToFrame.js | 142 + .../tests/browser/browser_debugger_server.js | 198 ++ .../browser/browser_document_devtools_basics.js | 103 + .../tests/browser/browser_document_rdp_basics.js | 129 + .../server/tests/browser/browser_getProcess.js | 129 + .../tests/browser/browser_inspector-anonymous.js | 204 ++ .../tests/browser/browser_inspector-iframe.js | 93 + .../tests/browser/browser_inspector-insert.js | 158 + .../browser/browser_inspector-isScrollable.js | 34 + .../browser_inspector-mutations-childlist.js | 282 ++ .../tests/browser/browser_inspector-release.js | 54 + .../tests/browser/browser_inspector-remove.js | 102 + .../tests/browser/browser_inspector-retain.js | 157 + .../tests/browser/browser_inspector-search.js | 347 +++ .../tests/browser/browser_inspector-shadow.js | 231 ++ .../tests/browser/browser_inspector-traversal.js | 350 +++ .../tests/browser/browser_inspector-utils.js | 25 + .../tests/browser/browser_layout_getGrids.js | 145 + .../server/tests/browser/browser_layout_simple.js | 31 + .../tests/browser/browser_memory_allocations_01.js | 107 + devtools/server/tests/browser/browser_perf-01.js | 57 + devtools/server/tests/browser/browser_perf-02.js | 37 + devtools/server/tests/browser/browser_perf-04.js | 53 + .../browser/browser_perf-getSupportedFeatures.js | 23 + .../browser_storage_cookies-duplicate-names.js | 134 + .../browser/browser_storage_dynamic_windows.js | 410 +++ .../tests/browser/browser_storage_listings.js | 743 +++++ .../tests/browser/browser_storage_updates.js | 343 ++ .../browser_style_utils_getFontPreviewData.js | 137 + .../tests/browser/browser_styles_getRuleText.js | 34 + .../browser/browser_stylesheets_getTextEmpty.js | 53 + .../tests/browser/director-script-target.html | 18 + .../server/tests/browser/doc_accessibility.html | 19 + .../tests/browser/doc_accessibility_audit.html | 10 + .../tests/browser/doc_accessibility_infobar.html | 12 + .../browser/doc_accessibility_keyboard_audit.html | 150 + .../doc_accessibility_text_label_audit.html | 463 +++ .../doc_accessibility_text_label_audit_frame.html | 10 + devtools/server/tests/browser/doc_allocations.html | 23 + .../server/tests/browser/doc_compatibility.html | 28 + devtools/server/tests/browser/doc_force_cc.html | 32 + devtools/server/tests/browser/doc_force_gc.html | 31 + devtools/server/tests/browser/doc_iframe.html | 17 + devtools/server/tests/browser/doc_iframe2.html | 15 + .../server/tests/browser/doc_iframe_content.html | 14 + devtools/server/tests/browser/doc_innerHTML.html | 21 + devtools/server/tests/browser/error-actor.js | 25 + devtools/server/tests/browser/grid.html | 42 + devtools/server/tests/browser/head.js | 337 ++ devtools/server/tests/browser/inspector-helpers.js | 161 + .../tests/browser/inspector-isScrollable-data.html | 79 + .../tests/browser/inspector-search-data.html | 54 + .../server/tests/browser/inspector-shadow.html | 117 + .../tests/browser/inspector-traversal-data.html | 98 + .../tests/browser/storage-cookies-same-name.html | 29 + .../tests/browser/storage-dynamic-windows.html | 117 + devtools/server/tests/browser/storage-helpers.js | 50 + .../server/tests/browser/storage-listings.html | 123 + .../tests/browser/storage-secured-iframe.html | 94 + .../tests/browser/storage-unsecured-iframe.html | 28 + devtools/server/tests/browser/storage-updates.html | 47 + devtools/server/tests/browser/test-errors-actor.js | 72 + devtools/server/tests/browser/test-window.xhtml | 5 + .../chrome/Debugger.Source.prototype.element-2.js | 4 + .../chrome/Debugger.Source.prototype.element.html | 25 + .../chrome/Debugger.Source.prototype.element.js | 7 + devtools/server/tests/chrome/chrome.toml | 150 + ...ebugger.Source.prototype.introductionType.xhtml | 7 + devtools/server/tests/chrome/hello-actor.js | 28 + .../chrome/iframe1_makeGlobalObjectReference.html | 1 + .../chrome/iframe2_makeGlobalObjectReference.html | 1 + .../inactive-property-helper/align-content.mjs | 92 + .../inactive-property-helper/border-image.mjs | 162 + .../cue-pseudo-element.mjs | 371 +++ .../first-letter-pseudo-element.mjs | 32 + .../first-line-pseudo-element.mjs | 50 + .../flex-grid-item-properties.mjs | 229 ++ .../chrome/inactive-property-helper/float.mjs | 76 + .../tests/chrome/inactive-property-helper/gap.mjs | 133 + .../grid-container-properties.mjs | 43 + .../grid-with-absolute-properties.mjs | 71 + .../highlight-pseudo-elements.mjs | 155 + .../inactive-property-helper/margin-padding.mjs | 260 ++ .../max-min-width-height.mjs | 366 +++ .../multicol-container-properties.mjs | 39 + .../place-items-content.mjs | 159 + .../placeholder-pseudo-element.mjs | 122 + .../chrome/inactive-property-helper/positioned.mjs | 82 + .../inactive-property-helper/scroll-padding.mjs | 159 + .../chrome/inactive-property-helper/table-cell.mjs | 21 + .../chrome/inactive-property-helper/table.mjs | 28 + .../inactive-property-helper/text-overflow.mjs | 92 + .../chrome/inactive-property-helper/text-wrap.mjs | 86 + .../inactive-property-helper/vertical-align.mjs | 56 + .../inactive-property-helper/width-height-ruby.mjs | 147 + .../chrome/inspector-delay-image-response.sjs | 46 + .../server/tests/chrome/inspector-eyedropper.html | 20 + devtools/server/tests/chrome/inspector-helpers.js | 133 + .../server/tests/chrome/inspector-search-data.html | 54 + .../server/tests/chrome/inspector-styles-data.css | 3 + .../server/tests/chrome/inspector-styles-data.html | 85 + .../server/tests/chrome/inspector-template.html | 17 + .../tests/chrome/inspector-traversal-data.html | 103 + .../tests/chrome/inspector_css-properties.html | 12 + .../tests/chrome/inspector_display-type.html | 17 + .../tests/chrome/inspector_getImageData.html | 23 + .../tests/chrome/inspector_getOffsetParent.html | 18 + devtools/server/tests/chrome/large-image.jpg | Bin 0 -> 793541 bytes devtools/server/tests/chrome/memory-helpers.js | 72 + .../tests/chrome/nonchrome_unsafeDereference.html | 10 + devtools/server/tests/chrome/small-image.gif | Bin 0 -> 510655 bytes .../tests/chrome/suspendTimeouts_content.html | 1 + .../server/tests/chrome/suspendTimeouts_content.js | 75 + .../server/tests/chrome/suspendTimeouts_worker.js | 12 + .../test_Debugger.Script.prototype.global.html | 48 + ...Debugger.Source.prototype.elementAttribute.html | 159 + ...bugger.Source.prototype.introductionScript.html | 96 + ...Debugger.Source.prototype.introductionType.html | 159 + .../tests/chrome/test_animation-type-longhand.html | 42 + .../tests/chrome/test_css-logic-specificity.html | 84 + devtools/server/tests/chrome/test_css-logic.html | 73 + .../server/tests/chrome/test_css-properties.html | 72 + devtools/server/tests/chrome/test_device.html | 79 + .../test_executeInGlobal-outerized_this.html | 73 + .../chrome/test_highlighter_paused_debugger.html | 88 + .../tests/chrome/test_inspector-changeattrs.html | 90 + .../tests/chrome/test_inspector-changevalue.html | 68 + .../tests/chrome/test_inspector-display-type.html | 81 + .../chrome/test_inspector-duplicate-node.html | 61 + .../server/tests/chrome/test_inspector-hide.html | 71 + .../test_inspector-inactive-property-helper.html | 124 + .../chrome/test_inspector-mutations-attr.html | 169 + .../chrome/test_inspector-mutations-events.html | 187 ++ .../chrome/test_inspector-mutations-value.html | 163 + .../tests/chrome/test_inspector-pick-color.html | 94 + .../chrome/test_inspector-pseudoclass-lock.html | 185 ++ .../server/tests/chrome/test_inspector-reload.html | 90 + .../server/tests/chrome/test_inspector-resize.html | 69 + .../tests/chrome/test_inspector-resolve-url.html | 87 + .../chrome/test_inspector-scroll-into-view.html | 60 + .../tests/chrome/test_inspector-search-front.html | 163 + .../tests/chrome/test_inspector-template.html | 66 + .../test_inspector_getImageData-wait-for-load.html | 133 + .../tests/chrome/test_inspector_getImageData.html | 142 + .../chrome/test_inspector_getImageDataFromURL.html | 116 + .../chrome/test_inspector_getNodeFromActor.html | 84 + .../chrome/test_inspector_getOffsetParent.html | 129 + .../chrome/test_makeGlobalObjectReference.html | 96 + devtools/server/tests/chrome/test_memory.html | 39 + .../tests/chrome/test_memory_allocations_02.html | 80 + .../tests/chrome/test_memory_allocations_03.html | 80 + .../tests/chrome/test_memory_allocations_04.html | 62 + .../tests/chrome/test_memory_allocations_05.html | 93 + .../tests/chrome/test_memory_allocations_06.html | 51 + .../tests/chrome/test_memory_allocations_07.html | 58 + .../server/tests/chrome/test_memory_attach_01.html | 33 + .../server/tests/chrome/test_memory_attach_02.html | 44 + .../server/tests/chrome/test_memory_census.html | 35 + .../server/tests/chrome/test_memory_gc_01.html | 50 + .../server/tests/chrome/test_memory_gc_events.html | 44 + .../server/tests/chrome/test_overflowing-body.html | 42 + .../tests/chrome/test_overflowing-children.html | 131 + devtools/server/tests/chrome/test_preference.html | 128 + .../server/tests/chrome/test_styles-applied.html | 155 + .../server/tests/chrome/test_styles-computed.html | 130 + .../server/tests/chrome/test_styles-layout.html | 110 + .../server/tests/chrome/test_styles-matched.html | 110 + .../server/tests/chrome/test_styles-modify.html | 110 + devtools/server/tests/chrome/test_styles-svg.html | 61 + .../server/tests/chrome/test_suspendTimeouts.html | 20 + .../server/tests/chrome/test_suspendTimeouts.js | 139 + .../tests/chrome/test_unsafeDereference.html | 53 + .../tests/chrome/test_webconsole-node-grip.html | 66 + devtools/server/tests/chrome/webconsole-helpers.js | 54 + devtools/server/tests/xpcshell/.eslintrc.js | 9 + .../addons/web-extension-upgrade/manifest.json | 10 + .../xpcshell/addons/web-extension/manifest.json | 10 + .../xpcshell/addons/web-extension2/manifest.json | 10 + devtools/server/tests/xpcshell/completions.js | 23 + devtools/server/tests/xpcshell/head_dbg.js | 984 ++++++ devtools/server/tests/xpcshell/hello-actor.js | 23 + .../tests/xpcshell/post_init_global_actors.js | 22 + .../xpcshell/post_init_target_scoped_actors.js | 22 + .../tests/xpcshell/pre_init_global_actors.js | 22 + .../xpcshell/pre_init_target_scoped_actors.js | 22 + .../tests/xpcshell/registertestactors-lazy.js | 43 + .../setBreakpoint-on-column-in-gcd-script.js | 7 + .../xpcshell/setBreakpoint-on-column-minified.js | 8 + ...oint-on-column-with-no-offsets-in-gcd-script.js | 7 + .../setBreakpoint-on-column-with-no-offsets.js | 5 + .../tests/xpcshell/setBreakpoint-on-column.js | 5 + .../setBreakpoint-on-line-in-gcd-script.js | 9 + .../setBreakpoint-on-line-with-multiple-offsets.js | 7 + ...tBreakpoint-on-line-with-multiple-statements.js | 5 + ...kpoint-on-line-with-no-offsets-in-gcd-script.js | 9 + .../setBreakpoint-on-line-with-no-offsets.js | 7 + .../server/tests/xpcshell/setBreakpoint-on-line.js | 7 + devtools/server/tests/xpcshell/source-03.js | 7 + .../xpcshell/source-map-data/sourcemapped.coffee | 6 + .../xpcshell/source-map-data/sourcemapped.map | 10 + devtools/server/tests/xpcshell/sourcemapped.js | 16 + devtools/server/tests/xpcshell/stepping-async.js | 31 + devtools/server/tests/xpcshell/stepping.js | 36 + .../test_MemoryActor_saveHeapSnapshot_01.js | 22 + .../test_MemoryActor_saveHeapSnapshot_02.js | 24 + .../test_MemoryActor_saveHeapSnapshot_03.js | 22 + devtools/server/tests/xpcshell/test_add_actors.js | 107 + .../tests/xpcshell/test_addon_debugging_connect.js | 158 + .../server/tests/xpcshell/test_addon_events.js | 60 + .../server/tests/xpcshell/test_addon_reload.js | 116 + .../server/tests/xpcshell/test_addons_actor.js | 55 + .../server/tests/xpcshell/test_animation_name.js | 93 + .../server/tests/xpcshell/test_animation_type.js | 72 + devtools/server/tests/xpcshell/test_attach.js | 28 + .../server/tests/xpcshell/test_blackboxing-01.js | 155 + .../server/tests/xpcshell/test_blackboxing-02.js | 95 + .../server/tests/xpcshell/test_blackboxing-03.js | 115 + .../server/tests/xpcshell/test_blackboxing-04.js | 70 + .../server/tests/xpcshell/test_blackboxing-05.js | 97 + .../server/tests/xpcshell/test_blackboxing-08.js | 52 + .../server/tests/xpcshell/test_breakpoint-01.js | 53 + .../server/tests/xpcshell/test_breakpoint-03.js | 74 + .../server/tests/xpcshell/test_breakpoint-04.js | 56 + .../server/tests/xpcshell/test_breakpoint-05.js | 62 + .../server/tests/xpcshell/test_breakpoint-06.js | 68 + .../server/tests/xpcshell/test_breakpoint-07.js | 65 + .../server/tests/xpcshell/test_breakpoint-08.js | 75 + .../server/tests/xpcshell/test_breakpoint-09.js | 72 + .../server/tests/xpcshell/test_breakpoint-10.js | 81 + .../server/tests/xpcshell/test_breakpoint-11.js | 77 + .../server/tests/xpcshell/test_breakpoint-12.js | 93 + .../server/tests/xpcshell/test_breakpoint-13.js | 78 + .../server/tests/xpcshell/test_breakpoint-14.js | 90 + .../server/tests/xpcshell/test_breakpoint-16.js | 70 + .../server/tests/xpcshell/test_breakpoint-17.js | 130 + .../server/tests/xpcshell/test_breakpoint-18.js | 60 + .../server/tests/xpcshell/test_breakpoint-19.js | 45 + .../server/tests/xpcshell/test_breakpoint-20.js | 109 + .../server/tests/xpcshell/test_breakpoint-21.js | 62 + .../server/tests/xpcshell/test_breakpoint-22.js | 60 + .../server/tests/xpcshell/test_breakpoint-23.js | 35 + .../server/tests/xpcshell/test_breakpoint-24.js | 239 ++ .../server/tests/xpcshell/test_breakpoint-25.js | 79 + .../server/tests/xpcshell/test_breakpoint-26.js | 63 + .../tests/xpcshell/test_breakpoint-actor-map.js | 257 ++ .../server/tests/xpcshell/test_client_request.js | 220 ++ .../xpcshell/test_conditional_breakpoint-01.js | 54 + .../xpcshell/test_conditional_breakpoint-02.js | 52 + .../xpcshell/test_conditional_breakpoint-03.js | 52 + .../xpcshell/test_conditional_breakpoint-04.js | 56 + .../xpcshell/test_connection_closes_all_pools.js | 100 + .../server/tests/xpcshell/test_console_eval-01.js | 33 + .../server/tests/xpcshell/test_console_eval-02.js | 22 + devtools/server/tests/xpcshell/test_dbgactor.js | 46 + .../xpcshell/test_dbgclient_debuggerstatement.js | 39 + devtools/server/tests/xpcshell/test_dbgglobal.js | 86 + .../tests/xpcshell/test_extension_storage_actor.js | 1155 +++++++ .../test_extension_storage_actor_upgrade.js | 142 + .../server/tests/xpcshell/test_forwardingprefix.js | 226 ++ .../server/tests/xpcshell/test_frameactor-01.js | 35 + .../server/tests/xpcshell/test_frameactor-02.js | 36 + .../server/tests/xpcshell/test_frameactor-03.js | 54 + .../server/tests/xpcshell/test_frameactor-04.js | 64 + .../server/tests/xpcshell/test_frameactor-05.js | 39 + .../tests/xpcshell/test_frameactor_wasm-01.js | 67 + .../tests/xpcshell/test_framearguments-01.js | 43 + .../server/tests/xpcshell/test_framebindings-01.js | 71 + .../server/tests/xpcshell/test_framebindings-02.js | 60 + .../server/tests/xpcshell/test_framebindings-03.js | 63 + .../server/tests/xpcshell/test_framebindings-04.js | 77 + .../server/tests/xpcshell/test_framebindings-05.js | 54 + .../server/tests/xpcshell/test_framebindings-06.js | 45 + .../server/tests/xpcshell/test_framebindings-07.js | 41 + .../server/tests/xpcshell/test_front_destroy.js | 42 + .../server/tests/xpcshell/test_functiongrips-01.js | 64 + devtools/server/tests/xpcshell/test_getRuleText.js | 143 + .../tests/xpcshell/test_getTextAtLineColumn.js | 35 + .../tests/xpcshell/test_get_command_and_arg.js | 121 + .../server/tests/xpcshell/test_getyoungestframe.js | 38 + .../xpcshell/test_ignore_caught_exceptions.js | 53 + .../test_ignore_no_interface_exceptions.js | 50 + devtools/server/tests/xpcshell/test_interrupt.js | 15 + .../tests/xpcshell/test_layout-reflows-observer.js | 311 ++ .../server/tests/xpcshell/test_listsources-01.js | 56 + .../server/tests/xpcshell/test_listsources-02.js | 36 + .../server/tests/xpcshell/test_listsources-03.js | 45 + devtools/server/tests/xpcshell/test_logpoint-01.js | 83 + devtools/server/tests/xpcshell/test_logpoint-02.js | 85 + devtools/server/tests/xpcshell/test_logpoint-03.js | 82 + .../tests/xpcshell/test_longstringgrips-01.js | 75 + .../server/tests/xpcshell/test_nativewrappers.js | 39 + devtools/server/tests/xpcshell/test_nesting-03.js | 50 + devtools/server/tests/xpcshell/test_nesting-04.js | 86 + .../server/tests/xpcshell/test_new_source-01.js | 24 + .../server/tests/xpcshell/test_new_source-02.js | 46 + .../server/tests/xpcshell/test_nodelistactor.js | 30 + .../server/tests/xpcshell/test_objectgrips-02.js | 44 + .../server/tests/xpcshell/test_objectgrips-03.js | 52 + .../server/tests/xpcshell/test_objectgrips-04.js | 54 + .../server/tests/xpcshell/test_objectgrips-05.js | 56 + .../server/tests/xpcshell/test_objectgrips-06.js | 56 + .../server/tests/xpcshell/test_objectgrips-07.js | 65 + .../server/tests/xpcshell/test_objectgrips-08.js | 61 + .../server/tests/xpcshell/test_objectgrips-14.js | 55 + .../server/tests/xpcshell/test_objectgrips-15.js | 53 + .../server/tests/xpcshell/test_objectgrips-16.js | 139 + .../server/tests/xpcshell/test_objectgrips-17.js | 320 ++ .../server/tests/xpcshell/test_objectgrips-18.js | 173 ++ .../server/tests/xpcshell/test_objectgrips-19.js | 75 + .../server/tests/xpcshell/test_objectgrips-20.js | 387 +++ .../server/tests/xpcshell/test_objectgrips-21.js | 396 +++ .../server/tests/xpcshell/test_objectgrips-22.js | 50 + .../server/tests/xpcshell/test_objectgrips-23.js | 45 + .../server/tests/xpcshell/test_objectgrips-24.js | 57 + .../server/tests/xpcshell/test_objectgrips-25.js | 131 + .../tests/xpcshell/test_objectgrips-fn-apply-01.js | 117 + .../tests/xpcshell/test_objectgrips-fn-apply-02.js | 56 + .../tests/xpcshell/test_objectgrips-fn-apply-03.js | 51 + .../xpcshell/test_objectgrips-nested-promise.js | 56 + .../xpcshell/test_objectgrips-nested-proxy.js | 51 + .../xpcshell/test_objectgrips-property-value-01.js | 148 + .../xpcshell/test_objectgrips-property-value-02.js | 53 + .../xpcshell/test_objectgrips-property-value-03.js | 63 + .../xpcshell/test_objectgrips-sparse-array.js | 40 + .../tests/xpcshell/test_pause_exceptions-01.js | 43 + .../tests/xpcshell/test_pause_exceptions-02.js | 40 + .../tests/xpcshell/test_pause_exceptions-03.js | 53 + .../tests/xpcshell/test_pause_exceptions-04.js | 93 + .../server/tests/xpcshell/test_pauselifetime-01.js | 54 + .../server/tests/xpcshell/test_pauselifetime-02.js | 57 + .../server/tests/xpcshell/test_pauselifetime-03.js | 64 + .../server/tests/xpcshell/test_pauselifetime-04.js | 40 + .../server/tests/xpcshell/test_promise_state-01.js | 44 + .../server/tests/xpcshell/test_promise_state-02.js | 59 + .../server/tests/xpcshell/test_promise_state-03.js | 58 + .../xpcshell/test_promises_run_to_completion.js | 132 + .../server/tests/xpcshell/test_register_actor.js | 94 + .../server/tests/xpcshell/test_requestTypes.js | 28 + .../server/tests/xpcshell/test_restartFrame-01.js | 118 + devtools/server/tests/xpcshell/test_safe-getter.js | 54 + .../tests/xpcshell/test_sessionDataHelpers.js | 124 + ...Breakpoint-at-the-beginning-of-a-minified-fn.js | 41 + ...st_setBreakpoint-at-the-end-of-a-minified-fn.js | 41 + .../test_setBreakpoint-on-column-in-gcd-script.js | 46 + .../tests/xpcshell/test_setBreakpoint-on-column.js | 36 + .../test_setBreakpoint-on-line-in-gcd-script.js | 45 + ..._setBreakpoint-on-line-with-multiple-offsets.js | 52 + ...tBreakpoint-on-line-with-multiple-statements.js | 38 + ...kpoint-on-line-with-no-offsets-in-gcd-script.js | 56 + .../test_setBreakpoint-on-line-with-no-offsets.js | 44 + .../tests/xpcshell/test_setBreakpoint-on-line.js | 36 + .../xpcshell/test_shapes_highlighter_helpers.js | 274 ++ devtools/server/tests/xpcshell/test_source-01.js | 58 + devtools/server/tests/xpcshell/test_source-02.js | 64 + devtools/server/tests/xpcshell/test_source-03.js | 75 + devtools/server/tests/xpcshell/test_source-04.js | 74 + devtools/server/tests/xpcshell/test_stepping-01.js | 94 + devtools/server/tests/xpcshell/test_stepping-02.js | 57 + devtools/server/tests/xpcshell/test_stepping-03.js | 43 + devtools/server/tests/xpcshell/test_stepping-04.js | 50 + devtools/server/tests/xpcshell/test_stepping-05.js | 0 devtools/server/tests/xpcshell/test_stepping-06.js | 0 devtools/server/tests/xpcshell/test_stepping-07.js | 0 devtools/server/tests/xpcshell/test_stepping-08.js | 0 devtools/server/tests/xpcshell/test_stepping-09.js | 47 + devtools/server/tests/xpcshell/test_stepping-10.js | 52 + devtools/server/tests/xpcshell/test_stepping-11.js | 25 + devtools/server/tests/xpcshell/test_stepping-12.js | 162 + devtools/server/tests/xpcshell/test_stepping-13.js | 39 + devtools/server/tests/xpcshell/test_stepping-14.js | 52 + devtools/server/tests/xpcshell/test_stepping-15.js | 78 + devtools/server/tests/xpcshell/test_stepping-16.js | 81 + devtools/server/tests/xpcshell/test_stepping-17.js | 69 + devtools/server/tests/xpcshell/test_stepping-18.js | 100 + devtools/server/tests/xpcshell/test_stepping-19.js | 93 + .../test_stepping-with-skip-breakpoints.js | 84 + devtools/server/tests/xpcshell/test_symbolactor.js | 53 + devtools/server/tests/xpcshell/test_symbols-01.js | 50 + devtools/server/tests/xpcshell/test_symbols-02.js | 44 + .../tests/xpcshell/test_threadlifetime-01.js | 56 + .../tests/xpcshell/test_threadlifetime-02.js | 73 + .../tests/xpcshell/test_threadlifetime-04.js | 58 + .../tests/xpcshell/test_unsafeDereference.js | 130 + .../server/tests/xpcshell/test_wasm_source-01.js | 143 + .../server/tests/xpcshell/test_watchpoint-01.js | 197 ++ .../server/tests/xpcshell/test_watchpoint-02.js | 223 ++ .../server/tests/xpcshell/test_watchpoint-03.js | 72 + .../server/tests/xpcshell/test_watchpoint-04.js | 78 + .../server/tests/xpcshell/test_watchpoint-05.js | 113 + devtools/server/tests/xpcshell/test_webext_apis.js | 162 + .../tests/xpcshell/test_webextension_descriptor.js | 141 + .../tests/xpcshell/test_xpcshell_debugging.js | 90 + devtools/server/tests/xpcshell/testactors.js | 242 ++ .../server/tests/xpcshell/webextension-helpers.js | 197 ++ devtools/server/tests/xpcshell/xpcshell.toml | 436 +++ .../tests/xpcshell/xpcshell_debugging_script.js | 11 + devtools/server/tracer/moz.build | 14 + .../server/tracer/tests/browser/Worker.tracer.js | 10 + .../tracer/tests/browser/WorkerDebugger.tracer.js | 36 + devtools/server/tracer/tests/browser/browser.toml | 11 + .../tests/browser/browser_document_tracer.js | 68 + .../tracer/tests/browser/browser_worker_tracer.js | 68 + .../server/tracer/tests/xpcshell/test_tracer.js | 240 ++ .../server/tracer/tests/xpcshell/xpcshell.toml | 6 + devtools/server/tracer/tracer.jsm | 798 +++++ 721 files changed, 124792 insertions(+) create mode 100644 devtools/server/actors/accessibility/accessibility.js create mode 100644 devtools/server/actors/accessibility/accessible.js create mode 100644 devtools/server/actors/accessibility/audit/contrast.js create mode 100644 devtools/server/actors/accessibility/audit/keyboard.js create mode 100644 devtools/server/actors/accessibility/audit/moz.build create mode 100644 devtools/server/actors/accessibility/audit/text-label.js create mode 100644 devtools/server/actors/accessibility/constants.js create mode 100644 devtools/server/actors/accessibility/moz.build create mode 100644 devtools/server/actors/accessibility/parent-accessibility.js create mode 100644 devtools/server/actors/accessibility/simulator.js create mode 100644 devtools/server/actors/accessibility/walker.js create mode 100644 devtools/server/actors/accessibility/worker.js create mode 100644 devtools/server/actors/addon/addons.js create mode 100644 devtools/server/actors/addon/moz.build create mode 100644 devtools/server/actors/addon/webextension-inspected-window.js create mode 100644 devtools/server/actors/animation-type-longhand.js create mode 100644 devtools/server/actors/animation.js create mode 100644 devtools/server/actors/array-buffer.js create mode 100644 devtools/server/actors/blackboxing.js create mode 100644 devtools/server/actors/breakpoint-list.js create mode 100644 devtools/server/actors/breakpoint.js create mode 100644 devtools/server/actors/changes.js create mode 100644 devtools/server/actors/common.js create mode 100644 devtools/server/actors/compatibility/compatibility.js create mode 100644 devtools/server/actors/compatibility/lib/MDNCompatibility.js create mode 100644 devtools/server/actors/compatibility/lib/moz.build create mode 100644 devtools/server/actors/compatibility/lib/test/xpcshell/.eslintrc.js create mode 100644 devtools/server/actors/compatibility/lib/test/xpcshell/head.js create mode 100644 devtools/server/actors/compatibility/lib/test/xpcshell/test_mdn-compatibility.js create mode 100644 devtools/server/actors/compatibility/lib/test/xpcshell/xpcshell.toml create mode 100644 devtools/server/actors/compatibility/moz.build create mode 100644 devtools/server/actors/css-properties.js create mode 100644 devtools/server/actors/descriptors/moz.build create mode 100644 devtools/server/actors/descriptors/process.js create mode 100644 devtools/server/actors/descriptors/tab.js create mode 100644 devtools/server/actors/descriptors/webextension.js create mode 100644 devtools/server/actors/descriptors/worker.js create mode 100644 devtools/server/actors/device.js create mode 100644 devtools/server/actors/emulation/moz.build create mode 100644 devtools/server/actors/emulation/responsive.js create mode 100644 devtools/server/actors/emulation/touch-simulator.js create mode 100644 devtools/server/actors/environment.js create mode 100644 devtools/server/actors/errordocs.js create mode 100644 devtools/server/actors/frame.js create mode 100644 devtools/server/actors/heap-snapshot-file.js create mode 100644 devtools/server/actors/highlighters.js create mode 100644 devtools/server/actors/highlighters/accessible.js create mode 100644 devtools/server/actors/highlighters/auto-refresh.js create mode 100644 devtools/server/actors/highlighters/box-model.js create mode 100644 devtools/server/actors/highlighters/css-grid.js create mode 100644 devtools/server/actors/highlighters/css-transform.js create mode 100644 devtools/server/actors/highlighters/css/highlighters.css create mode 100644 devtools/server/actors/highlighters/css/moz.build create mode 100644 devtools/server/actors/highlighters/eye-dropper.js create mode 100644 devtools/server/actors/highlighters/flexbox.js create mode 100644 devtools/server/actors/highlighters/fonts.js create mode 100644 devtools/server/actors/highlighters/geometry-editor.js create mode 100644 devtools/server/actors/highlighters/measuring-tool.js create mode 100644 devtools/server/actors/highlighters/moz.build create mode 100644 devtools/server/actors/highlighters/node-tabbing-order.js create mode 100644 devtools/server/actors/highlighters/paused-debugger.js create mode 100644 devtools/server/actors/highlighters/remote-node-picker-notice.js create mode 100644 devtools/server/actors/highlighters/rulers.js create mode 100644 devtools/server/actors/highlighters/selector.js create mode 100644 devtools/server/actors/highlighters/shapes.js create mode 100644 devtools/server/actors/highlighters/tabbing-order.js create mode 100644 devtools/server/actors/highlighters/utils/accessibility.js create mode 100644 devtools/server/actors/highlighters/utils/canvas.js create mode 100644 devtools/server/actors/highlighters/utils/markup.js create mode 100644 devtools/server/actors/highlighters/utils/moz.build create mode 100644 devtools/server/actors/highlighters/viewport-size.js create mode 100644 devtools/server/actors/inspector/constants.js create mode 100644 devtools/server/actors/inspector/css-logic.js create mode 100644 devtools/server/actors/inspector/custom-element-watcher.js create mode 100644 devtools/server/actors/inspector/document-walker.js create mode 100644 devtools/server/actors/inspector/event-collector.js create mode 100644 devtools/server/actors/inspector/inspector.js create mode 100644 devtools/server/actors/inspector/moz.build create mode 100644 devtools/server/actors/inspector/node-picker.js create mode 100644 devtools/server/actors/inspector/node.js create mode 100644 devtools/server/actors/inspector/utils.js create mode 100644 devtools/server/actors/inspector/walker.js create mode 100644 devtools/server/actors/layout.js create mode 100644 devtools/server/actors/manifest.js create mode 100644 devtools/server/actors/memory.js create mode 100644 devtools/server/actors/moz.build create mode 100644 devtools/server/actors/network-monitor/channel-event-sink.js create mode 100644 devtools/server/actors/network-monitor/moz.build create mode 100644 devtools/server/actors/network-monitor/network-content.js create mode 100644 devtools/server/actors/network-monitor/network-event-actor.js create mode 100644 devtools/server/actors/network-monitor/network-parent.js create mode 100644 devtools/server/actors/object.js create mode 100644 devtools/server/actors/object/moz.build create mode 100644 devtools/server/actors/object/previewers.js create mode 100644 devtools/server/actors/object/private-properties-iterator.js create mode 100644 devtools/server/actors/object/property-iterator.js create mode 100644 devtools/server/actors/object/symbol-iterator.js create mode 100644 devtools/server/actors/object/symbol.js create mode 100644 devtools/server/actors/object/utils.js create mode 100644 devtools/server/actors/objects-manager.js create mode 100644 devtools/server/actors/page-style.js create mode 100644 devtools/server/actors/pause-scoped.js create mode 100644 devtools/server/actors/perf.js create mode 100644 devtools/server/actors/preference.js create mode 100644 devtools/server/actors/process.js create mode 100644 devtools/server/actors/reflow.js create mode 100644 devtools/server/actors/resources/console-messages.js create mode 100644 devtools/server/actors/resources/css-changes.js create mode 100644 devtools/server/actors/resources/css-messages.js create mode 100644 devtools/server/actors/resources/css-registered-properties.js create mode 100644 devtools/server/actors/resources/document-event.js create mode 100644 devtools/server/actors/resources/error-messages.js create mode 100644 devtools/server/actors/resources/extensions-backgroundscript-status.js create mode 100644 devtools/server/actors/resources/index.js create mode 100644 devtools/server/actors/resources/jstracer-state.js create mode 100644 devtools/server/actors/resources/jstracer-trace.js create mode 100644 devtools/server/actors/resources/last-private-context-exit.js create mode 100644 devtools/server/actors/resources/moz.build create mode 100644 devtools/server/actors/resources/network-events-content.js create mode 100644 devtools/server/actors/resources/network-events-stacktraces.js create mode 100644 devtools/server/actors/resources/network-events.js create mode 100644 devtools/server/actors/resources/parent-process-document-event.js create mode 100644 devtools/server/actors/resources/platform-messages.js create mode 100644 devtools/server/actors/resources/reflow.js create mode 100644 devtools/server/actors/resources/server-sent-events.js create mode 100644 devtools/server/actors/resources/sources.js create mode 100644 devtools/server/actors/resources/storage-cache.js create mode 100644 devtools/server/actors/resources/storage-cookie.js create mode 100644 devtools/server/actors/resources/storage-extension.js create mode 100644 devtools/server/actors/resources/storage-indexed-db.js create mode 100644 devtools/server/actors/resources/storage-local-storage.js create mode 100644 devtools/server/actors/resources/storage-session-storage.js create mode 100644 devtools/server/actors/resources/storage/cache.js create mode 100644 devtools/server/actors/resources/storage/cookies.js create mode 100644 devtools/server/actors/resources/storage/extension-storage.js create mode 100644 devtools/server/actors/resources/storage/index.js create mode 100644 devtools/server/actors/resources/storage/indexed-db.js create mode 100644 devtools/server/actors/resources/storage/local-and-session-storage.js create mode 100644 devtools/server/actors/resources/storage/moz.build create mode 100644 devtools/server/actors/resources/stylesheets.js create mode 100644 devtools/server/actors/resources/thread-states.js create mode 100644 devtools/server/actors/resources/utils/content-process-storage.js create mode 100644 devtools/server/actors/resources/utils/moz.build create mode 100644 devtools/server/actors/resources/utils/nsi-console-listener-watcher.js create mode 100644 devtools/server/actors/resources/utils/parent-process-storage.js create mode 100644 devtools/server/actors/resources/websockets.js create mode 100644 devtools/server/actors/root.js create mode 100644 devtools/server/actors/screenshot-content.js create mode 100644 devtools/server/actors/screenshot.js create mode 100644 devtools/server/actors/source.js create mode 100644 devtools/server/actors/string.js create mode 100644 devtools/server/actors/style-rule.js create mode 100644 devtools/server/actors/style-sheets.js create mode 100644 devtools/server/actors/target-configuration.js create mode 100644 devtools/server/actors/targets/base-target-actor.js create mode 100644 devtools/server/actors/targets/content-process.js create mode 100644 devtools/server/actors/targets/index.js create mode 100644 devtools/server/actors/targets/moz.build create mode 100644 devtools/server/actors/targets/parent-process.js create mode 100644 devtools/server/actors/targets/session-data-processors/blackboxing.js create mode 100644 devtools/server/actors/targets/session-data-processors/breakpoints.js create mode 100644 devtools/server/actors/targets/session-data-processors/event-breakpoints.js create mode 100644 devtools/server/actors/targets/session-data-processors/index.js create mode 100644 devtools/server/actors/targets/session-data-processors/moz.build create mode 100644 devtools/server/actors/targets/session-data-processors/resources.js create mode 100644 devtools/server/actors/targets/session-data-processors/target-configuration.js create mode 100644 devtools/server/actors/targets/session-data-processors/thread-configuration.js create mode 100644 devtools/server/actors/targets/session-data-processors/xhr-breakpoints.js create mode 100644 devtools/server/actors/targets/target-actor-registry.sys.mjs create mode 100644 devtools/server/actors/targets/webextension.js create mode 100644 devtools/server/actors/targets/window-global.js create mode 100644 devtools/server/actors/targets/worker.js create mode 100644 devtools/server/actors/thread-configuration.js create mode 100644 devtools/server/actors/thread.js create mode 100644 devtools/server/actors/tracer.js create mode 100644 devtools/server/actors/utils/accessibility.js create mode 100644 devtools/server/actors/utils/actor-registry.js create mode 100644 devtools/server/actors/utils/breakpoint-actor-map.js create mode 100644 devtools/server/actors/utils/capture-screenshot.js create mode 100644 devtools/server/actors/utils/css-grid-utils.js create mode 100644 devtools/server/actors/utils/custom-formatters.js create mode 100644 devtools/server/actors/utils/dbg-source.js create mode 100644 devtools/server/actors/utils/event-breakpoints.js create mode 100644 devtools/server/actors/utils/event-loop.js create mode 100644 devtools/server/actors/utils/gecko-profile-collector.js create mode 100644 devtools/server/actors/utils/inactive-property-helper.js create mode 100644 devtools/server/actors/utils/logEvent.js create mode 100644 devtools/server/actors/utils/make-debugger.js create mode 100644 devtools/server/actors/utils/moz.build create mode 100644 devtools/server/actors/utils/shapes-utils.js create mode 100644 devtools/server/actors/utils/source-map-utils.js create mode 100644 devtools/server/actors/utils/source-url.js create mode 100644 devtools/server/actors/utils/sources-manager.js create mode 100644 devtools/server/actors/utils/stack.js create mode 100644 devtools/server/actors/utils/style-utils.js create mode 100644 devtools/server/actors/utils/stylesheet-utils.js create mode 100644 devtools/server/actors/utils/stylesheets-manager.js create mode 100644 devtools/server/actors/utils/track-change-emitter.js create mode 100644 devtools/server/actors/utils/walker-search.js create mode 100644 devtools/server/actors/utils/watchpoint-map.js create mode 100644 devtools/server/actors/watcher.js create mode 100644 devtools/server/actors/watcher/SessionDataHelpers.jsm create mode 100644 devtools/server/actors/watcher/WatcherRegistry.sys.mjs create mode 100644 devtools/server/actors/watcher/browsing-context-helpers.sys.mjs create mode 100644 devtools/server/actors/watcher/moz.build create mode 100644 devtools/server/actors/watcher/session-context.js create mode 100644 devtools/server/actors/watcher/target-helpers/frame-helper.js create mode 100644 devtools/server/actors/watcher/target-helpers/moz.build create mode 100644 devtools/server/actors/watcher/target-helpers/process-helper.js create mode 100644 devtools/server/actors/watcher/target-helpers/service-worker-helper.js create mode 100644 devtools/server/actors/watcher/target-helpers/service-worker-jsprocessactor-startup.js create mode 100644 devtools/server/actors/watcher/target-helpers/worker-helper.js create mode 100644 devtools/server/actors/webbrowser.js create mode 100644 devtools/server/actors/webconsole.js create mode 100644 devtools/server/actors/webconsole/commands/experimental-commands.ftl create mode 100644 devtools/server/actors/webconsole/commands/manager.js create mode 100644 devtools/server/actors/webconsole/commands/moz.build create mode 100644 devtools/server/actors/webconsole/commands/parser.js create mode 100644 devtools/server/actors/webconsole/eager-ecma-allowlist.js create mode 100644 devtools/server/actors/webconsole/eager-function-allowlist.js create mode 100644 devtools/server/actors/webconsole/eval-with-debugger.js create mode 100644 devtools/server/actors/webconsole/listeners/console-api.js create mode 100644 devtools/server/actors/webconsole/listeners/console-file-activity.js create mode 100644 devtools/server/actors/webconsole/listeners/console-reflow.js create mode 100644 devtools/server/actors/webconsole/listeners/console-service.js create mode 100644 devtools/server/actors/webconsole/listeners/document-events.js create mode 100644 devtools/server/actors/webconsole/listeners/moz.build create mode 100644 devtools/server/actors/webconsole/moz.build create mode 100644 devtools/server/actors/webconsole/utils.js create mode 100644 devtools/server/actors/webconsole/webidl-pure-allowlist.js create mode 100644 devtools/server/actors/webconsole/webidl-unsafe-getters-names.js create mode 100644 devtools/server/actors/webconsole/worker-listeners.js create mode 100644 devtools/server/actors/worker/moz.build create mode 100644 devtools/server/actors/worker/push-subscription.js create mode 100644 devtools/server/actors/worker/service-worker-registration-list.js create mode 100644 devtools/server/actors/worker/service-worker-registration.js create mode 100644 devtools/server/actors/worker/service-worker.js create mode 100644 devtools/server/actors/worker/worker-descriptor-actor-list.js create mode 100644 devtools/server/connectors/content-process-connector.js create mode 100644 devtools/server/connectors/frame-connector.js create mode 100644 devtools/server/connectors/js-window-actor/DevToolsFrameChild.sys.mjs create mode 100644 devtools/server/connectors/js-window-actor/DevToolsFrameParent.sys.mjs create mode 100644 devtools/server/connectors/js-window-actor/DevToolsWorkerChild.sys.mjs create mode 100644 devtools/server/connectors/js-window-actor/DevToolsWorkerParent.sys.mjs create mode 100644 devtools/server/connectors/js-window-actor/WindowGlobalLogger.sys.mjs create mode 100644 devtools/server/connectors/js-window-actor/moz.build create mode 100644 devtools/server/connectors/moz.build create mode 100644 devtools/server/connectors/process-actor/DevToolsServiceWorkerChild.sys.mjs create mode 100644 devtools/server/connectors/process-actor/DevToolsServiceWorkerParent.sys.mjs create mode 100644 devtools/server/connectors/process-actor/moz.build create mode 100644 devtools/server/connectors/worker-connector.js create mode 100644 devtools/server/devtools-server-connection.js create mode 100644 devtools/server/devtools-server.js create mode 100644 devtools/server/jar.mn create mode 100644 devtools/server/moz.build create mode 100644 devtools/server/performance/memory.js create mode 100644 devtools/server/performance/moz.build create mode 100644 devtools/server/socket/moz.build create mode 100644 devtools/server/socket/tests/chrome/chrome.toml create mode 100644 devtools/server/socket/tests/chrome/test_websocket-server.html create mode 100644 devtools/server/socket/websocket-server.js create mode 100644 devtools/server/startup/content-process-script.js create mode 100644 devtools/server/startup/content-process.js create mode 100644 devtools/server/startup/content-process.sys.mjs create mode 100644 devtools/server/startup/frame.js create mode 100644 devtools/server/startup/moz.build create mode 100644 devtools/server/startup/worker.js create mode 100644 devtools/server/tests/browser/animation-data.html create mode 100644 devtools/server/tests/browser/animation.html create mode 100644 devtools/server/tests/browser/application-manifest-404-manifest.html create mode 100644 devtools/server/tests/browser/application-manifest-basic.html create mode 100644 devtools/server/tests/browser/application-manifest-invalid-json.html create mode 100644 devtools/server/tests/browser/application-manifest-no-manifest.html create mode 100644 devtools/server/tests/browser/application-manifest-warnings.html create mode 100644 devtools/server/tests/browser/browser.toml create mode 100644 devtools/server/tests/browser/browser_accessibility_highlighter_infobar.js create mode 100644 devtools/server/tests/browser/browser_accessibility_infobar_audit_keyboard.js create mode 100644 devtools/server/tests/browser/browser_accessibility_infobar_audit_text_label.js create mode 100644 devtools/server/tests/browser/browser_accessibility_infobar_show.js create mode 100644 devtools/server/tests/browser/browser_accessibility_keyboard_audit.js create mode 100644 devtools/server/tests/browser/browser_accessibility_node.js create mode 100644 devtools/server/tests/browser/browser_accessibility_node_audit.js create mode 100644 devtools/server/tests/browser/browser_accessibility_node_events.js create mode 100644 devtools/server/tests/browser/browser_accessibility_node_tabbing_order_highlighter.js create mode 100644 devtools/server/tests/browser/browser_accessibility_simple.js create mode 100644 devtools/server/tests/browser/browser_accessibility_simulator.js create mode 100644 devtools/server/tests/browser/browser_accessibility_tabbing_order_highlighter.js create mode 100644 devtools/server/tests/browser/browser_accessibility_text_label_audit.js create mode 100644 devtools/server/tests/browser/browser_accessibility_text_label_audit_frame.js create mode 100644 devtools/server/tests/browser/browser_accessibility_walker.js create mode 100644 devtools/server/tests/browser/browser_accessibility_walker_audit.js create mode 100644 devtools/server/tests/browser/browser_actor_error.js create mode 100644 devtools/server/tests/browser/browser_animation_actor-lifetime.js create mode 100644 devtools/server/tests/browser/browser_animation_emitMutations.js create mode 100644 devtools/server/tests/browser/browser_animation_getMultipleStates.js create mode 100644 devtools/server/tests/browser/browser_animation_getPlayers.js create mode 100644 devtools/server/tests/browser/browser_animation_getStateAfterFinished.js create mode 100644 devtools/server/tests/browser/browser_animation_getSubTreeAnimations.js create mode 100644 devtools/server/tests/browser/browser_animation_keepFinished.js create mode 100644 devtools/server/tests/browser/browser_animation_playPauseIframe.js create mode 100644 devtools/server/tests/browser/browser_animation_playPauseSeveral.js create mode 100644 devtools/server/tests/browser/browser_animation_playerState.js create mode 100644 devtools/server/tests/browser/browser_animation_reconstructState.js create mode 100644 devtools/server/tests/browser/browser_animation_refreshTransitions.js create mode 100644 devtools/server/tests/browser/browser_animation_setCurrentTime.js create mode 100644 devtools/server/tests/browser/browser_animation_setPlaybackRate.js create mode 100644 devtools/server/tests/browser/browser_animation_simple.js create mode 100644 devtools/server/tests/browser/browser_animation_updatedState.js create mode 100644 devtools/server/tests/browser/browser_application_manifest.js create mode 100644 devtools/server/tests/browser/browser_canvasframe_helper_01.js create mode 100644 devtools/server/tests/browser/browser_canvasframe_helper_02.js create mode 100644 devtools/server/tests/browser/browser_canvasframe_helper_03.js create mode 100644 devtools/server/tests/browser/browser_canvasframe_helper_04.js create mode 100644 devtools/server/tests/browser/browser_canvasframe_helper_05.js create mode 100644 devtools/server/tests/browser/browser_canvasframe_helper_06.js create mode 100644 devtools/server/tests/browser/browser_compatibility_cssIssues.js create mode 100644 devtools/server/tests/browser/browser_connectToFrame.js create mode 100644 devtools/server/tests/browser/browser_debugger_server.js create mode 100644 devtools/server/tests/browser/browser_document_devtools_basics.js create mode 100644 devtools/server/tests/browser/browser_document_rdp_basics.js create mode 100644 devtools/server/tests/browser/browser_getProcess.js create mode 100644 devtools/server/tests/browser/browser_inspector-anonymous.js create mode 100644 devtools/server/tests/browser/browser_inspector-iframe.js create mode 100644 devtools/server/tests/browser/browser_inspector-insert.js create mode 100644 devtools/server/tests/browser/browser_inspector-isScrollable.js create mode 100644 devtools/server/tests/browser/browser_inspector-mutations-childlist.js create mode 100644 devtools/server/tests/browser/browser_inspector-release.js create mode 100644 devtools/server/tests/browser/browser_inspector-remove.js create mode 100644 devtools/server/tests/browser/browser_inspector-retain.js create mode 100644 devtools/server/tests/browser/browser_inspector-search.js create mode 100644 devtools/server/tests/browser/browser_inspector-shadow.js create mode 100644 devtools/server/tests/browser/browser_inspector-traversal.js create mode 100644 devtools/server/tests/browser/browser_inspector-utils.js create mode 100644 devtools/server/tests/browser/browser_layout_getGrids.js create mode 100644 devtools/server/tests/browser/browser_layout_simple.js create mode 100644 devtools/server/tests/browser/browser_memory_allocations_01.js create mode 100644 devtools/server/tests/browser/browser_perf-01.js create mode 100644 devtools/server/tests/browser/browser_perf-02.js create mode 100644 devtools/server/tests/browser/browser_perf-04.js create mode 100644 devtools/server/tests/browser/browser_perf-getSupportedFeatures.js create mode 100644 devtools/server/tests/browser/browser_storage_cookies-duplicate-names.js create mode 100644 devtools/server/tests/browser/browser_storage_dynamic_windows.js create mode 100644 devtools/server/tests/browser/browser_storage_listings.js create mode 100644 devtools/server/tests/browser/browser_storage_updates.js create mode 100644 devtools/server/tests/browser/browser_style_utils_getFontPreviewData.js create mode 100644 devtools/server/tests/browser/browser_styles_getRuleText.js create mode 100644 devtools/server/tests/browser/browser_stylesheets_getTextEmpty.js create mode 100644 devtools/server/tests/browser/director-script-target.html create mode 100644 devtools/server/tests/browser/doc_accessibility.html create mode 100644 devtools/server/tests/browser/doc_accessibility_audit.html create mode 100644 devtools/server/tests/browser/doc_accessibility_infobar.html create mode 100644 devtools/server/tests/browser/doc_accessibility_keyboard_audit.html create mode 100644 devtools/server/tests/browser/doc_accessibility_text_label_audit.html create mode 100644 devtools/server/tests/browser/doc_accessibility_text_label_audit_frame.html create mode 100644 devtools/server/tests/browser/doc_allocations.html create mode 100644 devtools/server/tests/browser/doc_compatibility.html create mode 100644 devtools/server/tests/browser/doc_force_cc.html create mode 100644 devtools/server/tests/browser/doc_force_gc.html create mode 100644 devtools/server/tests/browser/doc_iframe.html create mode 100644 devtools/server/tests/browser/doc_iframe2.html create mode 100644 devtools/server/tests/browser/doc_iframe_content.html create mode 100644 devtools/server/tests/browser/doc_innerHTML.html create mode 100644 devtools/server/tests/browser/error-actor.js create mode 100644 devtools/server/tests/browser/grid.html create mode 100644 devtools/server/tests/browser/head.js create mode 100644 devtools/server/tests/browser/inspector-helpers.js create mode 100644 devtools/server/tests/browser/inspector-isScrollable-data.html create mode 100644 devtools/server/tests/browser/inspector-search-data.html create mode 100644 devtools/server/tests/browser/inspector-shadow.html create mode 100644 devtools/server/tests/browser/inspector-traversal-data.html create mode 100644 devtools/server/tests/browser/storage-cookies-same-name.html create mode 100644 devtools/server/tests/browser/storage-dynamic-windows.html create mode 100644 devtools/server/tests/browser/storage-helpers.js create mode 100644 devtools/server/tests/browser/storage-listings.html create mode 100644 devtools/server/tests/browser/storage-secured-iframe.html create mode 100644 devtools/server/tests/browser/storage-unsecured-iframe.html create mode 100644 devtools/server/tests/browser/storage-updates.html create mode 100644 devtools/server/tests/browser/test-errors-actor.js create mode 100644 devtools/server/tests/browser/test-window.xhtml create mode 100644 devtools/server/tests/chrome/Debugger.Source.prototype.element-2.js create mode 100644 devtools/server/tests/chrome/Debugger.Source.prototype.element.html create mode 100644 devtools/server/tests/chrome/Debugger.Source.prototype.element.js create mode 100644 devtools/server/tests/chrome/chrome.toml create mode 100644 devtools/server/tests/chrome/doc_Debugger.Source.prototype.introductionType.xhtml create mode 100644 devtools/server/tests/chrome/hello-actor.js create mode 100644 devtools/server/tests/chrome/iframe1_makeGlobalObjectReference.html create mode 100644 devtools/server/tests/chrome/iframe2_makeGlobalObjectReference.html create mode 100644 devtools/server/tests/chrome/inactive-property-helper/align-content.mjs create mode 100644 devtools/server/tests/chrome/inactive-property-helper/border-image.mjs create mode 100644 devtools/server/tests/chrome/inactive-property-helper/cue-pseudo-element.mjs create mode 100644 devtools/server/tests/chrome/inactive-property-helper/first-letter-pseudo-element.mjs create mode 100644 devtools/server/tests/chrome/inactive-property-helper/first-line-pseudo-element.mjs create mode 100644 devtools/server/tests/chrome/inactive-property-helper/flex-grid-item-properties.mjs create mode 100644 devtools/server/tests/chrome/inactive-property-helper/float.mjs create mode 100644 devtools/server/tests/chrome/inactive-property-helper/gap.mjs create mode 100644 devtools/server/tests/chrome/inactive-property-helper/grid-container-properties.mjs create mode 100644 devtools/server/tests/chrome/inactive-property-helper/grid-with-absolute-properties.mjs create mode 100644 devtools/server/tests/chrome/inactive-property-helper/highlight-pseudo-elements.mjs create mode 100644 devtools/server/tests/chrome/inactive-property-helper/margin-padding.mjs create mode 100644 devtools/server/tests/chrome/inactive-property-helper/max-min-width-height.mjs create mode 100644 devtools/server/tests/chrome/inactive-property-helper/multicol-container-properties.mjs create mode 100644 devtools/server/tests/chrome/inactive-property-helper/place-items-content.mjs create mode 100644 devtools/server/tests/chrome/inactive-property-helper/placeholder-pseudo-element.mjs create mode 100644 devtools/server/tests/chrome/inactive-property-helper/positioned.mjs create mode 100644 devtools/server/tests/chrome/inactive-property-helper/scroll-padding.mjs create mode 100644 devtools/server/tests/chrome/inactive-property-helper/table-cell.mjs create mode 100644 devtools/server/tests/chrome/inactive-property-helper/table.mjs create mode 100644 devtools/server/tests/chrome/inactive-property-helper/text-overflow.mjs create mode 100644 devtools/server/tests/chrome/inactive-property-helper/text-wrap.mjs create mode 100644 devtools/server/tests/chrome/inactive-property-helper/vertical-align.mjs create mode 100644 devtools/server/tests/chrome/inactive-property-helper/width-height-ruby.mjs create mode 100644 devtools/server/tests/chrome/inspector-delay-image-response.sjs create mode 100644 devtools/server/tests/chrome/inspector-eyedropper.html create mode 100644 devtools/server/tests/chrome/inspector-helpers.js create mode 100644 devtools/server/tests/chrome/inspector-search-data.html create mode 100644 devtools/server/tests/chrome/inspector-styles-data.css create mode 100644 devtools/server/tests/chrome/inspector-styles-data.html create mode 100644 devtools/server/tests/chrome/inspector-template.html create mode 100644 devtools/server/tests/chrome/inspector-traversal-data.html create mode 100644 devtools/server/tests/chrome/inspector_css-properties.html create mode 100644 devtools/server/tests/chrome/inspector_display-type.html create mode 100644 devtools/server/tests/chrome/inspector_getImageData.html create mode 100644 devtools/server/tests/chrome/inspector_getOffsetParent.html create mode 100644 devtools/server/tests/chrome/large-image.jpg create mode 100644 devtools/server/tests/chrome/memory-helpers.js create mode 100644 devtools/server/tests/chrome/nonchrome_unsafeDereference.html create mode 100644 devtools/server/tests/chrome/small-image.gif create mode 100644 devtools/server/tests/chrome/suspendTimeouts_content.html create mode 100644 devtools/server/tests/chrome/suspendTimeouts_content.js create mode 100644 devtools/server/tests/chrome/suspendTimeouts_worker.js create mode 100644 devtools/server/tests/chrome/test_Debugger.Script.prototype.global.html create mode 100644 devtools/server/tests/chrome/test_Debugger.Source.prototype.elementAttribute.html create mode 100644 devtools/server/tests/chrome/test_Debugger.Source.prototype.introductionScript.html create mode 100644 devtools/server/tests/chrome/test_Debugger.Source.prototype.introductionType.html create mode 100644 devtools/server/tests/chrome/test_animation-type-longhand.html create mode 100644 devtools/server/tests/chrome/test_css-logic-specificity.html create mode 100644 devtools/server/tests/chrome/test_css-logic.html create mode 100644 devtools/server/tests/chrome/test_css-properties.html create mode 100644 devtools/server/tests/chrome/test_device.html create mode 100644 devtools/server/tests/chrome/test_executeInGlobal-outerized_this.html create mode 100644 devtools/server/tests/chrome/test_highlighter_paused_debugger.html create mode 100644 devtools/server/tests/chrome/test_inspector-changeattrs.html create mode 100644 devtools/server/tests/chrome/test_inspector-changevalue.html create mode 100644 devtools/server/tests/chrome/test_inspector-display-type.html create mode 100644 devtools/server/tests/chrome/test_inspector-duplicate-node.html create mode 100644 devtools/server/tests/chrome/test_inspector-hide.html create mode 100644 devtools/server/tests/chrome/test_inspector-inactive-property-helper.html create mode 100644 devtools/server/tests/chrome/test_inspector-mutations-attr.html create mode 100644 devtools/server/tests/chrome/test_inspector-mutations-events.html create mode 100644 devtools/server/tests/chrome/test_inspector-mutations-value.html create mode 100644 devtools/server/tests/chrome/test_inspector-pick-color.html create mode 100644 devtools/server/tests/chrome/test_inspector-pseudoclass-lock.html create mode 100644 devtools/server/tests/chrome/test_inspector-reload.html create mode 100644 devtools/server/tests/chrome/test_inspector-resize.html create mode 100644 devtools/server/tests/chrome/test_inspector-resolve-url.html create mode 100644 devtools/server/tests/chrome/test_inspector-scroll-into-view.html create mode 100644 devtools/server/tests/chrome/test_inspector-search-front.html create mode 100644 devtools/server/tests/chrome/test_inspector-template.html create mode 100644 devtools/server/tests/chrome/test_inspector_getImageData-wait-for-load.html create mode 100644 devtools/server/tests/chrome/test_inspector_getImageData.html create mode 100644 devtools/server/tests/chrome/test_inspector_getImageDataFromURL.html create mode 100644 devtools/server/tests/chrome/test_inspector_getNodeFromActor.html create mode 100644 devtools/server/tests/chrome/test_inspector_getOffsetParent.html create mode 100644 devtools/server/tests/chrome/test_makeGlobalObjectReference.html create mode 100644 devtools/server/tests/chrome/test_memory.html create mode 100644 devtools/server/tests/chrome/test_memory_allocations_02.html create mode 100644 devtools/server/tests/chrome/test_memory_allocations_03.html create mode 100644 devtools/server/tests/chrome/test_memory_allocations_04.html create mode 100644 devtools/server/tests/chrome/test_memory_allocations_05.html create mode 100644 devtools/server/tests/chrome/test_memory_allocations_06.html create mode 100644 devtools/server/tests/chrome/test_memory_allocations_07.html create mode 100644 devtools/server/tests/chrome/test_memory_attach_01.html create mode 100644 devtools/server/tests/chrome/test_memory_attach_02.html create mode 100644 devtools/server/tests/chrome/test_memory_census.html create mode 100644 devtools/server/tests/chrome/test_memory_gc_01.html create mode 100644 devtools/server/tests/chrome/test_memory_gc_events.html create mode 100644 devtools/server/tests/chrome/test_overflowing-body.html create mode 100644 devtools/server/tests/chrome/test_overflowing-children.html create mode 100644 devtools/server/tests/chrome/test_preference.html create mode 100644 devtools/server/tests/chrome/test_styles-applied.html create mode 100644 devtools/server/tests/chrome/test_styles-computed.html create mode 100644 devtools/server/tests/chrome/test_styles-layout.html create mode 100644 devtools/server/tests/chrome/test_styles-matched.html create mode 100644 devtools/server/tests/chrome/test_styles-modify.html create mode 100644 devtools/server/tests/chrome/test_styles-svg.html create mode 100644 devtools/server/tests/chrome/test_suspendTimeouts.html create mode 100644 devtools/server/tests/chrome/test_suspendTimeouts.js create mode 100644 devtools/server/tests/chrome/test_unsafeDereference.html create mode 100644 devtools/server/tests/chrome/test_webconsole-node-grip.html create mode 100644 devtools/server/tests/chrome/webconsole-helpers.js create mode 100644 devtools/server/tests/xpcshell/.eslintrc.js create mode 100644 devtools/server/tests/xpcshell/addons/web-extension-upgrade/manifest.json create mode 100644 devtools/server/tests/xpcshell/addons/web-extension/manifest.json create mode 100644 devtools/server/tests/xpcshell/addons/web-extension2/manifest.json create mode 100644 devtools/server/tests/xpcshell/completions.js create mode 100644 devtools/server/tests/xpcshell/head_dbg.js create mode 100644 devtools/server/tests/xpcshell/hello-actor.js create mode 100644 devtools/server/tests/xpcshell/post_init_global_actors.js create mode 100644 devtools/server/tests/xpcshell/post_init_target_scoped_actors.js create mode 100644 devtools/server/tests/xpcshell/pre_init_global_actors.js create mode 100644 devtools/server/tests/xpcshell/pre_init_target_scoped_actors.js create mode 100644 devtools/server/tests/xpcshell/registertestactors-lazy.js create mode 100644 devtools/server/tests/xpcshell/setBreakpoint-on-column-in-gcd-script.js create mode 100644 devtools/server/tests/xpcshell/setBreakpoint-on-column-minified.js create mode 100644 devtools/server/tests/xpcshell/setBreakpoint-on-column-with-no-offsets-in-gcd-script.js create mode 100644 devtools/server/tests/xpcshell/setBreakpoint-on-column-with-no-offsets.js create mode 100644 devtools/server/tests/xpcshell/setBreakpoint-on-column.js create mode 100644 devtools/server/tests/xpcshell/setBreakpoint-on-line-in-gcd-script.js create mode 100644 devtools/server/tests/xpcshell/setBreakpoint-on-line-with-multiple-offsets.js create mode 100644 devtools/server/tests/xpcshell/setBreakpoint-on-line-with-multiple-statements.js create mode 100644 devtools/server/tests/xpcshell/setBreakpoint-on-line-with-no-offsets-in-gcd-script.js create mode 100644 devtools/server/tests/xpcshell/setBreakpoint-on-line-with-no-offsets.js create mode 100644 devtools/server/tests/xpcshell/setBreakpoint-on-line.js create mode 100644 devtools/server/tests/xpcshell/source-03.js create mode 100644 devtools/server/tests/xpcshell/source-map-data/sourcemapped.coffee create mode 100644 devtools/server/tests/xpcshell/source-map-data/sourcemapped.map create mode 100644 devtools/server/tests/xpcshell/sourcemapped.js create mode 100644 devtools/server/tests/xpcshell/stepping-async.js create mode 100644 devtools/server/tests/xpcshell/stepping.js create mode 100644 devtools/server/tests/xpcshell/test_MemoryActor_saveHeapSnapshot_01.js create mode 100644 devtools/server/tests/xpcshell/test_MemoryActor_saveHeapSnapshot_02.js create mode 100644 devtools/server/tests/xpcshell/test_MemoryActor_saveHeapSnapshot_03.js create mode 100644 devtools/server/tests/xpcshell/test_add_actors.js create mode 100644 devtools/server/tests/xpcshell/test_addon_debugging_connect.js create mode 100644 devtools/server/tests/xpcshell/test_addon_events.js create mode 100644 devtools/server/tests/xpcshell/test_addon_reload.js create mode 100644 devtools/server/tests/xpcshell/test_addons_actor.js create mode 100644 devtools/server/tests/xpcshell/test_animation_name.js create mode 100644 devtools/server/tests/xpcshell/test_animation_type.js create mode 100644 devtools/server/tests/xpcshell/test_attach.js create mode 100644 devtools/server/tests/xpcshell/test_blackboxing-01.js create mode 100644 devtools/server/tests/xpcshell/test_blackboxing-02.js create mode 100644 devtools/server/tests/xpcshell/test_blackboxing-03.js create mode 100644 devtools/server/tests/xpcshell/test_blackboxing-04.js create mode 100644 devtools/server/tests/xpcshell/test_blackboxing-05.js create mode 100644 devtools/server/tests/xpcshell/test_blackboxing-08.js create mode 100644 devtools/server/tests/xpcshell/test_breakpoint-01.js create mode 100644 devtools/server/tests/xpcshell/test_breakpoint-03.js create mode 100644 devtools/server/tests/xpcshell/test_breakpoint-04.js create mode 100644 devtools/server/tests/xpcshell/test_breakpoint-05.js create mode 100644 devtools/server/tests/xpcshell/test_breakpoint-06.js create mode 100644 devtools/server/tests/xpcshell/test_breakpoint-07.js create mode 100644 devtools/server/tests/xpcshell/test_breakpoint-08.js create mode 100644 devtools/server/tests/xpcshell/test_breakpoint-09.js create mode 100644 devtools/server/tests/xpcshell/test_breakpoint-10.js create mode 100644 devtools/server/tests/xpcshell/test_breakpoint-11.js create mode 100644 devtools/server/tests/xpcshell/test_breakpoint-12.js create mode 100644 devtools/server/tests/xpcshell/test_breakpoint-13.js create mode 100644 devtools/server/tests/xpcshell/test_breakpoint-14.js create mode 100644 devtools/server/tests/xpcshell/test_breakpoint-16.js create mode 100644 devtools/server/tests/xpcshell/test_breakpoint-17.js create mode 100644 devtools/server/tests/xpcshell/test_breakpoint-18.js create mode 100644 devtools/server/tests/xpcshell/test_breakpoint-19.js create mode 100644 devtools/server/tests/xpcshell/test_breakpoint-20.js create mode 100644 devtools/server/tests/xpcshell/test_breakpoint-21.js create mode 100644 devtools/server/tests/xpcshell/test_breakpoint-22.js create mode 100644 devtools/server/tests/xpcshell/test_breakpoint-23.js create mode 100644 devtools/server/tests/xpcshell/test_breakpoint-24.js create mode 100644 devtools/server/tests/xpcshell/test_breakpoint-25.js create mode 100644 devtools/server/tests/xpcshell/test_breakpoint-26.js create mode 100644 devtools/server/tests/xpcshell/test_breakpoint-actor-map.js create mode 100644 devtools/server/tests/xpcshell/test_client_request.js create mode 100644 devtools/server/tests/xpcshell/test_conditional_breakpoint-01.js create mode 100644 devtools/server/tests/xpcshell/test_conditional_breakpoint-02.js create mode 100644 devtools/server/tests/xpcshell/test_conditional_breakpoint-03.js create mode 100644 devtools/server/tests/xpcshell/test_conditional_breakpoint-04.js create mode 100644 devtools/server/tests/xpcshell/test_connection_closes_all_pools.js create mode 100644 devtools/server/tests/xpcshell/test_console_eval-01.js create mode 100644 devtools/server/tests/xpcshell/test_console_eval-02.js create mode 100644 devtools/server/tests/xpcshell/test_dbgactor.js create mode 100644 devtools/server/tests/xpcshell/test_dbgclient_debuggerstatement.js create mode 100644 devtools/server/tests/xpcshell/test_dbgglobal.js create mode 100644 devtools/server/tests/xpcshell/test_extension_storage_actor.js create mode 100644 devtools/server/tests/xpcshell/test_extension_storage_actor_upgrade.js create mode 100644 devtools/server/tests/xpcshell/test_forwardingprefix.js create mode 100644 devtools/server/tests/xpcshell/test_frameactor-01.js create mode 100644 devtools/server/tests/xpcshell/test_frameactor-02.js create mode 100644 devtools/server/tests/xpcshell/test_frameactor-03.js create mode 100644 devtools/server/tests/xpcshell/test_frameactor-04.js create mode 100644 devtools/server/tests/xpcshell/test_frameactor-05.js create mode 100644 devtools/server/tests/xpcshell/test_frameactor_wasm-01.js create mode 100644 devtools/server/tests/xpcshell/test_framearguments-01.js create mode 100644 devtools/server/tests/xpcshell/test_framebindings-01.js create mode 100644 devtools/server/tests/xpcshell/test_framebindings-02.js create mode 100644 devtools/server/tests/xpcshell/test_framebindings-03.js create mode 100644 devtools/server/tests/xpcshell/test_framebindings-04.js create mode 100644 devtools/server/tests/xpcshell/test_framebindings-05.js create mode 100644 devtools/server/tests/xpcshell/test_framebindings-06.js create mode 100644 devtools/server/tests/xpcshell/test_framebindings-07.js create mode 100644 devtools/server/tests/xpcshell/test_front_destroy.js create mode 100644 devtools/server/tests/xpcshell/test_functiongrips-01.js create mode 100644 devtools/server/tests/xpcshell/test_getRuleText.js create mode 100644 devtools/server/tests/xpcshell/test_getTextAtLineColumn.js create mode 100644 devtools/server/tests/xpcshell/test_get_command_and_arg.js create mode 100644 devtools/server/tests/xpcshell/test_getyoungestframe.js create mode 100644 devtools/server/tests/xpcshell/test_ignore_caught_exceptions.js create mode 100644 devtools/server/tests/xpcshell/test_ignore_no_interface_exceptions.js create mode 100644 devtools/server/tests/xpcshell/test_interrupt.js create mode 100644 devtools/server/tests/xpcshell/test_layout-reflows-observer.js create mode 100644 devtools/server/tests/xpcshell/test_listsources-01.js create mode 100644 devtools/server/tests/xpcshell/test_listsources-02.js create mode 100644 devtools/server/tests/xpcshell/test_listsources-03.js create mode 100644 devtools/server/tests/xpcshell/test_logpoint-01.js create mode 100644 devtools/server/tests/xpcshell/test_logpoint-02.js create mode 100644 devtools/server/tests/xpcshell/test_logpoint-03.js create mode 100644 devtools/server/tests/xpcshell/test_longstringgrips-01.js create mode 100644 devtools/server/tests/xpcshell/test_nativewrappers.js create mode 100644 devtools/server/tests/xpcshell/test_nesting-03.js create mode 100644 devtools/server/tests/xpcshell/test_nesting-04.js create mode 100644 devtools/server/tests/xpcshell/test_new_source-01.js create mode 100644 devtools/server/tests/xpcshell/test_new_source-02.js create mode 100644 devtools/server/tests/xpcshell/test_nodelistactor.js create mode 100644 devtools/server/tests/xpcshell/test_objectgrips-02.js create mode 100644 devtools/server/tests/xpcshell/test_objectgrips-03.js create mode 100644 devtools/server/tests/xpcshell/test_objectgrips-04.js create mode 100644 devtools/server/tests/xpcshell/test_objectgrips-05.js create mode 100644 devtools/server/tests/xpcshell/test_objectgrips-06.js create mode 100644 devtools/server/tests/xpcshell/test_objectgrips-07.js create mode 100644 devtools/server/tests/xpcshell/test_objectgrips-08.js create mode 100644 devtools/server/tests/xpcshell/test_objectgrips-14.js create mode 100644 devtools/server/tests/xpcshell/test_objectgrips-15.js create mode 100644 devtools/server/tests/xpcshell/test_objectgrips-16.js create mode 100644 devtools/server/tests/xpcshell/test_objectgrips-17.js create mode 100644 devtools/server/tests/xpcshell/test_objectgrips-18.js create mode 100644 devtools/server/tests/xpcshell/test_objectgrips-19.js create mode 100644 devtools/server/tests/xpcshell/test_objectgrips-20.js create mode 100644 devtools/server/tests/xpcshell/test_objectgrips-21.js create mode 100644 devtools/server/tests/xpcshell/test_objectgrips-22.js create mode 100644 devtools/server/tests/xpcshell/test_objectgrips-23.js create mode 100644 devtools/server/tests/xpcshell/test_objectgrips-24.js create mode 100644 devtools/server/tests/xpcshell/test_objectgrips-25.js create mode 100644 devtools/server/tests/xpcshell/test_objectgrips-fn-apply-01.js create mode 100644 devtools/server/tests/xpcshell/test_objectgrips-fn-apply-02.js create mode 100644 devtools/server/tests/xpcshell/test_objectgrips-fn-apply-03.js create mode 100644 devtools/server/tests/xpcshell/test_objectgrips-nested-promise.js create mode 100644 devtools/server/tests/xpcshell/test_objectgrips-nested-proxy.js create mode 100644 devtools/server/tests/xpcshell/test_objectgrips-property-value-01.js create mode 100644 devtools/server/tests/xpcshell/test_objectgrips-property-value-02.js create mode 100644 devtools/server/tests/xpcshell/test_objectgrips-property-value-03.js create mode 100644 devtools/server/tests/xpcshell/test_objectgrips-sparse-array.js create mode 100644 devtools/server/tests/xpcshell/test_pause_exceptions-01.js create mode 100644 devtools/server/tests/xpcshell/test_pause_exceptions-02.js create mode 100644 devtools/server/tests/xpcshell/test_pause_exceptions-03.js create mode 100644 devtools/server/tests/xpcshell/test_pause_exceptions-04.js create mode 100644 devtools/server/tests/xpcshell/test_pauselifetime-01.js create mode 100644 devtools/server/tests/xpcshell/test_pauselifetime-02.js create mode 100644 devtools/server/tests/xpcshell/test_pauselifetime-03.js create mode 100644 devtools/server/tests/xpcshell/test_pauselifetime-04.js create mode 100644 devtools/server/tests/xpcshell/test_promise_state-01.js create mode 100644 devtools/server/tests/xpcshell/test_promise_state-02.js create mode 100644 devtools/server/tests/xpcshell/test_promise_state-03.js create mode 100644 devtools/server/tests/xpcshell/test_promises_run_to_completion.js create mode 100644 devtools/server/tests/xpcshell/test_register_actor.js create mode 100644 devtools/server/tests/xpcshell/test_requestTypes.js create mode 100644 devtools/server/tests/xpcshell/test_restartFrame-01.js create mode 100644 devtools/server/tests/xpcshell/test_safe-getter.js create mode 100644 devtools/server/tests/xpcshell/test_sessionDataHelpers.js create mode 100644 devtools/server/tests/xpcshell/test_setBreakpoint-at-the-beginning-of-a-minified-fn.js create mode 100644 devtools/server/tests/xpcshell/test_setBreakpoint-at-the-end-of-a-minified-fn.js create mode 100644 devtools/server/tests/xpcshell/test_setBreakpoint-on-column-in-gcd-script.js create mode 100644 devtools/server/tests/xpcshell/test_setBreakpoint-on-column.js create mode 100644 devtools/server/tests/xpcshell/test_setBreakpoint-on-line-in-gcd-script.js create mode 100644 devtools/server/tests/xpcshell/test_setBreakpoint-on-line-with-multiple-offsets.js create mode 100644 devtools/server/tests/xpcshell/test_setBreakpoint-on-line-with-multiple-statements.js create mode 100644 devtools/server/tests/xpcshell/test_setBreakpoint-on-line-with-no-offsets-in-gcd-script.js create mode 100644 devtools/server/tests/xpcshell/test_setBreakpoint-on-line-with-no-offsets.js create mode 100644 devtools/server/tests/xpcshell/test_setBreakpoint-on-line.js create mode 100644 devtools/server/tests/xpcshell/test_shapes_highlighter_helpers.js create mode 100644 devtools/server/tests/xpcshell/test_source-01.js create mode 100644 devtools/server/tests/xpcshell/test_source-02.js create mode 100644 devtools/server/tests/xpcshell/test_source-03.js create mode 100644 devtools/server/tests/xpcshell/test_source-04.js create mode 100644 devtools/server/tests/xpcshell/test_stepping-01.js create mode 100644 devtools/server/tests/xpcshell/test_stepping-02.js create mode 100644 devtools/server/tests/xpcshell/test_stepping-03.js create mode 100644 devtools/server/tests/xpcshell/test_stepping-04.js create mode 100644 devtools/server/tests/xpcshell/test_stepping-05.js create mode 100644 devtools/server/tests/xpcshell/test_stepping-06.js create mode 100644 devtools/server/tests/xpcshell/test_stepping-07.js create mode 100644 devtools/server/tests/xpcshell/test_stepping-08.js create mode 100644 devtools/server/tests/xpcshell/test_stepping-09.js create mode 100644 devtools/server/tests/xpcshell/test_stepping-10.js create mode 100644 devtools/server/tests/xpcshell/test_stepping-11.js create mode 100644 devtools/server/tests/xpcshell/test_stepping-12.js create mode 100644 devtools/server/tests/xpcshell/test_stepping-13.js create mode 100644 devtools/server/tests/xpcshell/test_stepping-14.js create mode 100644 devtools/server/tests/xpcshell/test_stepping-15.js create mode 100644 devtools/server/tests/xpcshell/test_stepping-16.js create mode 100644 devtools/server/tests/xpcshell/test_stepping-17.js create mode 100644 devtools/server/tests/xpcshell/test_stepping-18.js create mode 100644 devtools/server/tests/xpcshell/test_stepping-19.js create mode 100644 devtools/server/tests/xpcshell/test_stepping-with-skip-breakpoints.js create mode 100644 devtools/server/tests/xpcshell/test_symbolactor.js create mode 100644 devtools/server/tests/xpcshell/test_symbols-01.js create mode 100644 devtools/server/tests/xpcshell/test_symbols-02.js create mode 100644 devtools/server/tests/xpcshell/test_threadlifetime-01.js create mode 100644 devtools/server/tests/xpcshell/test_threadlifetime-02.js create mode 100644 devtools/server/tests/xpcshell/test_threadlifetime-04.js create mode 100644 devtools/server/tests/xpcshell/test_unsafeDereference.js create mode 100644 devtools/server/tests/xpcshell/test_wasm_source-01.js create mode 100644 devtools/server/tests/xpcshell/test_watchpoint-01.js create mode 100644 devtools/server/tests/xpcshell/test_watchpoint-02.js create mode 100644 devtools/server/tests/xpcshell/test_watchpoint-03.js create mode 100644 devtools/server/tests/xpcshell/test_watchpoint-04.js create mode 100644 devtools/server/tests/xpcshell/test_watchpoint-05.js create mode 100644 devtools/server/tests/xpcshell/test_webext_apis.js create mode 100644 devtools/server/tests/xpcshell/test_webextension_descriptor.js create mode 100644 devtools/server/tests/xpcshell/test_xpcshell_debugging.js create mode 100644 devtools/server/tests/xpcshell/testactors.js create mode 100644 devtools/server/tests/xpcshell/webextension-helpers.js create mode 100644 devtools/server/tests/xpcshell/xpcshell.toml create mode 100644 devtools/server/tests/xpcshell/xpcshell_debugging_script.js create mode 100644 devtools/server/tracer/moz.build create mode 100644 devtools/server/tracer/tests/browser/Worker.tracer.js create mode 100644 devtools/server/tracer/tests/browser/WorkerDebugger.tracer.js create mode 100644 devtools/server/tracer/tests/browser/browser.toml create mode 100644 devtools/server/tracer/tests/browser/browser_document_tracer.js create mode 100644 devtools/server/tracer/tests/browser/browser_worker_tracer.js create mode 100644 devtools/server/tracer/tests/xpcshell/test_tracer.js create mode 100644 devtools/server/tracer/tests/xpcshell/xpcshell.toml create mode 100644 devtools/server/tracer/tracer.jsm (limited to 'devtools/server') diff --git a/devtools/server/actors/accessibility/accessibility.js b/devtools/server/actors/accessibility/accessibility.js new file mode 100644 index 0000000000..6bdf0e9f32 --- /dev/null +++ b/devtools/server/actors/accessibility/accessibility.js @@ -0,0 +1,130 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +const { Actor } = require("resource://devtools/shared/protocol.js"); +const { + accessibilitySpec, +} = require("resource://devtools/shared/specs/accessibility.js"); + +loader.lazyRequireGetter( + this, + "AccessibleWalkerActor", + "resource://devtools/server/actors/accessibility/walker.js", + true +); +loader.lazyRequireGetter( + this, + "SimulatorActor", + "resource://devtools/server/actors/accessibility/simulator.js", + true +); + +/** + * The AccessibilityActor is a top level container actor that initializes + * accessible walker and is the top-most point of interaction for accessibility + * tools UI for a top level content process. + */ +class AccessibilityActor extends Actor { + constructor(conn, targetActor) { + super(conn, accessibilitySpec); + // This event is fired when accessibility service is initialized or shut + // down. "init" and "shutdown" events are only relayed when the enabled + // state matches the event (e.g. the event came from the same process as + // the actor). + Services.obs.addObserver(this, "a11y-init-or-shutdown"); + this.targetActor = targetActor; + } + + getTraits() { + // The traits are used to know if accessibility actors support particular + // API on the server side. + return { + // @backward-compat { version 84 } Fixed on the server by Bug 1654956. + tabbingOrder: true, + }; + } + + bootstrap() { + return { + enabled: this.enabled, + }; + } + + get enabled() { + return Services.appinfo.accessibilityEnabled; + } + + /** + * Observe Accessibility service init and shutdown events. It relays these + * events to AccessibilityFront if the event is fired for the a11y service + * that lives in the same process. + * + * @param {null} subject + * Not used. + * @param {String} topic + * Name of the a11y service event: "a11y-init-or-shutdown". + * @param {String} data + * "0" corresponds to shutdown and "1" to init. + */ + observe(subject, topic, data) { + const enabled = data === "1"; + if (enabled && this.enabled) { + this.emit("init"); + } else if (!enabled && !this.enabled) { + if (this.walker) { + this.walker.reset(); + } + + this.emit("shutdown"); + } + } + + /** + * Get or create AccessibilityWalker actor, similar to WalkerActor. + * + * @return {Object} + * AccessibleWalkerActor for the current tab. + */ + getWalker() { + if (!this.walker) { + this.walker = new AccessibleWalkerActor(this.conn, this.targetActor); + this.manage(this.walker); + } + return this.walker; + } + + /** + * Get or create Simulator actor, managed by AccessibilityActor, + * only if webrender is enabled. Simulator applies color filters on an entire + * viewport. This needs to be done using webrender and not an SVG + * since it is accelerated and scrolling with filter applied + * needs to be smooth (Bug1431466). + * + * @return {Object|null} + * SimulatorActor for the current tab. + */ + getSimulator() { + if (!this.simulator) { + this.simulator = new SimulatorActor(this.conn, this.targetActor); + this.manage(this.simulator); + } + + return this.simulator; + } + + /** + * Destroy accessibility actor. This method also shutsdown accessibility + * service if possible. + */ + async destroy() { + super.destroy(); + Services.obs.removeObserver(this, "a11y-init-or-shutdown"); + this.walker = null; + this.targetActor = null; + } +} + +exports.AccessibilityActor = AccessibilityActor; diff --git a/devtools/server/actors/accessibility/accessible.js b/devtools/server/actors/accessibility/accessible.js new file mode 100644 index 0000000000..1866d0a91b --- /dev/null +++ b/devtools/server/actors/accessibility/accessible.js @@ -0,0 +1,675 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +const { Actor } = require("resource://devtools/shared/protocol.js"); +const { + accessibleSpec, +} = require("resource://devtools/shared/specs/accessibility.js"); + +const { + accessibility: { AUDIT_TYPE }, +} = require("resource://devtools/shared/constants.js"); + +loader.lazyRequireGetter( + this, + "getContrastRatioFor", + "resource://devtools/server/actors/accessibility/audit/contrast.js", + true +); +loader.lazyRequireGetter( + this, + "auditKeyboard", + "resource://devtools/server/actors/accessibility/audit/keyboard.js", + true +); +loader.lazyRequireGetter( + this, + "auditTextLabel", + "resource://devtools/server/actors/accessibility/audit/text-label.js", + true +); +loader.lazyRequireGetter( + this, + "isDefunct", + "resource://devtools/server/actors/utils/accessibility.js", + true +); +loader.lazyRequireGetter( + this, + "findCssSelector", + "resource://devtools/shared/inspector/css-logic.js", + true +); +loader.lazyRequireGetter( + this, + "events", + "resource://devtools/shared/event-emitter.js" +); +loader.lazyRequireGetter( + this, + "getBounds", + "resource://devtools/server/actors/highlighters/utils/accessibility.js", + true +); +loader.lazyRequireGetter( + this, + "isFrameWithChildTarget", + "resource://devtools/shared/layout/utils.js", + true +); +const lazy = {}; +loader.lazyGetter( + lazy, + "ContentDOMReference", + () => + ChromeUtils.importESModule( + "resource://gre/modules/ContentDOMReference.sys.mjs", + { + // ContentDOMReference needs to be retrieved from the shared global + // since it is a shared singleton. + loadInDevToolsLoader: false, + } + ).ContentDOMReference +); + +const RELATIONS_TO_IGNORE = new Set([ + Ci.nsIAccessibleRelation.RELATION_CONTAINING_APPLICATION, + Ci.nsIAccessibleRelation.RELATION_CONTAINING_TAB_PANE, + Ci.nsIAccessibleRelation.RELATION_CONTAINING_WINDOW, + Ci.nsIAccessibleRelation.RELATION_PARENT_WINDOW_OF, + Ci.nsIAccessibleRelation.RELATION_SUBWINDOW_OF, +]); + +const nsIAccessibleRole = Ci.nsIAccessibleRole; +const TEXT_ROLES = new Set([ + nsIAccessibleRole.ROLE_TEXT_LEAF, + nsIAccessibleRole.ROLE_STATICTEXT, +]); + +const STATE_DEFUNCT = Ci.nsIAccessibleStates.EXT_STATE_DEFUNCT; +const CSS_TEXT_SELECTOR = "#text"; + +/** + * Get node inforamtion such as nodeType and the unique CSS selector for the node. + * @param {DOMNode} node + * Node for which to get the information. + * @return {Object} + * Information about the type of the node and how to locate it. + */ +function getNodeDescription(node) { + if (!node || Cu.isDeadWrapper(node)) { + return { nodeType: undefined, nodeCssSelector: "" }; + } + + const { nodeType } = node; + return { + nodeType, + // If node is a text node, we find a unique CSS selector for its parent and add a + // CSS_TEXT_SELECTOR postfix to indicate that it's a text node. + nodeCssSelector: + nodeType === Node.TEXT_NODE + ? `${findCssSelector(node.parentNode)}${CSS_TEXT_SELECTOR}` + : findCssSelector(node), + }; +} + +/** + * Get a snapshot of the nsIAccessible object including its subtree. None of the subtree + * queried here is cached via accessible walker's refMap. + * @param {nsIAccessible} acc + * Accessible object to take a snapshot of. + * @param {nsIAccessibilityService} a11yService + * Accessibility service instance in the current process, used to get localized + * string representation of various accessible properties. + * @param {WindowGlobalTargetActor} targetActor + * @return {JSON} + * JSON snapshot of the accessibility tree with root at current accessible. + */ +function getSnapshot(acc, a11yService, targetActor) { + if (isDefunct(acc)) { + return { + states: [a11yService.getStringStates(0, STATE_DEFUNCT)], + }; + } + + const actions = []; + for (let i = 0; i < acc.actionCount; i++) { + actions.push(acc.getActionDescription(i)); + } + + const attributes = {}; + if (acc.attributes) { + for (const { key, value } of acc.attributes.enumerate()) { + attributes[key] = value; + } + } + + const state = {}; + const extState = {}; + acc.getState(state, extState); + const states = [...a11yService.getStringStates(state.value, extState.value)]; + + const children = []; + for (let child = acc.firstChild; child; child = child.nextSibling) { + // Ignore children from different documents when we have targets for every documents. + if ( + targetActor.ignoreSubFrames && + child.DOMNode.ownerDocument !== targetActor.contentDocument + ) { + continue; + } + children.push(getSnapshot(child, a11yService, targetActor)); + } + + const { nodeType, nodeCssSelector } = getNodeDescription(acc.DOMNode); + const snapshot = { + name: acc.name, + role: getStringRole(acc, a11yService), + actions, + value: acc.value, + nodeCssSelector, + nodeType, + description: acc.description, + keyboardShortcut: acc.accessKey || acc.keyboardShortcut, + childCount: acc.childCount, + indexInParent: acc.indexInParent, + states, + children, + attributes, + }; + const useChildTargetToFetchChildren = + acc.role === Ci.nsIAccessibleRole.ROLE_INTERNAL_FRAME && + isFrameWithChildTarget(targetActor, acc.DOMNode); + if (useChildTargetToFetchChildren) { + snapshot.useChildTargetToFetchChildren = useChildTargetToFetchChildren; + snapshot.childCount = 1; + snapshot.contentDOMReference = lazy.ContentDOMReference.get(acc.DOMNode); + } + + return snapshot; +} + +/** + * Get a string indicating the role of the nsIAccessible object. + * An ARIA role token will be returned unless the role can't be mapped to an + * ARIA role (e.g. + // The whole iframe source is going to be considered as an inline source because displayURL is null + // and introductionType is inlineScript. But Debugger.Source.text is the only way + // to retrieve the source content. + if (this._source.text !== "[no source]" && !this._isInlineSource) { + return { + content: this.actualText(), + contentType: "text/javascript", + }; + } + + return this.sourcesManager.urlContents( + this.url, + /* partial */ false, + /* canUseCache */ this._isInlineSource + ); + } + + // Get the actual text of this source, padded so that line numbers will match + // up with the source itself. + actualText() { + // If the source doesn't start at line 1, line numbers in the client will + // not match up with those in the source. Pad the text with blank lines to + // fix this. This can show up for sources associated with inline scripts + // in HTML created via document.write() calls: the script's source line + // number is relative to the start of the written HTML, but we show the + // source's content by itself. + const padding = this._source.startLine + ? "\n".repeat(this._source.startLine - 1) + : ""; + return padding + this._source.text; + } + + // Return whether the specified fetched contents includes the actual text of + // this source in the expected position. + contentMatches(fileContents) { + const lineBreak = /\r\n?|\n|\u2028|\u2029/; + const contentLines = fileContents.content.split(lineBreak); + const sourceLines = this._source.text.split(lineBreak); + let line = this._source.startLine - 1; + for (const sourceLine of sourceLines) { + const contentLine = contentLines[line++] || ""; + if (!contentLine.includes(sourceLine)) { + return false; + } + } + return true; + } + + getBreakableLines() { + const positions = this._getBreakpointPositions(); + const lines = new Set(); + for (const position of positions) { + if (!lines.has(position.line)) { + lines.add(position.line); + } + } + + return Array.from(lines); + } + + // Get all toplevel scripts in the source. Transitive child scripts must be + // found by traversing the child script tree. + _getTopLevelDebuggeeScripts() { + if (this._scripts) { + return this._scripts; + } + + let scripts = this.dbg.findScripts({ source: this._source }); + + if (!this._isWasm) { + // There is no easier way to get the top-level scripts right now, so + // we have to build that up the list manually. + // Note: It is not valid to simply look for scripts where + // `.isFunction == false` because a source may have executed multiple + // where some have been GCed and some have not (bug 1627712). + const allScripts = new Set(scripts); + for (const script of allScripts) { + for (const child of script.getChildScripts()) { + allScripts.delete(child); + } + } + scripts = [...allScripts]; + } + + this._scripts = scripts; + return scripts; + } + + resetDebuggeeScripts() { + this._scripts = null; + } + + // Get toplevel scripts which contain all breakpoint positions for the source. + // This is different from _scripts if we detected that some scripts have been + // GC'ed and reparsed the source contents. + _getTopLevelBreakpointPositionScripts() { + if (this._breakpointPositionScripts) { + return this._breakpointPositionScripts; + } + + let scripts = this._getTopLevelDebuggeeScripts(); + + // We need to find all breakpoint positions, even if scripts associated with + // this source have been GC'ed. We detect this by looking for a script which + // does not have a function: a source will typically have a top level + // non-function script. If this top level script still exists, then it keeps + // all its child scripts alive and we will find all breakpoint positions by + // scanning the existing scripts. If the top level script has been GC'ed + // then we won't find its breakpoint positions, and inner functions may have + // been GC'ed as well. In this case we reparse the source and generate a new + // and complete set of scripts to look for the breakpoint positions. + // Note that in some cases like "new Function(stuff)" there might not be a + // top level non-function script, but if there is a non-function script then + // it must be at the top level and will keep all other scripts in the source + // alive. + if (!this._isWasm && !scripts.some(script => !script.isFunction)) { + let newScript; + try { + newScript = this._source.reparse(); + } catch (e) { + // reparse() will throw if the source is not valid JS. This can happen + // if this source is the resurrection of a GC'ed source and there are + // parse errors in the refetched contents. + } + if (newScript) { + scripts = [newScript]; + } + } + + this._breakpointPositionScripts = scripts; + return scripts; + } + + // Get all scripts in this source that might include content in the range + // specified by the given query. + _findDebuggeeScripts(query, forBreakpointPositions) { + const scripts = forBreakpointPositions + ? this._getTopLevelBreakpointPositionScripts() + : this._getTopLevelDebuggeeScripts(); + + const { + start: { line: startLine = 0, column: startColumn = 0 } = {}, + end: { line: endLine = Infinity, column: endColumn = Infinity } = {}, + } = query || {}; + + const rv = []; + addMatchingScripts(scripts); + return rv; + + function scriptMatches(script) { + // These tests are approximate, as we can't easily get the script's end + // column. + let lineCount; + try { + lineCount = script.lineCount; + } catch (err) { + // Accessing scripts which were optimized out during parsing can throw + // an exception. Tolerate these so that we can still get positions for + // other scripts in the source. + return false; + } + + // NOTE: Debugger.Script.prototype.startColumn is 1-based. + // Convert to 0-based, while keeping the wasm's column (1) as is. + // (bug 1863878) + const columnBase = script.format === "wasm" ? 0 : 1; + if ( + script.startLine > endLine || + script.startLine + lineCount <= startLine || + (script.startLine == endLine && + script.startColumn - columnBase > endColumn) + ) { + return false; + } + + if ( + lineCount == 1 && + script.startLine == startLine && + script.startColumn - columnBase + script.sourceLength <= startColumn + ) { + return false; + } + + return true; + } + + function addMatchingScripts(childScripts) { + for (const script of childScripts) { + if (scriptMatches(script)) { + rv.push(script); + if (script.format === "js") { + addMatchingScripts(script.getChildScripts()); + } + } + } + } + } + + _getBreakpointPositions(query) { + const scripts = this._findDebuggeeScripts( + query, + /* forBreakpointPositions */ true + ); + + const positions = []; + for (const script of scripts) { + this._addScriptBreakpointPositions(query, script, positions); + } + + return ( + positions + // Sort the items by location. + .sort((a, b) => { + const lineDiff = a.line - b.line; + return lineDiff === 0 ? a.column - b.column : lineDiff; + }) + ); + } + + _addScriptBreakpointPositions(query, script, positions) { + const { + start: { line: startLine = 0, column: startColumn = 0 } = {}, + end: { line: endLine = Infinity, column: endColumn = Infinity } = {}, + } = query || {}; + + // NOTE: Debugger.Script.prototype.startColumn is 1-based. + // Convert to 0-based, while keeping the wasm's column (1) as is. + // (bug 1863878) + const columnBase = script.format === "wasm" ? 0 : 1; + + const offsets = script.getPossibleBreakpoints(); + for (const { lineNumber, columnNumber } of offsets) { + if ( + lineNumber < startLine || + (lineNumber === startLine && columnNumber - columnBase < startColumn) || + lineNumber > endLine || + (lineNumber === endLine && columnNumber - columnBase >= endColumn) + ) { + continue; + } + + positions.push({ + line: lineNumber, + column: columnNumber - columnBase, + }); + } + } + + getBreakpointPositionsCompressed(query) { + const items = this._getBreakpointPositions(query); + const compressed = {}; + for (const { line, column } of items) { + if (!compressed[line]) { + compressed[line] = []; + } + compressed[line].push(column); + } + return compressed; + } + + /** + * Handler for the "onSource" packet. + * @return Object + * The return of this function contains a field `contentType`, and + * a field `source`. `source` can either be an ArrayBuffer or + * a LongString. + */ + async source() { + try { + const { content, contentType } = await this._getSourceText(); + if ( + typeof content === "object" && + content && + content.constructor && + content.constructor.name === "ArrayBuffer" + ) { + return { + source: new ArrayBufferActor(this.threadActor.conn, content), + contentType, + }; + } + + return { + source: new LongStringActor(this.threadActor.conn, content), + contentType, + }; + } catch (error) { + throw new Error( + "Could not load the source for " + + this.url + + ".\n" + + DevToolsUtils.safeErrorString(error) + ); + } + } + + /** + * Handler for the "blackbox" packet. + */ + blackbox(range) { + this.sourcesManager.blackBox(this.url, range); + if ( + this.threadActor.state == "paused" && + this.threadActor.youngestFrame && + this.threadActor.youngestFrame.script.url == this.url + ) { + return true; + } + return false; + } + + /** + * Handler for the "unblackbox" packet. + */ + unblackbox(range) { + this.sourcesManager.unblackBox(this.url, range); + } + + /** + * Handler for the "setPausePoints" packet. + * + * @param Array pausePoints + * A dictionary of pausePoint objects + * + * type PausePoints = { + * line: { + * column: { break?: boolean, step?: boolean } + * } + * } + */ + setPausePoints(pausePoints) { + const uncompressed = {}; + const points = { + 0: {}, + 1: { break: true }, + 2: { step: true }, + 3: { break: true, step: true }, + }; + + for (const line in pausePoints) { + uncompressed[line] = {}; + for (const col in pausePoints[line]) { + uncompressed[line][col] = points[pausePoints[line][col]]; + } + } + + this.pausePoints = uncompressed; + } + + /* + * Ensure the given BreakpointActor is set as a breakpoint handler on all + * scripts that match its location in the generated source. + * + * @param BreakpointActor actor + * The BreakpointActor to be set as a breakpoint handler. + * + * @returns A Promise that resolves to the given BreakpointActor. + */ + async applyBreakpoint(actor) { + const { line, column } = actor.location; + + // Find all entry points that correspond to the given location. + const entryPoints = []; + if (column === undefined) { + // Find all scripts that match the given source actor and line + // number. + const query = { start: { line }, end: { line } }; + const scripts = this._findDebuggeeScripts(query).filter( + script => !actor.hasScript(script) + ); + + // NOTE: Debugger.Script.prototype.getPossibleBreakpoints returns + // columnNumber in 1-based. + // The following code uses columnNumber only for comparing against + // other columnNumber, and we don't need to convert to 0-based. + + // This is a line breakpoint, so we add a breakpoint on the first + // breakpoint on the line. + const lineMatches = []; + for (const script of scripts) { + const possibleBreakpoints = script.getPossibleBreakpoints({ line }); + for (const possibleBreakpoint of possibleBreakpoints) { + lineMatches.push({ ...possibleBreakpoint, script }); + } + } + lineMatches.sort((a, b) => a.columnNumber - b.columnNumber); + + if (lineMatches.length) { + // A single Debugger.Source may have _multiple_ Debugger.Scripts + // at the same position from multiple evaluations of the source, + // so we explicitly want to take all of the matches for the matched + // column number. + const firstColumn = lineMatches[0].columnNumber; + const firstColumnMatches = lineMatches.filter( + m => m.columnNumber === firstColumn + ); + + for (const { script, offset } of firstColumnMatches) { + entryPoints.push({ script, offsets: [offset] }); + } + } + } else { + // Find all scripts that match the given source actor, line, + // and column number. + const query = { start: { line, column }, end: { line, column } }; + const scripts = this._findDebuggeeScripts(query).filter( + script => !actor.hasScript(script) + ); + + for (const script of scripts) { + // NOTE: getPossibleBreakpoints's minColumn/maxColumn parameters are + // 1-based. + // Convert to 1-based, while keeping the wasm's column (1) as is. + // (bug 1863878) + const columnBase = script.format === "wasm" ? 0 : 1; + + // Check to see if the script contains a breakpoint position at + // this line and column. + const possibleBreakpoint = script + .getPossibleBreakpoints({ + line, + minColumn: column + columnBase, + maxColumn: column + columnBase + 1, + }) + .pop(); + + if (possibleBreakpoint) { + const { offset } = possibleBreakpoint; + entryPoints.push({ script, offsets: [offset] }); + } + } + } + + setBreakpointAtEntryPoints(actor, entryPoints); + } +} + +exports.SourceActor = SourceActor; diff --git a/devtools/server/actors/string.js b/devtools/server/actors/string.js new file mode 100644 index 0000000000..01c9353ecf --- /dev/null +++ b/devtools/server/actors/string.js @@ -0,0 +1,45 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +const { Actor } = require("resource://devtools/shared/protocol.js"); +const { + longStringSpec, +} = require("resource://devtools/shared/specs/string.js"); + +const { + DevToolsServer, +} = require("resource://devtools/server/devtools-server.js"); + +exports.LongStringActor = class LongStringActor extends Actor { + constructor(conn, str) { + super(conn, longStringSpec); + this.str = str; + this.short = this.str.length < DevToolsServer.LONG_STRING_LENGTH; + } + + destroy() { + this.str = null; + super.destroy(); + } + + form() { + if (this.short) { + return this.str; + } + return { + type: "longString", + actor: this.actorID, + length: this.str.length, + initial: this.str.substring(0, DevToolsServer.LONG_STRING_INITIAL_LENGTH), + }; + } + + substring(start, end) { + return this.str.substring(start, end); + } + + release() {} +}; diff --git a/devtools/server/actors/style-rule.js b/devtools/server/actors/style-rule.js new file mode 100644 index 0000000000..e9f39fa3d0 --- /dev/null +++ b/devtools/server/actors/style-rule.js @@ -0,0 +1,1328 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +const { Actor } = require("resource://devtools/shared/protocol.js"); +const { + styleRuleSpec, +} = require("resource://devtools/shared/specs/style-rule.js"); + +const { getCSSLexer } = require("resource://devtools/shared/css/lexer.js"); +const TrackChangeEmitter = require("resource://devtools/server/actors/utils/track-change-emitter.js"); +const { + getRuleText, + getTextAtLineColumn, +} = require("resource://devtools/server/actors/utils/style-utils.js"); + +const { + style: { ELEMENT_STYLE }, +} = require("resource://devtools/shared/constants.js"); + +loader.lazyRequireGetter( + this, + "CssLogic", + "resource://devtools/server/actors/inspector/css-logic.js", + true +); +loader.lazyRequireGetter( + this, + "SharedCssLogic", + "resource://devtools/shared/inspector/css-logic.js" +); +loader.lazyRequireGetter( + this, + "isCssPropertyKnown", + "resource://devtools/server/actors/css-properties.js", + true +); +loader.lazyRequireGetter( + this, + "isPropertyUsed", + "resource://devtools/server/actors/utils/inactive-property-helper.js", + true +); +loader.lazyRequireGetter( + this, + "parseNamedDeclarations", + "resource://devtools/shared/css/parsing-utils.js", + true +); +loader.lazyRequireGetter( + this, + ["UPDATE_PRESERVING_RULES", "UPDATE_GENERAL"], + "resource://devtools/server/actors/utils/stylesheets-manager.js", + true +); + +const XHTML_NS = "http://www.w3.org/1999/xhtml"; + +/** + * An actor that represents a CSS style object on the protocol. + * + * We slightly flatten the CSSOM for this actor, it represents + * both the CSSRule and CSSStyle objects in one actor. For nodes + * (which have a CSSStyle but no CSSRule) we create a StyleRuleActor + * with a special rule type (100). + */ +class StyleRuleActor extends Actor { + constructor(pageStyle, item, userAdded = false) { + super(pageStyle.conn, styleRuleSpec); + this.pageStyle = pageStyle; + this.rawStyle = item.style; + this._userAdded = userAdded; + this._parentSheet = null; + // Parsed CSS declarations from this.form().declarations used to check CSS property + // names and values before tracking changes. Using cached values instead of accessing + // this.form().declarations on demand because that would cause needless re-parsing. + this._declarations = []; + + this._pendingDeclarationChanges = []; + this._failedToGetRuleText = false; + + if (CSSRule.isInstance(item)) { + this.type = item.type; + this.ruleClassName = ChromeUtils.getClassName(item); + + this.rawRule = item; + this._computeRuleIndex(); + if (this.#isRuleSupported() && this.rawRule.parentStyleSheet) { + this.line = InspectorUtils.getRelativeRuleLine(this.rawRule); + this.column = InspectorUtils.getRuleColumn(this.rawRule); + this._parentSheet = this.rawRule.parentStyleSheet; + } + } else { + // Fake a rule + this.type = ELEMENT_STYLE; + this.ruleClassName = ELEMENT_STYLE; + this.rawNode = item; + this.rawRule = { + style: item.style, + toString() { + return "[element rule " + this.style + "]"; + }, + }; + } + } + + destroy() { + if (!this.rawStyle) { + return; + } + super.destroy(); + this.rawStyle = null; + this.pageStyle = null; + this.rawNode = null; + this.rawRule = null; + this._declarations = null; + } + + // Objects returned by this actor are owned by the PageStyleActor + // to which this rule belongs. + get marshallPool() { + return this.pageStyle; + } + + // True if this rule supports as-authored styles, meaning that the + // rule text can be rewritten using setRuleText. + get canSetRuleText() { + if (this.type === ELEMENT_STYLE) { + // Element styles are always editable. + return true; + } + if (!this._parentSheet) { + return false; + } + if (InspectorUtils.hasRulesModifiedByCSSOM(this._parentSheet)) { + // If a rule has been modified via CSSOM, then we should fall back to + // non-authored editing. + // https://bugzilla.mozilla.org/show_bug.cgi?id=1224121 + return false; + } + return true; + } + + /** + * Return an array with StyleRuleActor instances for each of this rule's ancestor rules + * (@media, @supports, @keyframes, etc) obtained by recursively reading rule.parentRule. + * If the rule has no ancestors, return an empty array. + * + * @return {Array} + */ + get ancestorRules() { + const ancestors = []; + let rule = this.rawRule; + + while (rule.parentRule) { + ancestors.unshift(this.pageStyle._styleRef(rule.parentRule)); + rule = rule.parentRule; + } + + return ancestors; + } + + /** + * Return an object with information about this rule used for tracking changes. + * It will be decorated with information about a CSS change before being tracked. + * + * It contains: + * - the rule selector (or generated selectror for inline styles) + * - the rule's host stylesheet (or element for inline styles) + * - the rule's ancestor rules (@media, @supports, @keyframes), if any + * - the rule's position within its ancestor tree, if any + * + * @return {Object} + */ + get metadata() { + const data = {}; + data.id = this.actorID; + // Collect information about the rule's ancestors (@media, @supports, @keyframes, parent rules). + // Used to show context for this change in the UI and to match the rule for undo/redo. + data.ancestors = this.ancestorRules.map(rule => { + const ancestorData = { + id: rule.actorID, + // Array with the indexes of this rule and its ancestors within the CSS rule tree. + ruleIndex: rule._ruleIndex, + }; + + // Rule type as human-readable string (ex: "@media", "@supports", "@keyframes") + const typeName = SharedCssLogic.getCSSAtRuleTypeName(rule.rawRule); + if (typeName) { + ancestorData.typeName = typeName; + } + + // Conditions of @container, @media and @supports rules (ex: "min-width: 1em") + if (rule.rawRule.conditionText !== undefined) { + ancestorData.conditionText = rule.rawRule.conditionText; + } + + // Name of @keyframes rule; referenced by the animation-name CSS property. + if (rule.rawRule.name !== undefined) { + ancestorData.name = rule.rawRule.name; + } + + // Selector of individual @keyframe rule within a @keyframes rule (ex: 0%, 100%). + if (rule.rawRule.keyText !== undefined) { + ancestorData.keyText = rule.rawRule.keyText; + } + + // Selector of the rule; might be useful in case for nested rules + if (rule.rawRule.selectorText !== undefined) { + ancestorData.selectorText = rule.rawRule.selectorText; + } + + return ancestorData; + }); + + // For changes in element style attributes, generate a unique selector. + if (this.type === ELEMENT_STYLE && this.rawNode) { + // findCssSelector() fails on XUL documents. Catch and silently ignore that error. + try { + data.selector = SharedCssLogic.findCssSelector(this.rawNode); + } catch (err) {} + + data.source = { + type: "element", + // Used to differentiate between elements which match the same generated selector + // but live in different documents (ex: host document and iframe). + href: this.rawNode.baseURI, + // Element style attributes don't have a rule index; use the generated selector. + index: data.selector, + // Whether the element lives in a different frame than the host document. + isFramed: this.rawNode.ownerGlobal !== this.pageStyle.ownerWindow, + }; + + const nodeActor = this.pageStyle.walker.getNode(this.rawNode); + if (nodeActor) { + data.source.id = nodeActor.actorID; + } + + data.ruleIndex = 0; + } else { + data.selector = + this.ruleClassName === "CSSKeyframeRule" + ? this.rawRule.keyText + : this.rawRule.selectorText; + // Used to differentiate between changes to rules with identical selectors. + data.ruleIndex = this._ruleIndex; + + const sheet = this._parentSheet; + const inspectorActor = this.pageStyle.inspector; + const resourceId = + this.pageStyle.styleSheetsManager.getStyleSheetResourceId(sheet); + const styleSheetIndex = + this.pageStyle.styleSheetsManager.getStyleSheetIndex(resourceId); + data.source = { + // Inline stylesheets have a null href; Use window URL instead. + type: sheet.href ? "stylesheet" : "inline", + href: sheet.href || inspectorActor.window.location.toString(), + id: resourceId, + index: styleSheetIndex, + // Whether the stylesheet lives in a different frame than the host document. + isFramed: inspectorActor.window !== inspectorActor.window.top, + }; + } + + return data; + } + + getDocument(sheet) { + if (!sheet.associatedDocument) { + throw new Error( + "Failed trying to get the document of an invalid stylesheet" + ); + } + return sheet.associatedDocument; + } + + /** + * When a rule is nested in another non-at-rule (aka CSS Nesting), the client + * will need its desugared selector, i.e. the full selector, which includes ancestor + * selectors, that is computed by the platform when applying the rule. + * To compute it, the parent selector (&) is recursively replaced by the parent + * rule selector wrapped in `:is()`. + * For example, with the following nested rule: `body { & > main {} }`, + * the desugared selector will be `:is(body) > main`. + * See https://www.w3.org/TR/css-nesting-1/#nest-selector for more information. + * + * Returns an array of the desugared selectors. For example, if rule is: + * + * body { + * & > main, & section { + * } + * } + * + * this will return: + * + * [ + * `:is(body) > main`, + * `:is(body) section`, + * ] + * + * @returns Array + */ + getDesugaredSelectors() { + // Cache the desugared selectors as it can be expensive to compute + if (!this._desugaredSelectors) { + this._desugaredSelectors = CssLogic.getSelectors(this.rawRule, true); + } + + return this._desugaredSelectors; + } + + toString() { + return "[StyleRuleActor for " + this.rawRule + "]"; + } + + // eslint-disable-next-line complexity + form() { + const form = { + actor: this.actorID, + type: this.type, + line: this.line || undefined, + column: this.column, + traits: { + // Indicates whether StyleRuleActor implements and can use the setRuleText method. + // It cannot use it if the stylesheet was programmatically mutated via the CSSOM. + canSetRuleText: this.canSetRuleText, + }, + }; + + // This rule was manually added by the user and may be automatically focused by the frontend. + if (this._userAdded) { + form.userAdded = true; + } + + const { computeDesugaredSelector, ancestorData } = + this._getAncestorDataForForm(); + form.ancestorData = ancestorData; + + if (this._parentSheet) { + form.parentStyleSheet = + this.pageStyle.styleSheetsManager.getStyleSheetResourceId( + this._parentSheet + ); + } + + // One tricky thing here is that other methods in this actor must + // ensure that authoredText has been set before |form| is called. + // This has to be treated specially, for now, because we cannot + // synchronously compute the authored text, but |form| also cannot + // return a promise. See bug 1205868. + form.authoredText = this.authoredText; + + switch (this.ruleClassName) { + case "CSSStyleRule": + form.selectors = CssLogic.getSelectors(this.rawRule); + + // Only add the property when there are elements in the array to save up on serialization. + const selectorWarnings = this.rawRule.getSelectorWarnings(); + if (selectorWarnings.length) { + form.selectorWarnings = selectorWarnings; + } + if (computeDesugaredSelector) { + form.desugaredSelectors = this.getDesugaredSelectors(); + } + form.cssText = this.rawStyle.cssText || ""; + break; + case ELEMENT_STYLE: + // Elements don't have a parent stylesheet, and therefore + // don't have an associated URI. Provide a URI for + // those. + const doc = this.rawNode.ownerDocument; + form.href = doc.location ? doc.location.href : ""; + form.cssText = this.rawStyle.cssText || ""; + form.authoredText = this.rawNode.getAttribute("style"); + break; + case "CSSCharsetRule": + form.encoding = this.rawRule.encoding; + break; + case "CSSImportRule": + form.href = this.rawRule.href; + break; + case "CSSKeyframesRule": + form.cssText = this.rawRule.cssText; + form.name = this.rawRule.name; + break; + case "CSSKeyframeRule": + form.cssText = this.rawStyle.cssText || ""; + form.keyText = this.rawRule.keyText || ""; + break; + } + + // Parse the text into a list of declarations so the client doesn't have to + // and so that we can safely determine if a declaration is valid rather than + // have the client guess it. + if (form.authoredText || form.cssText) { + // authoredText may be an empty string when deleting all properties; it's ok to use. + const cssText = + typeof form.authoredText === "string" + ? form.authoredText + : form.cssText; + const declarations = parseNamedDeclarations( + isCssPropertyKnown, + cssText, + true + ); + const el = this.pageStyle.selectedElement; + const style = this.pageStyle.cssLogic.computedStyle; + + // Whether the stylesheet is a user-agent stylesheet. This affects the + // validity of some properties and property values. + const userAgent = + this._parentSheet && + SharedCssLogic.isAgentStylesheet(this._parentSheet); + // Whether the stylesheet is a chrome stylesheet. Ditto. + // + // Note that chrome rules are also enabled in user sheets, see + // ParserContext::chrome_rules_enabled(). + // + // https://searchfox.org/mozilla-central/rev/919607a3610222099fbfb0113c98b77888ebcbfb/servo/components/style/parser.rs#164 + const chrome = (() => { + if (!this._parentSheet) { + return false; + } + if (SharedCssLogic.isUserStylesheet(this._parentSheet)) { + return true; + } + if (this._parentSheet.href) { + return this._parentSheet.href.startsWith("chrome:"); + } + return el && el.ownerDocument.documentURI.startsWith("chrome:"); + })(); + // Whether the document is in quirks mode. This affects whether stuff + // like `width: 10` is valid. + const quirks = + !userAgent && el && el.ownerDocument.compatMode == "BackCompat"; + const supportsOptions = { userAgent, chrome, quirks }; + form.declarations = declarations.map(decl => { + // InspectorUtils.supports only supports the 1-arg version, but that's + // what we want to do anyways so that we also accept !important in the + // value. + decl.isValid = InspectorUtils.supports( + `${decl.name}:${decl.value}`, + supportsOptions + ); + // TODO: convert from Object to Boolean. See Bug 1574471 + decl.isUsed = isPropertyUsed(el, style, this.rawRule, decl.name); + // Check property name. All valid CSS properties support "initial" as a value. + decl.isNameValid = InspectorUtils.supports( + `${decl.name}:initial`, + supportsOptions + ); + + if (SharedCssLogic.isCssVariable(decl.name)) { + decl.isCustomProperty = true; + // We only compute `inherits` for css variable declarations. + // For "regular" declaration, we use `CssPropertiesFront.isInherited`, + // which doesn't depend on the state of the document (a given property will + // always have the same isInherited value). + // CSS variables on the other hand can be registered custom properties (e.g., + // `@property`/`CSS.registerProperty`), with a `inherits` definition that can + // be true or false. + // As such custom properties can be registered at any time during the page + // lifecycle, we always recompute the `inherits` information for CSS variables. + decl.inherits = InspectorUtils.isInheritedProperty( + this.pageStyle.inspector.window.document, + decl.name + ); + } + + return decl; + }); + + // We have computed the new `declarations` array, before forgetting about + // the old declarations compute the CSS changes for pending modifications + // applied by the user. Comparing the old and new declarations arrays + // ensures we only rely on values understood by the engine and not authored + // values. See Bug 1590031. + this._pendingDeclarationChanges.forEach(change => + this.logDeclarationChange(change, declarations, this._declarations) + ); + this._pendingDeclarationChanges = []; + + // Cache parsed declarations so we don't needlessly re-parse authoredText every time + // we need to check previous property names and values when tracking changes. + this._declarations = declarations; + } + + return form; + } + + /** + * + * @returns {Object} Object with the following properties: + * - {Array} ancestorData: An array of ancestor item data + * - {Boolean} computeDesugaredSelector: true if the rule has a non-at-rule + * parent rule (i.e. rule is likely to be a nested rule) + */ + _getAncestorDataForForm() { + const ancestorData = []; + // Flag that will be set to true if the rule has a non-at-rule parent rule + let computeDesugaredSelector = false; + + // Go through all ancestor so we can build an array of all the media queries and + // layers this rule is in. + for (const ancestorRule of this.ancestorRules) { + const rawRule = ancestorRule.rawRule; + const ruleClassName = ChromeUtils.getClassName(rawRule); + const type = SharedCssLogic.CSSAtRuleClassNameType[ruleClassName]; + + if (ruleClassName === "CSSMediaRule" && rawRule.media?.length) { + ancestorData.push({ + type, + value: Array.from(rawRule.media).join(", "), + }); + } else if (ruleClassName === "CSSLayerBlockRule") { + ancestorData.push({ + // we need the actorID so we can uniquely identify nameless layers on the client + actorID: ancestorRule.actorID, + type, + value: rawRule.name, + }); + } else if (ruleClassName === "CSSContainerRule") { + ancestorData.push({ + type, + // Send containerName and containerQuery separately (instead of conditionText) + // so the client has more flexibility to display the information. + containerName: rawRule.containerName, + containerQuery: rawRule.containerQuery, + }); + } else if (ruleClassName === "CSSSupportsRule") { + ancestorData.push({ + type, + conditionText: rawRule.conditionText, + }); + } else if (rawRule.selectorText) { + // All the previous cases where about at-rules; this one is for regular rule + // that are ancestors because CSS nesting was used. + // In such case, we want to return the selectorText so it can be displayed in the UI. + const ancestor = { + type, + selectors: CssLogic.getSelectors(rawRule), + }; + + // Only add the property when there are elements in the array to save up on serialization. + const selectorWarnings = rawRule.getSelectorWarnings(); + if (selectorWarnings.length) { + ancestor.selectorWarnings = selectorWarnings; + } + + ancestorData.push(ancestor); + computeDesugaredSelector = true; + } + } + + if (this._parentSheet) { + // Loop through all parent stylesheets to get the whole list of @import rules. + let rule = this.rawRule; + while ((rule = rule.parentStyleSheet?.ownerRule)) { + // If the rule is in a imported stylesheet with a specified layer + if (rule.layerName !== null) { + // Put the item at the top of the ancestor data array, as we're going up + // in the stylesheet hierarchy, and we want to display ancestor rules in the + // orders they're applied. + ancestorData.unshift({ + type: "layer", + value: rule.layerName, + }); + } + + // If the rule is in a imported stylesheet with specified media/supports conditions + if (rule.media?.mediaText || rule.supportsText) { + const parts = []; + if (rule.supportsText) { + parts.push(`supports(${rule.supportsText})`); + } + + if (rule.media?.mediaText) { + parts.push(rule.media.mediaText); + } + + // Put the item at the top of the ancestor data array, as we're going up + // in the stylesheet hierarchy, and we want to display ancestor rules in the + // orders they're applied. + ancestorData.unshift({ + type: "import", + value: parts.join(" "), + }); + } + } + } + return { ancestorData, computeDesugaredSelector }; + } + + /** + * Send an event notifying that the location of the rule has + * changed. + * + * @param {Number} line the new line number + * @param {Number} column the new column number + */ + _notifyLocationChanged(line, column) { + this.emit("location-changed", line, column); + } + + /** + * Compute the index of this actor's raw rule in its parent style + * sheet. The index is a vector where each element is the index of + * a given CSS rule in its parent. A vector is used to support + * nested rules. + */ + _computeRuleIndex() { + const index = InspectorUtils.getRuleIndex(this.rawRule); + this._ruleIndex = index.length ? index : null; + } + + /** + * Get the rule corresponding to |this._ruleIndex| from the given + * style sheet. + * + * @param {DOMStyleSheet} sheet + * The style sheet. + * @return {CSSStyleRule} the rule corresponding to + * |this._ruleIndex| + */ + _getRuleFromIndex(parentSheet) { + let currentRule = null; + for (const i of this._ruleIndex) { + if (currentRule === null) { + currentRule = parentSheet.cssRules[i]; + } else { + currentRule = currentRule.cssRules.item(i); + } + } + return currentRule; + } + + /** + * Called from PageStyle actor _onStylesheetUpdated. + */ + onStyleApplied(kind) { + if (kind === UPDATE_GENERAL) { + // A general change means that the rule actors are invalidated, nothing + // to do here. + return; + } + + if (this._ruleIndex) { + // The sheet was updated by this actor, in a way that preserves + // the rules. Now, recompute our new rule from the style sheet, + // so that we aren't left with a reference to a dangling rule. + const oldRule = this.rawRule; + const oldActor = this.pageStyle.refMap.get(oldRule); + this.rawRule = this._getRuleFromIndex(this._parentSheet); + if (oldActor) { + // Also tell the page style so that future calls to _styleRef + // return the same StyleRuleActor. + this.pageStyle.updateStyleRef(oldRule, this.rawRule, this); + } + const line = InspectorUtils.getRelativeRuleLine(this.rawRule); + const column = InspectorUtils.getRuleColumn(this.rawRule); + if (line !== this.line || column !== this.column) { + this._notifyLocationChanged(line, column); + } + this.line = line; + this.column = column; + } + } + + #SUPPORTED_RULES_CLASSNAMES = new Set([ + "CSSContainerRule", + "CSSKeyframeRule", + "CSSKeyframesRule", + "CSSLayerBlockRule", + "CSSMediaRule", + "CSSStyleRule", + "CSSSupportsRule", + ]); + + #isRuleSupported() { + // this.rawRule might not be an actual CSSRule (e.g. when this represent an element style), + // and in such case, ChromeUtils.getClassName will throw + try { + const ruleClassName = ChromeUtils.getClassName(this.rawRule); + return this.#SUPPORTED_RULES_CLASSNAMES.has(ruleClassName); + } catch (e) {} + + return false; + } + + /** + * Return a promise that resolves to the authored form of a rule's + * text, if available. If the authored form is not available, the + * returned promise simply resolves to the empty string. If the + * authored form is available, this also sets |this.authoredText|. + * The authored text will include invalid and otherwise ignored + * properties. + * + * @param {Boolean} skipCache + * If a value for authoredText was previously found and cached, + * ignore it and parse the stylehseet again. The authoredText + * may be outdated if a descendant of this rule has changed. + */ + async getAuthoredCssText(skipCache = false) { + if (!this.canSetRuleText || !this.#isRuleSupported()) { + return ""; + } + + if (!skipCache) { + if (this._failedToGetRuleText) { + return ""; + } + if (typeof this.authoredText === "string") { + return this.authoredText; + } + } + + try { + const resourceId = + this.pageStyle.styleSheetsManager.getStyleSheetResourceId( + this._parentSheet + ); + const cssText = await this.pageStyle.styleSheetsManager.getText( + resourceId + ); + const { text } = getRuleText(cssText, this.line, this.column); + // Cache the result on the rule actor to avoid parsing again next time + this._failedToGetRuleText = false; + this.authoredText = text; + } catch (e) { + this._failedToGetRuleText = true; + this.authoredText = undefined; + return ""; + } + return this.authoredText; + } + + /** + * Return a promise that resolves to the complete cssText of the rule as authored. + * + * Unlike |getAuthoredCssText()|, which only returns the contents of the rule, this + * method includes the CSS selectors and at-rules (@media, @supports, @keyframes, etc.) + * + * If the rule type is unrecongized, the promise resolves to an empty string. + * If the rule is an element inline style, the promise resolves with the generated + * selector that uniquely identifies the element and with the rule body consisting of + * the element's style attribute. + * + * @return {String} + */ + async getRuleText() { + // Bail out if the rule is not supported or not an element inline style. + if (!this.#isRuleSupported(true) && this.type !== ELEMENT_STYLE) { + return ""; + } + + let ruleBodyText; + let selectorText; + + // For element inline styles, use the style attribute and generated unique selector. + if (this.type === ELEMENT_STYLE) { + ruleBodyText = this.rawNode.getAttribute("style"); + selectorText = this.metadata.selector; + } else { + // Get the rule's authored text and skip any cached value. + ruleBodyText = await this.getAuthoredCssText(true); + + const resourceId = + this.pageStyle.styleSheetsManager.getStyleSheetResourceId( + this._parentSheet + ); + const stylesheetText = await this.pageStyle.styleSheetsManager.getText( + resourceId + ); + + const [start, end] = getSelectorOffsets( + stylesheetText, + this.line, + this.column + ); + selectorText = stylesheetText.substring(start, end); + } + + const text = `${selectorText} {${ruleBodyText}}`; + const { result } = SharedCssLogic.prettifyCSS(text); + return result; + } + + /** + * Set the contents of the rule. This rewrites the rule in the + * stylesheet and causes it to be re-evaluated. + * + * @param {String} newText + * The new text of the rule + * @param {Array} modifications + * Array with modifications applied to the rule. Contains objects like: + * { + * type: "set", + * index: , + * name: , + * value: , + * priority: + * } + * or + * { + * type: "remove", + * index: , + * name: , + * } + * @returns the rule with updated properties + */ + async setRuleText(newText, modifications = []) { + if (!this.canSetRuleText) { + throw new Error("invalid call to setRuleText"); + } + + if (this.type === ELEMENT_STYLE) { + // For element style rules, set the node's style attribute. + this.rawNode.setAttributeDevtools("style", newText); + } else { + const resourceId = + this.pageStyle.styleSheetsManager.getStyleSheetResourceId( + this._parentSheet + ); + let cssText = await this.pageStyle.styleSheetsManager.getText(resourceId); + + const { offset, text } = getRuleText(cssText, this.line, this.column); + cssText = + cssText.substring(0, offset) + + newText + + cssText.substring(offset + text.length); + + await this.pageStyle.styleSheetsManager.setStyleSheetText( + resourceId, + cssText, + { kind: UPDATE_PRESERVING_RULES } + ); + } + + this.authoredText = newText; + await this.updateAncestorRulesAuthoredText(); + this.pageStyle.refreshObservedRules(this.ancestorRules); + + // Add processed modifications to the _pendingDeclarationChanges array, + // they will be emitted as CSS_CHANGE resources once `declarations` have + // been re-computed in `form`. + this._pendingDeclarationChanges.push(...modifications); + + // Returning this updated actor over the protocol will update its corresponding front + // and any references to it. + return this; + } + + /** + * Update the authored text of the ancestor rules. This should be called when setting + * the authored text of a (nested) rule, so all the references are properly updated. + */ + async updateAncestorRulesAuthoredText() { + return Promise.all( + this.ancestorRules.map(rule => rule.getAuthoredCssText(true)) + ); + } + + /** + * Modify a rule's properties. Passed an array of modifications: + * { + * type: "set", + * index: , + * name: , + * value: , + * priority: + * } + * or + * { + * type: "remove", + * index: , + * name: , + * } + * + * @returns the rule with updated properties + */ + modifyProperties(modifications) { + // Use a fresh element for each call to this function to prevent side + // effects that pop up based on property values that were already set on the + // element. + let document; + if (this.rawNode) { + document = this.rawNode.ownerDocument; + } else { + let parentStyleSheet = this._parentSheet; + while (parentStyleSheet.ownerRule) { + parentStyleSheet = parentStyleSheet.ownerRule.parentStyleSheet; + } + + document = this.getDocument(parentStyleSheet); + } + + const tempElement = document.createElementNS(XHTML_NS, "div"); + + for (const mod of modifications) { + if (mod.type === "set") { + tempElement.style.setProperty(mod.name, mod.value, mod.priority || ""); + this.rawStyle.setProperty( + mod.name, + tempElement.style.getPropertyValue(mod.name), + mod.priority || "" + ); + } else if (mod.type === "remove" || mod.type === "disable") { + this.rawStyle.removeProperty(mod.name); + } + } + + this.pageStyle.refreshObservedRules(this.ancestorRules); + + // Add processed modifications to the _pendingDeclarationChanges array, + // they will be emitted as CSS_CHANGE resources once `declarations` have + // been re-computed in `form`. + this._pendingDeclarationChanges.push(...modifications); + + return this; + } + + /** + * Helper function for modifySelector, inserts the new + * rule with the new selector into the parent style sheet and removes the + * current rule. Returns the newly inserted css rule or null if the rule is + * unsuccessfully inserted to the parent style sheet. + * + * @param {String} value + * The new selector value + * @param {Boolean} editAuthored + * True if the selector should be updated by editing the + * authored text; false if the selector should be updated via + * CSSOM. + * + * @returns {CSSRule} + * The new CSS rule added + */ + async _addNewSelector(value, editAuthored) { + const rule = this.rawRule; + const parentStyleSheet = this._parentSheet; + + // We know the selector modification is ok, so if the client asked + // for the authored text to be edited, do it now. + if (editAuthored) { + const document = this.getDocument(this._parentSheet); + try { + document.querySelector(value); + } catch (e) { + return null; + } + + const resourceId = + this.pageStyle.styleSheetsManager.getStyleSheetResourceId( + this._parentSheet + ); + let authoredText = await this.pageStyle.styleSheetsManager.getText( + resourceId + ); + + const [startOffset, endOffset] = getSelectorOffsets( + authoredText, + this.line, + this.column + ); + authoredText = + authoredText.substring(0, startOffset) + + value + + authoredText.substring(endOffset); + + await this.pageStyle.styleSheetsManager.setStyleSheetText( + resourceId, + authoredText, + { kind: UPDATE_PRESERVING_RULES } + ); + } else { + // We retrieve the parent of the rule, which can be a regular stylesheet, but also + // another rule, in case the underlying rule is nested. + // If the rule is nested in another rule, we need to use its parent rule to "edit" it. + // If the rule has no parent rules, we can simply use the stylesheet. + const parent = this.rawRule.parentRule || parentStyleSheet; + const cssRules = parent.cssRules; + const cssText = rule.cssText; + const selectorText = rule.selectorText; + + for (let i = 0; i < cssRules.length; i++) { + if (rule === cssRules.item(i)) { + try { + // Inserts the new style rule into the current style sheet and + // delete the current rule + const ruleText = cssText.slice(selectorText.length).trim(); + parent.insertRule(value + " " + ruleText, i); + parent.deleteRule(i + 1); + break; + } catch (e) { + // The selector could be invalid, or the rule could fail to insert. + return null; + } + } + } + } + + await this.updateAncestorRulesAuthoredText(); + + return this._getRuleFromIndex(parentStyleSheet); + } + + /** + * Take an object with instructions to modify a CSS declaration and log an object with + * normalized metadata which describes the change in the context of this rule. + * + * @param {Object} change + * Data about a modification to a declaration. @see |modifyProperties()| + * @param {Object} newDeclarations + * The current declarations array to get the latest values, names... + * @param {Object} oldDeclarations + * The previous declarations array to use to fetch old values, names... + */ + logDeclarationChange(change, newDeclarations, oldDeclarations) { + // Position of the declaration within its rule. + const index = change.index; + // Destructure properties from the previous CSS declaration at this index, if any, + // to new variable names to indicate the previous state. + let { + value: prevValue, + name: prevName, + priority: prevPriority, + commentOffsets, + } = oldDeclarations[index] || {}; + + const { value: currentValue, name: currentName } = + newDeclarations[index] || {}; + // A declaration is disabled if it has a `commentOffsets` array. + // Here we type coerce the value to a boolean with double-bang (!!) + const prevDisabled = !!commentOffsets; + // Append the "!important" string if defined in the previous priority flag. + prevValue = + prevValue && prevPriority ? `${prevValue} !important` : prevValue; + + const data = this.metadata; + + switch (change.type) { + case "set": + data.type = prevValue ? "declaration-add" : "declaration-update"; + // If `change.newName` is defined, use it because the property is being renamed. + // Otherwise, a new declaration is being created or the value of an existing + // declaration is being updated. In that case, use the currentName computed + // by the engine. + const changeName = currentName || change.name; + const name = change.newName ? change.newName : changeName; + // Append the "!important" string if defined in the incoming priority flag. + + const changeValue = currentValue || change.value; + const newValue = change.priority + ? `${changeValue} !important` + : changeValue; + + // Reuse the previous value string, when the property is renamed. + // Otherwise, use the incoming value string. + const value = change.newName ? prevValue : newValue; + + data.add = [{ property: name, value, index }]; + // If there is a previous value, log its removal together with the previous + // property name. Using the previous name handles the case for renaming a property + // and is harmless when updating an existing value (the name stays the same). + if (prevValue) { + data.remove = [{ property: prevName, value: prevValue, index }]; + } else { + data.remove = null; + } + + // When toggling a declaration from OFF to ON, if not renaming the property, + // do not mark the previous declaration for removal, otherwise the add and + // remove operations will cancel each other out when tracked. Tracked changes + // have no context of "disabled", only "add" or remove, like diffs. + if (prevDisabled && !change.newName && prevValue === newValue) { + data.remove = null; + } + + break; + + case "remove": + data.type = "declaration-remove"; + data.add = null; + data.remove = [{ property: change.name, value: prevValue, index }]; + break; + + case "disable": + data.type = "declaration-disable"; + data.add = null; + data.remove = [{ property: change.name, value: prevValue, index }]; + break; + } + + TrackChangeEmitter.trackChange(data); + } + + /** + * Helper method for tracking CSS changes. Logs the change of this rule's selector as + * two operations: a removal using the old selector and an addition using the new one. + * + * @param {String} oldSelector + * This rule's previous selector. + * @param {String} newSelector + * This rule's new selector. + */ + logSelectorChange(oldSelector, newSelector) { + TrackChangeEmitter.trackChange({ + ...this.metadata, + type: "selector-remove", + add: null, + remove: null, + selector: oldSelector, + }); + + TrackChangeEmitter.trackChange({ + ...this.metadata, + type: "selector-add", + add: null, + remove: null, + selector: newSelector, + }); + } + + /** + * Modify the current rule's selector by inserting a new rule with the new + * selector value and removing the current rule. + * + * Returns information about the new rule and applied style + * so that consumers can immediately display the new rule, whether or not the + * selector matches the current element without having to refresh the whole + * list. + * + * @param {DOMNode} node + * The current selected element + * @param {String} value + * The new selector value + * @param {Boolean} editAuthored + * True if the selector should be updated by editing the + * authored text; false if the selector should be updated via + * CSSOM. + * @returns {Object} + * Returns an object that contains the applied style properties of the + * new rule and a boolean indicating whether or not the new selector + * matches the current selected element + */ + modifySelector(node, value, editAuthored = false) { + if (this.type === ELEMENT_STYLE || this.rawRule.selectorText === value) { + return { ruleProps: null, isMatching: true }; + } + + // Nullify cached desugared selectors as it might be outdated + this._desugaredSelectors = null; + + // The rule's previous selector is lost after calling _addNewSelector(). Save it now. + const oldValue = this.rawRule.selectorText; + let selectorPromise = this._addNewSelector(value, editAuthored); + + if (editAuthored) { + selectorPromise = selectorPromise.then(newCssRule => { + if (newCssRule) { + this.logSelectorChange(oldValue, value); + const style = this.pageStyle._styleRef(newCssRule); + // See the comment in |form| to understand this. + return style.getAuthoredCssText().then(() => newCssRule); + } + return newCssRule; + }); + } + + return selectorPromise.then(newCssRule => { + let entries = null; + let isMatching = false; + + if (newCssRule) { + const ruleEntry = this.pageStyle.findEntryMatchingRule( + node, + newCssRule + ); + if (ruleEntry.length === 1) { + entries = this.pageStyle.getAppliedProps(node, ruleEntry, { + matchedSelectors: true, + }); + } else { + entries = this.pageStyle.getNewAppliedProps(node, newCssRule); + } + + isMatching = entries.some( + ruleProp => !!ruleProp.matchedDesugaredSelectors.length + ); + } + + const result = { isMatching }; + if (entries) { + result.ruleProps = { entries }; + } + + return result; + }); + } + + /** + * Get the eligible query container for a given @container rule and a given node + * + * @param {Number} ancestorRuleIndex: The index of the @container rule in this.ancestorRules + * @param {NodeActor} nodeActor: The nodeActor for which we want to retrieve the query container + * @returns {Object} An object with the following properties: + * - node: {NodeActor|null} The nodeActor representing the query container, + * null if none were found + * - containerType: {string} The computed `containerType` value of the query container + * - inlineSize: {string} The computed `inlineSize` value of the query container (e.g. `120px`) + * - blockSize: {string} The computed `blockSize` value of the query container (e.g. `812px`) + */ + getQueryContainerForNode(ancestorRuleIndex, nodeActor) { + const ancestorRule = this.ancestorRules[ancestorRuleIndex]; + if (!ancestorRule) { + console.error( + `Couldn't not find an ancestor rule at index ${ancestorRuleIndex}` + ); + return { node: null }; + } + + const containerEl = ancestorRule.rawRule.queryContainerFor( + nodeActor.rawNode + ); + + // queryContainerFor returns null when the container name wasn't find in any ancestor. + // In practice this shouldn't happen, as if the rule is applied, it means that an + // elligible container was found. + if (!containerEl) { + return { node: null }; + } + + const computedStyle = CssLogic.getComputedStyle(containerEl); + return { + node: this.pageStyle.walker.getNode(containerEl), + containerType: computedStyle.containerType, + inlineSize: computedStyle.inlineSize, + blockSize: computedStyle.blockSize, + }; + } + + /** + * Using the latest computed style applicable to the selected element, + * check the states of declarations in this CSS rule. + * + * If any have changed their used/unused state, potentially as a result of changes in + * another rule, fire a "rule-updated" event with this rule actor in its latest state. + * + * @param {Boolean} forceRefresh: Set to true to emit "rule-updated", even if the state + * of the declarations didn't change. + */ + maybeRefresh(forceRefresh) { + let hasChanged = false; + + const el = this.pageStyle.selectedElement; + const style = CssLogic.getComputedStyle(el); + + for (const decl of this._declarations) { + // TODO: convert from Object to Boolean. See Bug 1574471 + const isUsed = isPropertyUsed(el, style, this.rawRule, decl.name); + + if (decl.isUsed.used !== isUsed.used) { + decl.isUsed = isUsed; + hasChanged = true; + } + } + + if (hasChanged || forceRefresh) { + // ⚠️ IMPORTANT ⚠️ + // When an event is emitted via the protocol with the StyleRuleActor as payload, the + // corresponding StyleRuleFront will be automatically updated under the hood. + // Therefore, when the client looks up properties on the front reference it already + // has, it will get the latest values set on the actor, not the ones it originally + // had when the front was created. The client is not required to explicitly replace + // its previous front reference to the one it receives as this event's payload. + // The client doesn't even need to explicitly listen for this event. + // The update of the front happens automatically. + this.emit("rule-updated", this); + } + } +} +exports.StyleRuleActor = StyleRuleActor; + +/** + * Compute the start and end offsets of a rule's selector text, given + * the CSS text and the line and column at which the rule begins. + * @param {String} initialText + * @param {Number} line (1-indexed) + * @param {Number} column (1-indexed) + * @return {array} An array with two elements: [startOffset, endOffset]. + * The elements mark the bounds in |initialText| of + * the CSS rule's selector. + */ +function getSelectorOffsets(initialText, line, column) { + if (typeof line === "undefined" || typeof column === "undefined") { + throw new Error("Location information is missing"); + } + + const { offset: textOffset, text } = getTextAtLineColumn( + initialText, + line, + column + ); + const lexer = getCSSLexer(text); + + // Search forward for the opening brace. + let endOffset; + while (true) { + const token = lexer.nextToken(); + if (!token) { + break; + } + if (token.tokenType === "symbol" && token.text === "{") { + if (endOffset === undefined) { + break; + } + return [textOffset, textOffset + endOffset]; + } + // Preserve comments and whitespace just before the "{". + if (token.tokenType !== "comment" && token.tokenType !== "whitespace") { + endOffset = token.endOffset; + } + } + + throw new Error("could not find bounds of rule"); +} diff --git a/devtools/server/actors/style-sheets.js b/devtools/server/actors/style-sheets.js new file mode 100644 index 0000000000..64f16badc0 --- /dev/null +++ b/devtools/server/actors/style-sheets.js @@ -0,0 +1,105 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +const { Actor } = require("resource://devtools/shared/protocol.js"); +const { + styleSheetsSpec, +} = require("resource://devtools/shared/specs/style-sheets.js"); + +const { + LongStringActor, +} = require("resource://devtools/server/actors/string.js"); + +loader.lazyRequireGetter( + this, + "UPDATE_GENERAL", + "resource://devtools/server/actors/utils/stylesheets-manager.js", + true +); + +/** + * Creates a StyleSheetsActor. StyleSheetsActor provides remote access to the + * stylesheets of a document. + */ +class StyleSheetsActor extends Actor { + constructor(conn, targetActor) { + super(conn, styleSheetsSpec); + + this.parentActor = targetActor; + } + + /** + * The window we work with, taken from the parent actor. + */ + get window() { + return this.parentActor.window; + } + + /** + * The current content document of the window we work with. + */ + get document() { + return this.window.document; + } + + getTraits() { + return { + traits: {}, + }; + } + + destroy() { + for (const win of this.parentActor.windows) { + // This flag only exists for devtools, so we are free to clear + // it when we're done. + win.document.styleSheetChangeEventsEnabled = false; + } + + super.destroy(); + } + + /** + * Create a new style sheet in the document with the given text. + * Return an actor for it. + * + * @param {object} request + * Debugging protocol request object, with 'text property' + * @param {string} fileName + * If the stylesheet adding is from file, `fileName` indicates the path. + * @return {object} + * Object with 'styelSheet' property for form on new actor. + */ + async addStyleSheet(text, fileName = null) { + const styleSheetsManager = this._getStyleSheetsManager(); + await styleSheetsManager.addStyleSheet(this.document, text, fileName); + } + + _getStyleSheetsManager() { + return this.parentActor.getStyleSheetsManager(); + } + + toggleDisabled(resourceId) { + const styleSheetsManager = this._getStyleSheetsManager(); + return styleSheetsManager.toggleDisabled(resourceId); + } + + async getText(resourceId) { + const styleSheetsManager = this._getStyleSheetsManager(); + const text = await styleSheetsManager.getText(resourceId); + return new LongStringActor(this.conn, text || ""); + } + + update(resourceId, text, transition, cause = "") { + const styleSheetsManager = this._getStyleSheetsManager(); + return styleSheetsManager.setStyleSheetText(resourceId, text, { + transition, + kind: UPDATE_GENERAL, + cause, + }); + } +} + +exports.StyleSheetsActor = StyleSheetsActor; diff --git a/devtools/server/actors/target-configuration.js b/devtools/server/actors/target-configuration.js new file mode 100644 index 0000000000..35340ee668 --- /dev/null +++ b/devtools/server/actors/target-configuration.js @@ -0,0 +1,493 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +const { Actor } = require("resource://devtools/shared/protocol.js"); +const { + targetConfigurationSpec, +} = require("resource://devtools/shared/specs/target-configuration.js"); + +const { + SessionDataHelpers, +} = require("resource://devtools/server/actors/watcher/SessionDataHelpers.jsm"); +const { isBrowsingContextPartOfContext } = ChromeUtils.importESModule( + "resource://devtools/server/actors/watcher/browsing-context-helpers.sys.mjs" +); +const { SUPPORTED_DATA } = SessionDataHelpers; +const { TARGET_CONFIGURATION } = SUPPORTED_DATA; + +// List of options supported by this target configuration actor. +/* eslint sort-keys: "error" */ +const SUPPORTED_OPTIONS = { + // Disable network request caching. + cacheDisabled: true, + // Enable color scheme simulation. + colorSchemeSimulation: true, + // Enable custom formatters + customFormatters: true, + // Set a custom user agent + customUserAgent: true, + // Enable JavaScript + javascriptEnabled: true, + // Force a custom device pixel ratio (used in RDM). Set to null to restore origin ratio. + overrideDPPX: true, + // Enable print simulation mode. + printSimulationEnabled: true, + // Override navigator.maxTouchPoints (used in RDM and doesn't apply if RDM isn't enabled) + rdmPaneMaxTouchPoints: true, + // Page orientation (used in RDM and doesn't apply if RDM isn't enabled) + rdmPaneOrientation: true, + // Enable allocation tracking, if set, contains an object defining the tracking configurations + recordAllocations: true, + // Reload the page when the touch simulation state changes (only works alongside touchEventsOverride) + reloadOnTouchSimulationToggle: true, + // Restore focus in the page after closing DevTools. + restoreFocus: true, + // Enable service worker testing over HTTP (instead of HTTPS only). + serviceWorkersTestingEnabled: true, + // Set the current tab offline + setTabOffline: true, + // Enable touch events simulation + touchEventsOverride: true, + // Used to configure and start/stop the JavaScript tracer + tracerOptions: true, + // Use simplified highlighters when prefers-reduced-motion is enabled. + useSimpleHighlightersForReducedMotion: true, +}; +/* eslint-disable sort-keys */ + +/** + * This actor manages the configuration flags which apply to DevTools targets. + * + * Configuration flags should be applied to all concerned targets when the + * configuration is updated, and new targets should also be able to read the + * flags when they are created. The flags will be forwarded to the WatcherActor + * and stored as TARGET_CONFIGURATION data entries. + * Some flags will be set directly set from this actor, in the parent process + * (see _updateParentProcessConfiguration), and others will be set from the target actor, + * in the content process. + * + * @constructor + * + */ +class TargetConfigurationActor extends Actor { + constructor(watcherActor) { + super(watcherActor.conn, targetConfigurationSpec); + this.watcherActor = watcherActor; + + this._onBrowsingContextAttached = + this._onBrowsingContextAttached.bind(this); + // We need to be notified of new browsing context being created so we can re-set flags + // we already set on the "previous" browsing context. We're using this event as it's + // emitted very early in the document lifecycle (i.e. before any script on the page is + // executed), which is not the case for "window-global-created" for example. + Services.obs.addObserver( + this._onBrowsingContextAttached, + "browsing-context-attached" + ); + + // When we perform a bfcache navigation, the current browsing context gets + // replaced with a browsing which was previously stored in bfcache and we + // should update our reference accordingly. + this._onBfCacheNavigation = this._onBfCacheNavigation.bind(this); + this.watcherActor.on( + "bf-cache-navigation-pageshow", + this._onBfCacheNavigation + ); + + this._browsingContext = this.watcherActor.browserElement?.browsingContext; + } + + form() { + return { + actor: this.actorID, + configuration: this._getConfiguration(), + traits: { supportedOptions: SUPPORTED_OPTIONS }, + }; + } + + /** + * Returns whether or not this actor should handle the flag that should be set on the + * BrowsingContext in the parent process. + * + * @returns {Boolean} + */ + _shouldHandleConfigurationInParentProcess() { + // Only handle parent process configuration if the watcherActor is tied to a + // browser element. + // For now, the Browser Toolbox and Web Extension are having a unique target + // which applies the configuration by itself on new documents. + return this.watcherActor.sessionContext.type == "browser-element"; + } + + /** + * Event handler for attached browsing context. This will be called when + * a new browsing context is created that we might want to handle + * (e.g. when navigating to a page with Cross-Origin-Opener-Policy header) + */ + _onBrowsingContextAttached(browsingContext) { + if (!this._shouldHandleConfigurationInParentProcess()) { + return; + } + + // We only want to set flags on top-level browsing context. The platform + // will take care of propagating it to the entire browsing contexts tree. + if (browsingContext.parent) { + return; + } + + // Only process BrowsingContexts which are related to the debugged scope. + // As this callback fires very early, the BrowsingContext may not have + // any WindowGlobal yet and so we ignore all checks dones against the WindowGlobal + // if there is none. Meaning we might accept more BrowsingContext than expected. + if ( + !isBrowsingContextPartOfContext( + browsingContext, + this.watcherActor.sessionContext, + { acceptNoWindowGlobal: true, forceAcceptTopLevelTarget: true } + ) + ) { + return; + } + + const rdmEnabledInPreviousBrowsingContext = this._browsingContext.inRDMPane; + + // Before replacing the target browsing context, restore the configuration + // on the previous one if they share the same browser. + if ( + this._browsingContext && + this._browsingContext.browserId === browsingContext.browserId && + !this._browsingContext.isDiscarded + ) { + // For now this should always be true as long as we already had a browsing + // context set, but the same logic should be used when supporting EFT on + // toolboxes with several top level browsing contexts: when a new browsing + // context attaches, only reset the browsing context with the same browserId + this._restoreParentProcessConfiguration(); + } + + // We need to store the browsing context as this.watcherActor.browserElement.browsingContext + // can still refer to the previous browsing context at this point. + this._browsingContext = browsingContext; + + // If `inRDMPane` was set in the previous browsing context, set it again on the new one, + // otherwise some RDM-related configuration won't be applied (e.g. orientation). + if (rdmEnabledInPreviousBrowsingContext) { + this._browsingContext.inRDMPane = true; + } + this._updateParentProcessConfiguration(this._getConfiguration()); + } + + _onBfCacheNavigation({ windowGlobal } = {}) { + if (windowGlobal) { + this._onBrowsingContextAttached(windowGlobal.browsingContext); + } + } + + _getConfiguration() { + const targetConfigurationData = + this.watcherActor.getSessionDataForType(TARGET_CONFIGURATION); + if (!targetConfigurationData) { + return {}; + } + + const cfgMap = {}; + for (const { key, value } of targetConfigurationData) { + cfgMap[key] = value; + } + return cfgMap; + } + + /** + * + * @param {Object} configuration + * @returns Promise Applied configuration object + */ + async updateConfiguration(configuration) { + const cfgArray = Object.keys(configuration) + .filter(key => { + if (!SUPPORTED_OPTIONS[key]) { + console.warn(`Unsupported option for TargetConfiguration: ${key}`); + return false; + } + return true; + }) + .map(key => ({ key, value: configuration[key] })); + + this._updateParentProcessConfiguration(configuration); + await this.watcherActor.addOrSetDataEntry( + TARGET_CONFIGURATION, + cfgArray, + "add" + ); + return this._getConfiguration(); + } + + /** + * + * @param {Object} configuration: See `updateConfiguration` + */ + _updateParentProcessConfiguration(configuration) { + if (!this._shouldHandleConfigurationInParentProcess()) { + return; + } + + let shouldReload = false; + for (const [key, value] of Object.entries(configuration)) { + switch (key) { + case "colorSchemeSimulation": + this._setColorSchemeSimulation(value); + break; + case "customUserAgent": + this._setCustomUserAgent(value); + break; + case "javascriptEnabled": + if (value !== undefined) { + // This flag requires a reload in order to take full effect, + // so reload if it has changed. + if (value != this.isJavascriptEnabled()) { + shouldReload = true; + } + this._setJavascriptEnabled(value); + } + break; + case "overrideDPPX": + this._setDPPXOverride(value); + break; + case "printSimulationEnabled": + this._setPrintSimulationEnabled(value); + break; + case "rdmPaneMaxTouchPoints": + this._setRDMPaneMaxTouchPoints(value); + break; + case "rdmPaneOrientation": + this._setRDMPaneOrientation(value); + break; + case "serviceWorkersTestingEnabled": + this._setServiceWorkersTestingEnabled(value); + break; + case "touchEventsOverride": + this._setTouchEventsOverride(value); + break; + case "cacheDisabled": + this._setCacheDisabled(value); + break; + case "setTabOffline": + this._setTabOffline(value); + break; + } + } + + if (shouldReload) { + this._browsingContext.reload(Ci.nsIWebNavigation.LOAD_FLAGS_NONE); + } + } + + _restoreParentProcessConfiguration() { + if (!this._shouldHandleConfigurationInParentProcess()) { + return; + } + + this._setServiceWorkersTestingEnabled(false); + this._setPrintSimulationEnabled(false); + this._setCacheDisabled(false); + this._setTabOffline(false); + + // Restore the color scheme simulation only if it was explicitly updated + // by this actor. This will avoid side effects caused when destroying additional + // targets (e.g. RDM target, WebExtension target, …). + // TODO: We may want to review other configuration values to see if we should use + // the same pattern (Bug 1701553). + if (this._resetColorSchemeSimulationOnDestroy) { + this._setColorSchemeSimulation(null); + } + + // Restore the user agent only if it was explicitly updated by this specific actor. + if (this._initialUserAgent !== undefined) { + this._setCustomUserAgent(this._initialUserAgent); + } + + // Restore the origin device pixel ratio only if it was explicitly updated by this + // specific actor. + if (this._initialDPPXOverride !== undefined) { + this._setDPPXOverride(this._initialDPPXOverride); + } + + if (this._initialJavascriptEnabled !== undefined) { + this._setJavascriptEnabled(this._initialJavascriptEnabled); + } + + if (this._initialTouchEventsOverride !== undefined) { + this._setTouchEventsOverride(this._initialTouchEventsOverride); + } + } + + /** + * Disable or enable the service workers testing features. + */ + _setServiceWorkersTestingEnabled(enabled) { + if (this._browsingContext.serviceWorkersTestingEnabled != enabled) { + this._browsingContext.serviceWorkersTestingEnabled = enabled; + } + } + + /** + * Disable or enable the print simulation. + */ + _setPrintSimulationEnabled(enabled) { + const value = enabled ? "print" : ""; + if (this._browsingContext.mediumOverride != value) { + this._browsingContext.mediumOverride = value; + } + } + + /** + * Disable or enable the color-scheme simulation. + */ + _setColorSchemeSimulation(override) { + const value = override || "none"; + if (this._browsingContext.prefersColorSchemeOverride != value) { + this._browsingContext.prefersColorSchemeOverride = value; + this._resetColorSchemeSimulationOnDestroy = true; + } + } + + /** + * Set a custom user agent on the page + * + * @param {String} userAgent: The user agent to set on the page. If null, will reset the + * user agent to its original value. + * @returns {Boolean} Whether the user agent was changed or not. + */ + _setCustomUserAgent(userAgent = "") { + if (this._browsingContext.customUserAgent === userAgent) { + return; + } + + if (this._initialUserAgent === undefined) { + this._initialUserAgent = this._browsingContext.customUserAgent; + } + + this._browsingContext.customUserAgent = userAgent; + } + + isJavascriptEnabled() { + return this._browsingContext.allowJavascript; + } + + _setJavascriptEnabled(allow) { + if (this._initialJavascriptEnabled === undefined) { + this._initialJavascriptEnabled = this._browsingContext.allowJavascript; + } + if (allow !== undefined) { + this._browsingContext.allowJavascript = allow; + } + } + + /* DPPX override */ + _setDPPXOverride(dppx) { + if (this._browsingContext.overrideDPPX === dppx) { + return; + } + + if (!dppx && this._initialDPPXOverride) { + dppx = this._initialDPPXOverride; + } else if (dppx !== undefined && this._initialDPPXOverride === undefined) { + this._initialDPPXOverride = this._browsingContext.overrideDPPX; + } + + if (dppx !== undefined) { + this._browsingContext.overrideDPPX = dppx; + } + } + + /** + * Set the touchEventsOverride on the browsing context. + * + * @param {String} flag: See BrowsingContext.webidl `TouchEventsOverride` enum for values. + */ + _setTouchEventsOverride(flag) { + if (this._browsingContext.touchEventsOverride === flag) { + return; + } + + if (!flag && this._initialTouchEventsOverride) { + flag = this._initialTouchEventsOverride; + } else if ( + flag !== undefined && + this._initialTouchEventsOverride === undefined + ) { + this._initialTouchEventsOverride = + this._browsingContext.touchEventsOverride; + } + + if (flag !== undefined) { + this._browsingContext.touchEventsOverride = flag; + } + } + + /** + * Overrides navigator.maxTouchPoints. + * Note that we don't need to reset the original value when the actor is destroyed, + * as it's directly handled by the platform when RDM is closed. + * + * @param {Integer} maxTouchPoints + */ + _setRDMPaneMaxTouchPoints(maxTouchPoints) { + this._browsingContext.setRDMPaneMaxTouchPoints(maxTouchPoints); + } + + /** + * Set an orientation and an angle on the browsing context. This will be applied only + * if Responsive Design Mode is enabled. + * + * @param {Object} options + * @param {String} options.type: The orientation type of the rotated device. + * @param {Number} options.angle: The rotated angle of the device. + */ + _setRDMPaneOrientation({ type, angle }) { + this._browsingContext.setRDMPaneOrientation(type, angle); + } + + /** + * Disable or enable the cache via the browsing context. + * + * @param {Boolean} disabled: The state the cache should be changed to + */ + _setCacheDisabled(disabled) { + const value = disabled + ? Ci.nsIRequest.LOAD_BYPASS_CACHE + : Ci.nsIRequest.LOAD_NORMAL; + if (this._browsingContext.defaultLoadFlags != value) { + this._browsingContext.defaultLoadFlags = value; + } + } + + /** + * Set the browsing context to offline. + * + * @param {Boolean} offline: Whether the network throttling is set to offline + */ + _setTabOffline(offline) { + if (!this._browsingContext.isDiscarded) { + this._browsingContext.forceOffline = offline; + } + } + + destroy() { + Services.obs.removeObserver( + this._onBrowsingContextAttached, + "browsing-context-attached" + ); + this.watcherActor.off( + "bf-cache-navigation-pageshow", + this._onBfCacheNavigation + ); + this._restoreParentProcessConfiguration(); + super.destroy(); + } +} + +exports.TargetConfigurationActor = TargetConfigurationActor; diff --git a/devtools/server/actors/targets/base-target-actor.js b/devtools/server/actors/targets/base-target-actor.js new file mode 100644 index 0000000000..f3fc2a89e7 --- /dev/null +++ b/devtools/server/actors/targets/base-target-actor.js @@ -0,0 +1,214 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +const { Actor } = require("resource://devtools/shared/protocol.js"); +const { + TYPES, + getResourceWatcher, +} = require("resource://devtools/server/actors/resources/index.js"); +const Targets = require("devtools/server/actors/targets/index"); + +loader.lazyRequireGetter( + this, + "SessionDataProcessors", + "resource://devtools/server/actors/targets/session-data-processors/index.js", + true +); + +class BaseTargetActor extends Actor { + constructor(conn, targetType, spec) { + super(conn, spec); + + /** + * Type of target, a string of Targets.TYPES. + * @return {string} + */ + this.targetType = targetType; + } + + /** + * Process a new data entry, which can be watched resources, breakpoints, ... + * + * @param string type + * The type of data to be added + * @param Array entries + * The values to be added to this type of data + * @param Boolean isDocumentCreation + * Set to true if this function is called just after a new document (and its + * associated target) is created. + * @param String updateType + * "add" will only add the new entries in the existing data set. + * "set" will update the data set with the new entries. + */ + async addOrSetSessionDataEntry( + type, + entries, + isDocumentCreation = false, + updateType + ) { + const processor = SessionDataProcessors[type]; + if (processor) { + await processor.addOrSetSessionDataEntry( + this, + entries, + isDocumentCreation, + updateType + ); + } + } + + /** + * Remove data entries that have been previously added via addOrSetSessionDataEntry + * + * See addOrSetSessionDataEntry for argument description. + */ + removeSessionDataEntry(type, entries) { + const processor = SessionDataProcessors[type]; + if (processor) { + processor.removeSessionDataEntry(this, entries); + } + } + + /** + * Called by Resource Watchers, when new resources are available, updated or destroyed. + * + * @param String updateType + * Can be "available", "updated" or "destroyed" + * @param Array resources + * List of all resource's form. A resource is a JSON object piped over to the client. + * It can contain actor IDs, actor forms, to be manually marshalled by the client. + */ + notifyResources(updateType, resources) { + if (resources.length === 0 || this.isDestroyed()) { + // Don't try to emit if the resources array is empty or the actor was + // destroyed. + return; + } + + if (this.devtoolsSpawnedBrowsingContextForWebExtension) { + this.overrideResourceBrowsingContextForWebExtension(resources); + } + + this.emit(`resource-${updateType}-form`, resources); + } + + /** + * For WebExtension, we have to hack all resource's browsingContextID + * in order to ensure emitting them with the fixed, original browsingContextID + * related to the fallback document created by devtools which always exists. + * The target's form will always be relating to that BrowsingContext IDs (browsing context ID and inner window id). + * Even if the target switches internally to another document via WindowGlobalTargetActor._setWindow. + * + * @param {Array} List of resources + */ + overrideResourceBrowsingContextForWebExtension(resources) { + const browsingContextID = + this.devtoolsSpawnedBrowsingContextForWebExtension.id; + resources.forEach( + resource => (resource.browsingContextID = browsingContextID) + ); + } + + // List of actor prefixes (string) which have already been instantiated via getTargetScopedActor method. + #instantiatedTargetScopedActors = new Set(); + + /** + * Try to return any target scoped actor instance, if it exists. + * They are lazily instantiated and so will only be available + * if the client called at least one of their method. + * + * @param {String} prefix + * Prefix for the actor we would like to retrieve. + * Defined in devtools/server/actors/utils/actor-registry.js + */ + getTargetScopedActor(prefix) { + if (this.isDestroyed()) { + return null; + } + const form = this.form(); + this.#instantiatedTargetScopedActors.add(prefix); + return this.conn._getOrCreateActor(form[prefix + "Actor"]); + } + + /** + * Returns true, if the related target scoped actor has already been queried + * and instantiated via `getTargetScopedActor` method. + * + * @param {String} prefix + * See getTargetScopedActor definition + * @return Boolean + * True, if the actor has already been instantiated. + */ + hasTargetScopedActor(prefix) { + return this.#instantiatedTargetScopedActors.has(prefix); + } + + /** + * Apply target-specific options. + * + * This will be called by the watcher when the DevTools target-configuration + * is updated, or when a target is created via JSWindowActors. + * + * @param {JSON} options + * Configuration object provided by the client. + * See target-configuration actor. + * @param {Boolean} calledFromDocumentCreate + * True, when this is called with initial configuration when the related target + * actor is instantiated. + */ + updateTargetConfiguration(options = {}, calledFromDocumentCreation = false) { + // If there is some tracer options, we should start tracing, otherwise we should stop (if we were) + if (options.tracerOptions) { + // Ignore the SessionData update if the user requested to start the tracer on next page load and: + // - we apply it to an already loaded WindowGlobal, + // - the target isn't the top level one. + if ( + options.tracerOptions.traceOnNextLoad && + (!calledFromDocumentCreation || !this.isTopLevelTarget) + ) { + if (this.isTopLevelTarget) { + const consoleMessageWatcher = getResourceWatcher( + this, + TYPES.CONSOLE_MESSAGE + ); + if (consoleMessageWatcher) { + consoleMessageWatcher.emitMessages([ + { + arguments: [ + "Waiting for next navigation or page reload before starting tracing", + ], + styles: [], + level: "jstracer", + chromeContext: false, + timeStamp: ChromeUtils.dateNow(), + }, + ]); + } + } + return; + } + // Bug 1874204: For now, in the browser toolbox, only frame and workers are traced. + // Content process targets are ignored as they would also include each document/frame target. + // This would require some work to ignore FRAME targets from here, only in case of browser toolbox, + // and also handle all content process documents for DOM Event logging. + // + // Bug 1874219: Also ignore extensions for now as they are all running in the same process, + // whereas we can only spawn one tracer per thread. + if ( + this.targetType == Targets.TYPES.PROCESS || + this.url?.startsWith("moz-extension://") + ) { + return; + } + const tracerActor = this.getTargetScopedActor("tracer"); + tracerActor.startTracing(options.tracerOptions); + } else if (this.hasTargetScopedActor("tracer")) { + const tracerActor = this.getTargetScopedActor("tracer"); + tracerActor.stopTracing(); + } + } +} +exports.BaseTargetActor = BaseTargetActor; diff --git a/devtools/server/actors/targets/content-process.js b/devtools/server/actors/targets/content-process.js new file mode 100644 index 0000000000..56b1934ef1 --- /dev/null +++ b/devtools/server/actors/targets/content-process.js @@ -0,0 +1,265 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +/* + * Target actor for all resources in a content process of Firefox (chrome sandboxes, frame + * scripts, documents, etc.) + * + * See devtools/docs/backend/actor-hierarchy.md for more details. + */ + +const { ThreadActor } = require("resource://devtools/server/actors/thread.js"); +const { + WebConsoleActor, +} = require("resource://devtools/server/actors/webconsole.js"); +const makeDebugger = require("resource://devtools/server/actors/utils/make-debugger.js"); +const { Pool } = require("resource://devtools/shared/protocol.js"); +const { assert } = require("resource://devtools/shared/DevToolsUtils.js"); +const { + SourcesManager, +} = require("resource://devtools/server/actors/utils/sources-manager.js"); +const { + contentProcessTargetSpec, +} = require("resource://devtools/shared/specs/targets/content-process.js"); +const Targets = require("resource://devtools/server/actors/targets/index.js"); +const Resources = require("resource://devtools/server/actors/resources/index.js"); +const { + BaseTargetActor, +} = require("resource://devtools/server/actors/targets/base-target-actor.js"); +const { TargetActorRegistry } = ChromeUtils.importESModule( + "resource://devtools/server/actors/targets/target-actor-registry.sys.mjs", + { + loadInDevToolsLoader: false, + } +); + +loader.lazyRequireGetter( + this, + "WorkerDescriptorActorList", + "resource://devtools/server/actors/worker/worker-descriptor-actor-list.js", + true +); +loader.lazyRequireGetter( + this, + "MemoryActor", + "resource://devtools/server/actors/memory.js", + true +); +loader.lazyRequireGetter( + this, + "TracerActor", + "resource://devtools/server/actors/tracer.js", + true +); + +class ContentProcessTargetActor extends BaseTargetActor { + constructor(conn, { isXpcShellTarget = false, sessionContext } = {}) { + super(conn, Targets.TYPES.PROCESS, contentProcessTargetSpec); + + this.threadActor = null; + this.isXpcShellTarget = isXpcShellTarget; + this.sessionContext = sessionContext; + + // Use a see-everything debugger + this.makeDebugger = makeDebugger.bind(null, { + findDebuggees: dbg => + dbg.findAllGlobals().map(g => g.unsafeDereference()), + shouldAddNewGlobalAsDebuggee: global => true, + }); + + const sandboxPrototype = { + get tabs() { + return Array.from( + Services.ww.getWindowEnumerator(), + win => win.docShell.messageManager + ); + }, + }; + + // Scope into which the webconsole executes: + // A sandbox with chrome privileges with a `tabs` getter. + const systemPrincipal = Cc["@mozilla.org/systemprincipal;1"].createInstance( + Ci.nsIPrincipal + ); + const sandbox = Cu.Sandbox(systemPrincipal, { + sandboxPrototype, + wantGlobalProperties: ["ChromeUtils"], + }); + this._consoleScope = sandbox; + + this._workerList = null; + this._workerDescriptorActorPool = null; + this._onWorkerListChanged = this._onWorkerListChanged.bind(this); + + // Try to destroy the Content Process Target when the content process shuts down. + // The parent process can't communicate during shutdown as the communication channel + // is already down (message manager or JS Window Actor API). + // So that we have to observe to some event fired from this process. + // While such cleanup doesn't sound ultimately necessary (the process will be completely destroyed) + // mochitests are asserting that there is no leaks during process shutdown. + // Do not override destroy as Protocol.js may override it when calling destroy, + // and we won't be able to call removeObserver correctly. + this.destroyObserver = this.destroy.bind(this); + Services.obs.addObserver(this.destroyObserver, "xpcom-shutdown"); + if (this.isXpcShellTarget) { + TargetActorRegistry.registerXpcShellTargetActor(this); + } + } + + get isRootActor() { + return true; + } + + get url() { + return undefined; + } + + get window() { + return this._consoleScope; + } + + get sourcesManager() { + if (!this._sourcesManager) { + assert( + this.threadActor, + "threadActor should exist when creating SourcesManager." + ); + this._sourcesManager = new SourcesManager(this.threadActor); + } + return this._sourcesManager; + } + + /* + * Return a Debugger instance or create one if there is none yet + */ + get dbg() { + if (!this._dbg) { + this._dbg = this.makeDebugger(); + } + return this._dbg; + } + + form() { + if (!this._consoleActor) { + this._consoleActor = new WebConsoleActor(this.conn, this); + this.manage(this._consoleActor); + } + + if (!this.threadActor) { + this.threadActor = new ThreadActor(this, null); + this.manage(this.threadActor); + } + if (!this.memoryActor) { + this.memoryActor = new MemoryActor(this.conn, this); + this.manage(this.memoryActor); + } + if (!this.tracerActor) { + this.tracerActor = new TracerActor(this.conn, this); + this.manage(this.tracerActor); + } + + return { + actor: this.actorID, + isXpcShellTarget: this.isXpcShellTarget, + processID: Services.appinfo.processID, + remoteType: Services.appinfo.remoteType, + + consoleActor: this._consoleActor.actorID, + memoryActor: this.memoryActor.actorID, + threadActor: this.threadActor.actorID, + tracerActor: this.tracerActor.actorID, + + traits: { + networkMonitor: false, + // See trait description in browsing-context.js + supportsTopLevelTargetFlag: false, + }, + }; + } + + ensureWorkerList() { + if (!this._workerList) { + this._workerList = new WorkerDescriptorActorList(this.conn, {}); + } + return this._workerList; + } + + listWorkers() { + return this.ensureWorkerList() + .getList() + .then(actors => { + const pool = new Pool(this.conn, "workers"); + for (const actor of actors) { + pool.manage(actor); + } + + // Do not destroy the pool before transfering ownership to the newly created + // pool, so that we do not accidentally destroy actors that are still in use. + if (this._workerDescriptorActorPool) { + this._workerDescriptorActorPool.destroy(); + } + + this._workerDescriptorActorPool = pool; + this._workerList.onListChanged = this._onWorkerListChanged; + + return { workers: actors }; + }); + } + + _onWorkerListChanged() { + this.conn.send({ from: this.actorID, type: "workerListChanged" }); + this._workerList.onListChanged = null; + } + + pauseMatchingServiceWorkers(request) { + this.ensureWorkerList().workerPauser.setPauseServiceWorkers(request.origin); + } + + destroy() { + // Avoid reentrancy. We will destroy the Transport when emitting "destroyed", + // which will force destroying all actors. + if (this.destroying) { + return; + } + this.destroying = true; + + // Unregistering watchers first is important + // otherwise you might have leaks reported when running browser_browser_toolbox_netmonitor.js in debug builds + Resources.unwatchAllResources(this); + + this.emit("destroyed"); + + super.destroy(); + + if (this.threadActor) { + this.threadActor = null; + } + + // Tell the live lists we aren't watching any more. + if (this._workerList) { + this._workerList.destroy(); + this._workerList = null; + } + + if (this._sourcesManager) { + this._sourcesManager.destroy(); + this._sourcesManager = null; + } + + if (this._dbg) { + this._dbg.disable(); + this._dbg = null; + } + + Services.obs.removeObserver(this.destroyObserver, "xpcom-shutdown"); + + if (this.isXpcShellTarget) { + TargetActorRegistry.unregisterXpcShellTargetActor(this); + } + } +} + +exports.ContentProcessTargetActor = ContentProcessTargetActor; diff --git a/devtools/server/actors/targets/index.js b/devtools/server/actors/targets/index.js new file mode 100644 index 0000000000..61501d37e8 --- /dev/null +++ b/devtools/server/actors/targets/index.js @@ -0,0 +1,14 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +const TYPES = { + FRAME: "frame", + PROCESS: "process", + WORKER: "worker", + SERVICE_WORKER: "service_worker", + SHARED_WORKER: "shared_worker", +}; +exports.TYPES = TYPES; diff --git a/devtools/server/actors/targets/moz.build b/devtools/server/actors/targets/moz.build new file mode 100644 index 0000000000..f4d44ae669 --- /dev/null +++ b/devtools/server/actors/targets/moz.build @@ -0,0 +1,20 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +DIRS += [ + "session-data-processors", +] + +DevToolsModules( + "base-target-actor.js", + "content-process.js", + "index.js", + "parent-process.js", + "target-actor-registry.sys.mjs", + "webextension.js", + "window-global.js", + "worker.js", +) diff --git a/devtools/server/actors/targets/parent-process.js b/devtools/server/actors/targets/parent-process.js new file mode 100644 index 0000000000..4b7da5e9a4 --- /dev/null +++ b/devtools/server/actors/targets/parent-process.js @@ -0,0 +1,167 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +/* + * Target actor for the entire parent process. + * + * This actor extends WindowGlobalTargetActor. + * This actor is extended by WebExtensionTargetActor. + * + * See devtools/docs/backend/actor-hierarchy.md for more details. + */ + +const { + DevToolsServer, +} = require("resource://devtools/server/devtools-server.js"); +const { + getChildDocShells, + WindowGlobalTargetActor, +} = require("resource://devtools/server/actors/targets/window-global.js"); +const makeDebugger = require("resource://devtools/server/actors/utils/make-debugger.js"); + +const { + parentProcessTargetSpec, +} = require("resource://devtools/shared/specs/targets/parent-process.js"); + +class ParentProcessTargetActor extends WindowGlobalTargetActor { + /** + * Creates a target actor for debugging all the chrome content in the parent process. + * Most of the implementation is inherited from WindowGlobalTargetActor. + * ParentProcessTargetActor is a child of RootActor, it can be instantiated via + * RootActor.getProcess request. ParentProcessTargetActor exposes all target-scoped actors + * via its form() request, like WindowGlobalTargetActor. + * + * @param conn DevToolsServerConnection + * The connection to the client. + * @param {Object} options + * - isTopLevelTarget: {Boolean} flag to indicate if this is the top + * level target of the DevTools session + * - sessionContext Object + * The Session Context to help know what is debugged. + * See devtools/server/actors/watcher/session-context.js + * - customSpec Object + * WebExtensionTargetActor inherits from ParentProcessTargetActor + * and has to use its own protocol.js specification object. + */ + constructor( + conn, + { isTopLevelTarget, sessionContext, customSpec = parentProcessTargetSpec } + ) { + super(conn, { + isTopLevelTarget, + sessionContext, + customSpec, + }); + + // This creates a Debugger instance for chrome debugging all globals. + this.makeDebugger = makeDebugger.bind(null, { + findDebuggees: dbg => + dbg.findAllGlobals().map(g => g.unsafeDereference()), + shouldAddNewGlobalAsDebuggee: () => true, + }); + + // Ensure catching the creation of any new content docshell + this.watchNewDocShells = true; + + this.isRootActor = true; + + // Listen for any new/destroyed chrome docshell + Services.obs.addObserver(this, "chrome-webnavigation-create"); + Services.obs.addObserver(this, "chrome-webnavigation-destroy"); + + // If we are the parent process target actor and not a subclass + // (i.e. if we aren't the webext target actor) + // set the parent process docshell: + if (customSpec == parentProcessTargetSpec) { + this.setDocShell(this._getInitialDocShell()); + } + } + + // Overload setDocShell in order to observe all the docshells. + // WindowGlobalTargetActor only observes the top level one, + // but we also need to observe all of them for WebExtensionTargetActor subclass. + setDocShell(initialDocShell) { + super.setDocShell(initialDocShell); + + // Iterate over all top-level windows. + for (const { docShell } of Services.ww.getWindowEnumerator()) { + if (docShell == this.docShell) { + continue; + } + this._progressListener.watch(docShell); + } + } + + _getInitialDocShell() { + // Defines the default docshell selected for the target actor + let window = Services.wm.getMostRecentWindow( + DevToolsServer.chromeWindowType + ); + + // Default to any available top level window if there is no expected window + // eg when running ./mach run --chrome chrome://browser/content/aboutTabCrashed.xhtml --jsdebugger + if (!window) { + window = Services.wm.getMostRecentWindow(null); + } + + // We really want _some_ window at least, so fallback to the hidden window if + // there's nothing else (such as during early startup). + if (!window) { + window = Services.appShell.hiddenDOMWindow; + } + return window.docShell; + } + + /** + * Getter for the list of all docshells in this targetActor + * @return {Array} + */ + get docShells() { + // Iterate over all top-level windows and all their docshells. + let docShells = []; + for (const { docShell } of Services.ww.getWindowEnumerator()) { + docShells = docShells.concat(getChildDocShells(docShell)); + } + + return docShells; + } + + observe(subject, topic, data) { + super.observe(subject, topic, data); + if (this.isDestroyed()) { + return; + } + + subject.QueryInterface(Ci.nsIDocShell); + + if (topic == "chrome-webnavigation-create") { + this._onDocShellCreated(subject); + } else if (topic == "chrome-webnavigation-destroy") { + this._onDocShellDestroy(subject); + } + } + + _detach() { + if (this.isDestroyed()) { + return false; + } + + Services.obs.removeObserver(this, "chrome-webnavigation-create"); + Services.obs.removeObserver(this, "chrome-webnavigation-destroy"); + + // Iterate over all top-level windows. + for (const { docShell } of Services.ww.getWindowEnumerator()) { + if (docShell == this.docShell) { + continue; + } + this._progressListener.unwatch(docShell); + } + + return super._detach(); + } +} + +exports.ParentProcessTargetActor = ParentProcessTargetActor; diff --git a/devtools/server/actors/targets/session-data-processors/blackboxing.js b/devtools/server/actors/targets/session-data-processors/blackboxing.js new file mode 100644 index 0000000000..70f4397a72 --- /dev/null +++ b/devtools/server/actors/targets/session-data-processors/blackboxing.js @@ -0,0 +1,28 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +module.exports = { + async addOrSetSessionDataEntry( + targetActor, + entries, + isDocumentCreation, + updateType + ) { + const { sourcesManager } = targetActor; + if (updateType == "set") { + sourcesManager.clearAllBlackBoxing(); + } + for (const { url, range } of entries) { + sourcesManager.blackBox(url, range); + } + }, + + removeSessionDataEntry(targetActor, entries, isDocumentCreation) { + for (const { url, range } of entries) { + targetActor.sourcesManager.unblackBox(url, range); + } + }, +}; diff --git a/devtools/server/actors/targets/session-data-processors/breakpoints.js b/devtools/server/actors/targets/session-data-processors/breakpoints.js new file mode 100644 index 0000000000..ff7cb7ec0a --- /dev/null +++ b/devtools/server/actors/targets/session-data-processors/breakpoints.js @@ -0,0 +1,45 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +const { + STATES: THREAD_STATES, +} = require("resource://devtools/server/actors/thread.js"); + +module.exports = { + async addOrSetSessionDataEntry( + targetActor, + entries, + isDocumentCreation, + updateType + ) { + const { threadActor } = targetActor; + if (updateType == "set") { + threadActor.removeAllBreakpoints(); + } + const isTargetCreation = threadActor.state == THREAD_STATES.DETACHED; + if (isTargetCreation && !targetActor.targetType.endsWith("worker")) { + // If addOrSetSessionDataEntry is called during target creation, attach the + // thread actor automatically and pass the initial breakpoints. + // However, do not attach the thread actor for Workers. They use a codepath + // which releases the worker on `attach`. For them, the client will call `attach`. (bug 1691986) + await threadActor.attach({ breakpoints: entries }); + } else { + // If addOrSetSessionDataEntry is called for an existing target, set the new + // breakpoints on the already running thread actor. + await Promise.all( + entries.map(({ location, options }) => + threadActor.setBreakpoint(location, options) + ) + ); + } + }, + + removeSessionDataEntry(targetActor, entries, isDocumentCreation) { + for (const { location } of entries) { + targetActor.threadActor.removeBreakpoint(location); + } + }, +}; diff --git a/devtools/server/actors/targets/session-data-processors/event-breakpoints.js b/devtools/server/actors/targets/session-data-processors/event-breakpoints.js new file mode 100644 index 0000000000..c0a2fb7ffe --- /dev/null +++ b/devtools/server/actors/targets/session-data-processors/event-breakpoints.js @@ -0,0 +1,36 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +const { + STATES: THREAD_STATES, +} = require("resource://devtools/server/actors/thread.js"); + +module.exports = { + async addOrSetSessionDataEntry( + targetActor, + entries, + isDocumentCreation, + updateType + ) { + const { threadActor } = targetActor; + // Same as comments for XHR breakpoints. See lines 117-118 + if ( + threadActor.state == THREAD_STATES.DETACHED && + !targetActor.targetType.endsWith("worker") + ) { + threadActor.attach(); + } + if (updateType == "set") { + threadActor.setActiveEventBreakpoints(entries); + } else { + threadActor.addEventBreakpoints(entries); + } + }, + + removeSessionDataEntry(targetActor, entries, isDocumentCreation) { + targetActor.threadActor.removeEventBreakpoints(entries); + }, +}; diff --git a/devtools/server/actors/targets/session-data-processors/index.js b/devtools/server/actors/targets/session-data-processors/index.js new file mode 100644 index 0000000000..19b7d69302 --- /dev/null +++ b/devtools/server/actors/targets/session-data-processors/index.js @@ -0,0 +1,50 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +const { + SessionDataHelpers, +} = require("resource://devtools/server/actors/watcher/SessionDataHelpers.jsm"); +const { SUPPORTED_DATA } = SessionDataHelpers; + +const SessionDataProcessors = {}; + +loader.lazyRequireGetter( + SessionDataProcessors, + SUPPORTED_DATA.BLACKBOXING, + "resource://devtools/server/actors/targets/session-data-processors/blackboxing.js" +); +loader.lazyRequireGetter( + SessionDataProcessors, + SUPPORTED_DATA.BREAKPOINTS, + "resource://devtools/server/actors/targets/session-data-processors/breakpoints.js" +); +loader.lazyRequireGetter( + SessionDataProcessors, + SUPPORTED_DATA.EVENT_BREAKPOINTS, + "resource://devtools/server/actors/targets/session-data-processors/event-breakpoints.js" +); +loader.lazyRequireGetter( + SessionDataProcessors, + SUPPORTED_DATA.RESOURCES, + "resource://devtools/server/actors/targets/session-data-processors/resources.js" +); +loader.lazyRequireGetter( + SessionDataProcessors, + SUPPORTED_DATA.TARGET_CONFIGURATION, + "resource://devtools/server/actors/targets/session-data-processors/target-configuration.js" +); +loader.lazyRequireGetter( + SessionDataProcessors, + SUPPORTED_DATA.THREAD_CONFIGURATION, + "resource://devtools/server/actors/targets/session-data-processors/thread-configuration.js" +); +loader.lazyRequireGetter( + SessionDataProcessors, + SUPPORTED_DATA.XHR_BREAKPOINTS, + "resource://devtools/server/actors/targets/session-data-processors/xhr-breakpoints.js" +); + +exports.SessionDataProcessors = SessionDataProcessors; diff --git a/devtools/server/actors/targets/session-data-processors/moz.build b/devtools/server/actors/targets/session-data-processors/moz.build new file mode 100644 index 0000000000..ea924d7d79 --- /dev/null +++ b/devtools/server/actors/targets/session-data-processors/moz.build @@ -0,0 +1,16 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +DevToolsModules( + "blackboxing.js", + "breakpoints.js", + "event-breakpoints.js", + "index.js", + "resources.js", + "target-configuration.js", + "thread-configuration.js", + "xhr-breakpoints.js", +) diff --git a/devtools/server/actors/targets/session-data-processors/resources.js b/devtools/server/actors/targets/session-data-processors/resources.js new file mode 100644 index 0000000000..8f33ba8e0f --- /dev/null +++ b/devtools/server/actors/targets/session-data-processors/resources.js @@ -0,0 +1,25 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +const Resources = require("resource://devtools/server/actors/resources/index.js"); + +module.exports = { + async addOrSetSessionDataEntry( + targetActor, + entries, + isDocumentCreation, + updateType + ) { + if (updateType == "set") { + Resources.unwatchAllResources(targetActor); + } + await Resources.watchResources(targetActor, entries); + }, + + removeSessionDataEntry(targetActor, entries, isDocumentCreation) { + Resources.unwatchResources(targetActor, entries); + }, +}; diff --git a/devtools/server/actors/targets/session-data-processors/target-configuration.js b/devtools/server/actors/targets/session-data-processors/target-configuration.js new file mode 100644 index 0000000000..f68e82d69f --- /dev/null +++ b/devtools/server/actors/targets/session-data-processors/target-configuration.js @@ -0,0 +1,32 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +module.exports = { + async addOrSetSessionDataEntry( + targetActor, + entries, + isDocumentCreation, + updateType + ) { + // Only WindowGlobalTargetActor implements updateTargetConfiguration, + // skip targetActor data entry update for other targets. + if (typeof targetActor.updateTargetConfiguration == "function") { + const options = {}; + for (const { key, value } of entries) { + options[key] = value; + } + // Regarding `updateType`, `entries` is always a partial set of configurations. + // We will acknowledge the passed attribute, but if we had set some other attributes + // before this call, they will stay as-is. + // So it is as if this session data was also using "add" updateType. + targetActor.updateTargetConfiguration(options, isDocumentCreation); + } + }, + + removeSessionDataEntry(targetActor, entries, isDocumentCreation) { + // configuration data entries are always added/updated, never removed. + }, +}; diff --git a/devtools/server/actors/targets/session-data-processors/thread-configuration.js b/devtools/server/actors/targets/session-data-processors/thread-configuration.js new file mode 100644 index 0000000000..716d2a9b21 --- /dev/null +++ b/devtools/server/actors/targets/session-data-processors/thread-configuration.js @@ -0,0 +1,41 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +const { + STATES: THREAD_STATES, +} = require("resource://devtools/server/actors/thread.js"); + +module.exports = { + async addOrSetSessionDataEntry( + targetActor, + entries, + isDocumentCreation, + updateType + ) { + const threadOptions = {}; + + for (const { key, value } of entries) { + threadOptions[key] = value; + } + + if ( + !targetActor.targetType.endsWith("worker") && + targetActor.threadActor.state == THREAD_STATES.DETACHED + ) { + await targetActor.threadActor.attach(threadOptions); + } else { + // Regarding `updateType`, `entries` is always a partial set of configurations. + // We will acknowledge the passed attribute, but if we had set some other attributes + // before this call, they will stay as-is. + // So it is as if this session data was also using "add" updateType. + await targetActor.threadActor.reconfigure(threadOptions); + } + }, + + removeSessionDataEntry(targetActor, entries, isDocumentCreation) { + // configuration data entries are always added/updated, never removed. + }, +}; diff --git a/devtools/server/actors/targets/session-data-processors/xhr-breakpoints.js b/devtools/server/actors/targets/session-data-processors/xhr-breakpoints.js new file mode 100644 index 0000000000..7a0fd815aa --- /dev/null +++ b/devtools/server/actors/targets/session-data-processors/xhr-breakpoints.js @@ -0,0 +1,44 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +const { + STATES: THREAD_STATES, +} = require("resource://devtools/server/actors/thread.js"); + +module.exports = { + async addOrSetSessionDataEntry( + targetActor, + entries, + isDocumentCreation, + updateType + ) { + const { threadActor } = targetActor; + if (updateType == "set") { + threadActor.removeAllXHRBreakpoints(); + } + + // The thread actor has to be initialized in order to correctly + // retrieve the stack trace when hitting an XHR + if ( + threadActor.state == THREAD_STATES.DETACHED && + !targetActor.targetType.endsWith("worker") + ) { + await threadActor.attach(); + } + + await Promise.all( + entries.map(({ path, method }) => + threadActor.setXHRBreakpoint(path, method) + ) + ); + }, + + removeSessionDataEntry(targetActor, entries, isDocumentCreation) { + for (const { path, method } of entries) { + targetActor.threadActor.removeXHRBreakpoint(path, method); + } + }, +}; diff --git a/devtools/server/actors/targets/target-actor-registry.sys.mjs b/devtools/server/actors/targets/target-actor-registry.sys.mjs new file mode 100644 index 0000000000..4cb6d13868 --- /dev/null +++ b/devtools/server/actors/targets/target-actor-registry.sys.mjs @@ -0,0 +1,82 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +// Keep track of all WindowGlobal target actors. +// This is especially used to track the actors using Message manager connector, +// or the ones running in the parent process. +// Top level actors, like tab's top level target or parent process target +// are still using message manager in order to avoid being destroyed on navigation. +// And because of this, these actors aren't using JS Window Actor. +const windowGlobalTargetActors = new Set(); +let xpcShellTargetActor = null; + +export var TargetActorRegistry = { + registerTargetActor(targetActor) { + windowGlobalTargetActors.add(targetActor); + }, + + unregisterTargetActor(targetActor) { + windowGlobalTargetActors.delete(targetActor); + }, + + registerXpcShellTargetActor(targetActor) { + xpcShellTargetActor = targetActor; + }, + + unregisterXpcShellTargetActor(targetActor) { + xpcShellTargetActor = null; + }, + + get xpcShellTargetActor() { + return xpcShellTargetActor; + }, + + /** + * Return the target actors matching the passed browser element id. + * In some scenarios, the registry can have multiple target actors for a given + * browserId (e.g. the regular DevTools content toolbox + DevTools WebExtensions targets). + * + * @param {Object} sessionContext: The Session Context to help know what is debugged. + * See devtools/server/actors/watcher/session-context.js + * @param {String} connectionPrefix: DevToolsServerConnection's prefix, in order to select only actor + * related to the same connection. i.e. the same client. + * @returns {Array} + */ + getTargetActors(sessionContext, connectionPrefix) { + const actors = []; + for (const actor of windowGlobalTargetActors) { + const isMatchingPrefix = actor.actorID.startsWith(connectionPrefix); + const isMatchingContext = + sessionContext.type == "all" || + (sessionContext.type == "browser-element" && + (actor.browserId == sessionContext.browserId || + actor.openerBrowserId == sessionContext.browserId)) || + (sessionContext.type == "webextension" && + actor.addonId == sessionContext.addonId); + if (isMatchingPrefix && isMatchingContext) { + actors.push(actor); + } + } + return actors; + }, + + /** + * Helper for tests to help track the number of targets created for a given tab. + * (Used by browser_ext_devtools_inspectedWindow.js) + * + * @param {Number} browserId: ID for the tab + * + * @returns {Number} Number of targets for this tab. + */ + + getTargetActorsCountForBrowserElement(browserId) { + let count = 0; + for (const actor of windowGlobalTargetActors) { + if (actor.browserId == browserId) { + count++; + } + } + return count; + }, +}; diff --git a/devtools/server/actors/targets/webextension.js b/devtools/server/actors/targets/webextension.js new file mode 100644 index 0000000000..c717b53011 --- /dev/null +++ b/devtools/server/actors/targets/webextension.js @@ -0,0 +1,374 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +/* + * Target actor for a WebExtension add-on. + * + * This actor extends ParentProcessTargetActor. + * + * See devtools/docs/backend/actor-hierarchy.md for more details. + */ + +const { + ParentProcessTargetActor, +} = require("resource://devtools/server/actors/targets/parent-process.js"); +const makeDebugger = require("resource://devtools/server/actors/utils/make-debugger.js"); +const { + webExtensionTargetSpec, +} = require("resource://devtools/shared/specs/targets/webextension.js"); + +const { + getChildDocShells, +} = require("resource://devtools/server/actors/targets/window-global.js"); + +loader.lazyRequireGetter( + this, + "unwrapDebuggerObjectGlobal", + "resource://devtools/server/actors/thread.js", + true +); + +const lazy = {}; +ChromeUtils.defineESModuleGetters(lazy, { + getAddonIdForWindowGlobal: + "resource://devtools/server/actors/watcher/browsing-context-helpers.sys.mjs", +}); + +const FALLBACK_DOC_URL = + "chrome://devtools/content/shared/webextension-fallback.html"; + +class WebExtensionTargetActor extends ParentProcessTargetActor { + /** + * Creates a target actor for debugging all the contexts associated to a target + * WebExtensions add-on running in a child extension process. Most of the implementation + * is inherited from ParentProcessTargetActor (which inherits most of its implementation + * from WindowGlobalTargetActor). + * + * WebExtensionTargetActor is created by a WebExtensionActor counterpart, when its + * parent actor's `connect` method has been called (on the listAddons RDP package), + * it runs in the same process that the extension is running into (which can be the main + * process if the extension is running in non-oop mode, or the child extension process + * if the extension is running in oop-mode). + * + * A WebExtensionTargetActor contains all target-scoped actors, like a regular + * ParentProcessTargetActor or WindowGlobalTargetActor. + * + * History lecture: + * - The add-on actors used to not inherit WindowGlobalTargetActor because of the + * different way the add-on APIs where exposed to the add-on itself, and for this reason + * the Addon Debugger has only a sub-set of the feature available in the Tab or in the + * Browser Toolbox. + * - In a WebExtensions add-on all the provided contexts (background, popups etc.), + * besides the Content Scripts which run in the content process, hooked to an existent + * tab, by creating a new WebExtensionActor which inherits from + * ParentProcessTargetActor, we can provide a full features Addon Toolbox (which is + * basically like a BrowserToolbox which filters the visible sources and frames to the + * one that are related to the target add-on). + * - When the WebExtensions OOP mode has been introduced, this actor has been refactored + * and moved from the main process to the new child extension process. + * + * @param {DevToolsServerConnection} conn + * The connection to the client. + * @param {nsIMessageSender} chromeGlobal. + * The chromeGlobal where this actor has been injected by the + * frame-connector.js connectToFrame method. + * @param {Object} options + * - addonId: {String} the addonId of the target WebExtension. + * - addonBrowsingContextGroupId: {String} the BrowsingContextGroupId used by this addon. + * - chromeGlobal: {nsIMessageSender} The chromeGlobal where this actor + * has been injected by the frame-connector.js connectToFrame method. + * - isTopLevelTarget: {Boolean} flag to indicate if this is the top + * level target of the DevTools session + * - prefix: {String} the custom RDP prefix to use. + * - sessionContext Object + * The Session Context to help know what is debugged. + * See devtools/server/actors/watcher/session-context.js + */ + constructor( + conn, + { + addonId, + addonBrowsingContextGroupId, + chromeGlobal, + isTopLevelTarget, + prefix, + sessionContext, + } + ) { + super(conn, { + isTopLevelTarget, + sessionContext, + customSpec: webExtensionTargetSpec, + }); + + this.addonId = addonId; + this.addonBrowsingContextGroupId = addonBrowsingContextGroupId; + this._chromeGlobal = chromeGlobal; + this._prefix = prefix; + + // Expose the BrowsingContext of the fallback document, + // which is the one this target actor will always refer to via its form() + // and all resources should be related to this one as we currently spawn + // only just this one target actor to debug all webextension documents. + this.devtoolsSpawnedBrowsingContextForWebExtension = + chromeGlobal.browsingContext; + + // Redefine the messageManager getter to return the chromeGlobal + // as the messageManager for this actor (which is the browser XUL + // element used by the parent actor running in the main process to + // connect to the extension process). + Object.defineProperty(this, "messageManager", { + enumerable: true, + configurable: true, + get: () => { + return this._chromeGlobal; + }, + }); + + this._onParentExit = this._onParentExit.bind(this); + + this._chromeGlobal.addMessageListener( + "debug:webext_parent_exit", + this._onParentExit + ); + + // Set the consoleAPIListener filtering options + // (retrieved and used in the related webconsole child actor). + this.consoleAPIListenerOptions = { + addonId: this.addonId, + }; + + // This creates a Debugger instance for debugging all the add-on globals. + this.makeDebugger = makeDebugger.bind(null, { + findDebuggees: dbg => { + return dbg + .findAllGlobals() + .filter(this._shouldAddNewGlobalAsDebuggee) + .map(g => g.unsafeDereference()); + }, + shouldAddNewGlobalAsDebuggee: + this._shouldAddNewGlobalAsDebuggee.bind(this), + }); + + // NOTE: This is needed to catch in the webextension webconsole all the + // errors raised by the WebExtension internals that are not currently + // associated with any window. + this.isRootActor = true; + + // Try to discovery an existent extension page to attach (which will provide the initial + // URL shown in the window tittle when the addon debugger is opened). + const extensionWindow = this._searchForExtensionWindow(); + this.setDocShell(extensionWindow.docShell); + } + + // Override the ParentProcessTargetActor's override in order to only iterate + // over the docshells specific to this add-on + get docShells() { + // Iterate over all top-level windows and all their docshells. + let docShells = []; + for (const window of Services.ww.getWindowEnumerator(null)) { + docShells = docShells.concat(getChildDocShells(window.docShell)); + } + // Then filter out the ones specific to the add-on + return docShells.filter(docShell => { + return this.isExtensionWindowDescendent(docShell.domWindow); + }); + } + + /** + * Called when the actor is removed from the connection. + */ + destroy() { + if (this._chromeGlobal) { + const chromeGlobal = this._chromeGlobal; + this._chromeGlobal = null; + + chromeGlobal.removeMessageListener( + "debug:webext_parent_exit", + this._onParentExit + ); + + chromeGlobal.sendAsyncMessage("debug:webext_child_exit", { + actor: this.actorID, + }); + } + + if (this.fallbackWindow) { + this.fallbackWindow = null; + } + + this.addon = null; + this.addonId = null; + + return super.destroy(); + } + + // Private helpers. + + _searchFallbackWindow() { + if (this.fallbackWindow) { + // Skip if there is already an existent fallback window. + return this.fallbackWindow; + } + + // Set and initialize the fallbackWindow (which initially is a empty + // about:blank browser), this window is related to a XUL browser element + // specifically created for the devtools server and it is never used + // or navigated anywhere else. + this.fallbackWindow = this._chromeGlobal.content; + + // Add the addonId in the URL to retrieve this information in other devtools + // helpers. The addonId is usually populated in the principal, but this will + // not be the case for the fallback window because it is loaded from chrome:// + // instead of moz-extension://${addonId} + this.fallbackWindow.document.location.href = `${FALLBACK_DOC_URL}#${this.addonId}`; + + return this.fallbackWindow; + } + + // Discovery an extension page to use as a default target window. + // NOTE: This currently fail to discovery an extension page running in a + // windowless browser when running in non-oop mode, and the background page + // is set later using _onNewExtensionWindow. + _searchForExtensionWindow() { + // Looks if there is any top level add-on document: + // (we do not want to pass any nested add-on iframe) + const docShell = this.docShells.find(d => + this.isTopLevelExtensionWindow(d.domWindow) + ); + if (docShell) { + return docShell.domWindow; + } + + return this._searchFallbackWindow(); + } + + // Customized ParentProcessTargetActor/WindowGlobalTargetActor hooks. + + _onDocShellCreated(docShell) { + // Compare against the BrowsingContext's group ID as the document's principal addonId + // won't be set yet for freshly created docshells. It will be later set, when loading the addon URL. + // But right now, it is still on the initial about:blank document and the principal isn't related to the add-on. + if (docShell.browsingContext.group.id != this.addonBrowsingContextGroupId) { + return; + } + super._onDocShellCreated(docShell); + } + + _onDocShellDestroy(docShell) { + if (docShell.browsingContext.group.id != this.addonBrowsingContextGroupId) { + return; + } + // Stop watching this docshell (the unwatch() method will check if we + // started watching it before). + this._unwatchDocShell(docShell); + + // Let the _onDocShellDestroy notify that the docShell has been destroyed. + const webProgress = docShell + .QueryInterface(Ci.nsIInterfaceRequestor) + .getInterface(Ci.nsIWebProgress); + this._notifyDocShellDestroy(webProgress); + + // If the destroyed docShell: + // * was the current docShell, + // * the actor is not destroyed, + // * isn't the background page, as it means the addon is being shutdown or reloaded + // and the target would be replaced by a new one to come, or everything is closing. + // => switch to the fallback window + if ( + !this.isDestroyed() && + docShell == this.docShell && + !docShell.domWindow.location.href.includes( + "_generated_background_page.html" + ) + ) { + this._changeTopLevelDocument(this._searchForExtensionWindow()); + } + } + + _onNewExtensionWindow(window) { + if (!this.window || this.window === this.fallbackWindow) { + this._changeTopLevelDocument(window); + // For new extension windows, the BrowsingContext group id might have + // changed, for instance when reloading the addon. + this.addonBrowsingContextGroupId = + window.docShell.browsingContext.group.id; + } + } + + isTopLevelExtensionWindow(window) { + const { docShell } = window; + const isTopLevel = docShell.sameTypeRootTreeItem == docShell; + // Note: We are not using getAddonIdForWindowGlobal here because the + // fallback window should not be considered as a top level extension window. + return isTopLevel && window.document.nodePrincipal.addonId == this.addonId; + } + + isExtensionWindowDescendent(window) { + // Check if the source is coming from a descendant docShell of an extension window. + // We may have an iframe that loads http content which won't use the add-on principal. + const rootWin = window.docShell.sameTypeRootTreeItem.domWindow; + const addonId = lazy.getAddonIdForWindowGlobal(rootWin.windowGlobalChild); + return addonId == this.addonId; + } + + /** + * Return true if the given global is associated with this addon and should be + * added as a debuggee, false otherwise. + */ + _shouldAddNewGlobalAsDebuggee(newGlobal) { + const global = unwrapDebuggerObjectGlobal(newGlobal); + + if (global instanceof Ci.nsIDOMWindow) { + try { + global.document; + } catch (e) { + // The global might be a sandbox with a window object in its proto chain. If the + // window navigated away since the sandbox was created, it can throw a security + // exception during this property check as the sandbox no longer has access to + // its own proto. + return false; + } + // When `global` is a sandbox it may be a nsIDOMWindow object, + // but won't be the real Window object. Retrieve it via document's ownerGlobal. + const window = global.document.ownerGlobal; + if (!window) { + return false; + } + + // Change top level document as a simulated frame switching. + if (this.isTopLevelExtensionWindow(window)) { + this._onNewExtensionWindow(window); + } + + return this.isExtensionWindowDescendent(window); + } + + try { + // This will fail for non-Sandbox objects, hence the try-catch block. + const metadata = Cu.getSandboxMetadata(global); + if (metadata) { + return metadata.addonID === this.addonId; + } + } catch (e) { + // Unable to retrieve the sandbox metadata. + } + + return false; + } + + // Handlers for the messages received from the parent actor. + + _onParentExit(msg) { + if (msg.json.actor !== this.actorID) { + return; + } + + this.destroy(); + } +} + +exports.WebExtensionTargetActor = WebExtensionTargetActor; diff --git a/devtools/server/actors/targets/window-global.js b/devtools/server/actors/targets/window-global.js new file mode 100644 index 0000000000..5d2bb10164 --- /dev/null +++ b/devtools/server/actors/targets/window-global.js @@ -0,0 +1,1935 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +// protocol.js uses objects as exceptions in order to define +// error packets. +/* eslint-disable no-throw-literal */ + +/* + * WindowGlobalTargetActor is an abstract class used by target actors that hold + * documents, such as frames, chrome windows, etc. + * + * This class is extended by ParentProcessTargetActor, itself being extented by WebExtensionTargetActor. + * + * See devtools/docs/backend/actor-hierarchy.md for more details. + * + * For performance matters, this file should only be loaded in the targeted context's + * process. For example, it shouldn't be evaluated in the parent process until we try to + * debug a document living in the parent process. + */ + +var { + ActorRegistry, +} = require("resource://devtools/server/actors/utils/actor-registry.js"); +var DevToolsUtils = require("resource://devtools/shared/DevToolsUtils.js"); +var { assert } = DevToolsUtils; +var { + SourcesManager, +} = require("resource://devtools/server/actors/utils/sources-manager.js"); +var makeDebugger = require("resource://devtools/server/actors/utils/make-debugger.js"); +const Targets = require("resource://devtools/server/actors/targets/index.js"); +const { TargetActorRegistry } = ChromeUtils.importESModule( + "resource://devtools/server/actors/targets/target-actor-registry.sys.mjs", + { + loadInDevToolsLoader: false, + } +); +const { PrivateBrowsingUtils } = ChromeUtils.importESModule( + "resource://gre/modules/PrivateBrowsingUtils.sys.mjs" +); + +const EXTENSION_CONTENT_SYS_MJS = + "resource://gre/modules/ExtensionContent.sys.mjs"; + +const { Pool } = require("resource://devtools/shared/protocol.js"); +const { + LazyPool, + createExtraActors, +} = require("resource://devtools/shared/protocol/lazy-pool.js"); +const { + windowGlobalTargetSpec, +} = require("resource://devtools/shared/specs/targets/window-global.js"); +const Resources = require("resource://devtools/server/actors/resources/index.js"); +const { + BaseTargetActor, +} = require("resource://devtools/server/actors/targets/base-target-actor.js"); + +loader.lazyRequireGetter( + this, + ["ThreadActor", "unwrapDebuggerObjectGlobal"], + "resource://devtools/server/actors/thread.js", + true +); +loader.lazyRequireGetter( + this, + "WorkerDescriptorActorList", + "resource://devtools/server/actors/worker/worker-descriptor-actor-list.js", + true +); +loader.lazyRequireGetter( + this, + "StyleSheetsManager", + "resource://devtools/server/actors/utils/stylesheets-manager.js", + true +); +const lazy = {}; +loader.lazyGetter(lazy, "ExtensionContent", () => { + return ChromeUtils.importESModule(EXTENSION_CONTENT_SYS_MJS, { + // ExtensionContent.sys.mjs is a singleton and must be loaded through the + // main loader. Note that the user of lazy.ExtensionContent elsewhere in + // this file (at webextensionsContentScriptGlobals) looks up the module + // via Cu.isESModuleLoaded, which also uses the main loader as desired. + loadInDevToolsLoader: false, + }).ExtensionContent; +}); + +loader.lazyRequireGetter( + this, + "TouchSimulator", + "resource://devtools/server/actors/emulation/touch-simulator.js", + true +); + +function getWindowID(window) { + return window.windowGlobalChild.innerWindowId; +} + +function getDocShellChromeEventHandler(docShell) { + let handler = docShell.chromeEventHandler; + if (!handler) { + try { + // Toplevel xul window's docshell doesn't have chromeEventHandler + // attribute. The chrome event handler is just the global window object. + handler = docShell.domWindow; + } catch (e) { + // ignore + } + } + return handler; +} + +/** + * Helper to retrieve all children docshells of a given docshell. + * + * Given that docshell interfaces can only be used within the same process, + * this only returns docshells for children documents that runs in the same process + * as the given docshell. + */ +function getChildDocShells(parentDocShell) { + return parentDocShell.browsingContext + .getAllBrowsingContextsInSubtree() + .filter(browsingContext => { + // Filter out browsingContext which don't expose any docshell (e.g. remote frame) + return browsingContext.docShell; + }) + .map(browsingContext => { + // Map BrowsingContext to DocShell + return browsingContext.docShell; + }); +} + +exports.getChildDocShells = getChildDocShells; + +/** + * Browser-specific actors. + */ + +function getInnerId(window) { + return window.windowGlobalChild.innerWindowId; +} + +class WindowGlobalTargetActor extends BaseTargetActor { + /** + * WindowGlobalTargetActor is the target actor to debug (HTML) documents. + * + * WindowGlobal's are the Gecko representation for a given document's window object. + * It relates to a given nsGlobalWindowInner instance. + * + * The main goal of this class is to expose the target-scoped actors being registered + * via `ActorRegistry.registerModule` and manage their lifetimes. In addition, this + * class also tracks the lifetime of the targeted window global. + * + * ### Main requests: + * + * `detach`: + * Stop document watching and cleanup everything that the target and its children actors created. + * It ultimately lead to destroy the target actor. + * `switchToFrame`: + * Change the targeted document of the whole actor, and its child target-scoped actors + * to an iframe or back to its original document. + * + * Most properties (like `chromeEventHandler` or `docShells`) are meant to be + * used by the various child target actors. + * + * ### RDP events: + * + * - `tabNavigated`: + * Sent when the window global is about to navigate or has just navigated + * to a different document. + * This event contains the following attributes: + * * url (string) + * The new URI being loaded. + * * state (string) + * `start` if we just start requesting the new URL + * `stop` if the new URL is done loading + * * isFrameSwitching (boolean) + * Indicates the event is dispatched when switching the actor context to a + * different frame. When we switch to an iframe, there is no document + * load. The targeted document is most likely going to be already done + * loading. + * * title (string) + * The document title being loaded. (sent only on state=stop) + * + * - `frameUpdate`: + * Sent when there was a change in the child frames contained in the document + * or when the actor's context was switched to another frame. + * This event can have four different forms depending on the type of change: + * * One or many frames are updated: + * { frames: [{ id, url, title, parentID }, ...] } + * * One frame got destroyed: + * { frames: [{ id, destroy: true }]} + * * All frames got destroyed: + * { destroyAll: true } + * * We switched the context of the actor to a specific frame: + * { selected: #id } + * + * ### Internal, non-rdp events: + * + * Various events are also dispatched on the actor itself without being sent to + * the client. They all relate to the documents tracked by this target actor + * (its main targeted document, but also any of its iframes): + * - will-navigate + * This event fires once navigation starts. All pending user prompts are + * dealt with, but it is fired before the first request starts. + * - navigate + * This event is fired once the document's readyState is "complete". + * - window-ready + * This event is fired in various distinct scenarios: + * * When a new Window object is crafted, equivalent of `DOMWindowCreated`. + * It is dispatched before any page script is executed. + * * We will have already received a window-ready event for this window + * when it was created, but we received a window-destroyed event when + * it was frozen into the bfcache, and now the user navigated back to + * this page, so it's now live again and we should resume handling it. + * * For each existing document, when an `attach` request is received. + * At this point scripts in the page will be already loaded. + * * When `swapFrameLoaders` is used, such as with moving window globals + * between windows or toggling Responsive Design Mode. + * - window-destroyed + * This event is fired in two cases: + * * When the window object is destroyed, i.e. when the related document + * is garbage collected. This can happen when the window global is + * closed or the iframe is removed from the DOM. + * It is equivalent of `inner-window-destroyed` event. + * * When the page goes into the bfcache and gets frozen. + * The equivalent of `pagehide`. + * - changed-toplevel-document + * This event fires when we switch the actor's targeted document + * to one of its iframes, or back to its original top document. + * It is dispatched between window-destroyed and window-ready. + * + * Note that *all* these events are dispatched in the following order + * when we switch the context of the actor to a given iframe: + * - will-navigate + * - window-destroyed + * - changed-toplevel-document + * - window-ready + * - navigate + * + * This class is subclassed by ParentProcessTargetActor and others. + * Subclasses are expected to implement a getter for the docShell property. + * + * @param conn DevToolsServerConnection + * The conection to the client. + * @param options Object + * Object with following attributes: + * - docShell nsIDocShell + * The |docShell| for the debugged frame. + * - followWindowGlobalLifeCycle Boolean + * If true, the target actor will only inspect the current WindowGlobal (and its children windows). + * But won't inspect next document loaded in the same BrowsingContext. + * The actor will behave more like a WindowGlobalTarget rather than a BrowsingContextTarget. + * This is always true for Tab debugging, but not yet for parent process/web extension. + * - isTopLevelTarget Boolean + * Should be set to true for all top-level targets. A top level target + * is the topmost target of a DevTools "session". For instance for a local + * tab toolbox, the WindowGlobalTargetActor for the content page is the top level target. + * For the Multiprocess Browser Toolbox, the parent process target is the top level + * target. + * At the moment this only impacts the WindowGlobalTarget `reconfigure` + * implementation. But for server-side target switching this flag will be exposed + * to the client and should be available for all target actor classes. It will be + * used to detect target switching. (Bug 1644397) + * - ignoreSubFrames Boolean + * If true, the actor will only focus on the passed docShell and not on the whole + * docShell tree. This should be enabled when we have targets for all documents. + * - sessionContext Object + * The Session Context to help know what is debugged. + * See devtools/server/actors/watcher/session-context.js + */ + constructor( + conn, + { + docShell, + followWindowGlobalLifeCycle, + isTopLevelTarget, + ignoreSubFrames, + sessionContext, + customSpec = windowGlobalTargetSpec, + } + ) { + super(conn, Targets.TYPES.FRAME, customSpec); + + this.followWindowGlobalLifeCycle = followWindowGlobalLifeCycle; + this.isTopLevelTarget = !!isTopLevelTarget; + this.ignoreSubFrames = ignoreSubFrames; + this.sessionContext = sessionContext; + + // A map of actor names to actor instances provided by extensions. + this._extraActors = {}; + this._sourcesManager = null; + + this._shouldAddNewGlobalAsDebuggee = + this._shouldAddNewGlobalAsDebuggee.bind(this); + + this.makeDebugger = makeDebugger.bind(null, { + findDebuggees: () => { + const result = []; + const inspectUAWidgets = Services.prefs.getBoolPref( + "devtools.inspector.showAllAnonymousContent", + false + ); + for (const win of this.windows) { + result.push(win); + // Only expose User Agent internal (like