From 43a97878ce14b72f0981164f87f2e35e14151312 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sun, 7 Apr 2024 11:22:09 +0200 Subject: Adding upstream version 110.0.1. Signed-off-by: Daniel Baumann --- .../server/actors/accessibility/accessibility.js | 133 + devtools/server/actors/accessibility/accessible.js | 654 ++++ .../server/actors/accessibility/audit/contrast.js | 312 ++ .../server/actors/accessibility/audit/keyboard.js | 515 +++ .../server/actors/accessibility/audit/moz.build | 12 + .../actors/accessibility/audit/text-label.js | 439 +++ devtools/server/actors/accessibility/constants.js | 150 + devtools/server/actors/accessibility/moz.build | 20 + .../actors/accessibility/parent-accessibility.js | 157 + devtools/server/actors/accessibility/simulator.js | 83 + devtools/server/actors/accessibility/walker.js | 1324 +++++++ devtools/server/actors/accessibility/worker.js | 105 + devtools/server/actors/addon/addons.js | 69 + devtools/server/actors/addon/moz.build | 10 + .../actors/addon/webextension-inspected-window.js | 680 ++++ devtools/server/actors/animation-type-longhand.js | 429 +++ devtools/server/actors/animation.js | 899 +++++ devtools/server/actors/array-buffer.js | 69 + devtools/server/actors/blackboxing.js | 96 + devtools/server/actors/breakpoint-list.js | 100 + devtools/server/actors/breakpoint.js | 216 ++ devtools/server/actors/changes.js | 122 + devtools/server/actors/common.js | 110 + .../server/actors/compatibility/compatibility.js | 171 + .../actors/compatibility/lib/MDNCompatibility.js | 498 +++ 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 | 187 + .../compatibility/lib/test/xpcshell/xpcshell.ini | 7 + devtools/server/actors/compatibility/moz.build | 16 + devtools/server/actors/css-properties.js | 123 + devtools/server/actors/descriptors/moz.build | 12 + devtools/server/actors/descriptors/process.js | 246 ++ devtools/server/actors/descriptors/tab.js | 257 ++ devtools/server/actors/descriptors/webextension.js | 344 ++ devtools/server/actors/descriptors/worker.js | 174 + 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 | 310 ++ devtools/server/actors/environment.js | 208 ++ devtools/server/actors/errordocs.js | 221 ++ devtools/server/actors/frame.js | 227 ++ devtools/server/actors/heap-snapshot-file.js | 81 + devtools/server/actors/highlighters.css | 1053 ++++++ devtools/server/actors/highlighters.js | 390 ++ devtools/server/actors/highlighters/accessible.js | 387 ++ .../server/actors/highlighters/auto-refresh.js | 317 ++ devtools/server/actors/highlighters/box-model.js | 876 +++++ devtools/server/actors/highlighters/css-grid.js | 1954 ++++++++++ .../server/actors/highlighters/css-transform.js | 265 ++ devtools/server/actors/highlighters/eye-dropper.js | 610 +++ devtools/server/actors/highlighters/flexbox.js | 1037 ++++++ devtools/server/actors/highlighters/fonts.js | 122 + .../server/actors/highlighters/geometry-editor.js | 798 ++++ .../server/actors/highlighters/measuring-tool.js | 763 ++++ devtools/server/actors/highlighters/moz.build | 29 + .../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 | 341 ++ devtools/server/actors/highlighters/selector.js | 97 + devtools/server/actors/highlighters/shapes.js | 3261 ++++++++++++++++ .../server/actors/highlighters/tabbing-order.js | 237 ++ .../actors/highlighters/utils/accessibility.js | 773 ++++ .../server/actors/highlighters/utils/canvas.js | 596 +++ .../server/actors/highlighters/utils/markup.js | 773 ++++ .../server/actors/highlighters/utils/moz.build | 7 + devtools/server/actors/inspector/constants.js | 17 + devtools/server/actors/inspector/css-logic.js | 1647 +++++++++ .../actors/inspector/custom-element-watcher.js | 145 + .../server/actors/inspector/document-walker.js | 221 ++ .../server/actors/inspector/event-collector.js | 1061 ++++++ devtools/server/actors/inspector/inspector.js | 353 ++ devtools/server/actors/inspector/moz.build | 21 + devtools/server/actors/inspector/node-picker.js | 443 +++ devtools/server/actors/inspector/node.js | 860 +++++ devtools/server/actors/inspector/utils.js | 592 +++ devtools/server/actors/inspector/walker.js | 2881 +++++++++++++++ devtools/server/actors/layout.js | 524 +++ devtools/server/actors/manifest.js | 45 + devtools/server/actors/media-rule.js | 83 + devtools/server/actors/memory.js | 88 + devtools/server/actors/moz.build | 95 + .../actors/network-monitor/channel-event-sink.js | 100 + .../actors/network-monitor/eventsource-actor.js | 92 + devtools/server/actors/network-monitor/moz.build | 14 + .../actors/network-monitor/network-content.js | 152 + .../actors/network-monitor/network-event-actor.js | 582 +++ .../actors/network-monitor/network-parent.js | 167 + .../actors/network-monitor/websocket-actor.js | 157 + devtools/server/actors/object.js | 813 ++++ devtools/server/actors/object/moz.build | 12 + devtools/server/actors/object/previewers.js | 1098 ++++++ .../actors/object/private-properties-iterator.js | 72 + devtools/server/actors/object/property-iterator.js | 651 ++++ devtools/server/actors/object/symbol-iterator.js | 66 + devtools/server/actors/object/symbol.js | 109 + devtools/server/actors/object/utils.js | 564 +++ devtools/server/actors/page-style.js | 1417 +++++++ devtools/server/actors/pause-scoped.js | 100 + devtools/server/actors/perf.js | 55 + devtools/server/actors/preference.js | 105 + devtools/server/actors/process.js | 76 + devtools/server/actors/reflow.js | 516 +++ .../server/actors/resources/console-messages.js | 278 ++ devtools/server/actors/resources/css-changes.js | 42 + devtools/server/actors/resources/css-messages.js | 139 + 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 | 438 +++ .../actors/resources/last-private-context-exit.js | 46 + devtools/server/actors/resources/moz.build | 39 + .../actors/resources/network-events-content.js | 258 ++ .../actors/resources/network-events-stacktraces.js | 207 ++ devtools/server/actors/resources/network-events.js | 392 ++ .../resources/parent-process-document-event.js | 177 + .../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 | 97 + devtools/server/actors/resources/storage-cache.js | 19 + devtools/server/actors/resources/storage-cookie.js | 19 + .../server/actors/resources/storage-indexed-db.js | 19 + .../actors/resources/storage-local-storage.js | 19 + .../actors/resources/storage-session-storage.js | 19 + devtools/server/actors/resources/stylesheets.js | 138 + devtools/server/actors/resources/thread-states.js | 136 + .../resources/utils/content-process-storage.js | 458 +++ devtools/server/actors/resources/utils/moz.build | 14 + .../utils/nsi-console-listener-watcher.js | 192 + .../resources/utils/parent-process-storage.js | 584 +++ devtools/server/actors/resources/websockets.js | 196 + devtools/server/actors/root.js | 620 ++++ devtools/server/actors/screenshot-content.js | 147 + devtools/server/actors/screenshot.js | 20 + devtools/server/actors/source.js | 789 ++++ devtools/server/actors/storage.js | 3888 ++++++++++++++++++++ devtools/server/actors/string.js | 45 + devtools/server/actors/style-rule.js | 1245 +++++++ devtools/server/actors/style-sheet.js | 568 +++ devtools/server/actors/style-sheets.js | 394 ++ devtools/server/actors/target-configuration.js | 471 +++ devtools/server/actors/targets/content-process.js | 257 ++ devtools/server/actors/targets/index.js | 12 + devtools/server/actors/targets/moz.build | 20 + devtools/server/actors/targets/parent-process.js | 168 + .../targets/session-data-processors/blackboxing.js | 19 + .../targets/session-data-processors/breakpoints.js | 37 + .../session-data-processors/event-breakpoints.js | 26 + .../targets/session-data-processors/index.js | 50 + .../targets/session-data-processors/moz.build | 16 + .../targets/session-data-processors/resources.js | 17 + .../target-configuration.js | 23 + .../thread-configuration.js | 32 + .../session-data-processors/xhr-breakpoints.js | 34 + .../server/actors/targets/target-actor-mixin.js | 130 + .../actors/targets/target-actor-registry.sys.mjs | 117 + devtools/server/actors/targets/webextension.js | 376 ++ devtools/server/actors/targets/window-global.js | 1933 ++++++++++ devtools/server/actors/targets/worker.js | 128 + devtools/server/actors/thread-configuration.js | 77 + devtools/server/actors/thread.js | 2306 ++++++++++++ devtools/server/actors/utils/accessibility.js | 102 + devtools/server/actors/utils/actor-registry.js | 429 +++ .../server/actors/utils/breakpoint-actor-map.js | 74 + devtools/server/actors/utils/capture-screenshot.js | 206 ++ devtools/server/actors/utils/css-grid-utils.js | 75 + devtools/server/actors/utils/custom-formatters.js | 331 ++ devtools/server/actors/utils/dbg-source.js | 97 + devtools/server/actors/utils/event-breakpoints.js | 503 +++ devtools/server/actors/utils/event-loop.js | 221 ++ .../actors/utils/inactive-property-helper.js | 1164 ++++++ devtools/server/actors/utils/logEvent.js | 106 + devtools/server/actors/utils/make-debugger.js | 119 + devtools/server/actors/utils/moz.build | 30 + 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 | 501 +++ devtools/server/actors/utils/stack.js | 183 + devtools/server/actors/utils/style-utils.js | 211 ++ .../server/actors/utils/stylesheets-manager.js | 948 +++++ .../server/actors/utils/track-change-emitter.js | 26 + devtools/server/actors/utils/walker-search.js | 320 ++ devtools/server/actors/utils/watchpoint-map.js | 163 + devtools/server/actors/watcher.js | 770 ++++ .../server/actors/watcher/SessionDataHelpers.jsm | 206 ++ .../server/actors/watcher/WatcherRegistry.sys.mjs | 379 ++ .../watcher/browsing-context-helpers.sys.mjs | 423 +++ devtools/server/actors/watcher/moz.build | 16 + devtools/server/actors/watcher/session-context.js | 213 ++ .../actors/watcher/target-helpers/frame-helper.js | 322 ++ .../server/actors/watcher/target-helpers/moz.build | 11 + .../watcher/target-helpers/process-helper.js | 380 ++ .../actors/watcher/target-helpers/worker-helper.js | 128 + devtools/server/actors/webbrowser.js | 775 ++++ devtools/server/actors/webconsole.js | 1857 ++++++++++ devtools/server/actors/webconsole/commands.js | 245 ++ .../actors/webconsole/eager-ecma-allowlist.js | 161 + .../actors/webconsole/eager-function-allowlist.js | 70 + .../server/actors/webconsole/eval-with-debugger.js | 659 ++++ .../actors/webconsole/listeners/console-api.js | 250 ++ .../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 | 245 ++ .../server/actors/webconsole/listeners/moz.build | 13 + devtools/server/actors/webconsole/moz.build | 20 + devtools/server/actors/webconsole/utils.js | 639 ++++ .../actors/webconsole/webidl-deprecated-list.js | 69 + .../actors/webconsole/webidl-pure-allowlist.js | 72 + .../server/actors/webconsole/worker-listeners.js | 35 + devtools/server/actors/worker/moz.build | 13 + devtools/server/actors/worker/push-subscription.js | 41 + .../worker/service-worker-registration-list.js | 114 + .../actors/worker/service-worker-registration.js | 271 ++ devtools/server/actors/worker/service-worker.js | 44 + .../actors/worker/worker-descriptor-actor-list.js | 213 ++ .../server/connectors/content-process-connector.js | 124 + devtools/server/connectors/frame-connector.js | 216 ++ .../js-window-actor/DevToolsFrameChild.sys.mjs | 691 ++++ .../js-window-actor/DevToolsFrameParent.sys.mjs | 272 ++ .../js-window-actor/DevToolsWorkerChild.sys.mjs | 551 +++ .../js-window-actor/DevToolsWorkerParent.sys.mjs | 293 ++ .../js-window-actor/WindowGlobalLogger.sys.mjs | 76 + .../server/connectors/js-window-actor/moz.build | 13 + devtools/server/connectors/moz.build | 15 + devtools/server/connectors/worker-connector.js | 207 ++ devtools/server/devtools-server-connection.js | 574 +++ devtools/server/devtools-server.js | 500 +++ devtools/server/moz.build | 29 + devtools/server/performance/memory.js | 502 +++ devtools/server/performance/moz.build | 12 + devtools/server/socket/moz.build | 11 + devtools/server/socket/tests/chrome/chrome.ini | 3 + .../socket/tests/chrome/test_websocket-server.html | 88 + devtools/server/socket/websocket-server.js | 250 ++ devtools/server/startup/content-process-script.js | 281 ++ devtools/server/startup/content-process.js | 33 + devtools/server/startup/content-process.sys.mjs | 105 + devtools/server/startup/frame.js | 195 + devtools/server/startup/moz.build | 13 + devtools/server/startup/worker.js | 152 + 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.ini | 154 + .../browser_accessibility_highlighter_infobar.js | 77 + ...browser_accessibility_infobar_audit_keyboard.js | 160 + ...owser_accessibility_infobar_audit_text_label.js | 170 + .../browser/browser_accessibility_infobar_show.js | 181 + .../browser_accessibility_keyboard_audit.js | 375 ++ .../tests/browser/browser_accessibility_node.js | 132 + .../browser/browser_accessibility_node_audit.js | 120 + .../browser/browser_accessibility_node_events.js | 203 + ...accessibility_node_tabbing_order_highlighter.js | 92 + .../tests/browser/browser_accessibility_simple.js | 106 + .../browser/browser_accessibility_simulator.js | 90 + ...wser_accessibility_tabbing_order_highlighter.js | 101 + .../browser_accessibility_text_label_audit.js | 1142 ++++++ ...browser_accessibility_text_label_audit_frame.js | 52 + .../tests/browser/browser_accessibility_walker.js | 191 + .../browser/browser_accessibility_walker_audit.js | 158 + .../server/tests/browser/browser_actor_error.js | 94 + .../browser/browser_animation_actor-lifetime.js | 81 + .../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 | 39 + .../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 | 65 + .../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 | 138 + .../tests/browser/browser_canvasframe_helper_05.js | 134 + .../tests/browser/browser_canvasframe_helper_06.js | 116 + .../browser/browser_compatibility_cssIssues.js | 135 + .../server/tests/browser/browser_connectToFrame.js | 143 + .../tests/browser/browser_debugger_server.js | 155 + .../server/tests/browser/browser_getProcess.js | 129 + .../tests/browser/browser_inspector-anonymous.js | 205 ++ .../tests/browser/browser_inspector-iframe.js | 92 + .../tests/browser/browser_inspector-insert.js | 158 + .../browser/browser_inspector-isScrollable.js | 34 + .../browser_inspector-mutations-childlist.js | 281 ++ .../tests/browser/browser_inspector-release.js | 55 + .../tests/browser/browser_inspector-remove.js | 104 + .../tests/browser/browser_inspector-retain.js | 158 + .../tests/browser/browser_inspector-search.js | 340 ++ .../tests/browser/browser_inspector-shadow.js | 231 ++ .../tests/browser/browser_inspector-traversal.js | 350 ++ .../tests/browser/browser_inspector-utils.js | 26 + .../tests/browser/browser_layout_getGrids.js | 145 + .../server/tests/browser/browser_layout_simple.js | 31 + .../tests/browser/browser_memory_allocations_01.js | 106 + 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 + .../tests/browser/browser_setup_in_parent.js | 57 + .../browser_storage_cookies-duplicate-names.js | 136 + .../browser/browser_storage_dynamic_windows.js | 411 +++ .../tests/browser/browser_storage_listings.js | 768 ++++ .../tests/browser/browser_storage_updates.js | 344 ++ .../browser_storage_webext_storage_local.js | 43 + .../browser_style_utils_getFontPreviewData.js | 131 + .../tests/browser/browser_styles_getRuleText.js | 34 + .../browser/browser_stylesheets_getTextEmpty.js | 54 + .../tests/browser/director-script-target.html | 18 + .../server/tests/browser/doc_accessibility.html | 15 + .../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 | 26 + devtools/server/tests/browser/grid.html | 42 + devtools/server/tests/browser/head.js | 355 ++ devtools/server/tests/browser/inspector-helpers.js | 166 + .../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 + devtools/server/tests/browser/setup-in-parent.js | 7 + .../tests/browser/storage-cookies-same-name.html | 29 + .../tests/browser/storage-dynamic-windows.html | 117 + devtools/server/tests/browser/storage-helpers.js | 56 + .../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 | 73 + .../server/tests/browser/test-setup-in-parent.js | 33 + 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.ini | 93 + ...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 | 93 + .../inactive-property-helper/border-image.mjs | 165 + .../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 + .../inactive-property-helper/margin-padding.mjs | 260 ++ .../max-min-width-height.mjs | 367 ++ .../place-items-content.mjs | 159 + .../chrome/inactive-property-helper/positioned.mjs | 84 + .../inactive-property-helper/scroll-padding.mjs | 169 + .../chrome/inactive-property-helper/table.mjs | 28 + .../inactive-property-helper/text-overflow.mjs | 98 + .../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-media-queries.html | 57 + .../tests/chrome/test_css-logic-specificity.html | 82 + 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-dead-nodes.html | 350 ++ .../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 | 105 + .../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 | 187 + .../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 | 970 +++++ devtools/server/tests/xpcshell/hello-actor.js | 19 + .../tests/xpcshell/post_init_global_actors.js | 23 + .../xpcshell/post_init_target_scoped_actors.js | 23 + .../tests/xpcshell/pre_init_global_actors.js | 23 + .../xpcshell/pre_init_target_scoped_actors.js | 23 + .../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 | 160 + .../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 | 154 + .../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 | 54 + .../server/tests/xpcshell/test_breakpoint-03.js | 75 + .../server/tests/xpcshell/test_breakpoint-04.js | 57 + .../server/tests/xpcshell/test_breakpoint-05.js | 63 + .../server/tests/xpcshell/test_breakpoint-06.js | 69 + .../server/tests/xpcshell/test_breakpoint-07.js | 66 + .../server/tests/xpcshell/test_breakpoint-08.js | 75 + .../server/tests/xpcshell/test_breakpoint-09.js | 73 + .../server/tests/xpcshell/test_breakpoint-10.js | 82 + .../server/tests/xpcshell/test_breakpoint-11.js | 78 + .../server/tests/xpcshell/test_breakpoint-12.js | 93 + .../server/tests/xpcshell/test_breakpoint-13.js | 78 + .../server/tests/xpcshell/test_breakpoint-14.js | 91 + .../server/tests/xpcshell/test_breakpoint-16.js | 71 + .../server/tests/xpcshell/test_breakpoint-17.js | 130 + .../server/tests/xpcshell/test_breakpoint-18.js | 60 + .../server/tests/xpcshell/test_breakpoint-19.js | 44 + .../server/tests/xpcshell/test_breakpoint-20.js | 109 + .../server/tests/xpcshell/test_breakpoint-21.js | 60 + .../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 | 262 ++ .../xpcshell/test_conditional_breakpoint-01.js | 54 + .../xpcshell/test_conditional_breakpoint-02.js | 52 + .../xpcshell/test_conditional_breakpoint-03.js | 60 + .../xpcshell/test_conditional_breakpoint-04.js | 53 + .../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 | 1087 ++++++ .../test_extension_storage_actor_upgrade.js | 150 + .../server/tests/xpcshell/test_format_command.js | 110 + .../server/tests/xpcshell/test_forwardingprefix.js | 225 ++ .../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 | 135 + .../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 | 144 + .../tests/xpcshell/test_getTextAtLineColumn.js | 35 + .../server/tests/xpcshell/test_getyoungestframe.js | 38 + .../xpcshell/test_ignore_caught_exceptions.js | 54 + .../test_ignore_no_interface_exceptions.js | 51 + 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 | 82 + devtools/server/tests/xpcshell/test_logpoint-02.js | 84 + devtools/server/tests/xpcshell/test_logpoint-03.js | 81 + .../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 | 56 + .../server/tests/xpcshell/test_objectgrips-05.js | 56 + .../server/tests/xpcshell/test_objectgrips-06.js | 56 + .../server/tests/xpcshell/test_objectgrips-07.js | 68 + .../server/tests/xpcshell/test_objectgrips-08.js | 61 + .../server/tests/xpcshell/test_objectgrips-14.js | 55 + .../server/tests/xpcshell/test_objectgrips-15.js | 55 + .../server/tests/xpcshell/test_objectgrips-16.js | 139 + .../server/tests/xpcshell/test_objectgrips-17.js | 321 ++ .../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 | 398 ++ .../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 | 42 + .../tests/xpcshell/test_pause_exceptions-02.js | 40 + .../tests/xpcshell/test_pause_exceptions-03.js | 54 + .../tests/xpcshell/test_pause_exceptions-04.js | 94 + .../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 | 43 + .../server/tests/xpcshell/test_promise_state-02.js | 58 + .../server/tests/xpcshell/test_promise_state-03.js | 57 + .../xpcshell/test_promises_run_to_completion.js | 132 + .../server/tests/xpcshell/test_register_actor.js | 94 + .../server/tests/xpcshell/test_requestTypes.js | 34 + .../server/tests/xpcshell/test_restartFrame-01.js | 118 + devtools/server/tests/xpcshell/test_safe-getter.js | 53 + .../tests/xpcshell/test_sessionDataHelpers.js | 79 + ...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 | 93 + 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 | 58 + devtools/server/tests/xpcshell/test_stepping-03.js | 44 + devtools/server/tests/xpcshell/test_stepping-04.js | 51 + 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 | 85 + devtools/server/tests/xpcshell/test_symbolactor.js | 53 + devtools/server/tests/xpcshell/test_symbols-01.js | 52 + devtools/server/tests/xpcshell/test_symbols-02.js | 43 + .../tests/xpcshell/test_threadlifetime-01.js | 56 + .../tests/xpcshell/test_threadlifetime-02.js | 73 + .../tests/xpcshell/test_threadlifetime-04.js | 61 + .../tests/xpcshell/test_unsafeDereference.js | 130 + .../server/tests/xpcshell/test_wasm_source-01.js | 197 + .../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 | 96 + .../tests/xpcshell/test_xpcshell_debugging.js | 90 + devtools/server/tests/xpcshell/testactors.js | 244 ++ .../server/tests/xpcshell/webextension-helpers.js | 201 + devtools/server/tests/xpcshell/xpcshell.ini | 248 ++ .../tests/xpcshell/xpcshell_debugging_script.js | 11 + 686 files changed, 120474 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.ini 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.css 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/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/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/media-rule.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/eventsource-actor.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/network-monitor/websocket-actor.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/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/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/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-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/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/storage.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-sheet.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/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-mixin.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/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/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/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/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.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-deprecated-list.js create mode 100644 devtools/server/actors/webconsole/webidl-pure-allowlist.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/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/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.ini 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.ini 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_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_setup_in_parent.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_storage_webext_storage_local.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/setup-in-parent.js 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-setup-in-parent.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.ini 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/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/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/place-items-content.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.mjs create mode 100644 devtools/server/tests/chrome/inactive-property-helper/text-overflow.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-media-queries.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-dead-nodes.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_format_command.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_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_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.ini create mode 100644 devtools/server/tests/xpcshell/xpcshell_debugging_script.js (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..b0c88fef94 --- /dev/null +++ b/devtools/server/actors/accessibility/accessibility.js @@ -0,0 +1,133 @@ +/* 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, + ActorClassWithSpec, +} = 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. + */ +const AccessibilityActor = ActorClassWithSpec(accessibilitySpec, { + initialize(conn, targetActor) { + Actor.prototype.initialize.call(this, conn); + // 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() { + Actor.prototype.destroy.call(this); + 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..a797cc833e --- /dev/null +++ b/devtools/server/actors/accessibility/accessible.js @@ -0,0 +1,654 @@ +/* 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, + ActorClassWithSpec, +} = 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 = {}; +ChromeUtils.defineESModuleGetters(lazy, { + ContentDOMReference: "resource://gre/modules/ContentDOMReference.sys.mjs", +}); + +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: a11yService.getStringRole(acc.role), + 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; +} + +/** + * The AccessibleActor provides information about a given accessible object: its + * role, name, states, etc. + */ +const AccessibleActor = ActorClassWithSpec(accessibleSpec, { + initialize(walker, rawAccessible) { + Actor.prototype.initialize.call(this, null); + this.walker = walker; + this.rawAccessible = rawAccessible; + + /** + * Indicates if the raw accessible is no longer alive. + * + * @return Boolean + */ + Object.defineProperty(this, "isDefunct", { + get() { + const defunct = isDefunct(this.rawAccessible); + if (defunct) { + delete this.isDefunct; + this.isDefunct = true; + return this.isDefunct; + } + + return defunct; + }, + configurable: true, + }); + }, + + /** + * Instead of storing a connection object, the NodeActor gets its connection + * from its associated walker. + */ + get conn() { + return this.walker.conn; + }, + + destroy() { + Actor.prototype.destroy.call(this); + this.walker = null; + this.rawAccessible = null; + }, + + get role() { + if (this.isDefunct) { + return null; + } + return this.walker.a11yService.getStringRole(this.rawAccessible.role); + }, + + get name() { + if (this.isDefunct) { + return null; + } + return this.rawAccessible.name; + }, + + get value() { + if (this.isDefunct) { + return null; + } + return this.rawAccessible.value; + }, + + get description() { + if (this.isDefunct) { + return null; + } + return this.rawAccessible.description; + }, + + get keyboardShortcut() { + if (this.isDefunct) { + return null; + } + // Gecko accessibility exposes two key bindings: Accessible::AccessKey and + // Accessible::KeyboardShortcut. The former is used for accesskey, where the latter + // is used for global shortcuts defined by XUL menu items, etc. Here - do what the + // Windows implementation does: try AccessKey first, and if that's empty, use + // KeyboardShortcut. + return this.rawAccessible.accessKey || this.rawAccessible.keyboardShortcut; + }, + + get childCount() { + if (this.isDefunct) { + return 0; + } + // In case of a remote frame declare at least one child (the #document + // element) so that they can be expanded. + if (this.useChildTargetToFetchChildren) { + return 1; + } + + return this.rawAccessible.childCount; + }, + + get domNodeType() { + if (this.isDefunct) { + return 0; + } + return this.rawAccessible.DOMNode ? this.rawAccessible.DOMNode.nodeType : 0; + }, + + get parentAcc() { + if (this.isDefunct) { + return null; + } + return this.walker.addRef(this.rawAccessible.parent); + }, + + children() { + const children = []; + if (this.isDefunct) { + return children; + } + + for ( + let child = this.rawAccessible.firstChild; + child; + child = child.nextSibling + ) { + children.push(this.walker.addRef(child)); + } + return children; + }, + + get indexInParent() { + if (this.isDefunct) { + return -1; + } + + try { + return this.rawAccessible.indexInParent; + } catch (e) { + // Accessible is dead. + return -1; + } + }, + + get actions() { + const actions = []; + if (this.isDefunct) { + return actions; + } + + for (let i = 0; i < this.rawAccessible.actionCount; i++) { + actions.push(this.rawAccessible.getActionDescription(i)); + } + return actions; + }, + + get states() { + if (this.isDefunct) { + return []; + } + + const state = {}; + const extState = {}; + this.rawAccessible.getState(state, extState); + return [ + ...this.walker.a11yService.getStringStates(state.value, extState.value), + ]; + }, + + get attributes() { + if (this.isDefunct || !this.rawAccessible.attributes) { + return {}; + } + + const attributes = {}; + for (const { key, value } of this.rawAccessible.attributes.enumerate()) { + attributes[key] = value; + } + + return attributes; + }, + + get bounds() { + if (this.isDefunct) { + return null; + } + + let x = {}, + y = {}, + w = {}, + h = {}; + try { + this.rawAccessible.getBoundsInCSSPixels(x, y, w, h); + x = x.value; + y = y.value; + w = w.value; + h = h.value; + } catch (e) { + return null; + } + + // Check if accessible bounds are invalid. + const left = x, + right = x + w, + top = y, + bottom = y + h; + if (left === right || top === bottom) { + return null; + } + + return { x, y, w, h }; + }, + + async getRelations() { + const relationObjects = []; + if (this.isDefunct) { + return relationObjects; + } + + const relations = [ + ...this.rawAccessible.getRelations().enumerate(Ci.nsIAccessibleRelation), + ]; + if (relations.length === 0) { + return relationObjects; + } + + const doc = await this.walker.getDocument(); + if (this.isDestroyed()) { + // This accessible actor is destroyed. + return relationObjects; + } + relations.forEach(relation => { + if (RELATIONS_TO_IGNORE.has(relation.relationType)) { + return; + } + + const type = this.walker.a11yService.getStringRelationType( + relation.relationType + ); + const targets = [...relation.getTargets().enumerate(Ci.nsIAccessible)]; + let relationObject; + for (const target of targets) { + let targetAcc; + try { + targetAcc = this.walker.attachAccessible(target, doc.rawAccessible); + } catch (e) { + // Target is not available. + } + + if (targetAcc) { + if (!relationObject) { + relationObject = { type, targets: [] }; + } + + relationObject.targets.push(targetAcc); + } + } + + if (relationObject) { + relationObjects.push(relationObject); + } + }); + + return relationObjects; + }, + + get useChildTargetToFetchChildren() { + if (this.isDefunct) { + return false; + } + + return ( + this.rawAccessible.role === Ci.nsIAccessibleRole.ROLE_INTERNAL_FRAME && + isFrameWithChildTarget( + this.walker.targetActor, + this.rawAccessible.DOMNode + ) + ); + }, + + form() { + return { + actor: this.actorID, + role: this.role, + name: this.name, + useChildTargetToFetchChildren: this.useChildTargetToFetchChildren, + childCount: this.childCount, + checks: this._lastAudit, + }; + }, + + /** + * Provide additional (full) information about the accessible object that is + * otherwise missing from the form. + * + * @return {Object} + * Object that contains accessible object information such as states, + * actions, attributes, etc. + */ + hydrate() { + return { + value: this.value, + description: this.description, + keyboardShortcut: this.keyboardShortcut, + domNodeType: this.domNodeType, + indexInParent: this.indexInParent, + states: this.states, + actions: this.actions, + attributes: this.attributes, + }; + }, + + _isValidTextLeaf(rawAccessible) { + return ( + !isDefunct(rawAccessible) && + TEXT_ROLES.has(rawAccessible.role) && + rawAccessible.name && + !!rawAccessible.name.trim().length + ); + }, + + /** + * Calculate the contrast ratio of the given accessible. + */ + async _getContrastRatio() { + if (!this._isValidTextLeaf(this.rawAccessible)) { + return null; + } + + const { bounds } = this; + if (!bounds) { + return null; + } + + const { DOMNode: rawNode } = this.rawAccessible; + const win = rawNode.ownerGlobal; + + // Keep the reference to the walker actor in case the actor gets destroyed + // during the colour contrast ratio calculation. + const { walker } = this; + await walker.clearStyles(win); + const contrastRatio = await getContrastRatioFor(rawNode.parentNode, { + bounds: getBounds(win, bounds), + win, + appliedColorMatrix: this.walker.colorMatrix, + }); + + if (this.isDestroyed()) { + // This accessible actor is destroyed. + return null; + } + await walker.restoreStyles(win); + + return contrastRatio; + }, + + /** + * Run an accessibility audit for a given audit type. + * @param {String} type + * Type of an audit (Check AUDIT_TYPE in devtools/shared/constants + * to see available audit types). + * + * @return {null|Object} + * Object that contains accessible audit data for a given type or null + * if there's nothing to report for this accessible. + */ + _getAuditByType(type) { + switch (type) { + case AUDIT_TYPE.CONTRAST: + return this._getContrastRatio(); + case AUDIT_TYPE.KEYBOARD: + // Determine if keyboard accessibility is lacking where it is necessary. + return auditKeyboard(this.rawAccessible); + case AUDIT_TYPE.TEXT_LABEL: + // Determine if text alternative is missing for an accessible where it + // is necessary. + return auditTextLabel(this.rawAccessible); + default: + return null; + } + }, + + /** + * Audit the state of the accessible object. + * + * @param {Object} options + * Options for running audit, may include: + * - types: Array of audit types to be performed during audit. + * + * @return {Object|null} + * Audit results for the accessible object. + */ + audit(options = {}) { + if (this._auditing) { + return this._auditing; + } + + const { types } = options; + let auditTypes = Object.values(AUDIT_TYPE); + if (types && types.length) { + auditTypes = auditTypes.filter(auditType => types.includes(auditType)); + } + + // For some reason keyboard checks for focus styling affect values (that are + // used by other types of checks (text names and values)) returned by + // accessible objects. This happens only when multiple checks are run at the + // same time (asynchronously) and the audit might return unexpected + // failures. We thus split the execution of the checks into two parts, first + // performing keyboard checks and only after the rest of the checks. See bug + // 1594743 for more detail. + let keyboardAuditResult; + const keyboardAuditIndex = auditTypes.indexOf(AUDIT_TYPE.KEYBOARD); + if (keyboardAuditIndex > -1) { + // If we are performing a keyboard audit, remove its value from the + // complete list and run it. + auditTypes.splice(keyboardAuditIndex, 1); + keyboardAuditResult = this._getAuditByType(AUDIT_TYPE.KEYBOARD); + } + + this._auditing = Promise.resolve(keyboardAuditResult) + .then(keyboardResult => { + const audits = auditTypes.map(auditType => + this._getAuditByType(auditType) + ); + + // If we are also performing a keyboard audit, add its type and its + // result back to the complete list of audits. + if (keyboardAuditIndex > -1) { + auditTypes.splice(keyboardAuditIndex, 0, AUDIT_TYPE.KEYBOARD); + audits.splice(keyboardAuditIndex, 0, keyboardResult); + } + + return Promise.all(audits); + }) + .then(results => { + if (this.isDefunct || this.isDestroyed()) { + return null; + } + + const audit = results.reduce((auditResults, result, index) => { + auditResults[auditTypes[index]] = result; + return auditResults; + }, {}); + this._lastAudit = this._lastAudit || {}; + Object.assign(this._lastAudit, audit); + events.emit(this, "audited", audit); + + return audit; + }) + .catch(error => { + if (!this.isDefunct && !this.isDestroyed()) { + throw error; + } + return null; + }) + .finally(() => { + this._auditing = null; + }); + + return this._auditing; + }, + + snapshot() { + return getSnapshot( + this.rawAccessible, + this.walker.a11yService, + this.walker.targetActor + ); + }, +}); + +exports.AccessibleActor = AccessibleActor; diff --git a/devtools/server/actors/accessibility/audit/contrast.js b/devtools/server/actors/accessibility/audit/contrast.js new file mode 100644 index 0000000000..7266598d0b --- /dev/null +++ b/devtools/server/actors/accessibility/audit/contrast.js @@ -0,0 +1,312 @@ +/* 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"; + +loader.lazyRequireGetter( + this, + "colorUtils", + "resource://devtools/shared/css/color.js", + true +); +loader.lazyRequireGetter( + this, + "CssLogic", + "resource://devtools/server/actors/inspector/css-logic.js", + true +); +loader.lazyRequireGetter( + this, + "getCurrentZoom", + "resource://devtools/shared/layout/utils.js", + true +); +loader.lazyRequireGetter( + this, + "addPseudoClassLock", + "resource://devtools/server/actors/highlighters/utils/markup.js", + true +); +loader.lazyRequireGetter( + this, + "removePseudoClassLock", + "resource://devtools/server/actors/highlighters/utils/markup.js", + true +); +loader.lazyRequireGetter( + this, + "getContrastRatioAgainstBackground", + "resource://devtools/shared/accessibility.js", + true +); +loader.lazyRequireGetter( + this, + "getTextProperties", + "resource://devtools/shared/accessibility.js", + true +); +loader.lazyRequireGetter( + this, + "DevToolsWorker", + "resource://devtools/shared/worker/worker.js", + true +); +loader.lazyRequireGetter( + this, + "InspectorActorUtils", + "resource://devtools/server/actors/inspector/utils.js" +); + +const WORKER_URL = "resource://devtools/server/actors/accessibility/worker.js"; +const HIGHLIGHTED_PSEUDO_CLASS = ":-moz-devtools-highlighted"; +const { + LARGE_TEXT: { BOLD_LARGE_TEXT_MIN_PIXELS, LARGE_TEXT_MIN_PIXELS }, +} = require("resource://devtools/shared/accessibility.js"); + +loader.lazyGetter(this, "worker", () => new DevToolsWorker(WORKER_URL)); + +/** + * Get canvas rendering context for the current target window bound by the bounds of the + * accessible objects. + * @param {Object} win + * Current target window. + * @param {Object} bounds + * Bounds for the accessible object. + * @param {Object} zoom + * Current zoom level for the window. + * @param {Object} scale + * Scale value to scale down the drawn image. + * @param {null|DOMNode} node + * If not null, a node that corresponds to the accessible object to be used to + * make its text color transparent. + * @return {CanvasRenderingContext2D} + * Canvas rendering context for the current window. + */ +function getImageCtx(win, bounds, zoom, scale, node) { + const doc = win.document; + const canvas = doc.createElementNS("http://www.w3.org/1999/xhtml", "canvas"); + + const { left, top, width, height } = bounds; + canvas.width = width * zoom * scale; + canvas.height = height * zoom * scale; + const ctx = canvas.getContext("2d", { alpha: false }); + ctx.imageSmoothingEnabled = false; + ctx.scale(scale, scale); + + // If node is passed, make its color related text properties invisible. + if (node) { + addPseudoClassLock(node, HIGHLIGHTED_PSEUDO_CLASS); + } + + ctx.drawWindow( + win, + left * zoom, + top * zoom, + width * zoom, + height * zoom, + "#fff", + ctx.DRAWWINDOW_USE_WIDGET_LAYERS + ); + + // Restore all inline styling. + if (node) { + removePseudoClassLock(node, HIGHLIGHTED_PSEUDO_CLASS); + } + + return ctx; +} + +/** + * Calculate the transformed RGBA when a color matrix is set in docShell by + * multiplying the color matrix with the RGBA vector. + * + * @param {Array} rgba + * Original RGBA array which we want to transform. + * @param {Array} colorMatrix + * Flattened 4x5 color matrix that is set in docShell. + * A 4x5 matrix of the form: + * 1 2 3 4 5 + * 6 7 8 9 10 + * 11 12 13 14 15 + * 16 17 18 19 20 + * will be set in docShell as: + * [1, 6, 11, 16, 2, 7, 12, 17, 3, 8, 13, 18, 4, 9, 14, 19, 5, 10, 15, 20] + * @return {Array} + * Transformed RGBA after the color matrix is multiplied with the original RGBA. + */ +function getTransformedRGBA(rgba, colorMatrix) { + const transformedRGBA = [0, 0, 0, 0]; + + // Only use the first four columns of the color matrix corresponding to R, G, B and A + // color channels respectively. The fifth column is a fixed offset that does not need + // to be considered for the matrix multiplication. We end up multiplying a 4x4 color + // matrix with a 4x1 RGBA vector. + for (let i = 0; i < 16; i++) { + const row = i % 4; + const col = Math.floor(i / 4); + transformedRGBA[row] += colorMatrix[i] * rgba[col]; + } + + return transformedRGBA; +} + +/** + * Find RGBA or a range of RGBAs for the background pixels under the text. + * + * @param {DOMNode} node + * Node for which we want to get the background color data. + * @param {Object} options + * - bounds {Object} + * Bounds for the accessible object. + * - win {Object} + * Target window. + * - size {Number} + * Font size of the selected text node + * - isBoldText {Boolean} + * True if selected text node is bold + * @return {Object} + * Object with one or more of the following RGBA fields: value, min, max + */ +function getBackgroundFor(node, { win, bounds, size, isBoldText }) { + const zoom = 1 / getCurrentZoom(win); + // When calculating colour contrast, we traverse image data for text nodes that are + // drawn both with and without transparent text. Image data arrays are typically really + // big. In cases when the font size is fairly large or when the page is zoomed in image + // data is especially large (retrieving it and/or traversing it takes significant amount + // of time). Here we optimize the size of the image data by scaling down the drawn nodes + // to a size where their text size equals either BOLD_LARGE_TEXT_MIN_PIXELS or + // LARGE_TEXT_MIN_PIXELS (lower threshold for large text size) depending on the font + // weight. + // + // IMPORTANT: this optimization, in some cases where background colour is non-uniform + // (gradient or image), can result in small (not noticeable) blending of the background + // colours. In turn this might affect the reported values of the contrast ratio. The + // delta is fairly small (<0.1) to noticeably skew the results. + // + // NOTE: this optimization does not help in cases where contrast is being calculated for + // nodes with a lot of text. + let scale = + ((isBoldText ? BOLD_LARGE_TEXT_MIN_PIXELS : LARGE_TEXT_MIN_PIXELS) / size) * + zoom; + // We do not need to scale the images if the font is smaller than large or if the page + // is zoomed out (scaling in this case would've been scaling up). + scale = scale > 1 ? 1 : scale; + + const textContext = getImageCtx(win, bounds, zoom, scale); + const backgroundContext = getImageCtx(win, bounds, zoom, scale, node); + + const { data: dataText } = textContext.getImageData( + 0, + 0, + bounds.width * scale, + bounds.height * scale + ); + const { data: dataBackground } = backgroundContext.getImageData( + 0, + 0, + bounds.width * scale, + bounds.height * scale + ); + + return worker.performTask( + "getBgRGBA", + { + dataTextBuf: dataText.buffer, + dataBackgroundBuf: dataBackground.buffer, + }, + [dataText.buffer, dataBackground.buffer] + ); +} + +/** + * Calculates the contrast ratio of the referenced DOM node. + * + * @param {DOMNode} node + * The node for which we want to calculate the contrast ratio. + * @param {Object} options + * - bounds {Object} + * Bounds for the accessible object. + * - win {Object} + * Target window. + * - appliedColorMatrix {Array|null} + * Simulation color matrix applied to + * to the viewport, if it exists. + * @return {Object} + * An object that may contain one or more of the following fields: error, + * isLargeText, value, min, max values for contrast. + */ +async function getContrastRatioFor(node, options = {}) { + const computedStyle = CssLogic.getComputedStyle(node); + const props = computedStyle ? getTextProperties(computedStyle) : null; + + if (!props) { + return { + error: true, + }; + } + + const { isLargeText, isBoldText, size, opacity } = props; + const { appliedColorMatrix } = options; + const color = appliedColorMatrix + ? getTransformedRGBA(props.color, appliedColorMatrix) + : props.color; + let rgba = await getBackgroundFor(node, { + ...options, + isBoldText, + size, + }); + + if (!rgba) { + // Fallback (original) contrast calculation algorithm. It tries to get the + // closest background colour for the node and use it to calculate contrast. + const backgroundColor = InspectorActorUtils.getClosestBackgroundColor(node); + const backgroundImage = InspectorActorUtils.getClosestBackgroundImage(node); + + if (backgroundImage !== "none") { + // Both approaches failed, at this point we don't have a better one yet. + return { + error: true, + }; + } + + let { r, g, b, a } = colorUtils.colorToRGBA(backgroundColor, true); + // If the element has opacity in addition to background alpha value, take it + // into account. TODO: this does not handle opacity set on ancestor + // elements (see bug https://bugzilla.mozilla.org/show_bug.cgi?id=1544721). + if (opacity < 1) { + a = opacity * a; + } + + return getContrastRatioAgainstBackground( + { + value: appliedColorMatrix + ? getTransformedRGBA([r, g, b, a], appliedColorMatrix) + : [r, g, b, a], + }, + { + color, + isLargeText, + } + ); + } + + if (appliedColorMatrix) { + rgba = rgba.value + ? { + value: getTransformedRGBA(rgba.value, appliedColorMatrix), + } + : { + min: getTransformedRGBA(rgba.min, appliedColorMatrix), + max: getTransformedRGBA(rgba.max, appliedColorMatrix), + }; + } + + return getContrastRatioAgainstBackground(rgba, { + color, + isLargeText, + }); +} + +exports.getContrastRatioFor = getContrastRatioFor; +exports.getBackgroundFor = getBackgroundFor; diff --git a/devtools/server/actors/accessibility/audit/keyboard.js b/devtools/server/actors/accessibility/audit/keyboard.js new file mode 100644 index 0000000000..8170f5d097 --- /dev/null +++ b/devtools/server/actors/accessibility/audit/keyboard.js @@ -0,0 +1,515 @@ +/* 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"; + +loader.lazyRequireGetter( + this, + "CssLogic", + "resource://devtools/server/actors/inspector/css-logic.js", + true +); +loader.lazyRequireGetter( + this, + "getCSSStyleRules", + "resource://devtools/shared/inspector/css-logic.js", + true +); +loader.lazyRequireGetter(this, "InspectorUtils", "InspectorUtils"); +loader.lazyRequireGetter( + this, + "nodeConstants", + "resource://devtools/shared/dom-node-constants.js" +); +loader.lazyRequireGetter( + this, + ["isDefunct", "getAriaRoles"], + "resource://devtools/server/actors/utils/accessibility.js", + true +); + +const { + accessibility: { + AUDIT_TYPE: { KEYBOARD }, + ISSUE_TYPE: { + [KEYBOARD]: { + FOCUSABLE_NO_SEMANTICS, + FOCUSABLE_POSITIVE_TABINDEX, + INTERACTIVE_NO_ACTION, + INTERACTIVE_NOT_FOCUSABLE, + MOUSE_INTERACTIVE_ONLY, + NO_FOCUS_VISIBLE, + }, + }, + SCORES: { FAIL, WARNING }, + }, +} = require("resource://devtools/shared/constants.js"); + +// Specified by the author CSS rule type. +const STYLE_RULE = 1; + +// Accessible action for showing long description. +const CLICK_ACTION = "click"; + +/** + * Focus specific pseudo classes that the keyboard audit simulates to determine + * focus styling. + */ +const FOCUS_PSEUDO_CLASS = ":focus"; +const MOZ_FOCUSRING_PSEUDO_CLASS = ":-moz-focusring"; + +const KEYBOARD_FOCUSABLE_ROLES = new Set([ + Ci.nsIAccessibleRole.ROLE_BUTTONMENU, + Ci.nsIAccessibleRole.ROLE_CHECKBUTTON, + Ci.nsIAccessibleRole.ROLE_COMBOBOX, + Ci.nsIAccessibleRole.ROLE_EDITCOMBOBOX, + Ci.nsIAccessibleRole.ROLE_ENTRY, + Ci.nsIAccessibleRole.ROLE_LINK, + Ci.nsIAccessibleRole.ROLE_LISTBOX, + Ci.nsIAccessibleRole.ROLE_PASSWORD_TEXT, + Ci.nsIAccessibleRole.ROLE_PUSHBUTTON, + Ci.nsIAccessibleRole.ROLE_RADIOBUTTON, + Ci.nsIAccessibleRole.ROLE_SLIDER, + Ci.nsIAccessibleRole.ROLE_SPINBUTTON, + Ci.nsIAccessibleRole.ROLE_SUMMARY, + Ci.nsIAccessibleRole.ROLE_SWITCH, + Ci.nsIAccessibleRole.ROLE_TOGGLE_BUTTON, +]); + +const INTERACTIVE_ROLES = new Set([ + ...KEYBOARD_FOCUSABLE_ROLES, + Ci.nsIAccessibleRole.ROLE_CHECK_MENU_ITEM, + Ci.nsIAccessibleRole.ROLE_CHECK_RICH_OPTION, + Ci.nsIAccessibleRole.ROLE_COMBOBOX_OPTION, + Ci.nsIAccessibleRole.ROLE_MENUITEM, + Ci.nsIAccessibleRole.ROLE_OPTION, + Ci.nsIAccessibleRole.ROLE_OUTLINE, + Ci.nsIAccessibleRole.ROLE_OUTLINEITEM, + Ci.nsIAccessibleRole.ROLE_PAGETAB, + Ci.nsIAccessibleRole.ROLE_PARENT_MENUITEM, + Ci.nsIAccessibleRole.ROLE_RADIO_MENU_ITEM, + Ci.nsIAccessibleRole.ROLE_RICH_OPTION, +]); + +const INTERACTIVE_IF_FOCUSABLE_ROLES = new Set([ + // If article is focusable, we can assume it is inside a feed. + Ci.nsIAccessibleRole.ROLE_ARTICLE, + // Column header can be focusable. + Ci.nsIAccessibleRole.ROLE_COLUMNHEADER, + Ci.nsIAccessibleRole.ROLE_GRID_CELL, + Ci.nsIAccessibleRole.ROLE_MENUBAR, + Ci.nsIAccessibleRole.ROLE_MENUPOPUP, + Ci.nsIAccessibleRole.ROLE_PAGETABLIST, + // Row header can be focusable. + Ci.nsIAccessibleRole.ROLE_ROWHEADER, + Ci.nsIAccessibleRole.ROLE_SCROLLBAR, + Ci.nsIAccessibleRole.ROLE_SEPARATOR, + Ci.nsIAccessibleRole.ROLE_TOOLBAR, +]); + +/** + * Determine if a node is dead or is not an element node. + * + * @param {DOMNode} node + * Node to be tested for validity. + * + * @returns {Boolean} + * True if the node is either dead or is not an element node. + */ +function isInvalidNode(node) { + return ( + !node || + Cu.isDeadWrapper(node) || + node.nodeType !== nodeConstants.ELEMENT_NODE || + !node.ownerGlobal + ); +} + +/** + * Determine if accessible is focusable with the keyboard. + * + * @param {nsIAccessible} accessible + * Accessible for which to determine if it is keyboard focusable. + * + * @returns {Boolean} + * True if focusable with the keyboard. + */ +function isKeyboardFocusable(accessible) { + const state = {}; + accessible.getState(state, {}); + // State will be focusable even if the tabindex is negative. + return ( + state.value & Ci.nsIAccessibleStates.STATE_FOCUSABLE && + // Platform accessibility will still report STATE_FOCUSABLE even with the + // tabindex="-1" so we need to check that it is >= 0 to be considered + // keyboard focusable. + accessible.DOMNode.tabIndex > -1 + ); +} + +/** + * Determine if a current node has focus specific styling by applying a + * focus-related pseudo class (such as :focus or :-moz-focusring) to a focusable + * node. + * + * @param {DOMNode} focusableNode + * Node to apply focus-related pseudo class to. + * @param {DOMNode} currentNode + * Node to be checked for having focus specific styling. + * @param {String} pseudoClass + * A focus related pseudo-class to be simulated for style comparison. + * + * @returns {Boolean} + * True if the currentNode has focus specific styling. + */ +function hasStylesForFocusRelatedPseudoClass( + focusableNode, + currentNode, + pseudoClass +) { + const defaultRules = getCSSStyleRules(currentNode); + + InspectorUtils.addPseudoClassLock(focusableNode, pseudoClass); + + // Determine a set of properties that are specific to CSS rules that are only + // present when a focus related pseudo-class is locked in. + const tempRules = getCSSStyleRules(currentNode); + const properties = new Set(); + for (const rule of tempRules) { + if (rule.type !== STYLE_RULE) { + continue; + } + + if (!defaultRules.includes(rule)) { + for (let index = 0; index < rule.style.length; index++) { + properties.add(rule.style.item(index)); + } + } + } + + // If there are no focus specific CSS rules or properties, currentNode does + // node have any focus specific styling, we are done. + if (properties.size === 0) { + InspectorUtils.removePseudoClassLock(focusableNode, pseudoClass); + return false; + } + + // Determine values for properties that are focus specific. + const tempStyle = CssLogic.getComputedStyle(currentNode); + const focusStyle = {}; + for (const name of properties.values()) { + focusStyle[name] = tempStyle.getPropertyValue(name); + } + + InspectorUtils.removePseudoClassLock(focusableNode, pseudoClass); + + // If values for focus specific properties are different from default style + // values, assume we have focus spefic styles for the currentNode. + const defaultStyle = CssLogic.getComputedStyle(currentNode); + for (const name of properties.values()) { + if (defaultStyle.getPropertyValue(name) !== focusStyle[name]) { + return true; + } + } + + return false; +} + +/** + * Check if an element node (currentNode) has distinct focus styling. This + * function also takes into account a case when focus styling is applied to a + * descendant too. + * + * @param {DOMNode} focusableNode + * Node to apply focus-related pseudo class to. + * @param {DOMNode} currentNode + * Node to be checked for having focus specific styling. + * + * @returns {Boolean} + * True if the node or its descendant has distinct focus styling. + */ +function hasFocusStyling(focusableNode, currentNode) { + if (isInvalidNode(currentNode)) { + return false; + } + + // Check if an element node has distinct :-moz-focusring styling. + const hasStylesForMozFocusring = hasStylesForFocusRelatedPseudoClass( + focusableNode, + currentNode, + MOZ_FOCUSRING_PSEUDO_CLASS + ); + if (hasStylesForMozFocusring) { + return true; + } + + // Check if an element node has distinct :focus styling. + const hasStylesForFocus = hasStylesForFocusRelatedPseudoClass( + focusableNode, + currentNode, + FOCUS_PSEUDO_CLASS + ); + if (hasStylesForFocus) { + return true; + } + + // If no element specific focus styles where found, check if its element + // children have them. + for ( + let child = currentNode.firstElementChild; + child; + child = currentNode.nextnextElementSibling + ) { + if (hasFocusStyling(focusableNode, child)) { + return true; + } + } + + return false; +} + +/** + * A rule that determines if a focusable accessible object has appropriate focus + * styling. + * + * @param {nsIAccessible} accessible + * Accessible to be checked for being focusable and having focus + * styling. + * + * @return {null|Object} + * Null if accessible has keyboard focus styling, audit report object + * otherwise. + */ +function focusStyleRule(accessible) { + const { DOMNode } = accessible; + if (isInvalidNode(DOMNode)) { + return null; + } + + // Ignore non-focusable elements. + if (!isKeyboardFocusable(accessible)) { + return null; + } + + if (hasFocusStyling(DOMNode, DOMNode)) { + return null; + } + + // If no browser or author focus styling was found, check if the node is a + // widget that is themed by platform native theme. + if (InspectorUtils.isElementThemed(DOMNode)) { + return null; + } + + return { score: WARNING, issue: NO_FOCUS_VISIBLE }; +} + +/** + * A rule that determines if an interactive accessible has any associated + * accessible actions with it. If the element is interactive but and has no + * actions, assistive technology users will not be able to interact with it. + * + * @param {nsIAccessible} accessible + * Accessible to be checked for being interactive and having accessible + * actions. + * + * @return {null|Object} + * Null if accessible is not interactive or if it is and it has + * accessible action associated with it, audit report object otherwise. + */ +function interactiveRule(accessible) { + if (!INTERACTIVE_ROLES.has(accessible.role)) { + return null; + } + + if (accessible.actionCount > 0) { + return null; + } + + return { score: FAIL, issue: INTERACTIVE_NO_ACTION }; +} + +/** + * A rule that determines if an interactive accessible is also focusable when + * not disabled. + * + * @param {nsIAccessible} accessible + * Accessible to be checked for being interactive and being focusable + * when enabled. + * + * @return {null|Object} + * Null if accessible is not interactive or if it is and it is focusable + * when enabled, audit report object otherwise. + */ +function focusableRule(accessible) { + if (!KEYBOARD_FOCUSABLE_ROLES.has(accessible.role)) { + return null; + } + + const state = {}; + accessible.getState(state, {}); + // We only expect in interactive accessible object to be focusable if it is + // not disabled. + if (state.value & Ci.nsIAccessibleStates.STATE_UNAVAILABLE) { + return null; + } + + if (isKeyboardFocusable(accessible)) { + return null; + } + + const ariaRoles = getAriaRoles(accessible); + if ( + ariaRoles && + (ariaRoles.includes("combobox") || ariaRoles.includes("listbox")) + ) { + // Do not force ARIA combobox or listbox to be focusable. + return null; + } + + return { score: FAIL, issue: INTERACTIVE_NOT_FOCUSABLE }; +} + +/** + * A rule that determines if a focusable accessible has an associated + * interactive role. + * + * @param {nsIAccessible} accessible + * Accessible to be checked for having an interactive role if it is + * focusable. + * + * @return {null|Object} + * Null if accessible is not interactive or if it is and it has an + * interactive role, audit report object otherwise. + */ +function semanticsRule(accessible) { + if ( + INTERACTIVE_ROLES.has(accessible.role) || + // Visible listboxes will have focusable state when inside comboboxes. + accessible.role === Ci.nsIAccessibleRole.ROLE_COMBOBOX_LIST + ) { + return null; + } + + if (isKeyboardFocusable(accessible)) { + if (INTERACTIVE_IF_FOCUSABLE_ROLES.has(accessible.role)) { + return null; + } + + // ROLE_TABLE is used for grids too which are considered interactive. + if (accessible.role === Ci.nsIAccessibleRole.ROLE_TABLE) { + const ariaRoles = getAriaRoles(accessible); + if (ariaRoles && ariaRoles.includes("grid")) { + return null; + } + } + + return { score: WARNING, issue: FOCUSABLE_NO_SEMANTICS }; + } + + const state = {}; + accessible.getState(state, {}); + if ( + // Ignore text leafs. + accessible.role === Ci.nsIAccessibleRole.ROLE_TEXT_LEAF || + // Ignore accessibles with no accessible actions. + accessible.actionCount === 0 || + // Ignore labels that have a label for relation with their target because + // they are clickable. + (accessible.role === Ci.nsIAccessibleRole.ROLE_LABEL && + accessible.getRelationByType(Ci.nsIAccessibleRelation.RELATION_LABEL_FOR) + .targetsCount > 0) || + // Ignore images that are inside an anchor (have linked state). + (accessible.role === Ci.nsIAccessibleRole.ROLE_GRAPHIC && + state.value & Ci.nsIAccessibleStates.STATE_LINKED) + ) { + return null; + } + + // Ignore anything but a click action in the list of actions. + for (let i = 0; i < accessible.actionCount; i++) { + if (accessible.getActionName(i) === CLICK_ACTION) { + return { score: FAIL, issue: MOUSE_INTERACTIVE_ONLY }; + } + } + + return null; +} + +/** + * A rule that determines if an element associated with a focusable accessible + * has a positive tabindex. + * + * @param {nsIAccessible} accessible + * Accessible to be checked for having an element with positive tabindex + * attribute. + * + * @return {null|Object} + * Null if accessible is not focusable or if it is and its element's + * tabindex attribute is less than 1, audit report object otherwise. + */ +function tabIndexRule(accessible) { + const { DOMNode } = accessible; + if (isInvalidNode(DOMNode)) { + return null; + } + + if (!isKeyboardFocusable(accessible)) { + return null; + } + + if (DOMNode.tabIndex > 0) { + return { score: WARNING, issue: FOCUSABLE_POSITIVE_TABINDEX }; + } + + return null; +} + +function auditKeyboard(accessible) { + if (isDefunct(accessible)) { + return null; + } + // Do not test anything on accessible objects for documents or frames. + if ( + accessible.role === Ci.nsIAccessibleRole.ROLE_DOCUMENT || + accessible.role === Ci.nsIAccessibleRole.ROLE_INTERNAL_FRAME + ) { + return null; + } + + // Check if interactive accessible can be used by the assistive + // technology. + let issue = interactiveRule(accessible); + if (issue) { + return issue; + } + + // Check if interactive accessible is also focusable when enabled. + issue = focusableRule(accessible); + if (issue) { + return issue; + } + + // Check if accessible object has an element with a positive tabindex. + issue = tabIndexRule(accessible); + if (issue) { + return issue; + } + + // Check if a focusable accessible has interactive semantics. + issue = semanticsRule(accessible); + if (issue) { + return issue; + } + + // Check if focusable accessible has associated focus styling. + issue = focusStyleRule(accessible); + if (issue) { + return issue; + } + + return issue; +} + +module.exports.auditKeyboard = auditKeyboard; diff --git a/devtools/server/actors/accessibility/audit/moz.build b/devtools/server/actors/accessibility/audit/moz.build new file mode 100644 index 0000000000..01bd0af849 --- /dev/null +++ b/devtools/server/actors/accessibility/audit/moz.build @@ -0,0 +1,12 @@ +# 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( + "contrast.js", + "keyboard.js", + "text-label.js", +) + +with Files("**"): + BUG_COMPONENT = ("DevTools", "Accessibility Tools") diff --git a/devtools/server/actors/accessibility/audit/text-label.js b/devtools/server/actors/accessibility/audit/text-label.js new file mode 100644 index 0000000000..f90146e684 --- /dev/null +++ b/devtools/server/actors/accessibility/audit/text-label.js @@ -0,0 +1,439 @@ +/* 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 { + accessibility: { + AUDIT_TYPE: { TEXT_LABEL }, + ISSUE_TYPE, + SCORES: { BEST_PRACTICES, FAIL, WARNING }, + }, +} = require("resource://devtools/shared/constants.js"); + +const { + AREA_NO_NAME_FROM_ALT, + DIALOG_NO_NAME, + DOCUMENT_NO_TITLE, + EMBED_NO_NAME, + FIGURE_NO_NAME, + FORM_FIELDSET_NO_NAME, + FORM_FIELDSET_NO_NAME_FROM_LEGEND, + FORM_NO_NAME, + FORM_NO_VISIBLE_NAME, + FORM_OPTGROUP_NO_NAME_FROM_LABEL, + FRAME_NO_NAME, + HEADING_NO_CONTENT, + HEADING_NO_NAME, + IFRAME_NO_NAME_FROM_TITLE, + IMAGE_NO_NAME, + INTERACTIVE_NO_NAME, + MATHML_GLYPH_NO_NAME, + TOOLBAR_NO_NAME, +} = ISSUE_TYPE[TEXT_LABEL]; + +/** + * Check if the accessible is visible to the assistive technology. + * @param {nsIAccessible} accessible + * Accessible object to be tested for visibility. + * + * @returns {Boolean} + * True if accessible object is visible to assistive technology. + */ +function isVisible(accessible) { + const state = {}; + accessible.getState(state, {}); + return !(state.value & Ci.nsIAccessibleStates.STATE_INVISIBLE); +} + +/** + * Get related accessible objects that are targets of labelled by relation e.g. + * labels. + * @param {nsIAccessible} accessible + * Accessible objects to get labels for. + * + * @returns {Array} + * A list of accessible objects that are labels for a given accessible. + */ +function getLabels(accessible) { + const relation = accessible.getRelationByType( + Ci.nsIAccessibleRelation.RELATION_LABELLED_BY + ); + return [...relation.getTargets().enumerate(Ci.nsIAccessible)]; +} + +/** + * Get a trimmed name of the accessible object. + * + * @param {nsIAccessible} accessible + * Accessible objects to get a name for. + * + * @returns {null|String} + * Trimmed name of the accessible object if available. + */ +function getAccessibleName(accessible) { + return accessible.name && accessible.name.trim(); +} + +/** + * A text label rule for accessible objects that must have a non empty + * accessible name. + * + * @returns {null|Object} + * Failure audit report if accessible object has no or empty name, null + * otherwise. + */ +const mustHaveNonEmptyNameRule = function(issue, accessible) { + const name = getAccessibleName(accessible); + return name ? null : { score: FAIL, issue }; +}; + +/** + * A text label rule for accessible objects that should have a non empty + * accessible name as a best practice. + * + * @returns {null|Object} + * Best practices audit report if accessible object has no or empty + * name, null otherwise. + */ +const shouldHaveNonEmptyNameRule = function(issue, accessible) { + const name = getAccessibleName(accessible); + return name ? null : { score: BEST_PRACTICES, issue }; +}; + +/** + * A text label rule for accessible objects that can be activated via user + * action and must have a non-empty name. + * + * @returns {null|Object} + * Failure audit report if interactive accessible object has no or + * empty name, null otherwise. + */ +const interactiveRule = mustHaveNonEmptyNameRule.bind( + null, + INTERACTIVE_NO_NAME +); + +/** + * A text label rule for accessible objects that correspond to dialogs and thus + * should have a non-empty name. + * + * @returns {null|Object} + * Best practices audit report if dialog accessible object has no or + * empty name, null otherwise. + */ +const dialogRule = shouldHaveNonEmptyNameRule.bind(null, DIALOG_NO_NAME); + +/** + * A text label rule for accessible objects that provide visual information + * (images, canvas, etc.) and must have a defined name (that can be empty, e.g. + * ""). + * + * @returns {null|Object} + * Failure audit report if interactive accessible object has no name, + * null otherwise. + */ +const imageRule = function(accessible) { + const name = getAccessibleName(accessible); + return name != null ? null : { score: FAIL, issue: IMAGE_NO_NAME }; +}; + +/** + * A text label rule for accessible objects that correspond to form elements. + * These objects must have a non-empty name and must have a visible label. + * + * @returns {null|Object} + * Failure audit report if form element accessible object has no name, + * warning if the name does not come from a visible label, null + * otherwise. + */ +const formRule = function(accessible) { + const name = getAccessibleName(accessible); + if (!name) { + return { score: FAIL, issue: FORM_NO_NAME }; + } + + const labels = getLabels(accessible); + const hasNameFromVisibleLabel = labels.some(label => isVisible(label)); + + return hasNameFromVisibleLabel + ? null + : { score: WARNING, issue: FORM_NO_VISIBLE_NAME }; +}; + +/** + * A text label rule for elements that map to ROLE_GROUPING: + * * must have a non-empty name and must be provided via the + * "label" attribute. + * *
must have a non-empty name and must be provided via the + * corresponding element. + * + * @returns {null|Object} + * Failure audit report if form grouping accessible object has no name, + * or has a name that is not derived from a required location, null + * otherwise. + */ +const formGroupingRule = function(accessible) { + const name = getAccessibleName(accessible); + const { DOMNode } = accessible; + + switch (DOMNode.nodeName) { + case "OPTGROUP": + return name && DOMNode.label && DOMNode.label.trim() === name + ? null + : { + score: FAIL, + issue: FORM_OPTGROUP_NO_NAME_FROM_LABEL, + }; + case "FIELDSET": + if (!name) { + return { score: FAIL, issue: FORM_FIELDSET_NO_NAME }; + } + + const labels = getLabels(accessible); + const hasNameFromLegend = labels.some( + label => + label.DOMNode.nodeName === "LEGEND" && + label.name && + label.name.trim() === name && + isVisible(label) + ); + + return hasNameFromLegend + ? null + : { + score: WARNING, + issue: FORM_FIELDSET_NO_NAME_FROM_LEGEND, + }; + default: + return null; + } +}; + +/** + * A text label rule for elements that map to ROLE_TEXT_CONTAINER: + * * mapps to ROLE_TEXT_CONTAINER and must have a name provided via + * the visible label. Note: Will only work when bug 559770 is resolved (right + * now, unlabelled meters are not mapped to an accessible object). + * + * @returns {null|Object} + * Failure audit report depending on requirements for dialogs or form + * meter element, null otherwise. + */ +const textContainerRule = function(accessible) { + const { DOMNode } = accessible; + + switch (DOMNode.nodeName) { + case "DIALOG": + return dialogRule(accessible); + case "METER": + return formRule(accessible); + default: + return null; + } +}; + +/** + * A text label rule for elements that map to ROLE_INTERNAL_FRAME: + * * maps to ROLE_INTERNAL_FRAME. Check the type attribute and whether + * it includes "image/" (e.g. image/jpeg, image/png, image/gif). If so, audit + * it the same way other image roles are audited. + * * maps to ROLE_INTERNAL_FRAME and must have a non-empty name. + * * and + // 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; + }, + + async getBreakableLines() { + const positions = await this.getBreakpointPositions(); + const lines = new Set(); + for (const position of positions) { + if (!lines.has(position.line)) { + lines.add(position.line); + } + } + + return Array.from(lines); + }, + + // For inline "/>, we can't easily fetch the srcdoc + // full html text content. So, consider each inline script as independant source with + // their own URL. Thus the ID appended to each URL. + if (source.url == "about:srcdoc") { + return source.url + "#" + source.id; + } + + return source.url; +} + +exports.getDebuggerSourceURL = getDebuggerSourceURL; diff --git a/devtools/server/actors/utils/sources-manager.js b/devtools/server/actors/utils/sources-manager.js new file mode 100644 index 0000000000..abfd106b4f --- /dev/null +++ b/devtools/server/actors/utils/sources-manager.js @@ -0,0 +1,501 @@ +/* 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 DevToolsUtils = require("resource://devtools/shared/DevToolsUtils.js"); +const { assert, fetch } = DevToolsUtils; +const EventEmitter = require("resource://devtools/shared/event-emitter.js"); +const { + SourceLocation, +} = require("resource://devtools/server/actors/common.js"); + +loader.lazyRequireGetter( + this, + "SourceActor", + "resource://devtools/server/actors/source.js", + true +); + +/** + * Matches strings of the form "foo.min.js" or "foo-min.js", etc. If the regular + * expression matches, we can be fairly sure that the source is minified, and + * treat it as such. + */ +const MINIFIED_SOURCE_REGEXP = /\bmin\.js$/; + +/** + * Manages the sources for a thread. Handles URL contents, locations in + * the sources, etc for ThreadActors. + */ +class SourcesManager extends EventEmitter { + constructor(threadActor) { + super(); + this._thread = threadActor; + + this.blackBoxedSources = new Map(); + + // Debugger.Source -> SourceActor + this._sourceActors = new Map(); + + // URL -> content + // + // Any possibly incomplete content that has been loaded for each URL. + this._urlContents = new Map(); + + // URL -> Promise[] + // + // Any promises waiting on a URL to be completely loaded. + this._urlWaiters = new Map(); + + // Debugger.Source.id -> Debugger.Source + // + // The IDs associated with ScriptSources and available via DebuggerSource.id + // are internal to this process and should not be exposed to the client. This + // map associates these IDs with the corresponding source, provided the source + // has not been GC'ed and the actor has been created. This is lazily populated + // the first time it is needed. + this._sourcesByInternalSourceId = null; + + if (!isWorker) { + Services.obs.addObserver(this, "devtools-html-content"); + } + } + + destroy() { + if (!isWorker) { + Services.obs.removeObserver(this, "devtools-html-content"); + } + } + + /** + * Clear existing sources so they are recreated on the next access. + */ + reset() { + this._sourceActors = new Map(); + this._urlContents = new Map(); + this._urlWaiters = new Map(); + this._sourcesByInternalSourceId = null; + } + + /** + * Create a source actor representing this source. + * + * @param Debugger.Source source + * The source to make an actor for. + * @returns a SourceActor representing the source. + */ + createSourceActor(source) { + assert(source, "SourcesManager.prototype.source needs a source"); + + if (this._sourceActors.has(source)) { + return this._sourceActors.get(source); + } + + const actor = new SourceActor({ + thread: this._thread, + source, + }); + + this._thread.threadLifetimePool.manage(actor); + + this._sourceActors.set(source, actor); + if (this._sourcesByInternalSourceId && source.id) { + this._sourcesByInternalSourceId.set(source.id, source); + } + + this.emit("newSource", actor); + return actor; + } + + _getSourceActor(source) { + if (this._sourceActors.has(source)) { + return this._sourceActors.get(source); + } + + return null; + } + + hasSourceActor(source) { + return !!this._getSourceActor(source); + } + + getSourceActor(source) { + const sourceActor = this._getSourceActor(source); + + if (!sourceActor) { + throw new Error( + "getSource: could not find source actor for " + (source.url || "source") + ); + } + + return sourceActor; + } + + getOrCreateSourceActor(source) { + // Tolerate the source coming from a different Debugger than the one + // associated with the thread. + try { + source = this._thread.dbg.adoptSource(source); + } catch (e) { + // We can't create actors for sources in the same compartment as the + // thread's Debugger. + if (/is in the same compartment as this debugger/.test(e)) { + return null; + } + throw e; + } + + if (this.hasSourceActor(source)) { + return this.getSourceActor(source); + } + return this.createSourceActor(source); + } + + getSourceActorByInternalSourceId(id) { + if (!this._sourcesByInternalSourceId) { + this._sourcesByInternalSourceId = new Map(); + for (const source of this._thread.dbg.findSources()) { + if (source.id) { + this._sourcesByInternalSourceId.set(source.id, source); + } + } + } + const source = this._sourcesByInternalSourceId.get(id); + if (source) { + return this.getOrCreateSourceActor(source); + } + return null; + } + + getSourceActorsByURL(url) { + const rv = []; + if (url) { + for (const [, actor] of this._sourceActors) { + if (actor.url === url) { + rv.push(actor); + } + } + } + return rv; + } + + getSourceActorById(actorId) { + for (const [, actor] of this._sourceActors) { + if (actor.actorID == actorId) { + return actor; + } + } + return null; + } + + /** + * Returns true if the URL likely points to a minified resource, false + * otherwise. + * + * @param String uri + * The url to test. + * @returns Boolean + */ + _isMinifiedURL(uri) { + if (!uri) { + return false; + } + + try { + const url = new URL(uri); + const pathname = url.pathname; + return MINIFIED_SOURCE_REGEXP.test( + pathname.slice(pathname.lastIndexOf("/") + 1) + ); + } catch (e) { + // Not a valid URL so don't try to parse out the filename, just test the + // whole thing with the minified source regexp. + return MINIFIED_SOURCE_REGEXP.test(uri); + } + } + + /** + * Return the non-source-mapped location of an offset in a script. + * + * @param Debugger.Script script + * The script associated with the offset. + * @param Number offset + * Offset within the script of the location. + * @returns Object + * Returns an object of the form { source, line, column } + */ + getScriptOffsetLocation(script, offset) { + const { lineNumber, columnNumber } = script.getOffsetMetadata(offset); + return new SourceLocation( + this.createSourceActor(script.source), + lineNumber, + columnNumber + ); + } + + /** + * Return the non-source-mapped location of the given Debugger.Frame. If the + * frame does not have a script, the location's properties are all null. + * + * @param Debugger.Frame frame + * The frame whose location we are getting. + * @returns Object + * Returns an object of the form { source, line, column } + */ + getFrameLocation(frame) { + if (!frame || !frame.script) { + return new SourceLocation(); + } + return this.getScriptOffsetLocation(frame.script, frame.offset); + } + + /** + * Returns true if URL for the given source is black boxed. + * + * * @param url String + * The URL of the source which we are checking whether it is black + * boxed or not. + */ + isBlackBoxed(url, line, column) { + const ranges = this.blackBoxedSources.get(url); + if (!ranges) { + return this.blackBoxedSources.has(url); + } + + const range = ranges.find(r => isLocationInRange({ line, column }, r)); + return !!range; + } + + isFrameBlackBoxed(frame) { + const { url, line, column } = this.getFrameLocation(frame); + return this.isBlackBoxed(url, line, column); + } + + /** + * Add the given source URL to the set of sources that are black boxed. + * + * @param url String + * The URL of the source which we are black boxing. + */ + blackBox(url, range) { + if (!range) { + // blackbox the whole source + return this.blackBoxedSources.set(url, null); + } + + const ranges = this.blackBoxedSources.get(url) || []; + // ranges are sorted in ascening order + const index = ranges.findIndex( + r => r.end.line <= range.start.line && r.end.column <= range.start.column + ); + + ranges.splice(index + 1, 0, range); + this.blackBoxedSources.set(url, ranges); + return true; + } + + /** + * Remove the given source URL to the set of sources that are black boxed. + * + * @param url String + * The URL of the source which we are no longer black boxing. + */ + unblackBox(url, range) { + if (!range) { + return this.blackBoxedSources.delete(url); + } + + const ranges = this.blackBoxedSources.get(url); + const index = ranges.findIndex( + r => + r.start.line === range.start.line && + r.start.column === range.start.column && + r.end.line === range.end.line && + r.end.column === range.end.column + ); + + if (index !== -1) { + ranges.splice(index, 1); + } + + if (ranges.length === 0) { + return this.blackBoxedSources.delete(url); + } + + return this.blackBoxedSources.set(url, ranges); + } + + iter() { + return [...this._sourceActors.values()]; + } + + /** + * Listener for new HTML content. + */ + observe(subject, topic, data) { + if (topic == "devtools-html-content") { + const { parserID, uri, contents, complete } = JSON.parse(data); + if (this._urlContents.has(uri)) { + // We received many devtools-html-content events, if we already received one, + // aggregate the data with the one we already received. + const existing = this._urlContents.get(uri); + if (existing.parserID == parserID) { + assert(!existing.complete); + existing.content = existing.content + contents; + existing.complete = complete; + + // After the HTML has finished loading, resolve any promises + // waiting for the complete file contents. Waits will only + // occur when the URL was ever partially loaded. + if (complete) { + const waiters = this._urlWaiters.get(uri); + if (waiters) { + for (const waiter of waiters) { + waiter(); + } + this._urlWaiters.delete(uri); + } + } + } + } else if (contents) { + // Ensure that `contents` is non-empty. We may miss all the devtools-html-content events except the last + // one which has a empty `contents` and complete set to true. + // This reproduces when opening a same-process iframe. In this particular scenario, we instantiate the target and thread actor + // on `DOMDocElementInserted` and the HTML document is already parsed, but we still receive this one very last notification. + this._urlContents.set(uri, { + content: contents, + complete, + contentType: "text/html", + parserID, + }); + } + } + } + + /** + * Get the contents of a URL, fetching it if necessary. If partial is set and + * any content for the URL has been received, that partial content is returned + * synchronously. + */ + urlContents(url, partial, canUseCache) { + if (this._urlContents.has(url)) { + const data = this._urlContents.get(url); + if (!partial && !data.complete) { + return new Promise(resolve => { + if (!this._urlWaiters.has(url)) { + this._urlWaiters.set(url, []); + } + this._urlWaiters.get(url).push(resolve); + }).then(() => { + assert(data.complete); + return { + content: data.content, + contentType: data.contentType, + }; + }); + } + return { + content: data.content, + contentType: data.contentType, + }; + } + if (partial) { + return { + content: "", + contentType: "", + }; + } + return this._fetchURLContents(url, partial, canUseCache); + } + + async _fetchURLContents(url, partial, canUseCache) { + // Only try the cache if it is currently enabled for the document. + // Without this check, the cache may return stale data that doesn't match + // the document shown in the browser. + let loadFromCache = canUseCache; + if (canUseCache && this._thread._parent.browsingContext) { + loadFromCache = !( + this._thread._parent.browsingContext.defaultLoadFlags === + Ci.nsIRequest.LOAD_BYPASS_CACHE + ); + } + + // Fetch the sources with the same principal as the original document + const win = this._thread._parent.window; + let principal, cacheKey; + // On xpcshell, we don't have a window but a Sandbox + if (!isWorker && win instanceof Ci.nsIDOMWindow) { + const docShell = win.docShell; + const channel = docShell.currentDocumentChannel; + principal = channel.loadInfo.loadingPrincipal; + + // Retrieve the cacheKey in order to load POST requests from cache + // Note that chrome:// URLs don't support this interface. + if ( + loadFromCache && + docShell.currentDocumentChannel instanceof Ci.nsICacheInfoChannel + ) { + cacheKey = docShell.currentDocumentChannel.cacheKey; + } + } + + let result; + try { + result = await fetch(url, { + principal, + cacheKey, + loadFromCache, + }); + } catch (error) { + this._reportLoadSourceError(error); + throw error; + } + + // When we fetch the contents, there is a risk that the contents we get + // do not match up with the actual text of the sources these contents will + // be associated with. We want to always show contents that include that + // actual text (otherwise it will be very confusing or unusable for users), + // so replace the contents with the actual text if there is a mismatch. + const actors = [...this._sourceActors.values()].filter( + actor => actor.url == url + ); + if (!actors.every(actor => actor.contentMatches(result))) { + if (actors.length > 1) { + // When there are multiple actors we won't be able to show the source + // for all of them. Ask the user to reload so that we don't have to do + // any fetching. + result.content = "Error: Incorrect contents fetched, please reload."; + } else { + result.content = actors[0].actualText(); + } + } + + this._urlContents.set(url, { ...result, complete: true }); + + return result; + } + + _reportLoadSourceError(error) { + try { + DevToolsUtils.reportException("SourceActor", error); + + const lines = JSON.stringify(this.form(), null, 4).split(/\n/g); + lines.forEach(line => console.error("\t", line)); + } catch (e) { + // ignore + } + } +} + +function isLocationInRange({ line, column }, range) { + return ( + (range.start.line <= line || + (range.start.line == line && range.start.column <= column)) && + (range.end.line >= line || + (range.end.line == line && range.end.column >= column)) + ); +} + +exports.SourcesManager = SourcesManager; diff --git a/devtools/server/actors/utils/stack.js b/devtools/server/actors/utils/stack.js new file mode 100644 index 0000000000..6a216b252c --- /dev/null +++ b/devtools/server/actors/utils/stack.js @@ -0,0 +1,183 @@ +/* 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"; + +/** + * A helper class that stores stack frame objects. Each frame is + * assigned an index, and if a frame is added more than once, the same + * index is used. Users of the class can get an array of all frames + * that have been added. + */ +class StackFrameCache { + /** + * Initialize this object. + */ + constructor() { + this._framesToIndices = null; + this._framesToForms = null; + this._lastEventSize = 0; + } + + /** + * Prepare to accept frames. + */ + initFrames() { + if (this._framesToIndices) { + // The maps are already initialized. + return; + } + + this._framesToIndices = new Map(); + this._framesToForms = new Map(); + this._lastEventSize = 0; + } + + /** + * Forget all stored frames and reset to the initialized state. + */ + clearFrames() { + this._framesToIndices.clear(); + this._framesToIndices = null; + this._framesToForms.clear(); + this._framesToForms = null; + this._lastEventSize = 0; + } + + /** + * Add a frame to this stack frame cache, and return the index of + * the frame. + */ + addFrame(frame) { + this._assignFrameIndices(frame); + this._createFrameForms(frame); + return this._framesToIndices.get(frame); + } + + /** + * A helper method for the memory actor. This populates the packet + * object with "frames" property. Each of these + * properties will be an array indexed by frame ID. "frames" will + * contain frame objects (see makeEvent). + * + * @param packet + * The packet to update. + * + * @returns packet + */ + updateFramePacket(packet) { + // Now that we are guaranteed to have a form for every frame, we know the + // size the "frames" property's array must be. We use that information to + // create dense arrays even though we populate them out of order. + const size = this._framesToForms.size; + packet.frames = Array(size).fill(null); + + // Populate the "frames" properties. + for (const [stack, index] of this._framesToIndices) { + packet.frames[index] = this._framesToForms.get(stack); + } + + return packet; + } + + /** + * If any new stack frames have been added to this cache since the + * last call to makeEvent (clearing the cache also resets the "last + * call"), then return a new array describing the new frames. If no + * new frames are available, return null. + * + * The frame cache assumes that the user of the cache keeps track of + * all previously-returned arrays and, in theory, concatenates them + * all to form a single array holding all frames added to the cache + * since the last reset. This concatenated array can be indexed by + * the frame ID. The array returned by this function, though, is + * dense and starts at 0. + * + * Each element in the array is an object of the form: + * { + * line: , + * column: , + * source: , + * functionDisplayName: , + * parent: + * asyncCause: the async cause, or null + * asyncParent: + * } + * + * The intent of this approach is to make it simpler to efficiently + * send frame information over the debugging protocol, by only + * sending new frames. + * + * @returns array or null + */ + makeEvent() { + const size = this._framesToForms.size; + if (!size || size <= this._lastEventSize) { + return null; + } + + const packet = Array(size - this._lastEventSize).fill(null); + for (const [stack, index] of this._framesToIndices) { + if (index >= this._lastEventSize) { + packet[index - this._lastEventSize] = this._framesToForms.get(stack); + } + } + + this._lastEventSize = size; + + return packet; + } + + /** + * Assigns an index to the given frame and its parents, if an index is not + * already assigned. + * + * @param SavedFrame frame + * A frame to assign an index to. + */ + _assignFrameIndices(frame) { + if (this._framesToIndices.has(frame)) { + return; + } + + if (frame) { + this._assignFrameIndices(frame.parent); + this._assignFrameIndices(frame.asyncParent); + } + + const index = this._framesToIndices.size; + this._framesToIndices.set(frame, index); + } + + /** + * Create the form for the given frame, if one doesn't already exist. + * + * @param SavedFrame frame + * A frame to create a form for. + */ + _createFrameForms(frame) { + if (this._framesToForms.has(frame)) { + return; + } + + let form = null; + if (frame) { + form = { + line: frame.line, + column: frame.column, + source: frame.source, + functionDisplayName: frame.functionDisplayName, + parent: this._framesToIndices.get(frame.parent), + asyncParent: this._framesToIndices.get(frame.asyncParent), + asyncCause: frame.asyncCause, + }; + this._createFrameForms(frame.parent); + this._createFrameForms(frame.asyncParent); + } + + this._framesToForms.set(frame, form); + } +} + +exports.StackFrameCache = StackFrameCache; diff --git a/devtools/server/actors/utils/style-utils.js b/devtools/server/actors/utils/style-utils.js new file mode 100644 index 0000000000..5f2e912002 --- /dev/null +++ b/devtools/server/actors/utils/style-utils.js @@ -0,0 +1,211 @@ +/* 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 { getCSSLexer } = require("resource://devtools/shared/css/lexer.js"); + +const XHTML_NS = "http://www.w3.org/1999/xhtml"; +const FONT_PREVIEW_TEXT = "Abc"; +const FONT_PREVIEW_FONT_SIZE = 40; +const FONT_PREVIEW_FILLSTYLE = "black"; +// Offset (in px) to avoid cutting off text edges of italic fonts. +const FONT_PREVIEW_OFFSET = 4; +// Factor used to resize the canvas in order to get better text quality. +const FONT_PREVIEW_OVERSAMPLING_FACTOR = 2; + +/** + * Helper function for getting an image preview of the given font. + * + * @param font {string} + * Name of font to preview + * @param doc {Document} + * Document to use to render font + * @param options {object} + * Object with options 'previewText' and 'previewFontSize' + * + * @return dataUrl + * The data URI of the font preview image + */ +function getFontPreviewData(font, doc, options) { + options = options || {}; + const previewText = options.previewText || FONT_PREVIEW_TEXT; + const previewTextLines = previewText.split("\n"); + const previewFontSize = options.previewFontSize || FONT_PREVIEW_FONT_SIZE; + const fillStyle = options.fillStyle || FONT_PREVIEW_FILLSTYLE; + const fontStyle = options.fontStyle || ""; + + const canvas = doc.createElementNS(XHTML_NS, "canvas"); + const ctx = canvas.getContext("2d"); + const fontValue = + fontStyle + " " + previewFontSize + "px " + font + ", serif"; + + // Get the correct preview text measurements and set the canvas dimensions + ctx.font = fontValue; + ctx.fillStyle = fillStyle; + const previewTextLinesWidths = previewTextLines.map( + previewTextLine => ctx.measureText(previewTextLine).width + ); + const textWidth = Math.round(Math.max(...previewTextLinesWidths)); + + // The canvas width is calculated as the width of the longest line plus + // an offset at the left and right of it. + // The canvas height is calculated as the font size multiplied by the + // number of lines plus an offset at the top and bottom. + // + // In order to get better text quality, we oversample the canvas. + // That means, after the width and height are calculated, we increase + // both sizes by some factor. + const simpleCanvasWidth = textWidth + FONT_PREVIEW_OFFSET * 2; + canvas.width = simpleCanvasWidth * FONT_PREVIEW_OVERSAMPLING_FACTOR; + canvas.height = + (previewFontSize * previewTextLines.length + FONT_PREVIEW_OFFSET * 2) * + FONT_PREVIEW_OVERSAMPLING_FACTOR; + + // we have to reset these after changing the canvas size + ctx.font = fontValue; + ctx.fillStyle = fillStyle; + + // Oversample the canvas for better text quality + ctx.scale(FONT_PREVIEW_OVERSAMPLING_FACTOR, FONT_PREVIEW_OVERSAMPLING_FACTOR); + + ctx.textBaseline = "top"; + ctx.textAlign = "center"; + const horizontalTextPosition = simpleCanvasWidth / 2; + let verticalTextPosition = FONT_PREVIEW_OFFSET; + for (let i = 0; i < previewTextLines.length; i++) { + ctx.fillText( + previewTextLines[i], + horizontalTextPosition, + verticalTextPosition + ); + + // Move vertical text position one line down + verticalTextPosition += previewFontSize; + } + + const dataURL = canvas.toDataURL("image/png"); + + return { + dataURL, + size: textWidth + FONT_PREVIEW_OFFSET * 2, + }; +} + +exports.getFontPreviewData = getFontPreviewData; + +/** + * Get the text content of a rule given some CSS text, a line and a column + * Consider the following example: + * body { + * color: red; + * } + * p { + * line-height: 2em; + * color: blue; + * } + * Calling the function with the whole text above and line=4 and column=1 would + * return "line-height: 2em; color: blue;" + * @param {String} initialText + * @param {Number} line (1-indexed) + * @param {Number} column (1-indexed) + * @return {object} An object of the form {offset: number, text: string} + * The offset is the index into the input string where + * the rule text started. The text is the content of + * the rule. + */ +function getRuleText(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. + while (true) { + const token = lexer.nextToken(); + if (!token) { + throw new Error("couldn't find start of the rule"); + } + if (token.tokenType === "symbol" && token.text === "{") { + break; + } + } + + // Now collect text until we see the matching close brace. + let braceDepth = 1; + let startOffset, endOffset; + while (true) { + const token = lexer.nextToken(); + if (!token) { + break; + } + if (startOffset === undefined) { + startOffset = token.startOffset; + } + if (token.tokenType === "symbol") { + if (token.text === "{") { + ++braceDepth; + } else if (token.text === "}") { + --braceDepth; + if (braceDepth == 0) { + break; + } + } + } + endOffset = token.endOffset; + } + + // If the rule was of the form "selector {" with no closing brace + // and no properties, just return an empty string. + if (startOffset === undefined) { + return { offset: 0, text: "" }; + } + // If the input didn't have any tokens between the braces (e.g., + // "div {}"), then the endOffset won't have been set yet; so account + // for that here. + if (endOffset === undefined) { + endOffset = startOffset; + } + + // Note that this approach will preserve comments, despite the fact + // that cssTokenizer skips them. + return { + offset: textOffset + startOffset, + text: text.substring(startOffset, endOffset), + }; +} + +exports.getRuleText = getRuleText; + +/** + * Return the offset and substring of |text| that starts at the given + * line and column. + * @param {String} text + * @param {Number} line (1-indexed) + * @param {Number} column (1-indexed) + * @return {object} An object of the form {offset: number, text: string}, + * where the offset is the offset into the input string + * where the text starts, and where text is the text. + */ +function getTextAtLineColumn(text, line, column) { + let offset; + if (line > 1) { + const rx = new RegExp( + "(?:[^\\r\\n\\f]*(?:\\r\\n|\\n|\\r|\\f)){" + (line - 1) + "}" + ); + offset = rx.exec(text)[0].length; + } else { + offset = 0; + } + offset += column - 1; + return { offset, text: text.substr(offset) }; +} + +exports.getTextAtLineColumn = getTextAtLineColumn; diff --git a/devtools/server/actors/utils/stylesheets-manager.js b/devtools/server/actors/utils/stylesheets-manager.js new file mode 100644 index 0000000000..a9e548d205 --- /dev/null +++ b/devtools/server/actors/utils/stylesheets-manager.js @@ -0,0 +1,948 @@ +/* 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 EventEmitter = require("resource://devtools/shared/event-emitter.js"); +const { fetch } = require("resource://devtools/shared/DevToolsUtils.js"); +const InspectorUtils = require("InspectorUtils"); +const { + getSourcemapBaseURL, +} = require("resource://devtools/server/actors/utils/source-map-utils.js"); +const { + TYPES, +} = require("resource://devtools/server/actors/resources/index.js"); + +loader.lazyRequireGetter( + this, + ["addPseudoClassLock", "removePseudoClassLock"], + "resource://devtools/server/actors/highlighters/utils/markup.js", + true +); +loader.lazyRequireGetter( + this, + "loadSheet", + "resource://devtools/shared/layout/utils.js", + true +); +loader.lazyRequireGetter( + this, + ["getSheetOwnerNode", "UPDATE_GENERAL", "UPDATE_PRESERVING_RULES"], + "resource://devtools/server/actors/style-sheet.js", + true +); + +const TRANSITION_PSEUDO_CLASS = ":-moz-styleeditor-transitioning"; +const TRANSITION_DURATION_MS = 500; +const TRANSITION_BUFFER_MS = 1000; +const TRANSITION_RULE_SELECTOR = `:root${TRANSITION_PSEUDO_CLASS}, :root${TRANSITION_PSEUDO_CLASS} *:not(:-moz-native-anonymous)`; +const TRANSITION_SHEET = + "data:text/css;charset=utf-8," + + encodeURIComponent(` + ${TRANSITION_RULE_SELECTOR} { + transition-duration: ${TRANSITION_DURATION_MS}ms !important; + transition-delay: 0ms !important; + transition-timing-function: ease-out !important; + transition-property: all !important; + } +`); + +// If the user edits a stylesheet, we stash a copy of the edited text +// here, keyed by the stylesheet. This way, if the tools are closed +// and then reopened, the edited text will be available. A weak map +// is used so that navigation by the user will eventually cause the +// edited text to be collected. +const modifiedStyleSheets = new WeakMap(); + +/** + * Manage stylesheets related to a given Target Actor. + * + * @emits applicable-stylesheet-added: emitted when an applicable stylesheet is added to the document. + * First arg is an object with the following properties: + * - resourceId {String}: The id that was assigned to the stylesheet + * - styleSheet {StyleSheet}: The actual stylesheet + * - creationData {Object}: An object with: + * - isCreatedByDevTools {Boolean}: Was the stylesheet created by DevTools (e.g. + * by the user clicking the new stylesheet button in the styleeditor) + * - fileName {String} + * @emits stylesheet-updated: emitted when there was changes in a stylesheet + * First arg is an object with the following properties: + * - resourceId {String}: The id that was assigned to the stylesheet + * - updateKind {String}: Which kind of update it is ("style-applied", + * "at-rules-changed", "matches-change", "property-change") + * - updates {Object}: The update data + */ +class StyleSheetsManager extends EventEmitter { + _styleSheetCount = 0; + _styleSheetMap = new Map(); + // List of all watched media queries. Change listeners are being registered from getAtRules. + _mqlList = []; + + /** + * @param TargetActor targetActor + * The target actor from which we should observe stylesheet changes. + */ + constructor(targetActor) { + super(); + + this._targetActor = targetActor; + this._onApplicableStateChanged = this._onApplicableStateChanged.bind(this); + this._onTargetActorWindowReady = this._onTargetActorWindowReady.bind(this); + } + + /** + * Calling this function will make the StyleSheetsManager start the event listeners needed + * to watch for stylesheet additions and modifications. + * It will also trigger applicable-stylesheet-added events for the existing stylesheets. + * This function resolves once it notified about existing stylesheets. + */ + async startWatching() { + // Process existing stylesheets + const promises = []; + for (const window of this._targetActor.windows) { + promises.push(this._getStyleSheetsForWindow(window)); + } + + // Listen for new stylesheet being added via StyleSheetApplicableStateChanged + this._targetActor.chromeEventHandler.addEventListener( + "StyleSheetApplicableStateChanged", + this._onApplicableStateChanged, + true + ); + this._watchStyleSheetChangeEvents(); + this._targetActor.on("window-ready", this._onTargetActorWindowReady); + + // Finally, notify about existing stylesheets + let styleSheets = await Promise.all(promises); + styleSheets = styleSheets.flat(); + for (const styleSheet of styleSheets) { + this._registerStyleSheet(styleSheet); + } + } + + _watchStyleSheetChangeEvents() { + for (const window of this._targetActor.windows) { + this._watchStyleSheetChangeEventsForWindow(window); + } + } + + _onTargetActorWindowReady({ window }) { + this._watchStyleSheetChangeEventsForWindow(window); + } + + _watchStyleSheetChangeEventsForWindow(window) { + // We have to set this flag in order to get the + // StyleSheetApplicableStateChanged events. See Document.webidl. + window.document.styleSheetChangeEventsEnabled = true; + } + + _unwatchStyleSheetChangeEvents() { + for (const window of this._targetActor.windows) { + window.document.styleSheetChangeEventsEnabled = false; + } + } + + /** + * Create a new style sheet in the document with the given text. + * + * @param {Document} document + * Document that the new style sheet belong to. + * @param {string} text + * Content of style sheet. + * @param {string} fileName + * If the stylesheet adding is from file, `fileName` indicates the path. + */ + async addStyleSheet(document, text, fileName) { + const parent = document.documentElement; + const style = document.createElementNS( + "http://www.w3.org/1999/xhtml", + "style" + ); + style.setAttribute("type", "text/css"); + style.setDevtoolsAsTriggeringPrincipal(); + + if (text) { + style.appendChild(document.createTextNode(text)); + } + + // This triggers StyleSheetApplicableStateChanged event. + parent.appendChild(style); + + // This promise will be resolved when the resource for this stylesheet is available. + let resolve = null; + const promise = new Promise(r => { + resolve = r; + }); + + if (!this._styleSheetCreationData) { + this._styleSheetCreationData = new WeakMap(); + } + this._styleSheetCreationData.set(style.sheet, { + isCreatedByDevTools: true, + fileName, + resolve, + }); + + await promise; + + return style.sheet; + } + + /** + * Return resourceId of the given style sheet or create one if the stylesheet wasn't + * registered yet. + * + * @params {StyleSheet} styleSheet + * @returns {String} resourceId + */ + getStyleSheetResourceId(styleSheet) { + const existingResourceId = this._findStyleSheetResourceId(styleSheet); + if (existingResourceId) { + return existingResourceId; + } + + // If we couldn't find an associated resourceId, that means the stylesheet isn't + // registered yet. Calling _registerStyleSheet will register it and return the + // associated resourceId it computed for it. + return this._registerStyleSheet(styleSheet); + } + + /** + * Return the associated resourceId of the given registered style sheet, or null if the + * stylesheet wasn't registered yet. + * + * @params {StyleSheet} styleSheet + * @returns {String} resourceId + */ + _findStyleSheetResourceId(styleSheet) { + for (const [ + resourceId, + existingStyleSheet, + ] of this._styleSheetMap.entries()) { + if (styleSheet === existingStyleSheet) { + return resourceId; + } + } + + return null; + } + + /** + * Return owner node of the style sheet of the given resource id. + * + * @params {String} resourceId + * The id associated with the stylesheet + * @returns {Element|null} + */ + getOwnerNode(resourceId) { + const styleSheet = this._styleSheetMap.get(resourceId); + return styleSheet.ownerNode; + } + + /** + * Return the index of given stylesheet of the given resource id. + * + * @params {String} resourceId + * The id associated with the stylesheet + * @returns {Number} + */ + getStyleSheetIndex(resourceId) { + const styleSheet = this._styleSheetMap.get(resourceId); + return this._getStyleSheetIndex(styleSheet); + } + + /** + * Get the text of a stylesheet given its resourceId. + * + * @params {String} resourceId + * The id associated with the stylesheet + * @returns {String} + */ + async getText(resourceId) { + const styleSheet = this._styleSheetMap.get(resourceId); + + const modifiedText = modifiedStyleSheets.get(styleSheet); + + // modifiedText is the content of the stylesheet updated by update function. + // In case not updating, this is undefined. + if (modifiedText !== undefined) { + return modifiedText; + } + + if (!styleSheet.href) { + if (styleSheet.ownerNode) { + // this is an inline + + +
+
+
+
+
+
+
+
+
+ + diff --git a/devtools/server/tests/browser/animation.html b/devtools/server/tests/browser/animation.html new file mode 100644 index 0000000000..f7b83df283 --- /dev/null +++ b/devtools/server/tests/browser/animation.html @@ -0,0 +1,170 @@ + + +
+
+
+
+
+
+
+
+
+
+
+ diff --git a/devtools/server/tests/browser/application-manifest-404-manifest.html b/devtools/server/tests/browser/application-manifest-404-manifest.html new file mode 100644 index 0000000000..fd182a69a6 --- /dev/null +++ b/devtools/server/tests/browser/application-manifest-404-manifest.html @@ -0,0 +1,10 @@ + + + + + Simple manifest + + + +

This page links to a manifest URL that is a 404.

+ diff --git a/devtools/server/tests/browser/application-manifest-basic.html b/devtools/server/tests/browser/application-manifest-basic.html new file mode 100644 index 0000000000..a8e11a645f --- /dev/null +++ b/devtools/server/tests/browser/application-manifest-basic.html @@ -0,0 +1,10 @@ + + + + + Simple manifest + + + +
{ "name": "Foo App" }
+ diff --git a/devtools/server/tests/browser/application-manifest-invalid-json.html b/devtools/server/tests/browser/application-manifest-invalid-json.html new file mode 100644 index 0000000000..2717a97ddd --- /dev/null +++ b/devtools/server/tests/browser/application-manifest-invalid-json.html @@ -0,0 +1,11 @@ + + + + + Invalid JSON + + + +

Invalid JSON:

+
foo:
+ diff --git a/devtools/server/tests/browser/application-manifest-no-manifest.html b/devtools/server/tests/browser/application-manifest-no-manifest.html new file mode 100644 index 0000000000..5f0668aa50 --- /dev/null +++ b/devtools/server/tests/browser/application-manifest-no-manifest.html @@ -0,0 +1,9 @@ + + + + + No manifest + + +

This page does not link to a manifest

+ diff --git a/devtools/server/tests/browser/application-manifest-warnings.html b/devtools/server/tests/browser/application-manifest-warnings.html new file mode 100644 index 0000000000..57f8b9b4e7 --- /dev/null +++ b/devtools/server/tests/browser/application-manifest-warnings.html @@ -0,0 +1,10 @@ + + + + + Empty manifest + + + +
{ }
+ diff --git a/devtools/server/tests/browser/browser.ini b/devtools/server/tests/browser/browser.ini new file mode 100644 index 0000000000..9aee69c83c --- /dev/null +++ b/devtools/server/tests/browser/browser.ini @@ -0,0 +1,154 @@ +[DEFAULT] +tags = devtools +subsuite = devtools +support-files = + head.js + animation.html + animation-data.html + application-manifest-404-manifest.html + application-manifest-basic.html + application-manifest-invalid-json.html + application-manifest-no-manifest.html + application-manifest-warnings.html + doc_accessibility_audit.html + doc_accessibility_infobar.html + doc_accessibility_keyboard_audit.html + doc_accessibility_text_label_audit_frame.html + doc_accessibility_text_label_audit.html + doc_accessibility.html + doc_allocations.html + doc_compatibility.html + doc_force_cc.html + doc_force_gc.html + doc_innerHTML.html + doc_iframe.html + doc_iframe_content.html + doc_iframe2.html + error-actor.js + grid.html + inspector-isScrollable-data.html + inspector-search-data.html + inspector-traversal-data.html + inspector-shadow.html + storage-cookies-same-name.html + storage-dynamic-windows.html + storage-listings.html + storage-unsecured-iframe.html + storage-updates.html + storage-secured-iframe.html + test-errors-actor.js + test-setup-in-parent.js + test-window.xhtml + inspector-helpers.js + setup-in-parent.js + storage-helpers.js + !/devtools/client/shared/test/shared-head.js + !/devtools/client/shared/test/telemetry-test-helpers.js + !/devtools/server/tests/chrome/hello-actor.js + +[browser_accessibility_highlighter_infobar.js] +skip-if = (os == 'win' && processor == 'aarch64') # bug 1533184 +[browser_accessibility_infobar_show.js] +[browser_accessibility_keyboard_audit.js] +skip-if = (os == 'win' && processor == 'aarch64') # bug 1533184 +[browser_accessibility_infobar_audit_keyboard.js] +[browser_accessibility_infobar_audit_text_label.js] +[browser_accessibility_node.js] +skip-if = (os == 'win' && processor == 'aarch64') # bug 1533184 +[browser_accessibility_node_audit.js] +skip-if = (os == 'win' && processor == 'aarch64') # bug 1533184 +[browser_accessibility_node_events.js] +skip-if = (os == 'win' && processor == 'aarch64') # bug 1533184 +[browser_accessibility_node_tabbing_order_highlighter.js] +skip-if = (os == 'win' && processor == 'aarch64') # bug 1533184 +[browser_accessibility_simple.js] +skip-if = (os == 'win' && processor == 'aarch64') # bug 1533184 +[browser_accessibility_simulator.js] +[browser_accessibility_tabbing_order_highlighter.js] +skip-if = (os == 'win' && processor == 'aarch64') # bug 1533184 +[browser_accessibility_text_label_audit_frame.js] +skip-if = + (os == 'win' && processor == 'aarch64') # bug 1533184 +[browser_accessibility_text_label_audit.js] +skip-if = (os == 'win' && processor == 'aarch64') # bug 1533184 +[browser_accessibility_walker_audit.js] +skip-if = (os == 'win' && processor == 'aarch64') # bug 1533184 +[browser_accessibility_walker.js] +skip-if = (os == 'win' && processor == 'aarch64') # bug 1533487 +[browser_actor_error.js] +[browser_animation_actor-lifetime.js] +[browser_animation_emitMutations.js] +[browser_animation_getMultipleStates.js] +[browser_animation_getPlayers.js] +[browser_animation_getStateAfterFinished.js] +[browser_animation_getSubTreeAnimations.js] +[browser_animation_keepFinished.js] +[browser_animation_playerState.js] +[browser_animation_playPauseIframe.js] +[browser_animation_playPauseSeveral.js] +[browser_animation_reconstructState.js] +[browser_animation_refreshTransitions.js] +[browser_animation_setCurrentTime.js] +[browser_animation_setPlaybackRate.js] +[browser_animation_simple.js] +[browser_animation_updatedState.js] +[browser_application_manifest.js] +[browser_canvasframe_helper_01.js] +skip-if = true # Bug 1183605 +[browser_canvasframe_helper_02.js] +skip-if = true # iframe will not be loaded in xul:window with strict xhtml. +[browser_canvasframe_helper_03.js] +skip-if = true # Bug 1183605 +[browser_canvasframe_helper_04.js] +skip-if = true # Bug 1183605 +[browser_canvasframe_helper_05.js] +skip-if = true # Bug 1183605 +[browser_canvasframe_helper_06.js] +skip-if = true # Bug 1183605 +[browser_compatibility_cssIssues.js] +[browser_connectToFrame.js] +[browser_debugger_server.js] +[browser_getProcess.js] +[browser_inspector-anonymous.js] +[browser_inspector-iframe.js] +[browser_inspector-insert.js] +[browser_inspector-isScrollable.js] +[browser_inspector-mutations-childlist.js] +skip-if = + win10_2004 && fission && debug && socketprocess_networking # high frequency intermittent +[browser_inspector-release.js] +[browser_inspector-remove.js] +[browser_inspector-retain.js] +[browser_inspector-search.js] +[browser_inspector-shadow.js] +[browser_inspector-traversal.js] +[browser_inspector-utils.js] +[browser_layout_getGrids.js] +[browser_layout_simple.js] +[browser_memory_allocations_01.js] +[browser_perf-01.js] +skip-if = tsan # bug 1804081, profiler issues in TSAN +[browser_perf-02.js] +skip-if = tsan # bug 1804081, profiler issues in TSAN +[browser_perf-04.js] +skip-if = tsan # bug 1804081, profiler issues in TSAN +[browser_perf-getSupportedFeatures.js] +skip-if = tsan # bug 1804081, profiler issues in TSAN +[browser_setup_in_parent.js] +[browser_storage_cookies-duplicate-names.js] +https_first_disabled = true +[browser_storage_dynamic_windows.js] +https_first_disabled = true +skip-if = + debug # Bug 1715916 - test is having race conditions on slow hardware + tsan # high frequency intermittent + win10_2004 && asan && fission # high frequency intermittent + win11_2009 && asan # high frequency intermittent +[browser_storage_listings.js] +https_first_disabled = true +[browser_storage_updates.js] +https_first_disabled = true +[browser_storage_webext_storage_local.js] +[browser_style_utils_getFontPreviewData.js] +[browser_styles_getRuleText.js] +[browser_stylesheets_getTextEmpty.js] diff --git a/devtools/server/tests/browser/browser_accessibility_highlighter_infobar.js b/devtools/server/tests/browser/browser_accessibility_highlighter_infobar.js new file mode 100644 index 0000000000..cbe3a0c7d0 --- /dev/null +++ b/devtools/server/tests/browser/browser_accessibility_highlighter_infobar.js @@ -0,0 +1,77 @@ +/* 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"; + +// Test the accessible highlighter's infobar content. + +const { + truncateString, +} = require("resource://devtools/shared/inspector/utils.js"); +const { + MAX_STRING_LENGTH, +} = require("resource://devtools/server/actors/highlighters/utils/accessibility.js"); + +add_task(async function() { + const { + target, + walker, + parentAccessibility, + a11yWalker, + } = await initAccessibilityFrontsForUrl( + MAIN_DOMAIN + "doc_accessibility_infobar.html" + ); + + info("Button front checks"); + await checkNameAndRole(walker, "#button", a11yWalker, "Accessible Button"); + + info("Front with long name checks"); + await checkNameAndRole( + walker, + "#h1", + a11yWalker, + "Lorem ipsum dolor sit ame" + "\u2026" + "e et dolore magna aliqua." + ); + + await waitForA11yShutdown(parentAccessibility); + await target.destroy(); + gBrowser.removeCurrentTab(); +}); + +/** + * A helper function for testing the accessible's displayed name and roles. + * + * @param {Object} walker + * The DOM walker. + * @param {String} querySelector + * The selector for the node to retrieve accessible from. + * @param {Object} a11yWalker + * The accessibility walker. + * @param {String} expectedName + * Expected string content for displaying the accessible's name. + * We are testing this in particular because name can be truncated. + */ +async function checkNameAndRole( + walker, + querySelector, + a11yWalker, + expectedName +) { + const node = await walker.querySelector(walker.rootNode, querySelector); + const accessibleFront = await a11yWalker.getAccessibleFor(node); + + const { name, role } = accessibleFront; + const onHighlightEvent = a11yWalker.once("highlighter-event"); + + await a11yWalker.highlightAccessible(accessibleFront); + const { options } = await onHighlightEvent; + is(options.name, name, "Accessible highlight has correct name option"); + is(options.role, role, "Accessible highlight has correct role option"); + + is( + `"${truncateString(name, MAX_STRING_LENGTH)}"`, + `"${expectedName}"`, + "Accessible has correct displayed name." + ); +} diff --git a/devtools/server/tests/browser/browser_accessibility_infobar_audit_keyboard.js b/devtools/server/tests/browser/browser_accessibility_infobar_audit_keyboard.js new file mode 100644 index 0000000000..2bc93a04bb --- /dev/null +++ b/devtools/server/tests/browser/browser_accessibility_infobar_audit_keyboard.js @@ -0,0 +1,160 @@ +/* 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"; + +// Checks for the AccessibleHighlighter's infobar component and its keyboard +// audit. + +add_task(async function() { + await BrowserTestUtils.withNewTab( + { + gBrowser, + url: MAIN_DOMAIN + "doc_accessibility_infobar.html", + }, + async function(browser) { + await SpecialPowers.spawn(browser, [], async function() { + const { require } = ChromeUtils.importESModule( + "resource://devtools/shared/loader/Loader.sys.mjs" + ); + const { + HighlighterEnvironment, + } = require("resource://devtools/server/actors/highlighters.js"); + const { + AccessibleHighlighter, + } = require("resource://devtools/server/actors/highlighters/accessible.js"); + const { + LocalizationHelper, + } = require("resource://devtools/shared/l10n.js"); + const L10N = new LocalizationHelper( + "devtools/shared/locales/accessibility.properties" + ); + + const { + accessibility: { + AUDIT_TYPE, + ISSUE_TYPE: { + [AUDIT_TYPE.KEYBOARD]: { + INTERACTIVE_NO_ACTION, + FOCUSABLE_NO_SEMANTICS, + }, + }, + SCORES: { FAIL, WARNING }, + }, + } = require("resource://devtools/shared/constants.js"); + + /** + * Checks for updated content for an infobar. + * + * @param {Object} infobar + * Accessible highlighter's infobar component. + * @param {Object} audit + * Audit information that is passed on highlighter show. + */ + function checkKeyboard(infobar, audit) { + const { issue, score } = audit || {}; + let expected = ""; + if (issue) { + const { ISSUE_TO_INFOBAR_LABEL_MAP } = infobar.audit.reports[ + AUDIT_TYPE.KEYBOARD + ].constructor; + expected = L10N.getStr(ISSUE_TO_INFOBAR_LABEL_MAP[issue]); + } + + is( + infobar.getTextContent("keyboard"), + expected, + "infobar keyboard audit text content is correct" + ); + if (score) { + ok(infobar.getElement("keyboard").classList.contains(score)); + } + } + + // Start testing. First, create highlighter environment and initialize. + const env = new HighlighterEnvironment(); + env.initFromWindow(content.window); + + // Wait for loading highlighter environment content to complete before creating the + // highlighter. + await new Promise(resolve => { + const doc = env.document; + + function onContentLoaded() { + if ( + doc.readyState === "interactive" || + doc.readyState === "complete" + ) { + resolve(); + } else { + doc.addEventListener("DOMContentLoaded", onContentLoaded, { + once: true, + }); + } + } + + onContentLoaded(); + }); + + // Now, we can test the Infobar's audit content. + const node = content.document.createElement("div"); + content.document.body.append(node); + const highlighter = new AccessibleHighlighter(env); + await highlighter.isReady; + const infobar = highlighter.accessibleInfobar; + const bounds = { + x: 0, + y: 0, + w: 250, + h: 100, + }; + + const tests = [ + { + desc: + "Infobar is shown with no keyboard audit content when no audit.", + }, + { + desc: + "Infobar is shown with no keyboard audit content when audit is null.", + audit: null, + }, + { + desc: + "Infobar is shown with no keyboard audit content when empty " + + "keyboard audit.", + audit: { [AUDIT_TYPE.KEYBOARD]: null }, + }, + { + desc: "Infobar is shown with keyboard audit content for an error.", + audit: { + [AUDIT_TYPE.KEYBOARD]: { + score: FAIL, + issue: INTERACTIVE_NO_ACTION, + }, + }, + }, + { + desc: "Infobar is shown with keyboard audit content for a warning.", + audit: { + [AUDIT_TYPE.KEYBOARD]: { + score: WARNING, + issue: FOCUSABLE_NO_SEMANTICS, + }, + }, + }, + ]; + + for (const test of tests) { + const { desc, audit } = test; + + info(desc); + highlighter.show(node, { ...bounds, audit }); + checkKeyboard(infobar, audit && audit[AUDIT_TYPE.KEYBOARD]); + highlighter.hide(); + } + }); + } + ); +}); diff --git a/devtools/server/tests/browser/browser_accessibility_infobar_audit_text_label.js b/devtools/server/tests/browser/browser_accessibility_infobar_audit_text_label.js new file mode 100644 index 0000000000..7f8d9938e1 --- /dev/null +++ b/devtools/server/tests/browser/browser_accessibility_infobar_audit_text_label.js @@ -0,0 +1,170 @@ +/* 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"; + +// Checks for the AccessibleHighlighter's infobar component and its text label +// audit. + +add_task(async function() { + await BrowserTestUtils.withNewTab( + { + gBrowser, + url: MAIN_DOMAIN + "doc_accessibility_infobar.html", + }, + async function(browser) { + await SpecialPowers.spawn(browser, [], async function() { + const { require } = ChromeUtils.importESModule( + "resource://devtools/shared/loader/Loader.sys.mjs" + ); + const { + HighlighterEnvironment, + } = require("resource://devtools/server/actors/highlighters.js"); + const { + AccessibleHighlighter, + } = require("resource://devtools/server/actors/highlighters/accessible.js"); + const { + LocalizationHelper, + } = require("resource://devtools/shared/l10n.js"); + const L10N = new LocalizationHelper( + "devtools/shared/locales/accessibility.properties" + ); + + const { + accessibility: { + AUDIT_TYPE, + ISSUE_TYPE: { + [AUDIT_TYPE.TEXT_LABEL]: { + DIALOG_NO_NAME, + FORM_NO_VISIBLE_NAME, + TOOLBAR_NO_NAME, + }, + }, + SCORES: { BEST_PRACTICES, FAIL, WARNING }, + }, + } = require("resource://devtools/shared/constants.js"); + + /** + * Checks for updated content for an infobar. + * + * @param {Object} infobar + * Accessible highlighter's infobar component. + * @param {Object} audit + * Audit information that is passed on highlighter show. + */ + function checkTextLabel(infobar, audit) { + const { issue, score } = audit || {}; + let expected = ""; + if (issue) { + const { ISSUE_TO_INFOBAR_LABEL_MAP } = infobar.audit.reports[ + AUDIT_TYPE.TEXT_LABEL + ].constructor; + expected = L10N.getStr(ISSUE_TO_INFOBAR_LABEL_MAP[issue]); + } + + is( + infobar.getTextContent("text-label"), + expected, + "infobar text label audit text content is correct" + ); + if (score) { + ok(infobar.getElement("text-label").classList.contains(score)); + } + } + + // Start testing. First, create highlighter environment and initialize. + const env = new HighlighterEnvironment(); + env.initFromWindow(content.window); + + // Wait for loading highlighter environment content to complete before creating the + // highlighter. + await new Promise(resolve => { + const doc = env.document; + + function onContentLoaded() { + if ( + doc.readyState === "interactive" || + doc.readyState === "complete" + ) { + resolve(); + } else { + doc.addEventListener("DOMContentLoaded", onContentLoaded, { + once: true, + }); + } + } + + onContentLoaded(); + }); + + // Now, we can test the Infobar's audit content. + const node = content.document.createElement("div"); + content.document.body.append(node); + const highlighter = new AccessibleHighlighter(env); + await highlighter.isReady; + const infobar = highlighter.accessibleInfobar; + const bounds = { + x: 0, + y: 0, + w: 250, + h: 100, + }; + + const tests = [ + { + desc: + "Infobar is shown with no text label audit content when no audit.", + }, + { + desc: + "Infobar is shown with no text label audit content when audit is null.", + audit: null, + }, + { + desc: + "Infobar is shown with no text label audit content when empty " + + "text label audit.", + audit: { [AUDIT_TYPE.TEXT_LABEL]: null }, + }, + { + desc: + "Infobar is shown with text label audit content for an error.", + audit: { + [AUDIT_TYPE.TEXT_LABEL]: { score: FAIL, issue: TOOLBAR_NO_NAME }, + }, + }, + { + desc: + "Infobar is shown with text label audit content for a warning.", + audit: { + [AUDIT_TYPE.TEXT_LABEL]: { + score: WARNING, + issue: FORM_NO_VISIBLE_NAME, + }, + }, + }, + { + desc: + "Infobar is shown with text label audit content for best practices.", + audit: { + [AUDIT_TYPE.TEXT_LABEL]: { + score: BEST_PRACTICES, + issue: DIALOG_NO_NAME, + }, + }, + }, + ]; + + for (const test of tests) { + const { desc, audit } = test; + + info(desc); + highlighter.show(node, { ...bounds, audit }); + checkTextLabel(infobar, audit && audit[AUDIT_TYPE.TEXT_LABEL]); + highlighter.hide(); + } + }); + } + ); +}); diff --git a/devtools/server/tests/browser/browser_accessibility_infobar_show.js b/devtools/server/tests/browser/browser_accessibility_infobar_show.js new file mode 100644 index 0000000000..1c962da9e7 --- /dev/null +++ b/devtools/server/tests/browser/browser_accessibility_infobar_show.js @@ -0,0 +1,181 @@ +/* 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"; + +// Checks for the AccessibleHighlighter's and XULWindowHighlighter's infobar components. + +add_task(async function() { + await BrowserTestUtils.withNewTab( + { + gBrowser, + url: MAIN_DOMAIN + "doc_accessibility_infobar.html", + }, + async function(browser) { + await SpecialPowers.spawn(browser, [], async function() { + const { require } = ChromeUtils.importESModule( + "resource://devtools/shared/loader/Loader.sys.mjs" + ); + const { + HighlighterEnvironment, + } = require("resource://devtools/server/actors/highlighters.js"); + const { + AccessibleHighlighter, + } = require("resource://devtools/server/actors/highlighters/accessible.js"); + + /** + * Get whether or not infobar container is hidden. + * + * @param {Object} infobar + * Accessible highlighter's infobar component. + * @return {String|null} If the infobar container is hidden. + */ + function isContainerHidden(infobar) { + return !!infobar + .getElement("infobar-container") + .getAttribute("hidden"); + } + + /** + * Get name of accessible object. + * + * @param {Object} infobar + * Accessible highlighter's infobar component. + * @return {String} The text content of the infobar-name element. + */ + function getName(infobar) { + return infobar.getTextContent("infobar-name"); + } + + /** + * Get role of accessible object. + * + * @param {Object} infobar + * Accessible highlighter's infobar component. + * @return {String} The text content of the infobar-role element. + */ + function getRole(infobar) { + return infobar.getTextContent("infobar-role"); + } + + /** + * Checks for updated content for an infobar with valid bounds. + * + * @param {Object} infobar + * Accessible highlighter's infobar component. + * @param {Object} options + * Options to pass for the highlighter's show method. + * Available options: + * - {String} role + * Role value of the accessible. + * - {String} name + * Name value of the accessible. + * - {Boolean} shouldBeHidden + * If the infobar component should be hidden. + */ + function checkInfobar(infobar, { shouldBeHidden, role, name }) { + is( + isContainerHidden(infobar), + shouldBeHidden, + "Infobar's hidden state is correct." + ); + + if (shouldBeHidden) { + return; + } + + is(getRole(infobar), role, "infobarRole text content is correct"); + is( + getName(infobar), + `"${name}"`, + "infoBarName text content is correct" + ); + } + + /** + * Checks for updated content of an infobar with valid bounds. + * + * @param {Element} node + * Node to check infobar content on. + * @param {Object} highlighter + * Accessible highlighter. + */ + function testInfobar(node, highlighter) { + const infobar = highlighter.accessibleInfobar; + const bounds = { + x: 0, + y: 0, + w: 250, + h: 100, + }; + + info("Check that infobar is shown with valid bounds."); + highlighter.show(node, { + ...bounds, + role: "button", + name: "Accessible Button", + }); + + checkInfobar(infobar, { + role: "button", + name: "Accessible Button", + shouldBeHidden: false, + }); + highlighter.hide(); + + info("Check that infobar is hidden after .hide() is called."); + checkInfobar(infobar, { shouldBeHidden: true }); + + info("Check to make sure content is updated with new options."); + highlighter.show(node, { + ...bounds, + name: "Test link", + role: "link", + }); + checkInfobar(infobar, { + name: "Test link", + role: "link", + shouldBeHidden: false, + }); + highlighter.hide(); + } + + // Start testing. First, create highlighter environment and initialize. + const env = new HighlighterEnvironment(); + env.initFromWindow(content.window); + + // Wait for loading highlighter environment content to complete before creating the + // highlighter. + await new Promise(resolve => { + const doc = env.document; + + function onContentLoaded() { + if ( + doc.readyState === "interactive" || + doc.readyState === "complete" + ) { + resolve(); + } else { + doc.addEventListener("DOMContentLoaded", onContentLoaded, { + once: true, + }); + } + } + + onContentLoaded(); + }); + + // Now, we can test the Infobar and XULWindowInfobar components with their + // respective highlighters. + const node = content.document.createElement("div"); + content.document.body.append(node); + + info("Checks for Infobar's show method"); + const highlighter = new AccessibleHighlighter(env); + await highlighter.isReady; + testInfobar(node, highlighter); + }); + } + ); +}); diff --git a/devtools/server/tests/browser/browser_accessibility_keyboard_audit.js b/devtools/server/tests/browser/browser_accessibility_keyboard_audit.js new file mode 100644 index 0000000000..b00fe9fc1d --- /dev/null +++ b/devtools/server/tests/browser/browser_accessibility_keyboard_audit.js @@ -0,0 +1,375 @@ +/* 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"; + +/** + * Checks functionality around text label audit for the AccessibleActor. + */ + +const { + accessibility: { + AUDIT_TYPE: { KEYBOARD }, + SCORES: { FAIL, WARNING }, + ISSUE_TYPE: { + [KEYBOARD]: { + FOCUSABLE_NO_SEMANTICS, + FOCUSABLE_POSITIVE_TABINDEX, + INTERACTIVE_NO_ACTION, + INTERACTIVE_NOT_FOCUSABLE, + MOUSE_INTERACTIVE_ONLY, + NO_FOCUS_VISIBLE, + }, + }, + }, +} = require("resource://devtools/shared/constants.js"); + +add_task(async function() { + const { + target, + walker, + parentAccessibility, + a11yWalker, + } = await initAccessibilityFrontsForUrl( + `${MAIN_DOMAIN}doc_accessibility_keyboard_audit.html` + ); + + const tests = [ + [ + "Focusable element (styled button) with no semantics.", + "#button-1", + { score: WARNING, issue: FOCUSABLE_NO_SEMANTICS }, + ], + ["Element (styled button) with no semantics.", "#button-2", null], + [ + "Container element for out of order focusable element.", + "#input-container", + null, + ], + [ + "Interactive element with focus out of order (-1).", + "#input-1", + { + score: FAIL, + issue: INTERACTIVE_NOT_FOCUSABLE, + }, + ], + [ + "Interactive element with focus out of order (-1) when disabled.", + "#input-2", + null, + ], + ["Interactive element when disabled.", "#input-3", null], + ["Focusable interactive element.", "#input-4", null], + [ + "Interactive accesible (link with no attributes) with no accessible actions.", + "#link-1", + { + score: FAIL, + issue: INTERACTIVE_NO_ACTION, + }, + ], + ["Interactive accessible (link with valid href).", "#link-2", null], + ["Interactive accessible (link with # as href).", "#link-3", null], + [ + "Interactive accessible (link with empty string as href).", + "#link-4", + null, + ], + ["Interactive accessible with no tabindex.", "#button-3", null], + [ + "Interactive accessible with -1 tabindex.", + "#button-4", + { + score: FAIL, + issue: INTERACTIVE_NOT_FOCUSABLE, + }, + ], + ["Interactive accessible with 0 tabindex.", "#button-5", null], + [ + "Interactive accessible with 1 tabindex.", + "#button-6", + { score: WARNING, issue: FOCUSABLE_POSITIVE_TABINDEX }, + ], + [ + "Focusable ARIA button with no focus styling.", + "#focusable-1", + { score: WARNING, issue: NO_FOCUS_VISIBLE }, + ], + ["Focusable ARIA button with focus styling.", "#focusable-2", null], + ["Focusable ARIA button with browser styling.", "#focusable-3", null], + [ + "Not focusable, non-semantic element that has a click handler.", + "#mouse-only-1", + { score: FAIL, issue: MOUSE_INTERACTIVE_ONLY }, + ], + [ + "Focusable, non-semantic element that has a click handler.", + "#focusable-4", + { score: WARNING, issue: FOCUSABLE_NO_SEMANTICS }, + ], + [ + "Not focusable, ARIA button that has a click handler.", + "#button-7", + { score: FAIL, issue: INTERACTIVE_NOT_FOCUSABLE }, + ], + ["Focusable, ARIA button with a click handler.", "#button-8", null], + ["Regular image, no keyboard checks should flag an issue.", "#img-1", null], + [ + "Image with a longdesc (accessible will have showlongdesc action).", + "#img-2", + null, + ], + [ + "Clickable image with a longdesc (accessible will have click and showlongdesc actions).", + "#img-3", + { score: FAIL, issue: MOUSE_INTERACTIVE_ONLY }, + ], + [ + "Clickable image (accessible will have click action).", + "#img-4", + { score: FAIL, issue: MOUSE_INTERACTIVE_ONLY }, + ], + ["Focusable button with aria-haspopup.", "#buttonmenu-1", null], + [ + "Not focusable aria button with aria-haspopup.", + "#buttonmenu-2", + { + score: FAIL, + issue: INTERACTIVE_NOT_FOCUSABLE, + }, + ], + ["Focusable checkbox.", "#checkbox-1", null], + ["Focusable select element size > 1", "#listbox-1", null], + ["Focusable select element with one option", "#combobox-1", null], + ["Focusable select element with no options", "#combobox-2", null], + ["Focusable select element with two options", "#combobox-3", null], + [ + "Non-focusable aria combobox with one aria option.", + "#editcombobox-1", + null, + ], + ["Non-focusable aria combobox with no options.", "#editcombobox-2", null], + ["Focusable aria combobox with no options.", "#editcombobox-3", null], + [ + "Non-focusable aria switch", + "#switch-1", + { + score: FAIL, + issue: INTERACTIVE_NOT_FOCUSABLE, + }, + ], + ["Focusable aria switch", "#switch-2", null], + [ + "Combobox list that is visible (has focusable state)", + "#owned_listbox", + null, + ], + [ + "Mouse interactive, label that contains form element (linked)", + "#label-1", + null, + ], + ["Mouse interactive label for external element (linked)", "#label-2", null], + ["Not interactive unlinked label", "#label-3", null], + [ + "Not interactive unlinked label with folloing form element", + "#label-4", + null, + ], + ["Image inside an anchor (href)", "#img-5", null], + ["Image inside an anchor (onmousedown)", "#img-6", null], + ["Image inside an anchor (onclick)", "#img-7", null], + ["Image inside an anchor (onmouseup)", "#img-8", null], + [ + "Section with a collapse action from aria-expanded attribute", + "#section-1", + null, + ], + ["Tabindex -1 should not report an element as focusable", "#main", null], + [ + "Not keyboard focusable element with no focus styling.", + "#not-keyboard-focusable-1", + null, + ], + ["Interactive grid that is not focusable.", "#grid-1", null], + ["Focusable interactive grid.", "#grid-2", null], + [ + "Non interactive ARIA table does not need to be focusable.", + "#table-1", + null, + ], + [ + "Focusable ARIA table does not have interactive semantics", + "#table-2", + { score: "WARNING", issue: "FOCUSABLE_NO_SEMANTICS" }, + ], + ["Non interactive table does not need to be focusable.", "#table-3", null], + [ + "Focusable table does not have interactive semantics", + "#table-4", + { score: "WARNING", issue: "FOCUSABLE_NO_SEMANTICS" }, + ], + [ + "Article that is not focusable is not considered interactive", + "#article-1", + null, + ], + ["Focusable article is considered interactive", "#article-2", null], + [ + "Column header that is not focusable is not considered interactive (ARIA grid)", + "#columnheader-1", + null, + ], + [ + "Column header that is not focusable is not considered interactive (ARIA table)", + "#columnheader-2", + null, + ], + [ + "Column header that is not focusable is not considered interactive (table)", + "#columnheader-3", + null, + ], + [ + "Column header that is focusable is considered interactive (table)", + "#columnheader-4", + null, + ], + [ + "Column header that is not focusable is not considered interactive (table as ARIA grid)", + "#columnheader-5", + null, + ], + [ + "Column header that is focusable is considered interactive (table as ARIA grid)", + "#columnheader-6", + null, + ], + [ + "Row header that is not focusable is not considered interactive", + "#rowheader-1", + null, + ], + [ + "Row header that is not focusable is not considered interactive", + "#rowheader-2", + null, + ], + [ + "Row header that is not focusable is not considered interactive", + "#rowheader-3", + null, + ], + [ + "Row header that is focusable is considered interactive", + "#rowheader-4", + null, + ], + [ + "Row header that is not focusable is not considered interactive (table as ARIA grid)", + "#rowheader-5", + null, + ], + [ + "Row header that is focusable is considered interactive (table as ARIA grid)", + "#rowheader-6", + null, + ], + [ + "Gridcell that is not focusable is not considered interactive (ARIA grid)", + "#gridcell-1", + null, + ], + [ + "Gridcell that is focusable is considered interactive (ARIA grid)", + "#gridcell-2", + null, + ], + [ + "Gridcell that is not focusable is not considered interactive (table as ARIA grid)", + "#gridcell-3", + null, + ], + [ + "Gridcell that is focusable is considered interactive (table as ARIA grid)", + "#gridcell-4", + null, + ], + [ + "Tab list that is not focusable is not considered interactive", + "#tablist-1", + null, + ], + ["Focusable tab list is considered interactive", "#tablist-2", null], + [ + "Scrollbar that is not focusable is not considered interactive", + "#scrollbar-1", + null, + ], + ["Focusable scrollbar is considered interactive", "#scrollbar-2", null], + [ + "Separator that is not focusable is not considered interactive", + "#separator-1", + null, + ], + ["Focusable separator is considered interactive", "#separator-2", null], + [ + "Toolbar that is not focusable is not considered interactive", + "#toolbar-1", + null, + ], + ["Focusable toolbar is considered interactive", "#toolbar-2", null], + [ + "Menu popup that is not focusable is not considered interactive", + "#menu-1", + null, + ], + ["Focusable menu popup is considered interactive", "#menu-2", null], + [ + "Menubar that is not focusable is not considered interactive", + "#menubar-1", + null, + ], + ["Focusable menubar is considered interactive", "#menubar-2", null], + ]; + + for (const [description, selector, expected] of tests) { + info(description); + const node = await walker.querySelector(walker.rootNode, selector); + const front = await a11yWalker.getAccessibleFor(node); + const audit = await front.audit({ types: [KEYBOARD] }); + Assert.deepEqual( + audit[KEYBOARD], + expected, + `Audit result for ${selector} is correct.` + ); + } + + info("Text leaf inside a link (jump action is propagated to the text link)"); + let node = await walker.querySelector(walker.rootNode, "#link-5"); + let parent = await a11yWalker.getAccessibleFor(node); + let front = (await parent.children())[0]; + let audit = await front.audit({ types: [KEYBOARD] }); + Assert.deepEqual( + audit[KEYBOARD], + null, + "Text leafs are excluded from semantics rule." + ); + + info("Combobox list that is invisible"); + node = await walker.querySelector(walker.rootNode, "#combobox-1"); + parent = await a11yWalker.getAccessibleFor(node); + front = (await parent.children())[0]; + audit = await front.audit({ types: [KEYBOARD] }); + Assert.deepEqual( + audit[KEYBOARD], + null, + "Combobox lists (invisible) are excluded from semantics rule." + ); + + await waitForA11yShutdown(parentAccessibility); + await target.destroy(); + gBrowser.removeCurrentTab(); +}); diff --git a/devtools/server/tests/browser/browser_accessibility_node.js b/devtools/server/tests/browser/browser_accessibility_node.js new file mode 100644 index 0000000000..2adaea980f --- /dev/null +++ b/devtools/server/tests/browser/browser_accessibility_node.js @@ -0,0 +1,132 @@ +/* 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"; + +// Checks for the AccessibleActor + +add_task(async function() { + const { + target, + walker, + a11yWalker, + parentAccessibility, + } = await initAccessibilityFrontsForUrl( + MAIN_DOMAIN + "doc_accessibility.html" + ); + const modifiers = + Services.appinfo.OS === "Darwin" ? "\u2303\u2325" : "Alt+Shift+"; + + const buttonNode = await walker.querySelector(walker.rootNode, "#button"); + const accessibleFront = await a11yWalker.getAccessibleFor(buttonNode); + + checkA11yFront(accessibleFront, { + name: "Accessible Button", + role: "pushbutton", + childCount: 1, + }); + + await accessibleFront.hydrate(); + + checkA11yFront(accessibleFront, { + name: "Accessible Button", + role: "pushbutton", + value: "", + description: "Accessibility Test", + keyboardShortcut: modifiers + "b", + childCount: 1, + domNodeType: 1, + indexInParent: 1, + states: ["focusable", "opaque", "enabled", "sensitive"], + actions: ["Press"], + attributes: { + "margin-top": "0px", + display: "inline-block", + "text-align": "center", + "text-indent": "0px", + "margin-left": "0px", + tag: "button", + "margin-right": "0px", + id: "button", + "margin-bottom": "0px", + }, + }); + + info("Children"); + const children = await accessibleFront.children(); + is(children.length, 1, "Accessible Front has correct number of children"); + checkA11yFront(children[0], { + name: "Accessible Button", + role: "text leaf", + }); + + info("Relations"); + const labelNode = await walker.querySelector(walker.rootNode, "#label"); + const controlNode = await walker.querySelector(walker.rootNode, "#control"); + const labelAccessibleFront = await a11yWalker.getAccessibleFor(labelNode); + const controlAccessibleFront = await a11yWalker.getAccessibleFor(controlNode); + const docAccessibleFront = await a11yWalker.getAccessibleFor(walker.rootNode); + const relations = await labelAccessibleFront.getRelations(); + is(relations.length, 2, "Accessible front has a correct number of relations"); + is(relations[0].type, "label for", "Label has a label for relation"); + is(relations[0].targets.length, 1, "Label is a label for one target"); + is( + relations[0].targets[0], + controlAccessibleFront, + "Label is a label for control accessible front" + ); + is( + relations[1].type, + "containing document", + "Label has a containing document relation" + ); + is(relations[1].targets.length, 1, "Label is contained by just one document"); + is( + relations[1].targets[0], + docAccessibleFront, + "Label's containing document is a root document" + ); + + info("Snapshot"); + const snapshot = await controlAccessibleFront.snapshot(); + Assert.deepEqual(snapshot, { + name: "Label", + role: "entry", + actions: ["Activate"], + value: "", + nodeCssSelector: "#control", + nodeType: 1, + description: "", + keyboardShortcut: "", + childCount: 0, + indexInParent: 1, + states: [ + "focusable", + "autocompletion", + "selectable text", + "editable", + "opaque", + "single line", + "enabled", + "sensitive", + ], + children: [], + attributes: { + "margin-left": "0px", + "text-align": "start", + "text-indent": "0px", + id: "control", + tag: "input", + "margin-top": "0px", + "margin-bottom": "0px", + "margin-right": "0px", + display: "inline-block", + "explicit-name": "true", + }, + }); + + await waitForA11yShutdown(parentAccessibility); + await target.destroy(); + gBrowser.removeCurrentTab(); +}); diff --git a/devtools/server/tests/browser/browser_accessibility_node_audit.js b/devtools/server/tests/browser/browser_accessibility_node_audit.js new file mode 100644 index 0000000000..81032398f6 --- /dev/null +++ b/devtools/server/tests/browser/browser_accessibility_node_audit.js @@ -0,0 +1,120 @@ +/* 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"; + +/** + * Checks functionality around audit for the AccessibleActor. This includes + * tests for the return value when calling the audit method, payload of the + * corresponding event as well as the AccesibleFront state being up to date. + */ + +const { + accessibility: { AUDIT_TYPE, SCORES }, +} = require("resource://devtools/shared/constants.js"); +const EMPTY_AUDIT = Object.keys(AUDIT_TYPE).reduce((audit, key) => { + audit[key] = null; + return audit; +}, {}); + +const EXPECTED_CONTRAST_DATA = { + value: 21, + color: [0, 0, 0, 1], + backgroundColor: [255, 255, 255, 1], + isLargeText: true, + score: SCORES.AAA, +}; + +const EMPTY_CONTRAST_AUDIT = { + [AUDIT_TYPE.CONTRAST]: null, +}; + +const CONTRAST_AUDIT = { + [AUDIT_TYPE.CONTRAST]: EXPECTED_CONTRAST_DATA, +}; + +const FULL_AUDIT = { + ...EMPTY_AUDIT, + [AUDIT_TYPE.CONTRAST]: EXPECTED_CONTRAST_DATA, +}; + +async function checkAudit(a11yWalker, node, expected, options) { + const front = await a11yWalker.getAccessibleFor(node); + const [textLeafNode] = await front.children(); + + const onAudited = textLeafNode.once("audited"); + const audit = await textLeafNode.audit(options); + const auditFromEvent = await onAudited; + + Assert.deepEqual(audit, expected.audit, "Audit results are correct."); + Assert.deepEqual(textLeafNode.checks, expected.checks, "Checks are correct."); + Assert.deepEqual( + auditFromEvent, + expected.audit, + "Audit results from event are correct." + ); +} + +add_task(async function() { + const { + target, + walker, + a11yWalker, + parentAccessibility, + } = await initAccessibilityFrontsForUrl( + MAIN_DOMAIN + "doc_accessibility_infobar.html" + ); + + const headerNode = await walker.querySelector(walker.rootNode, "#h1"); + await checkAudit( + a11yWalker, + headerNode, + { audit: CONTRAST_AUDIT, checks: CONTRAST_AUDIT }, + { types: [AUDIT_TYPE.CONTRAST] } + ); + await checkAudit(a11yWalker, headerNode, { + audit: FULL_AUDIT, + checks: FULL_AUDIT, + }); + await checkAudit( + a11yWalker, + headerNode, + { audit: CONTRAST_AUDIT, checks: FULL_AUDIT }, + { types: [AUDIT_TYPE.CONTRAST] } + ); + await checkAudit( + a11yWalker, + headerNode, + { audit: FULL_AUDIT, checks: FULL_AUDIT }, + { types: [] } + ); + + const paragraphNode = await walker.querySelector(walker.rootNode, "#p"); + await checkAudit( + a11yWalker, + paragraphNode, + { audit: EMPTY_CONTRAST_AUDIT, checks: EMPTY_CONTRAST_AUDIT }, + { types: [AUDIT_TYPE.CONTRAST] } + ); + await checkAudit(a11yWalker, paragraphNode, { + audit: EMPTY_AUDIT, + checks: EMPTY_AUDIT, + }); + await checkAudit( + a11yWalker, + paragraphNode, + { audit: EMPTY_CONTRAST_AUDIT, checks: EMPTY_AUDIT }, + { types: [AUDIT_TYPE.CONTRAST] } + ); + await checkAudit( + a11yWalker, + paragraphNode, + { audit: EMPTY_AUDIT, checks: EMPTY_AUDIT }, + { types: [] } + ); + + await waitForA11yShutdown(parentAccessibility); + await target.destroy(); + gBrowser.removeCurrentTab(); +}); diff --git a/devtools/server/tests/browser/browser_accessibility_node_events.js b/devtools/server/tests/browser/browser_accessibility_node_events.js new file mode 100644 index 0000000000..4abd10a9b9 --- /dev/null +++ b/devtools/server/tests/browser/browser_accessibility_node_events.js @@ -0,0 +1,203 @@ +/* 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"; + +// Checks for the AccessibleActor events + +add_task(async function() { + const { + target, + walker, + a11yWalker, + parentAccessibility, + } = await initAccessibilityFrontsForUrl( + MAIN_DOMAIN + "doc_accessibility.html" + ); + const modifiers = + Services.appinfo.OS === "Darwin" ? "\u2303\u2325" : "Alt+Shift+"; + + const rootNode = await walker.getRootNode(); + const a11yDoc = await a11yWalker.getAccessibleFor(rootNode); + const buttonNode = await walker.querySelector(walker.rootNode, "#button"); + const accessibleFront = await a11yWalker.getAccessibleFor(buttonNode); + const sliderNode = await walker.querySelector(walker.rootNode, "#slider"); + const accessibleSliderFront = await a11yWalker.getAccessibleFor(sliderNode); + const browser = gBrowser.selectedBrowser; + + checkA11yFront(accessibleFront, { + name: "Accessible Button", + role: "pushbutton", + childCount: 1, + }); + + await accessibleFront.hydrate(); + + checkA11yFront(accessibleFront, { + name: "Accessible Button", + role: "pushbutton", + value: "", + description: "Accessibility Test", + keyboardShortcut: modifiers + "b", + childCount: 1, + domNodeType: 1, + indexInParent: 1, + states: ["focusable", "opaque", "enabled", "sensitive"], + actions: ["Press"], + attributes: { + "margin-top": "0px", + display: "inline-block", + "text-align": "center", + "text-indent": "0px", + "margin-left": "0px", + tag: "button", + "margin-right": "0px", + id: "button", + "margin-bottom": "0px", + }, + }); + + info("Name change event"); + await emitA11yEvent( + accessibleFront, + "name-change", + (name, parent) => { + checkA11yFront(accessibleFront, { name: "Renamed" }); + checkA11yFront(parent, {}, a11yDoc); + }, + () => + SpecialPowers.spawn(browser, [], () => + content.document + .getElementById("button") + .setAttribute("aria-label", "Renamed") + ) + ); + + info("Description change event"); + await emitA11yEvent( + accessibleFront, + "description-change", + () => checkA11yFront(accessibleFront, { description: "" }), + () => + SpecialPowers.spawn(browser, [], () => + content.document + .getElementById("button") + .removeAttribute("aria-describedby") + ) + ); + + info("State change event"); + const expectedStates = ["unavailable", "opaque"]; + await emitA11yEvent( + accessibleFront, + "states-change", + newStates => { + checkA11yFront(accessibleFront, { states: expectedStates }); + SimpleTest.isDeeply(newStates, expectedStates, "States are updated"); + }, + () => + SpecialPowers.spawn(browser, [], () => + content.document.getElementById("button").setAttribute("disabled", true) + ) + ); + + info("Attributes change event"); + await emitA11yEvent( + accessibleFront, + "attributes-change", + newAttrs => { + checkA11yFront(accessibleFront, { + attributes: { + "container-live": "polite", + display: "inline-block", + "event-from-input": "false", + "explicit-name": "true", + id: "button", + live: "polite", + "margin-bottom": "0px", + "margin-left": "0px", + "margin-right": "0px", + "margin-top": "0px", + tag: "button", + "text-align": "center", + "text-indent": "0px", + }, + }); + is(newAttrs.live, "polite", "Attributes are updated"); + }, + () => + SpecialPowers.spawn(browser, [], () => + content.document + .getElementById("button") + .setAttribute("aria-live", "polite") + ) + ); + + info("Value change event"); + await accessibleSliderFront.hydrate(); + checkA11yFront(accessibleSliderFront, { value: "5" }); + await emitA11yEvent( + accessibleSliderFront, + "value-change", + () => checkA11yFront(accessibleSliderFront, { value: "6" }), + () => + SpecialPowers.spawn(browser, [], () => + content.document + .getElementById("slider") + .setAttribute("aria-valuenow", "6") + ) + ); + + info("Reorder event"); + is(accessibleSliderFront.childCount, 1, "Slider has only 1 child"); + const [firstChild] = await accessibleSliderFront.children(); + await firstChild.hydrate(); + is( + firstChild.indexInParent, + 0, + "Slider's first child has correct index in parent" + ); + await emitA11yEvent( + accessibleSliderFront, + "reorder", + childCount => { + is(childCount, 2, "Child count is updated"); + is(accessibleSliderFront.childCount, 2, "Child count is updated"); + is( + firstChild.indexInParent, + 1, + "Slider's first child has an updated index in parent" + ); + }, + () => + SpecialPowers.spawn(browser, [], () => { + const doc = content.document; + const slider = doc.getElementById("slider"); + const button = doc.createElement("button"); + button.innerText = "Slider button"; + content.document + .getElementById("slider") + .insertBefore(button, slider.firstChild); + }) + ); + + await emitA11yEvent( + firstChild, + "index-in-parent-change", + indexInParent => + is( + indexInParent, + 0, + "Slider's first child has an updated index in parent" + ), + () => + SpecialPowers.spawn(browser, [], () => + content.document.getElementById("slider").firstChild.remove() + ) + ); + + await waitForA11yShutdown(parentAccessibility); + await target.destroy(); + gBrowser.removeCurrentTab(); +}); diff --git a/devtools/server/tests/browser/browser_accessibility_node_tabbing_order_highlighter.js b/devtools/server/tests/browser/browser_accessibility_node_tabbing_order_highlighter.js new file mode 100644 index 0000000000..45cfea6865 --- /dev/null +++ b/devtools/server/tests/browser/browser_accessibility_node_tabbing_order_highlighter.js @@ -0,0 +1,92 @@ +/* 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"; + +// Checks for the NodeTabbingOrderHighlighter. + +add_task(async function() { + await BrowserTestUtils.withNewTab( + { + gBrowser, + url: MAIN_DOMAIN + "doc_accessibility_infobar.html", + }, + async function(browser) { + await SpecialPowers.spawn(browser, [], async function() { + const { require } = ChromeUtils.importESModule( + "resource://devtools/shared/loader/Loader.sys.mjs" + ); + const { + HighlighterEnvironment, + } = require("resource://devtools/server/actors/highlighters.js"); + const { + NodeTabbingOrderHighlighter, + } = require("resource://devtools/server/actors/highlighters/node-tabbing-order.js"); + + // Checks for updated content for an infobar. + async function testShowHide(highlighter, node, index) { + const shown = highlighter.show(node, { index }); + const infoBarText = highlighter.getElement("infobar-text"); + + ok(shown, "Highlighter is shown."); + is( + parseInt(infoBarText.getTextContent(), 10), + index, + "infobar text content is correct" + ); + + highlighter.hide(); + } + + // Start testing. First, create highlighter environment and initialize. + const env = new HighlighterEnvironment(); + env.initFromWindow(content.window); + + // Wait for loading highlighter environment content to complete before + // creating the highlighter. + await new Promise(resolve => { + const doc = env.document; + + function onContentLoaded() { + if ( + doc.readyState === "interactive" || + doc.readyState === "complete" + ) { + resolve(); + } else { + doc.addEventListener("DOMContentLoaded", onContentLoaded, { + once: true, + }); + } + } + + onContentLoaded(); + }); + + // Now, we can test the Infobar's index content. + const node = content.document.createElement("div"); + content.document.body.append(node); + const highlighter = new NodeTabbingOrderHighlighter(env); + await highlighter.isReady; + + info("Showing Node tabbing order highlighter with index"); + await testShowHide(highlighter, node, 1); + + info("Showing Node tabbing order highlighter with new index"); + await testShowHide(highlighter, node, 9); + + info( + "Showing and highlighting focused node with the Node tabbing order highlighter" + ); + highlighter.show(node, { index: 1 }); + highlighter.updateFocus(true); + const { classList } = highlighter.getElement("root"); + ok(classList.contains("focused"), "Focus styling is applied"); + highlighter.updateFocus(false); + ok(!classList.contains("focused"), "Focus styling is removed"); + highlighter.hide(); + }); + } + ); +}); diff --git a/devtools/server/tests/browser/browser_accessibility_simple.js b/devtools/server/tests/browser/browser_accessibility_simple.js new file mode 100644 index 0000000000..593343936a --- /dev/null +++ b/devtools/server/tests/browser/browser_accessibility_simple.js @@ -0,0 +1,106 @@ +/* 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 PREF_ACCESSIBILITY_FORCE_DISABLED = "accessibility.force_disabled"; + +function checkAccessibilityState(accessibility, parentAccessibility, expected) { + const { enabled } = accessibility; + const { canBeDisabled, canBeEnabled } = parentAccessibility; + is(enabled, expected.enabled, "Enabled state is correct."); + is(canBeDisabled, expected.canBeDisabled, "canBeDisabled state is correct."); + is(canBeEnabled, expected.canBeEnabled, "canBeEnabled state is correct."); +} + +// Simple checks for the AccessibilityActor and AccessibleWalkerActor + +add_task(async function() { + const { + walker: domWalker, + target, + accessibility, + parentAccessibility, + a11yWalker, + } = await initAccessibilityFrontsForUrl( + "data:text/html;charset=utf-8,test
", + { enableByDefault: false } + ); + + ok(accessibility, "The AccessibilityFront was created"); + ok(accessibility.getWalker, "The getWalker method exists"); + ok(accessibility.getSimulator, "The getSimulator method exists"); + + ok(accessibility.accessibleWalkerFront, "Accessible walker was initialized"); + + is( + a11yWalker, + accessibility.accessibleWalkerFront, + "The AccessibleWalkerFront was returned" + ); + + const a11ySimulator = accessibility.simulatorFront; + ok(accessibility.simulatorFront, "Accessible simulator was initialized"); + is( + a11ySimulator, + accessibility.simulatorFront, + "The SimulatorFront was returned" + ); + + checkAccessibilityState(accessibility, parentAccessibility, { + enabled: false, + canBeDisabled: true, + canBeEnabled: true, + }); + + info("Force disable accessibility service: updates canBeEnabled flag"); + let onEvent = parentAccessibility.once("can-be-enabled-change"); + Services.prefs.setIntPref(PREF_ACCESSIBILITY_FORCE_DISABLED, 1); + await onEvent; + checkAccessibilityState(accessibility, parentAccessibility, { + enabled: false, + canBeDisabled: true, + canBeEnabled: false, + }); + + info("Clear force disable accessibility service: updates canBeEnabled flag"); + onEvent = parentAccessibility.once("can-be-enabled-change"); + Services.prefs.clearUserPref(PREF_ACCESSIBILITY_FORCE_DISABLED); + await onEvent; + checkAccessibilityState(accessibility, parentAccessibility, { + enabled: false, + canBeDisabled: true, + canBeEnabled: true, + }); + + info("Initialize accessibility service"); + const initEvent = accessibility.once("init"); + await parentAccessibility.enable(); + await waitForA11yInit(); + await initEvent; + checkAccessibilityState(accessibility, parentAccessibility, { + enabled: true, + canBeDisabled: true, + canBeEnabled: true, + }); + + const rootNode = await domWalker.getRootNode(); + const a11yDoc = await accessibility.accessibleWalkerFront.getAccessibleFor( + rootNode + ); + ok(a11yDoc, "Accessible document actor is created"); + + info("Shutdown accessibility service"); + const shutdownEvent = accessibility.once("shutdown"); + await waitForA11yShutdown(parentAccessibility); + await shutdownEvent; + checkAccessibilityState(accessibility, parentAccessibility, { + enabled: false, + canBeDisabled: true, + canBeEnabled: true, + }); + + await target.destroy(); + gBrowser.removeCurrentTab(); +}); diff --git a/devtools/server/tests/browser/browser_accessibility_simulator.js b/devtools/server/tests/browser/browser_accessibility_simulator.js new file mode 100644 index 0000000000..9045160c44 --- /dev/null +++ b/devtools/server/tests/browser/browser_accessibility_simulator.js @@ -0,0 +1,90 @@ +/* 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 { + accessibility: { + SIMULATION_TYPE: { PROTANOPIA }, + }, +} = require("resource://devtools/shared/constants.js"); +const { + simulation: { + COLOR_TRANSFORMATION_MATRICES: { + PROTANOPIA: PROTANOPIA_MATRIX, + NONE: DEFAULT_MATRIX, + }, + }, +} = require("resource://devtools/server/actors/accessibility/constants.js"); + +// Checks for the SimulatorActor + +async function setup() { + await SpecialPowers.spawn(gBrowser.selectedBrowser, [], () => { + content.window.testColorMatrix = function(actual, expected) { + for (const idx in actual) { + is( + actual[idx].toFixed(3), + expected[idx].toFixed(3), + "Color matrix value is set correctly." + ); + } + }; + }); + SimpleTest.registerCleanupFunction(async function() { + await SpecialPowers.spawn(gBrowser.selectedBrowser, [], () => { + content.window.testColorMatrix = null; + }); + }); +} + +async function testSimulate(simulator, matrix, type = null) { + const matrixApplied = await simulator.simulate({ types: type ? [type] : [] }); + ok(matrixApplied, "Simulation color matrix is successfully applied."); + + await SpecialPowers.spawn( + gBrowser.selectedBrowser, + [[type, matrix]], + ([simulationType, simulationMatrix]) => { + const { window } = content; + info( + `Test that color matrix is set to ${simulationType || + "default"} simulation values.` + ); + window.testColorMatrix( + window.docShell.getColorMatrix(), + simulationMatrix + ); + } + ); +} + +add_task(async function() { + const { + target, + accessibility, + } = await initAccessibilityFrontsForUrl( + MAIN_DOMAIN + "doc_accessibility.html", + { enableByDefault: false } + ); + + const simulator = accessibility.simulatorFront; + if (!simulator) { + ok(false, "Missing simulator actor."); + return; + } + + await setup(); + + info("Test that protanopia is successfully simulated."); + await testSimulate(simulator, PROTANOPIA_MATRIX, PROTANOPIA); + + info( + "Test that simulations are successfully removed by setting default color matrix." + ); + await testSimulate(simulator, DEFAULT_MATRIX); + + await target.destroy(); + gBrowser.removeCurrentTab(); +}); diff --git a/devtools/server/tests/browser/browser_accessibility_tabbing_order_highlighter.js b/devtools/server/tests/browser/browser_accessibility_tabbing_order_highlighter.js new file mode 100644 index 0000000000..039f1345e5 --- /dev/null +++ b/devtools/server/tests/browser/browser_accessibility_tabbing_order_highlighter.js @@ -0,0 +1,101 @@ +/* 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"; + +// Checks for the TabbingOrderHighlighter. + +add_task(async function() { + await BrowserTestUtils.withNewTab( + { + gBrowser, + url: MAIN_DOMAIN + "doc_accessibility_infobar.html", + }, + async function(browser) { + await SpecialPowers.spawn(browser, [], async function() { + const { require } = ChromeUtils.importESModule( + "resource://devtools/shared/loader/Loader.sys.mjs" + ); + const { + HighlighterEnvironment, + } = require("resource://devtools/server/actors/highlighters.js"); + const { + TabbingOrderHighlighter, + } = require("resource://devtools/server/actors/highlighters/tabbing-order.js"); + + // Start testing. First, create highlighter environment and initialize. + const env = new HighlighterEnvironment(); + env.initFromWindow(content.window); + + // Wait for loading highlighter environment content to complete before + // creating the highlighter. + await new Promise(resolve => { + const doc = env.document; + + function onContentLoaded() { + if ( + doc.readyState === "interactive" || + doc.readyState === "complete" + ) { + resolve(); + } else { + doc.addEventListener("DOMContentLoaded", onContentLoaded, { + once: true, + }); + } + } + + onContentLoaded(); + }); + + // Now, we can test the Infobar's index content. + const node = content.document.createElement("div"); + content.document.body.append(node); + const highlighter = new TabbingOrderHighlighter(env); + await highlighter.isReady; + + info("Showing tabbing order highlighter for all tabbable nodes"); + const { contentDOMReference, index } = await highlighter.show( + content.document, + { + index: 0, + } + ); + + is( + contentDOMReference, + null, + "No current element when at the end of the tab order" + ); + is(index, 2, "Current index is correct"); + is( + highlighter._highlighters.size, + 2, + "Number of node tabbing order highlighters is correct" + ); + for (let i = 0; i < highlighter._highlighters.size; i++) { + const nodeHighlighter = [...highlighter._highlighters.values()][i]; + const infoBarText = nodeHighlighter.getElement("infobar-text"); + + is( + parseInt(infoBarText.getTextContent(), 10), + i + 1, + "infobar text content is correct" + ); + } + + info("Showing focus highlighting"); + const input = content.document.getElementById("input"); + highlighter.updateFocus({ node: input, focused: true }); + const nodeHighlighter = highlighter._highlighters.get(input); + const { classList } = nodeHighlighter.getElement("root"); + ok(classList.contains("focused"), "Focus styling is applied"); + highlighter.updateFocus({ node: input, focused: false }); + ok(!classList.contains("focused"), "Focus styling is removed"); + + highlighter.hide(); + }); + } + ); +}); diff --git a/devtools/server/tests/browser/browser_accessibility_text_label_audit.js b/devtools/server/tests/browser/browser_accessibility_text_label_audit.js new file mode 100644 index 0000000000..dd0a0786f4 --- /dev/null +++ b/devtools/server/tests/browser/browser_accessibility_text_label_audit.js @@ -0,0 +1,1142 @@ +/* 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"; + +/** + * Checks functionality around text label audit for the AccessibleActor. + */ + +const { + accessibility: { + AUDIT_TYPE: { TEXT_LABEL }, + SCORES: { BEST_PRACTICES, FAIL, WARNING }, + ISSUE_TYPE: { + [TEXT_LABEL]: { + DIALOG_NO_NAME, + DOCUMENT_NO_TITLE, + EMBED_NO_NAME, + FIGURE_NO_NAME, + FORM_FIELDSET_NO_NAME, + FORM_FIELDSET_NO_NAME_FROM_LEGEND, + FORM_NO_NAME, + FORM_NO_VISIBLE_NAME, + FORM_OPTGROUP_NO_NAME_FROM_LABEL, + HEADING_NO_CONTENT, + HEADING_NO_NAME, + IFRAME_NO_NAME_FROM_TITLE, + IMAGE_NO_NAME, + INTERACTIVE_NO_NAME, + MATHML_GLYPH_NO_NAME, + TOOLBAR_NO_NAME, + }, + }, + }, +} = require("resource://devtools/shared/constants.js"); + +add_task(async function() { + const { + target, + walker, + a11yWalker, + parentAccessibility, + } = await initAccessibilityFrontsForUrl( + `${MAIN_DOMAIN}doc_accessibility_text_label_audit.html` + ); + + const tests = [ + ["Button menu with inner content", "#buttonmenu-1", null], + ["Button menu nested inside a