From 36d22d82aa202bb199967e9512281e9a53db42c9 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sun, 7 Apr 2024 21:33:14 +0200 Subject: Adding upstream version 115.7.0esr. Signed-off-by: Daniel Baumann --- devtools/client/shared/components/.eslintrc.js | 11 + devtools/client/shared/components/Accordion.css | 87 + devtools/client/shared/components/Accordion.js | 257 ++ .../client/shared/components/AppErrorBoundary.css | 85 + .../client/shared/components/AppErrorBoundary.js | 161 + devtools/client/shared/components/Frame.js | 401 ++ devtools/client/shared/components/HSplitBox.js | 165 + devtools/client/shared/components/List.css | 41 + devtools/client/shared/components/List.js | 352 ++ devtools/client/shared/components/MdnLink.css | 33 + devtools/client/shared/components/MdnLink.js | 38 + .../client/shared/components/NotificationBox.css | 130 + .../client/shared/components/NotificationBox.js | 403 ++ devtools/client/shared/components/SearchBox.js | 269 ++ .../components/SearchBoxAutocompletePopup.js | 150 + .../client/shared/components/SearchModifiers.css | 64 + .../client/shared/components/SearchModifiers.js | 84 + devtools/client/shared/components/Sidebar.js | 98 + .../client/shared/components/SidebarToggle.css | 39 + devtools/client/shared/components/SidebarToggle.js | 89 + devtools/client/shared/components/SmartTrace.css | 167 + devtools/client/shared/components/SmartTrace.js | 308 ++ devtools/client/shared/components/StackTrace.js | 96 + devtools/client/shared/components/Tree.css | 86 + devtools/client/shared/components/Tree.js | 1055 +++++ .../client/shared/components/VirtualizedTree.js | 1071 +++++ .../client/shared/components/VisibilityHandler.js | 57 + .../client/shared/components/menu/MenuButton.js | 450 +++ devtools/client/shared/components/menu/MenuItem.js | 211 + devtools/client/shared/components/menu/MenuList.js | 164 + devtools/client/shared/components/menu/moz.build | 12 + devtools/client/shared/components/menu/utils.js | 62 + devtools/client/shared/components/moz.build | 41 + .../shared/components/object-inspector/actions.js | 225 ++ .../components/ObjectInspector.css | 99 + .../object-inspector/components/ObjectInspector.js | 371 ++ .../components/ObjectInspectorItem.js | 285 ++ .../object-inspector/components/moz.build | 10 + .../shared/components/object-inspector/index.js | 10 + .../shared/components/object-inspector/moz.build | 16 + .../shared/components/object-inspector/reducer.js | 147 + .../components/object-inspector/utils/client.js | 124 + .../components/object-inspector/utils/index.js | 52 + .../object-inspector/utils/load-properties.js | 260 ++ .../components/object-inspector/utils/moz.build | 13 + .../components/object-inspector/utils/node.js | 1039 +++++ .../components/object-inspector/utils/selection.js | 16 + .../client/shared/components/reps/images/input.svg | 7 + .../components/reps/images/jump-definition.svg | 8 + .../shared/components/reps/images/open-a11y.svg | 10 + .../components/reps/images/open-inspector.svg | 6 + devtools/client/shared/components/reps/index.js | 32 + devtools/client/shared/components/reps/moz.build | 14 + devtools/client/shared/components/reps/reps.css | 394 ++ .../shared/components/reps/reps/accessible.js | 197 + .../client/shared/components/reps/reps/accessor.js | 106 + .../client/shared/components/reps/reps/array.js | 170 + .../shared/components/reps/reps/attribute.js | 74 + .../client/shared/components/reps/reps/big-int.js | 57 + .../shared/components/reps/reps/comment-node.js | 76 + .../shared/components/reps/reps/constants.js | 16 + .../components/reps/reps/custom-formatter.js | 256 ++ .../shared/components/reps/reps/date-time.js | 95 + .../shared/components/reps/reps/document-type.js | 60 + .../client/shared/components/reps/reps/document.js | 79 + .../shared/components/reps/reps/element-node.js | 310 ++ .../client/shared/components/reps/reps/error.js | 330 ++ .../client/shared/components/reps/reps/event.js | 115 + .../client/shared/components/reps/reps/function.js | 264 ++ .../shared/components/reps/reps/grip-array.js | 255 ++ .../shared/components/reps/reps/grip-entry.js | 77 + .../client/shared/components/reps/reps/grip-map.js | 234 ++ .../client/shared/components/reps/reps/grip.js | 396 ++ .../client/shared/components/reps/reps/infinity.js | 52 + .../client/shared/components/reps/reps/moz.build | 45 + devtools/client/shared/components/reps/reps/nan.js | 51 + .../client/shared/components/reps/reps/null.js | 59 + .../client/shared/components/reps/reps/number.js | 63 + .../components/reps/reps/object-with-text.js | 70 + .../shared/components/reps/reps/object-with-url.js | 73 + .../client/shared/components/reps/reps/object.js | 207 + .../client/shared/components/reps/reps/promise.js | 101 + .../client/shared/components/reps/reps/prop-rep.js | 105 + .../client/shared/components/reps/reps/regexp.js | 66 + .../shared/components/reps/reps/rep-utils.js | 574 +++ devtools/client/shared/components/reps/reps/rep.js | 210 + .../client/shared/components/reps/reps/string.js | 407 ++ .../shared/components/reps/reps/stylesheet.js | 78 + .../client/shared/components/reps/reps/symbol.js | 82 + .../shared/components/reps/reps/text-node.js | 136 + .../shared/components/reps/reps/undefined.js | 59 + .../client/shared/components/reps/reps/window.js | 102 + .../components/reps/shared/dom-node-constants.js | 31 + .../components/reps/shared/grip-length-bubble.js | 64 + .../client/shared/components/reps/shared/moz.build | 10 + .../client/shared/components/splitter/Draggable.js | 106 + .../components/splitter/GridElementResizer.css | 32 + .../components/splitter/GridElementWidthResizer.js | 138 + .../client/shared/components/splitter/SplitBox.css | 93 + .../client/shared/components/splitter/SplitBox.js | 351 ++ .../client/shared/components/splitter/moz.build | 11 + devtools/client/shared/components/tabs/TabBar.js | 378 ++ devtools/client/shared/components/tabs/Tabs.css | 127 + devtools/client/shared/components/tabs/Tabs.js | 468 +++ devtools/client/shared/components/tabs/moz.build | 10 + .../shared/components/test/browser/browser.ini | 9 + .../test/browser/browser_notification_box_basic.js | 34 + .../components/test/browser/browser_reps_stubs.js | 347 ++ .../components/test/chrome/accordion.snapshots.js | 176 + .../shared/components/test/chrome/chrome.ini | 46 + .../client/shared/components/test/chrome/head.js | 379 ++ .../test/chrome/test_GridElementWidthResizer.html | 209 + .../chrome/test_GridElementWidthResizer_RTL.html | 210 + .../components/test/chrome/test_HSplitBox_01.html | 140 + .../components/test/chrome/test_accordion.html | 141 + .../components/test/chrome/test_frame_01.html | 361 ++ .../components/test/chrome/test_frame_02.html | 103 + .../shared/components/test/chrome/test_list.html | 127 + .../components/test/chrome/test_list_keyboard.html | 283 ++ .../test/chrome/test_notification_box_01.html | 136 + .../test/chrome/test_notification_box_02.html | 73 + .../test/chrome/test_notification_box_03.html | 87 + .../test/chrome/test_notification_box_04.html | 67 + .../test/chrome/test_notification_box_05.html | 63 + .../chrome/test_searchbox-with-autocomplete.html | 301 ++ .../components/test/chrome/test_searchbox.html | 74 + .../test/chrome/test_sidebar_toggle.html | 59 + .../test/chrome/test_smart-trace-grouping.html | 141 + .../test/chrome/test_smart-trace-source-maps.html | 290 ++ .../components/test/chrome/test_smart-trace.html | 172 + .../test/chrome/test_stack-trace-source-maps.html | 98 + .../components/test/chrome/test_stack-trace.html | 100 + .../test/chrome/test_tabs_accessibility.html | 82 + .../components/test/chrome/test_tabs_menu.html | 84 + .../components/test/chrome/test_tree-view_01.html | 290 ++ .../components/test/chrome/test_tree-view_02.html | 136 + .../components/test/chrome/test_tree_01.html | 68 + .../components/test/chrome/test_tree_02.html | 49 + .../components/test/chrome/test_tree_03.html | 50 + .../components/test/chrome/test_tree_04.html | 133 + .../components/test/chrome/test_tree_05.html | 195 + .../components/test/chrome/test_tree_06.html | 340 ++ .../components/test/chrome/test_tree_07.html | 69 + .../components/test/chrome/test_tree_08.html | 61 + .../components/test/chrome/test_tree_09.html | 85 + .../components/test/chrome/test_tree_10.html | 57 + .../components/test/chrome/test_tree_11.html | 100 + .../components/test/chrome/test_tree_12.html | 146 + .../components/test/chrome/test_tree_13.html | 88 + .../components/test/chrome/test_tree_14.html | 245 ++ .../components/test/chrome/test_tree_15.html | 99 + .../components/test/chrome/test_tree_16.html | 145 + .../shared/components/test/node/.eslintrc.js | 10 + .../components/test/node/__mocks__/Services.js | 14 + .../components/test/node/__mocks__/object-front.js | 55 + .../components/test/node/__mocks__/string-front.js | 15 + .../shared/components/test/node/babel.config.js | 13 + .../components/__snapshots__/tree.test.js.snap | 1171 ++++++ .../component/__snapshots__/basic.test.js.snap | 63 + .../__snapshots__/classnames.test.js.snap | 351 ++ .../component/__snapshots__/entries.test.js.snap | 94 + .../component/__snapshots__/expand.test.js.snap | 175 + .../__snapshots__/getter-setter.test.js.snap | 51 + .../__snapshots__/keyboard-navigation.test.js.snap | 55 + .../__snapshots__/properties.test.js.snap | 19 + .../component/__snapshots__/proxy.test.js.snap | 9 + .../component/__snapshots__/window.test.js.snap | 2114 ++++++++++ .../object-inspector/component/basic.test.js | 439 ++ .../object-inspector/component/classnames.test.js | 53 + .../component/create-long-string-front.test.js | 94 + .../component/create-object-client.test.js | 114 + .../object-inspector/component/entries.test.js | 137 + .../object-inspector/component/events.test.js | 171 + .../object-inspector/component/expand.test.js | 435 ++ .../object-inspector/component/function.test.js | 90 + .../component/getter-setter.test.js | 106 + .../component/keyboard-navigation.test.js | 89 + .../object-inspector/component/properties.test.js | 158 + .../object-inspector/component/proxy.test.js | 133 + .../component/should-item-update.test.js | 96 + .../object-inspector/component/window.test.js | 96 + .../node/components/object-inspector/test-utils.js | 231 ++ .../utils/__snapshots__/promises.test.js.snap | 49 + .../object-inspector/utils/create-node.test.js | 87 + .../object-inspector/utils/get-children.test.js | 278 ++ .../utils/get-closest-grip-node.test.js | 52 + .../object-inspector/utils/get-value.test.js | 91 + .../utils/make-node-for-properties.test.js | 295 ++ .../utils/make-numerical-buckets.test.js | 138 + .../utils/node-has-entries.test.js | 51 + .../object-inspector/utils/node-is-window.test.js | 20 + .../node-supports-numerical-bucketing.test.js | 72 + .../object-inspector/utils/promises.test.js | 54 + .../utils/should-load-item-entries.test.js | 171 + .../utils/should-load-item-full-text.test.js | 56 + .../should-load-item-indexed-properties.test.js | 259 ++ ...should-load-item-non-indexed-properties.test.js | 222 ++ .../utils/should-load-item-prototype.test.js | 218 + .../utils/should-load-item-symbols.test.js | 218 + .../utils/should-render-roots-in-reps.test.js | 153 + .../reps/__snapshots__/accessor.test.js.snap | 3 + .../reps/__snapshots__/element-node.test.js.snap | 42 + .../reps/__snapshots__/error.test.js.snap | 1210 ++++++ .../components/reps/__snapshots__/nan.test.js.snap | 10 + .../test/node/components/reps/accessible.test.js | 321 ++ .../test/node/components/reps/accessor.test.js | 137 + .../test/node/components/reps/array.test.js | 117 + .../test/node/components/reps/attribute.test.js | 44 + .../test/node/components/reps/big-int.test.js | 106 + .../test/node/components/reps/comment-node.test.js | 74 + .../test/node/components/reps/date-time.test.js | 61 + .../node/components/reps/document-type.test.js | 51 + .../test/node/components/reps/document.test.js | 52 + .../test/node/components/reps/element-node.test.js | 654 +++ .../test/node/components/reps/error.test.js | 748 ++++ .../test/node/components/reps/event.test.js | 160 + .../test/node/components/reps/failure.test.js | 66 + .../test/node/components/reps/function.test.js | 584 +++ .../test/node/components/reps/grip-array.test.js | 705 ++++ .../test/node/components/reps/grip-entry.test.js | 191 + .../test/node/components/reps/grip-map.test.js | 377 ++ .../test/node/components/reps/grip.test.js | 705 ++++ .../test/node/components/reps/helper-tests.test.js | 122 + .../test/node/components/reps/infinity.test.js | 70 + .../test/node/components/reps/long-string.test.js | 135 + .../test/node/components/reps/nan.test.js | 43 + .../test/node/components/reps/null.test.js | 47 + .../test/node/components/reps/number.test.js | 136 + .../node/components/reps/object-with-text.test.js | 66 + .../node/components/reps/object-with-url.test.js | 45 + .../test/node/components/reps/object.test.js | 356 ++ .../test/node/components/reps/promise.test.js | 216 + .../test/node/components/reps/regexp.test.js | 59 + .../node/components/reps/string-with-url.test.js | 630 +++ .../test/node/components/reps/string.test.js | 257 ++ .../test/node/components/reps/stylesheet.test.js | 41 + .../test/node/components/reps/symbol.test.js | 64 + .../test/node/components/reps/test-helpers.js | 116 + .../test/node/components/reps/text-node.test.js | 186 + .../test/node/components/reps/undefined.test.js | 58 + .../test/node/components/reps/window.test.js | 131 + .../components/test/node/components/tree.test.js | 911 +++++ .../shared/components/test/node/jest.config.js | 16 + .../shared/components/test/node/package.json | 27 + .../client/shared/components/test/node/setup.js | 15 + .../test/node/stubs/object-inspector/grip.js | 64 + .../test/node/stubs/object-inspector/map.js | 154 + .../node/stubs/object-inspector/performance.js | 784 ++++ .../components/test/node/stubs/reps/accessible.js | 74 + .../components/test/node/stubs/reps/accessor.js | 85 + .../components/test/node/stubs/reps/attribute.js | 36 + .../components/test/node/stubs/reps/big-int.js | 196 + .../test/node/stubs/reps/browser_dummy.js | 11 + .../test/node/stubs/reps/comment-node.js | 36 + .../components/test/node/stubs/reps/date-time.js | 47 + .../test/node/stubs/reps/document-type.js | 40 + .../components/test/node/stubs/reps/document.js | 39 + .../test/node/stubs/reps/element-node.js | 292 ++ .../components/test/node/stubs/reps/error.js | 396 ++ .../components/test/node/stubs/reps/event.js | 269 ++ .../components/test/node/stubs/reps/failure.js | 21 + .../components/test/node/stubs/reps/function.js | 227 ++ .../components/test/node/stubs/reps/grip-array.js | 1087 +++++ .../components/test/node/stubs/reps/grip-entry.js | 16 + .../components/test/node/stubs/reps/grip-map.js | 908 +++++ .../shared/components/test/node/stubs/reps/grip.js | 1057 +++++ .../components/test/node/stubs/reps/infinity.js | 19 + .../components/test/node/stubs/reps/long-string.js | 39 + .../shared/components/test/node/stubs/reps/nan.js | 15 + .../shared/components/test/node/stubs/reps/null.js | 15 + .../components/test/node/stubs/reps/number.js | 21 + .../test/node/stubs/reps/object-with-text.js | 36 + .../test/node/stubs/reps/object-with-url.js | 22 + .../components/test/node/stubs/reps/promise.js | 244 ++ .../components/test/node/stubs/reps/regexp.js | 36 + .../components/test/node/stubs/reps/stubs.ini | 19 + .../components/test/node/stubs/reps/stylesheet.js | 29 + .../components/test/node/stubs/reps/symbol.js | 33 + .../components/test/node/stubs/reps/text-node.js | 141 + .../components/test/node/stubs/reps/undefined.js | 15 + .../components/test/node/stubs/reps/window.js | 29 + .../client/shared/components/test/node/yarn.lock | 4209 ++++++++++++++++++++ .../components/throttling/NetworkThrottlingMenu.js | 100 + .../client/shared/components/throttling/actions.js | 22 + .../client/shared/components/throttling/moz.build | 13 + .../shared/components/throttling/profiles.js | 104 + .../client/shared/components/throttling/reducer.js | 29 + .../client/shared/components/throttling/types.js | 17 + .../client/shared/components/tree/LabelCell.js | 76 + .../shared/components/tree/ObjectProvider.js | 86 + devtools/client/shared/components/tree/TreeCell.js | 139 + .../client/shared/components/tree/TreeHeader.js | 120 + devtools/client/shared/components/tree/TreeRow.js | 304 ++ .../client/shared/components/tree/TreeView.css | 199 + devtools/client/shared/components/tree/TreeView.js | 799 ++++ devtools/client/shared/components/tree/moz.build | 13 + 296 files changed, 56231 insertions(+) create mode 100644 devtools/client/shared/components/.eslintrc.js create mode 100644 devtools/client/shared/components/Accordion.css create mode 100644 devtools/client/shared/components/Accordion.js create mode 100644 devtools/client/shared/components/AppErrorBoundary.css create mode 100644 devtools/client/shared/components/AppErrorBoundary.js create mode 100644 devtools/client/shared/components/Frame.js create mode 100644 devtools/client/shared/components/HSplitBox.js create mode 100644 devtools/client/shared/components/List.css create mode 100644 devtools/client/shared/components/List.js create mode 100644 devtools/client/shared/components/MdnLink.css create mode 100644 devtools/client/shared/components/MdnLink.js create mode 100644 devtools/client/shared/components/NotificationBox.css create mode 100644 devtools/client/shared/components/NotificationBox.js create mode 100644 devtools/client/shared/components/SearchBox.js create mode 100644 devtools/client/shared/components/SearchBoxAutocompletePopup.js create mode 100644 devtools/client/shared/components/SearchModifiers.css create mode 100644 devtools/client/shared/components/SearchModifiers.js create mode 100644 devtools/client/shared/components/Sidebar.js create mode 100644 devtools/client/shared/components/SidebarToggle.css create mode 100644 devtools/client/shared/components/SidebarToggle.js create mode 100644 devtools/client/shared/components/SmartTrace.css create mode 100644 devtools/client/shared/components/SmartTrace.js create mode 100644 devtools/client/shared/components/StackTrace.js create mode 100644 devtools/client/shared/components/Tree.css create mode 100644 devtools/client/shared/components/Tree.js create mode 100644 devtools/client/shared/components/VirtualizedTree.js create mode 100644 devtools/client/shared/components/VisibilityHandler.js create mode 100644 devtools/client/shared/components/menu/MenuButton.js create mode 100644 devtools/client/shared/components/menu/MenuItem.js create mode 100644 devtools/client/shared/components/menu/MenuList.js create mode 100644 devtools/client/shared/components/menu/moz.build create mode 100644 devtools/client/shared/components/menu/utils.js create mode 100644 devtools/client/shared/components/moz.build create mode 100644 devtools/client/shared/components/object-inspector/actions.js create mode 100644 devtools/client/shared/components/object-inspector/components/ObjectInspector.css create mode 100644 devtools/client/shared/components/object-inspector/components/ObjectInspector.js create mode 100644 devtools/client/shared/components/object-inspector/components/ObjectInspectorItem.js create mode 100644 devtools/client/shared/components/object-inspector/components/moz.build create mode 100644 devtools/client/shared/components/object-inspector/index.js create mode 100644 devtools/client/shared/components/object-inspector/moz.build create mode 100644 devtools/client/shared/components/object-inspector/reducer.js create mode 100644 devtools/client/shared/components/object-inspector/utils/client.js create mode 100644 devtools/client/shared/components/object-inspector/utils/index.js create mode 100644 devtools/client/shared/components/object-inspector/utils/load-properties.js create mode 100644 devtools/client/shared/components/object-inspector/utils/moz.build create mode 100644 devtools/client/shared/components/object-inspector/utils/node.js create mode 100644 devtools/client/shared/components/object-inspector/utils/selection.js create mode 100644 devtools/client/shared/components/reps/images/input.svg create mode 100644 devtools/client/shared/components/reps/images/jump-definition.svg create mode 100644 devtools/client/shared/components/reps/images/open-a11y.svg create mode 100644 devtools/client/shared/components/reps/images/open-inspector.svg create mode 100644 devtools/client/shared/components/reps/index.js create mode 100644 devtools/client/shared/components/reps/moz.build create mode 100644 devtools/client/shared/components/reps/reps.css create mode 100644 devtools/client/shared/components/reps/reps/accessible.js create mode 100644 devtools/client/shared/components/reps/reps/accessor.js create mode 100644 devtools/client/shared/components/reps/reps/array.js create mode 100644 devtools/client/shared/components/reps/reps/attribute.js create mode 100644 devtools/client/shared/components/reps/reps/big-int.js create mode 100644 devtools/client/shared/components/reps/reps/comment-node.js create mode 100644 devtools/client/shared/components/reps/reps/constants.js create mode 100644 devtools/client/shared/components/reps/reps/custom-formatter.js create mode 100644 devtools/client/shared/components/reps/reps/date-time.js create mode 100644 devtools/client/shared/components/reps/reps/document-type.js create mode 100644 devtools/client/shared/components/reps/reps/document.js create mode 100644 devtools/client/shared/components/reps/reps/element-node.js create mode 100644 devtools/client/shared/components/reps/reps/error.js create mode 100644 devtools/client/shared/components/reps/reps/event.js create mode 100644 devtools/client/shared/components/reps/reps/function.js create mode 100644 devtools/client/shared/components/reps/reps/grip-array.js create mode 100644 devtools/client/shared/components/reps/reps/grip-entry.js create mode 100644 devtools/client/shared/components/reps/reps/grip-map.js create mode 100644 devtools/client/shared/components/reps/reps/grip.js create mode 100644 devtools/client/shared/components/reps/reps/infinity.js create mode 100644 devtools/client/shared/components/reps/reps/moz.build create mode 100644 devtools/client/shared/components/reps/reps/nan.js create mode 100644 devtools/client/shared/components/reps/reps/null.js create mode 100644 devtools/client/shared/components/reps/reps/number.js create mode 100644 devtools/client/shared/components/reps/reps/object-with-text.js create mode 100644 devtools/client/shared/components/reps/reps/object-with-url.js create mode 100644 devtools/client/shared/components/reps/reps/object.js create mode 100644 devtools/client/shared/components/reps/reps/promise.js create mode 100644 devtools/client/shared/components/reps/reps/prop-rep.js create mode 100644 devtools/client/shared/components/reps/reps/regexp.js create mode 100644 devtools/client/shared/components/reps/reps/rep-utils.js create mode 100644 devtools/client/shared/components/reps/reps/rep.js create mode 100644 devtools/client/shared/components/reps/reps/string.js create mode 100644 devtools/client/shared/components/reps/reps/stylesheet.js create mode 100644 devtools/client/shared/components/reps/reps/symbol.js create mode 100644 devtools/client/shared/components/reps/reps/text-node.js create mode 100644 devtools/client/shared/components/reps/reps/undefined.js create mode 100644 devtools/client/shared/components/reps/reps/window.js create mode 100644 devtools/client/shared/components/reps/shared/dom-node-constants.js create mode 100644 devtools/client/shared/components/reps/shared/grip-length-bubble.js create mode 100644 devtools/client/shared/components/reps/shared/moz.build create mode 100644 devtools/client/shared/components/splitter/Draggable.js create mode 100644 devtools/client/shared/components/splitter/GridElementResizer.css create mode 100644 devtools/client/shared/components/splitter/GridElementWidthResizer.js create mode 100644 devtools/client/shared/components/splitter/SplitBox.css create mode 100644 devtools/client/shared/components/splitter/SplitBox.js create mode 100644 devtools/client/shared/components/splitter/moz.build create mode 100644 devtools/client/shared/components/tabs/TabBar.js create mode 100644 devtools/client/shared/components/tabs/Tabs.css create mode 100644 devtools/client/shared/components/tabs/Tabs.js create mode 100644 devtools/client/shared/components/tabs/moz.build create mode 100644 devtools/client/shared/components/test/browser/browser.ini create mode 100644 devtools/client/shared/components/test/browser/browser_notification_box_basic.js create mode 100644 devtools/client/shared/components/test/browser/browser_reps_stubs.js create mode 100644 devtools/client/shared/components/test/chrome/accordion.snapshots.js create mode 100644 devtools/client/shared/components/test/chrome/chrome.ini create mode 100644 devtools/client/shared/components/test/chrome/head.js create mode 100644 devtools/client/shared/components/test/chrome/test_GridElementWidthResizer.html create mode 100644 devtools/client/shared/components/test/chrome/test_GridElementWidthResizer_RTL.html create mode 100644 devtools/client/shared/components/test/chrome/test_HSplitBox_01.html create mode 100644 devtools/client/shared/components/test/chrome/test_accordion.html create mode 100644 devtools/client/shared/components/test/chrome/test_frame_01.html create mode 100644 devtools/client/shared/components/test/chrome/test_frame_02.html create mode 100644 devtools/client/shared/components/test/chrome/test_list.html create mode 100644 devtools/client/shared/components/test/chrome/test_list_keyboard.html create mode 100644 devtools/client/shared/components/test/chrome/test_notification_box_01.html create mode 100644 devtools/client/shared/components/test/chrome/test_notification_box_02.html create mode 100644 devtools/client/shared/components/test/chrome/test_notification_box_03.html create mode 100644 devtools/client/shared/components/test/chrome/test_notification_box_04.html create mode 100644 devtools/client/shared/components/test/chrome/test_notification_box_05.html create mode 100644 devtools/client/shared/components/test/chrome/test_searchbox-with-autocomplete.html create mode 100644 devtools/client/shared/components/test/chrome/test_searchbox.html create mode 100644 devtools/client/shared/components/test/chrome/test_sidebar_toggle.html create mode 100644 devtools/client/shared/components/test/chrome/test_smart-trace-grouping.html create mode 100644 devtools/client/shared/components/test/chrome/test_smart-trace-source-maps.html create mode 100644 devtools/client/shared/components/test/chrome/test_smart-trace.html create mode 100644 devtools/client/shared/components/test/chrome/test_stack-trace-source-maps.html create mode 100644 devtools/client/shared/components/test/chrome/test_stack-trace.html create mode 100644 devtools/client/shared/components/test/chrome/test_tabs_accessibility.html create mode 100644 devtools/client/shared/components/test/chrome/test_tabs_menu.html create mode 100644 devtools/client/shared/components/test/chrome/test_tree-view_01.html create mode 100644 devtools/client/shared/components/test/chrome/test_tree-view_02.html create mode 100644 devtools/client/shared/components/test/chrome/test_tree_01.html create mode 100644 devtools/client/shared/components/test/chrome/test_tree_02.html create mode 100644 devtools/client/shared/components/test/chrome/test_tree_03.html create mode 100644 devtools/client/shared/components/test/chrome/test_tree_04.html create mode 100644 devtools/client/shared/components/test/chrome/test_tree_05.html create mode 100644 devtools/client/shared/components/test/chrome/test_tree_06.html create mode 100644 devtools/client/shared/components/test/chrome/test_tree_07.html create mode 100644 devtools/client/shared/components/test/chrome/test_tree_08.html create mode 100644 devtools/client/shared/components/test/chrome/test_tree_09.html create mode 100644 devtools/client/shared/components/test/chrome/test_tree_10.html create mode 100644 devtools/client/shared/components/test/chrome/test_tree_11.html create mode 100644 devtools/client/shared/components/test/chrome/test_tree_12.html create mode 100644 devtools/client/shared/components/test/chrome/test_tree_13.html create mode 100644 devtools/client/shared/components/test/chrome/test_tree_14.html create mode 100644 devtools/client/shared/components/test/chrome/test_tree_15.html create mode 100644 devtools/client/shared/components/test/chrome/test_tree_16.html create mode 100644 devtools/client/shared/components/test/node/.eslintrc.js create mode 100644 devtools/client/shared/components/test/node/__mocks__/Services.js create mode 100644 devtools/client/shared/components/test/node/__mocks__/object-front.js create mode 100644 devtools/client/shared/components/test/node/__mocks__/string-front.js create mode 100644 devtools/client/shared/components/test/node/babel.config.js create mode 100644 devtools/client/shared/components/test/node/components/__snapshots__/tree.test.js.snap create mode 100644 devtools/client/shared/components/test/node/components/object-inspector/component/__snapshots__/basic.test.js.snap create mode 100644 devtools/client/shared/components/test/node/components/object-inspector/component/__snapshots__/classnames.test.js.snap create mode 100644 devtools/client/shared/components/test/node/components/object-inspector/component/__snapshots__/entries.test.js.snap create mode 100644 devtools/client/shared/components/test/node/components/object-inspector/component/__snapshots__/expand.test.js.snap create mode 100644 devtools/client/shared/components/test/node/components/object-inspector/component/__snapshots__/getter-setter.test.js.snap create mode 100644 devtools/client/shared/components/test/node/components/object-inspector/component/__snapshots__/keyboard-navigation.test.js.snap create mode 100644 devtools/client/shared/components/test/node/components/object-inspector/component/__snapshots__/properties.test.js.snap create mode 100644 devtools/client/shared/components/test/node/components/object-inspector/component/__snapshots__/proxy.test.js.snap create mode 100644 devtools/client/shared/components/test/node/components/object-inspector/component/__snapshots__/window.test.js.snap create mode 100644 devtools/client/shared/components/test/node/components/object-inspector/component/basic.test.js create mode 100644 devtools/client/shared/components/test/node/components/object-inspector/component/classnames.test.js create mode 100644 devtools/client/shared/components/test/node/components/object-inspector/component/create-long-string-front.test.js create mode 100644 devtools/client/shared/components/test/node/components/object-inspector/component/create-object-client.test.js create mode 100644 devtools/client/shared/components/test/node/components/object-inspector/component/entries.test.js create mode 100644 devtools/client/shared/components/test/node/components/object-inspector/component/events.test.js create mode 100644 devtools/client/shared/components/test/node/components/object-inspector/component/expand.test.js create mode 100644 devtools/client/shared/components/test/node/components/object-inspector/component/function.test.js create mode 100644 devtools/client/shared/components/test/node/components/object-inspector/component/getter-setter.test.js create mode 100644 devtools/client/shared/components/test/node/components/object-inspector/component/keyboard-navigation.test.js create mode 100644 devtools/client/shared/components/test/node/components/object-inspector/component/properties.test.js create mode 100644 devtools/client/shared/components/test/node/components/object-inspector/component/proxy.test.js create mode 100644 devtools/client/shared/components/test/node/components/object-inspector/component/should-item-update.test.js create mode 100644 devtools/client/shared/components/test/node/components/object-inspector/component/window.test.js create mode 100644 devtools/client/shared/components/test/node/components/object-inspector/test-utils.js create mode 100644 devtools/client/shared/components/test/node/components/object-inspector/utils/__snapshots__/promises.test.js.snap create mode 100644 devtools/client/shared/components/test/node/components/object-inspector/utils/create-node.test.js create mode 100644 devtools/client/shared/components/test/node/components/object-inspector/utils/get-children.test.js create mode 100644 devtools/client/shared/components/test/node/components/object-inspector/utils/get-closest-grip-node.test.js create mode 100644 devtools/client/shared/components/test/node/components/object-inspector/utils/get-value.test.js create mode 100644 devtools/client/shared/components/test/node/components/object-inspector/utils/make-node-for-properties.test.js create mode 100644 devtools/client/shared/components/test/node/components/object-inspector/utils/make-numerical-buckets.test.js create mode 100644 devtools/client/shared/components/test/node/components/object-inspector/utils/node-has-entries.test.js create mode 100644 devtools/client/shared/components/test/node/components/object-inspector/utils/node-is-window.test.js create mode 100644 devtools/client/shared/components/test/node/components/object-inspector/utils/node-supports-numerical-bucketing.test.js create mode 100644 devtools/client/shared/components/test/node/components/object-inspector/utils/promises.test.js create mode 100644 devtools/client/shared/components/test/node/components/object-inspector/utils/should-load-item-entries.test.js create mode 100644 devtools/client/shared/components/test/node/components/object-inspector/utils/should-load-item-full-text.test.js create mode 100644 devtools/client/shared/components/test/node/components/object-inspector/utils/should-load-item-indexed-properties.test.js create mode 100644 devtools/client/shared/components/test/node/components/object-inspector/utils/should-load-item-non-indexed-properties.test.js create mode 100644 devtools/client/shared/components/test/node/components/object-inspector/utils/should-load-item-prototype.test.js create mode 100644 devtools/client/shared/components/test/node/components/object-inspector/utils/should-load-item-symbols.test.js create mode 100644 devtools/client/shared/components/test/node/components/object-inspector/utils/should-render-roots-in-reps.test.js create mode 100644 devtools/client/shared/components/test/node/components/reps/__snapshots__/accessor.test.js.snap create mode 100644 devtools/client/shared/components/test/node/components/reps/__snapshots__/element-node.test.js.snap create mode 100644 devtools/client/shared/components/test/node/components/reps/__snapshots__/error.test.js.snap create mode 100644 devtools/client/shared/components/test/node/components/reps/__snapshots__/nan.test.js.snap create mode 100644 devtools/client/shared/components/test/node/components/reps/accessible.test.js create mode 100644 devtools/client/shared/components/test/node/components/reps/accessor.test.js create mode 100644 devtools/client/shared/components/test/node/components/reps/array.test.js create mode 100644 devtools/client/shared/components/test/node/components/reps/attribute.test.js create mode 100644 devtools/client/shared/components/test/node/components/reps/big-int.test.js create mode 100644 devtools/client/shared/components/test/node/components/reps/comment-node.test.js create mode 100644 devtools/client/shared/components/test/node/components/reps/date-time.test.js create mode 100644 devtools/client/shared/components/test/node/components/reps/document-type.test.js create mode 100644 devtools/client/shared/components/test/node/components/reps/document.test.js create mode 100644 devtools/client/shared/components/test/node/components/reps/element-node.test.js create mode 100644 devtools/client/shared/components/test/node/components/reps/error.test.js create mode 100644 devtools/client/shared/components/test/node/components/reps/event.test.js create mode 100644 devtools/client/shared/components/test/node/components/reps/failure.test.js create mode 100644 devtools/client/shared/components/test/node/components/reps/function.test.js create mode 100644 devtools/client/shared/components/test/node/components/reps/grip-array.test.js create mode 100644 devtools/client/shared/components/test/node/components/reps/grip-entry.test.js create mode 100644 devtools/client/shared/components/test/node/components/reps/grip-map.test.js create mode 100644 devtools/client/shared/components/test/node/components/reps/grip.test.js create mode 100644 devtools/client/shared/components/test/node/components/reps/helper-tests.test.js create mode 100644 devtools/client/shared/components/test/node/components/reps/infinity.test.js create mode 100644 devtools/client/shared/components/test/node/components/reps/long-string.test.js create mode 100644 devtools/client/shared/components/test/node/components/reps/nan.test.js create mode 100644 devtools/client/shared/components/test/node/components/reps/null.test.js create mode 100644 devtools/client/shared/components/test/node/components/reps/number.test.js create mode 100644 devtools/client/shared/components/test/node/components/reps/object-with-text.test.js create mode 100644 devtools/client/shared/components/test/node/components/reps/object-with-url.test.js create mode 100644 devtools/client/shared/components/test/node/components/reps/object.test.js create mode 100644 devtools/client/shared/components/test/node/components/reps/promise.test.js create mode 100644 devtools/client/shared/components/test/node/components/reps/regexp.test.js create mode 100644 devtools/client/shared/components/test/node/components/reps/string-with-url.test.js create mode 100644 devtools/client/shared/components/test/node/components/reps/string.test.js create mode 100644 devtools/client/shared/components/test/node/components/reps/stylesheet.test.js create mode 100644 devtools/client/shared/components/test/node/components/reps/symbol.test.js create mode 100644 devtools/client/shared/components/test/node/components/reps/test-helpers.js create mode 100644 devtools/client/shared/components/test/node/components/reps/text-node.test.js create mode 100644 devtools/client/shared/components/test/node/components/reps/undefined.test.js create mode 100644 devtools/client/shared/components/test/node/components/reps/window.test.js create mode 100644 devtools/client/shared/components/test/node/components/tree.test.js create mode 100644 devtools/client/shared/components/test/node/jest.config.js create mode 100644 devtools/client/shared/components/test/node/package.json create mode 100644 devtools/client/shared/components/test/node/setup.js create mode 100644 devtools/client/shared/components/test/node/stubs/object-inspector/grip.js create mode 100644 devtools/client/shared/components/test/node/stubs/object-inspector/map.js create mode 100644 devtools/client/shared/components/test/node/stubs/object-inspector/performance.js create mode 100644 devtools/client/shared/components/test/node/stubs/reps/accessible.js create mode 100644 devtools/client/shared/components/test/node/stubs/reps/accessor.js create mode 100644 devtools/client/shared/components/test/node/stubs/reps/attribute.js create mode 100644 devtools/client/shared/components/test/node/stubs/reps/big-int.js create mode 100644 devtools/client/shared/components/test/node/stubs/reps/browser_dummy.js create mode 100644 devtools/client/shared/components/test/node/stubs/reps/comment-node.js create mode 100644 devtools/client/shared/components/test/node/stubs/reps/date-time.js create mode 100644 devtools/client/shared/components/test/node/stubs/reps/document-type.js create mode 100644 devtools/client/shared/components/test/node/stubs/reps/document.js create mode 100644 devtools/client/shared/components/test/node/stubs/reps/element-node.js create mode 100644 devtools/client/shared/components/test/node/stubs/reps/error.js create mode 100644 devtools/client/shared/components/test/node/stubs/reps/event.js create mode 100644 devtools/client/shared/components/test/node/stubs/reps/failure.js create mode 100644 devtools/client/shared/components/test/node/stubs/reps/function.js create mode 100644 devtools/client/shared/components/test/node/stubs/reps/grip-array.js create mode 100644 devtools/client/shared/components/test/node/stubs/reps/grip-entry.js create mode 100644 devtools/client/shared/components/test/node/stubs/reps/grip-map.js create mode 100644 devtools/client/shared/components/test/node/stubs/reps/grip.js create mode 100644 devtools/client/shared/components/test/node/stubs/reps/infinity.js create mode 100644 devtools/client/shared/components/test/node/stubs/reps/long-string.js create mode 100644 devtools/client/shared/components/test/node/stubs/reps/nan.js create mode 100644 devtools/client/shared/components/test/node/stubs/reps/null.js create mode 100644 devtools/client/shared/components/test/node/stubs/reps/number.js create mode 100644 devtools/client/shared/components/test/node/stubs/reps/object-with-text.js create mode 100644 devtools/client/shared/components/test/node/stubs/reps/object-with-url.js create mode 100644 devtools/client/shared/components/test/node/stubs/reps/promise.js create mode 100644 devtools/client/shared/components/test/node/stubs/reps/regexp.js create mode 100644 devtools/client/shared/components/test/node/stubs/reps/stubs.ini create mode 100644 devtools/client/shared/components/test/node/stubs/reps/stylesheet.js create mode 100644 devtools/client/shared/components/test/node/stubs/reps/symbol.js create mode 100644 devtools/client/shared/components/test/node/stubs/reps/text-node.js create mode 100644 devtools/client/shared/components/test/node/stubs/reps/undefined.js create mode 100644 devtools/client/shared/components/test/node/stubs/reps/window.js create mode 100644 devtools/client/shared/components/test/node/yarn.lock create mode 100644 devtools/client/shared/components/throttling/NetworkThrottlingMenu.js create mode 100644 devtools/client/shared/components/throttling/actions.js create mode 100644 devtools/client/shared/components/throttling/moz.build create mode 100644 devtools/client/shared/components/throttling/profiles.js create mode 100644 devtools/client/shared/components/throttling/reducer.js create mode 100644 devtools/client/shared/components/throttling/types.js create mode 100644 devtools/client/shared/components/tree/LabelCell.js create mode 100644 devtools/client/shared/components/tree/ObjectProvider.js create mode 100644 devtools/client/shared/components/tree/TreeCell.js create mode 100644 devtools/client/shared/components/tree/TreeHeader.js create mode 100644 devtools/client/shared/components/tree/TreeRow.js create mode 100644 devtools/client/shared/components/tree/TreeView.css create mode 100644 devtools/client/shared/components/tree/TreeView.js create mode 100644 devtools/client/shared/components/tree/moz.build (limited to 'devtools/client/shared/components') diff --git a/devtools/client/shared/components/.eslintrc.js b/devtools/client/shared/components/.eslintrc.js new file mode 100644 index 0000000000..b67123ad2c --- /dev/null +++ b/devtools/client/shared/components/.eslintrc.js @@ -0,0 +1,11 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +module.exports = { + globals: { + define: true, + }, +}; diff --git a/devtools/client/shared/components/Accordion.css b/devtools/client/shared/components/Accordion.css new file mode 100644 index 0000000000..1fbc5c3e3e --- /dev/null +++ b/devtools/client/shared/components/Accordion.css @@ -0,0 +1,87 @@ +/* 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/. */ + +/* Accordion */ + +.accordion { + width: 100%; + padding: 0; + margin: 0; + list-style-type: none; + /* Accordion root has tabindex="-1" to get focus programatically. + * This can give it a focus outline when clicked, which we don't want. + * The container itself is not in the focus order at all. */ + outline: none; + background-color: var(--theme-sidebar-background); +} + +.accordion-header { + box-sizing: border-box; + display: flex; + align-items: center; + /* Reserve 1px for the border */ + min-height: calc(var(--theme-toolbar-height) + 1px); + margin: 0; + border-bottom: 1px solid var(--theme-splitter-color); + padding: 2px 4px; + font-size: inherit; + font-weight: normal; + user-select: none; + cursor: default; + background-color: var(--theme-accordion-header-background); +} + +.accordion-header:hover { + background-color: var(--theme-accordion-header-hover); +} + +/* + Arrow should be a bit closer to the text than to the start edge: + - total distance between text and start edge = 20px + - arrow width = 10px + - distance between arrow and start edge = 6px + - distance between arrow and text = 4px +*/ +.accordion-header .theme-twisty { + display: inline-block; + flex: none; + width: 10px; + height: 10px; + margin-inline-start: 2px; + margin-inline-end: 4px; + pointer-events: none; +} + +.accordion-header-label { + display: block; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + font-size: 12px; + line-height: 16px; + color: var(--theme-toolbar-color); +} + +.accordion-header-buttons { + flex: none; + display: flex; + align-items: center; + justify-content: flex-end; + max-width: 50%; + margin-inline-start: auto; + padding-inline-start: 4px; +} + +.accordion-content { + overflow: auto; + border-bottom: 1px solid var(--theme-splitter-color); +} + +.accordion-content[hidden] { + display: none; +} + +.accordion-item:last-child > .accordion-content { + border-bottom: none; +} diff --git a/devtools/client/shared/components/Accordion.js b/devtools/client/shared/components/Accordion.js new file mode 100644 index 0000000000..c3f1afa418 --- /dev/null +++ b/devtools/client/shared/components/Accordion.js @@ -0,0 +1,257 @@ +/* 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 . */ + +"use strict"; + +const { + Component, + createElement, +} = require("resource://devtools/client/shared/vendor/react.js"); +const PropTypes = require("resource://devtools/client/shared/vendor/react-prop-types.js"); +const { + ul, + li, + h2, + div, + span, +} = require("resource://devtools/client/shared/vendor/react-dom-factories.js"); + +class Accordion extends Component { + static get propTypes() { + return { + className: PropTypes.string, + // A list of all items to be rendered using an Accordion component. + items: PropTypes.arrayOf( + PropTypes.shape({ + buttons: PropTypes.arrayOf(PropTypes.object), + className: PropTypes.string, + component: PropTypes.oneOfType([PropTypes.object, PropTypes.func]), + componentProps: PropTypes.object, + contentClassName: PropTypes.string, + header: PropTypes.string.isRequired, + id: PropTypes.string.isRequired, + onToggle: PropTypes.func, + // Determines the initial open state of the accordion item + opened: PropTypes.bool.isRequired, + // Enables dynamically changing the open state of the accordion + // on update. + shouldOpen: PropTypes.func, + }) + ).isRequired, + }; + } + + constructor(props) { + super(props); + + this.state = { + opened: {}, + }; + + this.onHeaderClick = this.onHeaderClick.bind(this); + this.onHeaderKeyDown = this.onHeaderKeyDown.bind(this); + this.setInitialState = this.setInitialState.bind(this); + this.updateCurrentState = this.updateCurrentState.bind(this); + } + + componentDidMount() { + this.setInitialState(); + } + + componentDidUpdate(prevProps) { + if (prevProps.items !== this.props.items) { + this.updateCurrentState(); + } + } + + setInitialState() { + /** + * Add initial data to the `state.opened` map. + * This happens only on initial mount of the accordion. + */ + const newItems = this.props.items.filter( + ({ id }) => typeof this.state.opened[id] !== "boolean" + ); + + if (newItems.length) { + const everOpened = { ...this.state.everOpened }; + const opened = { ...this.state.opened }; + for (const item of newItems) { + everOpened[item.id] = item.opened; + opened[item.id] = item.opened; + } + + this.setState({ everOpened, opened }); + } + } + + updateCurrentState() { + /** + * Updates the `state.opened` map based on the + * new items that have been added and those that + * `item.shouldOpen()` has changed. This happens + * on each update. + */ + const updatedItems = this.props.items.filter(item => { + const notExist = typeof this.state.opened[item.id] !== "boolean"; + if (typeof item.shouldOpen == "function") { + const currentState = this.state.opened[item.id]; + return notExist || currentState !== item.shouldOpen(item, currentState); + } + return notExist; + }); + + if (updatedItems.length) { + const everOpened = { ...this.state.everOpened }; + const opened = { ...this.state.opened }; + for (const item of updatedItems) { + let itemOpen = item.opened; + if (typeof item.shouldOpen == "function") { + itemOpen = item.shouldOpen(item, itemOpen); + } + everOpened[item.id] = itemOpen; + opened[item.id] = itemOpen; + } + this.setState({ everOpened, opened }); + } + } + + /** + * @param {Event} event Click event. + * @param {Object} item The item to be collapsed/expanded. + */ + onHeaderClick(event, item) { + event.preventDefault(); + // In the Browser Toolbox's Inspector/Layout view, handleHeaderClick is + // called twice unless we call stopPropagation, making the accordion item + // open-and-close or close-and-open + event.stopPropagation(); + this.toggleItem(item); + } + + /** + * @param {Event} event Keyboard event. + * @param {Object} item The item to be collapsed/expanded. + */ + onHeaderKeyDown(event, item) { + if (event.key === " " || event.key === "Enter") { + event.preventDefault(); + this.toggleItem(item); + } + } + + /** + * Expand or collapse an accordion list item. + * @param {Object} item The item to be collapsed or expanded. + */ + toggleItem(item) { + const opened = !this.state.opened[item.id]; + + this.setState({ + everOpened: { + ...this.state.everOpened, + [item.id]: true, + }, + opened: { + ...this.state.opened, + [item.id]: opened, + }, + }); + + if (typeof item.onToggle === "function") { + item.onToggle(opened, item); + } + } + + renderItem(item) { + const { + buttons, + className = "", + component, + componentProps = {}, + contentClassName = "", + header, + id, + } = item; + + const headerId = `${id}-header`; + const opened = this.state.opened[id]; + let itemContent; + + // Only render content if the accordion item is open or has been opened once before. + // This saves us rendering complex components when users are keeping + // them closed (e.g. in Inspector/Layout) or may not open them at all. + if (this.state.everOpened && this.state.everOpened[id]) { + if (typeof component === "function") { + itemContent = createElement(component, componentProps); + } else if (typeof component === "object") { + itemContent = component; + } + } + + return li( + { + key: id, + id, + className: `accordion-item ${ + opened ? "accordion-open" : "" + } ${className} `.trim(), + "aria-labelledby": headerId, + }, + h2( + { + id: headerId, + className: "accordion-header", + tabIndex: 0, + "aria-expanded": opened, + // If the header contains buttons, make sure the heading name only + // contains the "header" text and not the button text + "aria-label": header, + onKeyDown: event => this.onHeaderKeyDown(event, item), + onClick: event => this.onHeaderClick(event, item), + }, + span({ + className: `theme-twisty${opened ? " open" : ""}`, + role: "presentation", + }), + span( + { + className: "accordion-header-label", + }, + header + ), + buttons && + span( + { + className: "accordion-header-buttons", + role: "presentation", + }, + buttons + ) + ), + div( + { + className: `accordion-content ${contentClassName}`.trim(), + hidden: !opened, + role: "presentation", + }, + itemContent + ) + ); + } + + render() { + return ul( + { + className: + "accordion" + + (this.props.className ? ` ${this.props.className}` : ""), + tabIndex: -1, + }, + this.props.items.map(item => this.renderItem(item)) + ); + } +} + +module.exports = Accordion; diff --git a/devtools/client/shared/components/AppErrorBoundary.css b/devtools/client/shared/components/AppErrorBoundary.css new file mode 100644 index 0000000000..4ff5964f6a --- /dev/null +++ b/devtools/client/shared/components/AppErrorBoundary.css @@ -0,0 +1,85 @@ +/* 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/. */ + +/* Base styles (common to most error boundaries) */ + + +/* Container */ +.app-error-panel { + color: var(--theme-text-color-strong); + display: flex; + flex-direction: column; + font-family: inherit; + margin: 0 0 2rem; + overflow-y: scroll; + padding: 1rem 3rem; + width: 100%; + height: 100%; +} + +/* "Has crashed" header */ +.app-error-panel .error-panel-header { + align-self: center; + font-size: 22px; + font-weight: 300; +} + +/* "File a Bug" button */ +.app-error-panel .error-panel-file-button { + align-self: center; + background-color: var(--blue-60); + outline: none; + color: white; + font-size: 15px; + font-weight: 400; + margin-bottom: 14.74px; + padding: 0.75rem; + text-align: center; + inline-size: 200px; + text-decoration: none; +} + +.app-error-panel .error-panel-file-button:hover { + background-color: var(--blue-70); +} + +.app-error-panel .error-panel-file-button:hover:active { + background-color: var(--blue-80); +} + +/* Text of the error itself, not the stack trace */ +.app-error-panel .error-panel-error { + background-color: var(--theme-body-emphasized-background); + border: 1px solid var(--theme-toolbar-separator); + border-block-end: 0; + font-size: 17px; + font-weight: 500; + margin: 0; + padding: 2rem; +} + +/* Stack trace; composed of

elements */ +.app-error-panel .stack-trace-section { + background-color: var(--theme-body-emphasized-background); + border: 1px solid var(--theme-toolbar-separator); + padding: 2rem; + margin-bottom: 1rem; +} + +.app-error-panel .stack-trace-section p { + color: var(--theme-stack-trace-text); + margin: 0; + margin-inline-start: 1rem; +} + +.app-error-panel .stack-trace-section p:first-child { + margin: 0; +} + +/* Instructions to reopen the toolbox */ +.app-error-panel .error-panel-reload-info { + font-size: 15px; + font-weight: 400; + margin: 2rem 0 1rem; +} diff --git a/devtools/client/shared/components/AppErrorBoundary.js b/devtools/client/shared/components/AppErrorBoundary.js new file mode 100644 index 0000000000..66f839f8a9 --- /dev/null +++ b/devtools/client/shared/components/AppErrorBoundary.js @@ -0,0 +1,161 @@ +/* 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"; + +// React deps +const { + Component, +} = require("resource://devtools/client/shared/vendor/react.js"); +const PropTypes = require("resource://devtools/client/shared/vendor/react-prop-types.js"); +const dom = require("resource://devtools/client/shared/vendor/react-dom-factories.js"); +const { div, h1, h2, h3, p, a } = dom; + +// Localized strings for (devtools/client/locales/en-US/components.properties) +loader.lazyGetter(this, "L10N", function () { + const { LocalizationHelper } = require("resource://devtools/shared/l10n.js"); + return new LocalizationHelper( + "devtools/client/locales/components.properties" + ); +}); + +loader.lazyGetter(this, "FILE_BUG_BUTTON", function () { + return L10N.getStr("appErrorBoundary.fileBugButton"); +}); + +loader.lazyGetter(this, "RELOAD_PAGE_INFO", function () { + return L10N.getStr("appErrorBoundary.reloadPanelInfo"); +}); + +// File a bug for the selected component specifically +const bugLink = + "https://bugzilla.mozilla.org/enter_bug.cgi?product=DevTools&component="; + +/** + * Error boundary that wraps around the a given component. + */ +class AppErrorBoundary extends Component { + static get propTypes() { + return { + children: PropTypes.any.isRequired, + panel: PropTypes.any.isRequired, + componentName: PropTypes.string.isRequired, + }; + } + + constructor(props) { + super(props); + + this.state = { + errorMsg: "No error", + errorStack: null, + errorInfo: null, + }; + } + + /** + * Map the `info` object to a render. + * Currently, `info` usually just contains something similar to the + * following object (which is provided to componentDidCatch): + * componentStack: {"\n in (component) \n in (other component)..."} + */ + renderErrorInfo(info = {}) { + if (Object.keys(info).length) { + return Object.keys(info).map((obj, outerIdx) => { + const traceParts = info[obj] + .split("\n") + .map((part, idx) => p({ key: `strace${idx}` }, part)); + return div( + { key: `st-div-${outerIdx}`, className: "stack-trace-section" }, + h3({}, "React Component Stack"), + p({ key: `st-p-${outerIdx}` }, obj.toString()), + traceParts + ); + }); + } + + return p({}, "undefined errorInfo"); + } + + renderStackTrace(stacktrace = "") { + const re = /:\d+:\d+/g; + const traces = stacktrace + .replace(re, "$&,") + .split(",") + .map((trace, index) => { + return p({ key: `rst-${index}` }, trace); + }); + + return div( + { className: "stack-trace-section" }, + h3({}, "Stacktrace"), + traces + ); + } + + // Return a valid object, even if we don't receive one + getValidInfo(infoObj) { + if (!infoObj.componentStack) { + try { + return { componentStack: JSON.stringify(infoObj) }; + } catch (err) { + return { componentStack: `Unknown Error: ${err}` }; + } + } + return infoObj; + } + + // Called when a child component throws an error. + componentDidCatch(error, info) { + const validInfo = this.getValidInfo(info); + this.setState({ + errorMsg: error.toString(), + errorStack: error.stack, + errorInfo: validInfo, + }); + } + + getBugLink() { + const compStack = this.getValidInfo(this.state.errorInfo).componentStack; + const errorMsg = this.state.errorMsg; + const errorStack = this.state.errorStack; + const msg = `Error: \n${errorMsg}\n\nReact Component Stack: ${compStack}\n\nStacktrace: \n${errorStack}`; + return `${bugLink}${this.props.componentName}&comment=${encodeURIComponent( + msg + )}`; + } + + render() { + if (this.state.errorInfo !== null) { + // "The (componentDesc) has crashed" + const errorDescription = L10N.getFormatStr( + "appErrorBoundary.description", + this.props.panel + ); + return div( + { + className: `app-error-panel`, + }, + h1({ className: "error-panel-header" }, errorDescription), + a( + { + className: "error-panel-file-button", + href: this.getBugLink(), + onClick: () => { + window.open(this.getBugLink(), "_blank"); + }, + }, + FILE_BUG_BUTTON + ), + h2({ className: "error-panel-error" }, this.state.errorMsg), + div({}, this.renderErrorInfo(this.state.errorInfo)), + div({}, this.renderStackTrace(this.state.errorStack)), + p({ className: "error-panel-reload-info" }, RELOAD_PAGE_INFO) + ); + } + return this.props.children; + } +} + +module.exports = AppErrorBoundary; diff --git a/devtools/client/shared/components/Frame.js b/devtools/client/shared/components/Frame.js new file mode 100644 index 0000000000..4efc7d3bd6 --- /dev/null +++ b/devtools/client/shared/components/Frame.js @@ -0,0 +1,401 @@ +/* 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 { + Component, +} = require("resource://devtools/client/shared/vendor/react.js"); +const dom = require("resource://devtools/client/shared/vendor/react-dom-factories.js"); +const PropTypes = require("resource://devtools/client/shared/vendor/react-prop-types.js"); +const { + getUnicodeUrl, + getUnicodeUrlPath, + getUnicodeHostname, +} = require("resource://devtools/client/shared/unicode-url.js"); +const { + getSourceNames, + parseURL, + getSourceMappedFile, +} = require("resource://devtools/client/shared/source-utils.js"); +const { LocalizationHelper } = require("resource://devtools/shared/l10n.js"); +const { + MESSAGE_SOURCE, +} = require("resource://devtools/client/webconsole/constants.js"); + +const l10n = new LocalizationHelper( + "devtools/client/locales/components.properties" +); +const webl10n = new LocalizationHelper( + "devtools/client/locales/webconsole.properties" +); + +function savedFrameToLocation(frame) { + const { source: url, line, column, sourceId } = frame; + return { + url, + line, + column, + // The sourceId will be a string if it's a source actor ID, otherwise + // it is either a Spidermonkey-internal ID from a SavedFrame or missing, + // and in either case we can't use the ID for anything useful. + id: typeof sourceId === "string" ? sourceId : null, + }; +} + +/** + * Get the tooltip message. + * @param {string|undefined} messageSource + * @param {string} url + * @returns {string} + */ +function getTooltipMessage(messageSource, url) { + if (messageSource && messageSource === MESSAGE_SOURCE.CSS) { + return l10n.getFormatStr("frame.viewsourceinstyleeditor", url); + } + return l10n.getFormatStr("frame.viewsourceindebugger", url); +} + +class Frame extends Component { + static get propTypes() { + return { + // Optional className that will be put into the element. + className: PropTypes.string, + // SavedFrame, or an object containing all the required properties. + frame: PropTypes.shape({ + functionDisplayName: PropTypes.string, + // This could be a SavedFrame with a numeric sourceId, or it could + // be a SavedFrame-like client-side object, in which case the + // "sourceId" will be a source actor ID. + sourceId: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), + source: PropTypes.string.isRequired, + line: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), + column: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), + }).isRequired, + // Clicking on the frame link -- probably should link to the debugger. + onClick: PropTypes.func, + // Option to display a function name before the source link. + showFunctionName: PropTypes.bool, + // Option to display a function name even if it's anonymous. + showAnonymousFunctionName: PropTypes.bool, + // Option to display a host name after the source link. + showHost: PropTypes.bool, + // Option to display a host name if the filename is empty or just '/' + showEmptyPathAsHost: PropTypes.bool, + // Option to display a full source instead of just the filename. + showFullSourceUrl: PropTypes.bool, + // Service to enable the source map feature for console. + sourceMapURLService: PropTypes.object, + // The source of the message + messageSource: PropTypes.string, + }; + } + + static get defaultProps() { + return { + showFunctionName: false, + showAnonymousFunctionName: false, + showHost: false, + showEmptyPathAsHost: false, + showFullSourceUrl: false, + }; + } + + constructor(props) { + super(props); + this.state = { + originalLocation: null, + }; + this._locationChanged = this._locationChanged.bind(this); + } + + // FIXME: https://bugzilla.mozilla.org/show_bug.cgi?id=1774507 + UNSAFE_componentWillMount() { + if (this.props.sourceMapURLService) { + const location = savedFrameToLocation(this.props.frame); + // Many things that make use of this component either: + // a) Pass in no sourceId because they have no way to know. + // b) Pass in no sourceId because the actor wasn't created when the + // server sent its response. + // + // and due to that, we need to use subscribeByLocation in order to + // handle both cases with an without an ID. + this.unsubscribeSourceMapURLService = + this.props.sourceMapURLService.subscribeByLocation( + location, + this._locationChanged + ); + } + } + + componentWillUnmount() { + if (this.unsubscribeSourceMapURLService) { + this.unsubscribeSourceMapURLService(); + } + } + + _locationChanged(originalLocation) { + this.setState({ originalLocation }); + } + + /** + * Get current location's source, line, and column. + * @returns {{source: string, line: number|null, column: number|null}} + */ + #getCurrentLocationInfo = () => { + const { frame } = this.props; + const { originalLocation } = this.state; + + const generatedLocation = savedFrameToLocation(frame); + const currentLocation = originalLocation || generatedLocation; + + const source = currentLocation.url || ""; + const line = + currentLocation.line != void 0 ? Number(currentLocation.line) : null; + const column = + currentLocation.column != void 0 ? Number(currentLocation.column) : null; + return { + source, + line, + column, + }; + }; + + /** + * Get unicode hostname of the source link. + * @returns {string} + */ + #getCurrentLocationUnicodeHostName = () => { + const { source } = this.#getCurrentLocationInfo(); + + const { host } = getSourceNames(source); + return host ? getUnicodeHostname(host) : ""; + }; + + /** + * Check if the current location is linkable. + * @returns {boolean} + */ + #isCurrentLocationLinkable = () => { + const { frame } = this.props; + const { originalLocation } = this.state; + + const generatedLocation = savedFrameToLocation(frame); + + // Reparse the URL to determine if we should link this; `getSourceNames` + // has already cached this indirectly. We don't want to attempt to + // link to "self-hosted" and "(unknown)". + // Source mapped sources might not necessary linkable, but they + // are still valid in the debugger. + // If we have a source ID then we can show the source in the debugger. + return !!( + originalLocation || + generatedLocation.id || + !!parseURL(generatedLocation.url) + ); + }; + + /** + * Get the props of the top element. + */ + #getTopElementProps = () => { + const { className } = this.props; + + const { source, line, column } = this.#getCurrentLocationInfo(); + const { long } = getSourceNames(source); + const props = { + "data-url": long, + className: "frame-link" + (className ? ` ${className}` : ""), + }; + + // If we have a line number > 0. + if (line) { + // Add `data-line` attribute for testing + props["data-line"] = line; + + // Intentionally exclude 0 + if (column) { + // Add `data-column` attribute for testing + props["data-column"] = column; + } + } + return props; + }; + + /** + * Get the props of the source element. + */ + #getSourceElementsProps = () => { + const { frame, onClick, messageSource } = this.props; + + const generatedLocation = savedFrameToLocation(frame); + const { source, line, column } = this.#getCurrentLocationInfo(); + const { long } = getSourceNames(source); + let url = getUnicodeUrl(long); + + // Exclude all falsy values, including `0`, as line numbers start with 1. + if (line) { + url += `:${line}`; + // Intentionally exclude 0 + if (column) { + url += `:${column}`; + } + } + + const isLinkable = this.#isCurrentLocationLinkable(); + + // Inner el is useful for achieving ellipsis on the left and correct LTR/RTL + // ordering. See CSS styles for frame-link-source-[inner] and bug 1290056. + const tooltipMessage = getTooltipMessage(messageSource, url); + + const sourceElConfig = { + key: "source", + className: "frame-link-source", + title: isLinkable ? tooltipMessage : url, + }; + + if (isLinkable) { + return { + ...sourceElConfig, + onClick: e => { + e.preventDefault(); + e.stopPropagation(); + + onClick(generatedLocation); + }, + href: source, + draggable: false, + }; + } + + return sourceElConfig; + }; + + /** + * Render the source elements. + * @returns {React.ReactNode} + */ + #renderSourceElements = () => { + const { line, column } = this.#getCurrentLocationInfo(); + + const sourceElements = [this.#renderDisplaySource()]; + + if (line) { + let lineInfo = `:${line}`; + + // Intentionally exclude 0 + if (column) { + lineInfo += `:${column}`; + } + + sourceElements.push( + dom.span( + { + key: "line", + className: "frame-link-line", + }, + lineInfo + ) + ); + } + + if (this.#isCurrentLocationLinkable()) { + return dom.a(this.#getSourceElementsProps(), sourceElements); + } + // If source is not a URL (self-hosted, eval, etc.), don't make + // it an anchor link, as we can't link to it. + return dom.span(this.#getSourceElementsProps(), sourceElements); + }; + + /** + * Render the display source. + * @returns {React.ReactNode} + */ + #renderDisplaySource = () => { + const { showEmptyPathAsHost, showFullSourceUrl } = this.props; + const { originalLocation } = this.state; + + const { source } = this.#getCurrentLocationInfo(); + const { short, long, host } = getSourceNames(source); + const unicodeShort = getUnicodeUrlPath(short); + const unicodeLong = getUnicodeUrl(long); + let displaySource = showFullSourceUrl ? unicodeLong : unicodeShort; + if (originalLocation) { + displaySource = getSourceMappedFile(displaySource); + + // In case of pretty-printed HTML file, we would only get the formatted suffix; replace + // it with the full URL instead + if (showEmptyPathAsHost && displaySource == ":formatted") { + displaySource = host + displaySource; + } + } else if ( + showEmptyPathAsHost && + (displaySource === "" || displaySource === "/") + ) { + displaySource = host; + } + + return dom.span( + { + key: "filename", + className: "frame-link-filename", + }, + displaySource + ); + }; + + /** + * Render the function display name. + * @returns {React.ReactNode} + */ + #renderFunctionDisplayName = () => { + const { frame, showFunctionName, showAnonymousFunctionName } = this.props; + if (!showFunctionName) { + return null; + } + const functionDisplayName = frame.functionDisplayName; + if (functionDisplayName || showAnonymousFunctionName) { + return [ + dom.span( + { + key: "function-display-name", + className: "frame-link-function-display-name", + }, + functionDisplayName || webl10n.getStr("stacktrace.anonymousFunction") + ), + " ", + ]; + } + return null; + }; + + render() { + const { showHost } = this.props; + + const elements = [ + this.#renderFunctionDisplayName(), + this.#renderSourceElements(), + ]; + + const unicodeHost = showHost + ? this.#getCurrentLocationUnicodeHostName() + : null; + if (unicodeHost) { + elements.push(" "); + elements.push( + dom.span( + { + key: "host", + className: "frame-link-host", + }, + unicodeHost + ) + ); + } + + return dom.span(this.#getTopElementProps(), ...elements); + } +} + +module.exports = Frame; diff --git a/devtools/client/shared/components/HSplitBox.js b/devtools/client/shared/components/HSplitBox.js new file mode 100644 index 0000000000..65dfc0aaf6 --- /dev/null +++ b/devtools/client/shared/components/HSplitBox.js @@ -0,0 +1,165 @@ +/* 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/. */ + +/* eslint-env browser */ +"use strict"; + +// A box with a start and a end pane, separated by a dragable splitter that +// allows the user to resize the relative widths of the panes. +// +// +-----------------------+---------------------+ +// | | | +// | | | +// | S | +// | Start Pane p End Pane | +// | l | +// | i | +// | t | +// | t | +// | e | +// | r | +// | | | +// | | | +// +-----------------------+---------------------+ + +const { + Component, +} = require("resource://devtools/client/shared/vendor/react.js"); +const PropTypes = require("resource://devtools/client/shared/vendor/react-prop-types.js"); +const dom = require("resource://devtools/client/shared/vendor/react-dom-factories.js"); +const { assert } = require("resource://devtools/shared/DevToolsUtils.js"); + +class HSplitBox extends Component { + static get propTypes() { + return { + // The contents of the start pane. + start: PropTypes.any.isRequired, + + // The contents of the end pane. + end: PropTypes.any.isRequired, + + // The relative width of the start pane, expressed as a number between 0 and + // 1. The relative width of the end pane is 1 - startWidth. For example, + // with startWidth = .5, both panes are of equal width; with startWidth = + // .25, the start panel will take up 1/4 width and the end panel will take + // up 3/4 width. + startWidth: PropTypes.number, + + // A minimum css width value for the start and end panes. + minStartWidth: PropTypes.any, + minEndWidth: PropTypes.any, + + // A callback fired when the user drags the splitter to resize the relative + // pane widths. The function is passed the startWidth value that would put + // the splitter underneath the users mouse. + onResize: PropTypes.func.isRequired, + }; + } + + static get defaultProps() { + return { + startWidth: 0.5, + minStartWidth: "20px", + minEndWidth: "20px", + }; + } + + constructor(props) { + super(props); + + this.state = { + mouseDown: false, + }; + + this._onMouseDown = this._onMouseDown.bind(this); + this._onMouseUp = this._onMouseUp.bind(this); + this._onMouseMove = this._onMouseMove.bind(this); + } + + componentDidMount() { + document.defaultView.top.addEventListener("mouseup", this._onMouseUp); + document.defaultView.top.addEventListener("mousemove", this._onMouseMove); + } + + componentWillUnmount() { + document.defaultView.top.removeEventListener("mouseup", this._onMouseUp); + document.defaultView.top.removeEventListener( + "mousemove", + this._onMouseMove + ); + } + + _onMouseDown(event) { + if (event.button !== 0) { + return; + } + + this.setState({ mouseDown: true }); + event.preventDefault(); + } + + _onMouseUp(event) { + if (event.button !== 0 || !this.state.mouseDown) { + return; + } + + this.setState({ mouseDown: false }); + event.preventDefault(); + } + + _onMouseMove(event) { + if (!this.state.mouseDown) { + return; + } + + const rect = this.refs.box.getBoundingClientRect(); + const { left, right } = rect; + const width = right - left; + const direction = this.refs.box.ownerDocument.dir; + const relative = + direction == "rtl" ? right - event.clientX : event.clientX - left; + this.props.onResize(relative / width); + + event.preventDefault(); + } + + render() { + /* eslint-disable no-shadow */ + const { start, end, startWidth, minStartWidth, minEndWidth } = this.props; + assert( + startWidth >= 0 && startWidth <= 1, + "0 <= this.props.startWidth <= 1" + ); + /* eslint-enable */ + return dom.div( + { + className: "h-split-box", + ref: "box", + }, + + dom.div( + { + className: "h-split-box-pane", + style: { flex: startWidth, minWidth: minStartWidth }, + }, + start + ), + + dom.div({ + className: "devtools-side-splitter", + onMouseDown: this._onMouseDown, + }), + + dom.div( + { + className: "h-split-box-pane", + style: { flex: 1 - startWidth, minWidth: minEndWidth }, + }, + end + ) + ); + } +} + +module.exports = HSplitBox; diff --git a/devtools/client/shared/components/List.css b/devtools/client/shared/components/List.css new file mode 100644 index 0000000000..1d0f668180 --- /dev/null +++ b/devtools/client/shared/components/List.css @@ -0,0 +1,41 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* List */ + +.list { + background-color: var(--theme-sidebar-background); + list-style-type: none; + padding: 0; + margin: 0; + width: 100%; + white-space: nowrap; + overflow: auto; +} + +.list:focus, .list .list-item-content:focus { + outline: 0; +} + +.list::-moz-focus-inner, .list .list-item-content::-moz-focus-inner { + border: 0; +} + +.list li.current { + background-color: var(--theme-toolbar-hover); +} + +.list:focus li.current, .list li.active.current { + background-color: var(--theme-emphasized-splitter-color); +} + +.list:focus li:not(.current):hover, +.list:not(:focus) li:not(.active):hover { + background-color: var(--theme-selection-background-hover); +} + +.list .list-item-content:not(:empty) { + font-size: 12px; + overflow: auto; +} diff --git a/devtools/client/shared/components/List.js b/devtools/client/shared/components/List.js new file mode 100644 index 0000000000..95c3ffe4dd --- /dev/null +++ b/devtools/client/shared/components/List.js @@ -0,0 +1,352 @@ +/* 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 . */ + +"use strict"; + +const { + createFactory, + createRef, + Component, + cloneElement, +} = require("resource://devtools/client/shared/vendor/react.js"); +const PropTypes = require("resource://devtools/client/shared/vendor/react-prop-types.js"); +const { + ul, + li, + div, +} = require("resource://devtools/client/shared/vendor/react-dom-factories.js"); + +const { + scrollIntoView, +} = require("resource://devtools/client/shared/scroll.js"); +const { + preventDefaultAndStopPropagation, +} = require("resource://devtools/client/shared/events.js"); + +loader.lazyRequireGetter( + this, + ["getFocusableElements", "wrapMoveFocus"], + "resource://devtools/client/shared/focus.js", + true +); + +class ListItemClass extends Component { + static get propTypes() { + return { + active: PropTypes.bool, + current: PropTypes.bool, + onClick: PropTypes.func, + item: PropTypes.shape({ + key: PropTypes.string, + component: PropTypes.object, + componentProps: PropTypes.object, + className: PropTypes.string, + }).isRequired, + }; + } + + constructor(props) { + super(props); + + this.contentRef = createRef(); + + this._setTabbableState = this._setTabbableState.bind(this); + this._onKeyDown = this._onKeyDown.bind(this); + } + + componentDidMount() { + this._setTabbableState(); + } + + componentDidUpdate() { + this._setTabbableState(); + } + + _onKeyDown(event) { + const { target, key, shiftKey } = event; + + if (key !== "Tab") { + return; + } + + const focusMoved = !!wrapMoveFocus( + getFocusableElements(this.contentRef.current), + target, + shiftKey + ); + if (focusMoved) { + // Focus was moved to the begining/end of the list, so we need to prevent the + // default focus change that would happen here. + event.preventDefault(); + } + + event.stopPropagation(); + } + + /** + * Makes sure that none of the focusable elements inside the list item container are + * tabbable if the list item is not active. If the list item is active and focus is + * outside its container, focus on the first focusable element inside. + */ + _setTabbableState() { + const elms = getFocusableElements(this.contentRef.current); + if (elms.length === 0) { + return; + } + + if (!this.props.active) { + elms.forEach(elm => elm.setAttribute("tabindex", "-1")); + return; + } + + if (!elms.includes(document.activeElement)) { + elms[0].focus(); + } + } + + render() { + const { active, item, current, onClick } = this.props; + const { className, component, componentProps } = item; + + return li( + { + className: `${className}${current ? " current" : ""}${ + active ? " active" : "" + }`, + id: item.key, + onClick, + onKeyDownCapture: active ? this._onKeyDown : null, + }, + div( + { + className: "list-item-content", + role: "presentation", + ref: this.contentRef, + }, + cloneElement(component, componentProps || {}) + ) + ); + } +} + +const ListItem = createFactory(ListItemClass); + +class List extends Component { + static get propTypes() { + return { + // A list of all items to be rendered using a List component. + items: PropTypes.arrayOf( + PropTypes.shape({ + component: PropTypes.object, + componentProps: PropTypes.object, + className: PropTypes.string, + key: PropTypes.string.isRequired, + }) + ).isRequired, + + // Note: the two properties below are mutually exclusive. Only one of the + // label properties is necessary. + // ID of an element whose textual content serves as an accessible label for + // a list. + labelledBy: PropTypes.string, + + // Accessibility label for a list widget. + label: PropTypes.string, + }; + } + + constructor(props) { + super(props); + + this.listRef = createRef(); + + this.state = { + active: null, + current: null, + mouseDown: false, + }; + + this._setCurrentItem = this._setCurrentItem.bind(this); + this._preventArrowKeyScrolling = this._preventArrowKeyScrolling.bind(this); + this._onKeyDown = this._onKeyDown.bind(this); + } + + shouldComponentUpdate(nextProps, nextState) { + const { active, current, mouseDown } = this.state; + + return ( + current !== nextState.current || + active !== nextState.active || + mouseDown === nextState.mouseDown + ); + } + + _preventArrowKeyScrolling(e) { + switch (e.key) { + case "ArrowUp": + case "ArrowDown": + case "ArrowLeft": + case "ArrowRight": + preventDefaultAndStopPropagation(e); + break; + } + } + + /** + * Sets the passed in item to be the current item. + * + * @param {null|Number} index + * The index of the item in to be set as current, or undefined to unset the + * current item. + */ + _setCurrentItem(index = -1, options = {}) { + const item = this.props.items[index]; + if (item !== undefined && !options.preventAutoScroll) { + const element = document.getElementById(item.key); + scrollIntoView(element, { + ...options, + container: this.listRef.current, + }); + } + + const state = {}; + if (this.state.active != undefined) { + state.active = null; + if (this.listRef.current !== document.activeElement) { + this.listRef.current.focus(); + } + } + + if (this.state.current !== index) { + this.setState({ + ...state, + current: index, + }); + } + } + + /** + * Handles key down events in the list's container. + * + * @param {Event} e + */ + _onKeyDown(e) { + const { active, current } = this.state; + if (current == null) { + return; + } + + if (e.altKey || e.ctrlKey || e.shiftKey || e.metaKey) { + return; + } + + this._preventArrowKeyScrolling(e); + + const { length } = this.props.items; + switch (e.key) { + case "ArrowUp": + current > 0 && this._setCurrentItem(current - 1, { alignTo: "top" }); + break; + + case "ArrowDown": + current < length - 1 && + this._setCurrentItem(current + 1, { alignTo: "bottom" }); + break; + + case "Home": + this._setCurrentItem(0, { alignTo: "top" }); + break; + + case "End": + this._setCurrentItem(length - 1, { alignTo: "bottom" }); + break; + + case "Enter": + case " ": + // On space or enter make current list item active. This means keyboard focus + // handling is passed on to the component within the list item. + if (document.activeElement === this.listRef.current) { + preventDefaultAndStopPropagation(e); + if (active !== current) { + this.setState({ active: current }); + } + } + break; + + case "Escape": + // If current list item is active, make it inactive and let keyboard focusing be + // handled normally. + preventDefaultAndStopPropagation(e); + if (active != null) { + this.setState({ active: null }); + } + + this.listRef.current.focus(); + break; + } + } + + render() { + const { active, current } = this.state; + const { items } = this.props; + + return ul( + { + ref: this.listRef, + className: "list", + tabIndex: 0, + onKeyDown: this._onKeyDown, + onKeyPress: this._preventArrowKeyScrolling, + onKeyUp: this._preventArrowKeyScrolling, + onMouseDown: () => this.setState({ mouseDown: true }), + onMouseUp: () => this.setState({ mouseDown: false }), + onFocus: () => { + if (current != null || this.state.mouseDown) { + return; + } + + // Only set default current to the first list item if current item is + // not yet set and the focus event is not the result of a mouse + // interarction. + this._setCurrentItem(0); + }, + onClick: () => { + // Focus should always remain on the list container itself. + this.listRef.current.focus(); + }, + onBlur: e => { + if (active != null) { + const { relatedTarget } = e; + if (!this.listRef.current.contains(relatedTarget)) { + this.setState({ active: null }); + } + } + }, + "aria-label": this.props.label, + "aria-labelledby": this.props.labelledBy, + "aria-activedescendant": current != null ? items[current].key : null, + }, + items.map((item, index) => { + return ListItem({ + item, + current: index === current, + active: index === active, + // We make a key unique depending on whether the list item is in active or + // inactive state to make sure that it is actually replaced and the tabbable + // state is reset. + key: `${item.key}-${index === active ? "active" : "inactive"}`, + // Since the user just clicked the item, there's no need to check if it should + // be scrolled into view. + onClick: () => + this._setCurrentItem(index, { preventAutoScroll: true }), + }); + }) + ); + } +} + +module.exports = { + ListItem: ListItemClass, + List, +}; diff --git a/devtools/client/shared/components/MdnLink.css b/devtools/client/shared/components/MdnLink.css new file mode 100644 index 0000000000..0fef9c0bba --- /dev/null +++ b/devtools/client/shared/components/MdnLink.css @@ -0,0 +1,33 @@ +/* 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/. */ + +/* Learn more links */ + +.network-monitor .learn-more-link { + display: inline-block; + line-height: 16px; +} + +.network-monitor .learn-more-link::before { + background-image: url(chrome://devtools/skin/images/help.svg); + background-size: contain; +} + +.network-monitor .tree-container .learn-more-link { + position: absolute; + top: 0; + inset-inline-start: 2px; + /* Override devtools-button styles to make this button 20x20, + * so that the icon is vertically centered in the table row */ + padding: 1px 0; +} + +.network-monitor .tree-container tr:not(:hover) .learn-more-link { + opacity: 0.4; +} + +.network-monitor .tabpanel-summary-value.status { + display: flex; + align-items: center; +} diff --git a/devtools/client/shared/components/MdnLink.js b/devtools/client/shared/components/MdnLink.js new file mode 100644 index 0000000000..344143f54c --- /dev/null +++ b/devtools/client/shared/components/MdnLink.js @@ -0,0 +1,38 @@ +/* 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 PropTypes = require("resource://devtools/client/shared/vendor/react-prop-types.js"); +const dom = require("resource://devtools/client/shared/vendor/react-dom-factories.js"); +const { a } = dom; + +loader.lazyRequireGetter( + this, + "openDocLink", + "resource://devtools/client/shared/link.js", + true +); + +function MDNLink({ url, title }) { + return a({ + className: "devtools-button learn-more-link", + title, + onClick: e => onLearnMoreClick(e, url), + }); +} + +MDNLink.displayName = "MDNLink"; + +MDNLink.propTypes = { + url: PropTypes.string.isRequired, +}; + +function onLearnMoreClick(e, url) { + e.stopPropagation(); + e.preventDefault(); + openDocLink(url); +} + +module.exports = MDNLink; diff --git a/devtools/client/shared/components/NotificationBox.css b/devtools/client/shared/components/NotificationBox.css new file mode 100644 index 0000000000..f2ff550f46 --- /dev/null +++ b/devtools/client/shared/components/NotificationBox.css @@ -0,0 +1,130 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* Layout */ + +.notificationbox .notificationInner { + display: flex; + flex-direction: row; + align-items: center; +} + +.notificationInner .messageText { + flex: 1; + width: 0; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.notificationInner .messageImage, +.notificationbox .notificationButton, +.notificationbox .messageCloseButton { + flex: none; +} + +.notificationbox .notificationInner:dir(rtl) { + flex-direction: row-reverse; +} + +/* Style */ + +.notificationbox .notification { + color: var(--theme-toolbar-color); + background-color: var(--theme-body-background); + text-shadow: none; + border-color: var(--theme-splitter-color); + border-style: solid; + border-width: 0; +} + +.notificationbox.border-top .notification { + border-top-width: 1px; +} + +.notificationbox.border-bottom .notification { + border-bottom-width: 1px; +} + +.notificationbox .notification[data-type="info"] { + color: -moz-DialogText; + background-color: -moz-Dialog; +} + +.notificationbox .notification[data-type="new"] { + color: var(--theme-contrast-color); + background-color: var(--theme-body-alternate-emphasized-background); +} + +/** + * Remove button borders for notifications highlighting New features. + */ +.notification[data-type="new"] .notificationButton { + border-radius: 2px; + border-width: 0; + padding: 4px; +} + +.notificationbox .notification[data-type="critical"] { + color: white; + background-image: linear-gradient(rgb(212,0,0), rgb(152,0,0)); +} + +.notificationbox .messageImage { + -moz-context-properties: fill; + fill: currentColor; + background-size: 16px; + width: 16px; + height: 16px; + margin: 6px; +} + +/* Default icons for notifications */ + +.notificationbox .messageImage[data-type="info"] { + background-image: url("chrome://devtools/skin/images/info.svg"); +} + +.notificationbox .messageImage[data-type="new"] { + background-image: url("chrome://global/skin/icons/whatsnew.svg"); + fill: var(--theme-highlight-blue); +} + +.notificationbox .messageImage[data-type="warning"] { + background-image: url("chrome://devtools/skin/images/alert.svg"); + /* Keep the icon colored to make it more eye-catching */ + fill: #ffbf00; +} + +.notificationbox .messageImage[data-type="critical"] { + background-image: url("chrome://devtools/skin/images/error.svg"); +} + +/* Close button */ + +.notificationbox .messageCloseButton { + width: 24px; + height: 24px; + margin: 2px 4px; + background-image: url("chrome://devtools/skin/images/close.svg"); + background-position: center; + background-color: transparent; + background-repeat: no-repeat; + border-radius: 2px; + border-width: 0; + -moz-context-properties: fill; + fill: var(--theme-icon-color); +} + +.notificationbox .messageCloseButton:hover { + background-color: var(--theme-button-active-background); +} + +.notificationbox .messageCloseButton:active { + background-color: rgba(170, 170, 170, .4); /* --toolbar-tab-hover-active */ +} + +.notificationbox.wrapping .notificationInner .messageText { + white-space: normal; +} diff --git a/devtools/client/shared/components/NotificationBox.js b/devtools/client/shared/components/NotificationBox.js new file mode 100644 index 0000000000..53e1073d7a --- /dev/null +++ b/devtools/client/shared/components/NotificationBox.js @@ -0,0 +1,403 @@ +/* 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 { + Component, + createFactory, +} = require("resource://devtools/client/shared/vendor/react.js"); +const PropTypes = require("resource://devtools/client/shared/vendor/react-prop-types.js"); +const dom = require("resource://devtools/client/shared/vendor/react-dom-factories.js"); +const { LocalizationHelper } = require("resource://devtools/shared/l10n.js"); + +const l10n = new LocalizationHelper( + "devtools/client/locales/components.properties" +); +const { div, span, button } = dom; +loader.lazyGetter(this, "MDNLink", function () { + return createFactory( + require("resource://devtools/client/shared/components/MdnLink.js") + ); +}); + +// Priority Levels +const PriorityLevels = { + PRIORITY_INFO_LOW: 1, + PRIORITY_INFO_MEDIUM: 2, + PRIORITY_INFO_HIGH: 3, + // Type NEW should be used to highlight new features, and should be more + // eye-catchy than INFO level notifications. + PRIORITY_NEW: 4, + PRIORITY_WARNING_LOW: 5, + PRIORITY_WARNING_MEDIUM: 6, + PRIORITY_WARNING_HIGH: 7, + PRIORITY_CRITICAL_LOW: 8, + PRIORITY_CRITICAL_MEDIUM: 9, + PRIORITY_CRITICAL_HIGH: 10, + PRIORITY_CRITICAL_BLOCK: 11, +}; + +/** + * This component represents Notification Box - HTML alternative for + * binding. + * + * See also MDN for more info about : + * https://developer.mozilla.org/en-US/docs/Mozilla/Tech/XUL/notificationbox + * + * This component can maintain its own state (list of notifications) + * as well as consume list of notifications provided as a prop + * (coming e.g. from Redux store). + */ +class NotificationBox extends Component { + static get propTypes() { + return { + // Optional box ID (used for mounted node ID attribute) + id: PropTypes.string, + /** + * List of notifications appended into the box. Each item of the map is an object + * of the following shape: + * - {String} label: Label to appear on the notification. + * - {String} value: Value used to identify the notification. Should be the same + * as the map key used for this notification. + * - {String} image: URL of image to appear on the notification. If "" then an + * appropriate icon for the priority level is used. + * - {Number} priority: Notification priority; see Priority Levels. + * - {Function} eventCallback: A function to call to notify you of interesting + things that happen with the notification box. + - {String} type: One of "info", "warning", or "critical" used to determine + what styling and icon are used for the notification. + * - {Array} buttons: Array of button descriptions to appear on the + * notification. Should be of the following shape: + * - {Function} callback: This function is passed 3 arguments: + 1) the NotificationBox component + the button is associated with. + 2) the button description as passed + to appendNotification. + 3) the element which was the target + of the button press event. + If the return value from this function + is not true, then the notification is + closed. The notification is also not + closed if an error is thrown. + - {String} label: The label to appear on the button. + - {String} accesskey: The accesskey attribute set on the +