From 1217ab2f99d1bfaccc6e860b73606e8fb8b6039c Mon Sep 17 00:00:00 2001 From: Fedor Date: Sat, 19 Apr 2025 19:11:17 +0300 Subject: [PATCH] 68.14.8 - devtools --- devtools/.eslintrc.js | 16 - devtools/client/aboutdebugging/index.html | 2 +- .../aboutdebugging/src/actions/runtimes.js | 3 +- .../aboutdebugging/src/components/App.js | 2 +- .../src/middleware/event-recording.js | 10 +- ...owser_aboutdebugging_addons_debug_popup.js | 6 - .../browser_aboutdebugging_addons_warnings.js | 2 + .../browser_aboutdebugging_message_close.js | 2 + .../accessibility/accessibility-view.js | 2 +- .../client/accessibility/accessibility.css | 20 +- .../accessibility/components/Accessible.js | 13 +- devtools/client/accessibility/panel.js | 13 +- .../accessibility/reducers/accessibles.js | 2 +- devtools/client/application/panel.js | 2 - .../test/components/fixtures/PluralForm.js | 11 - .../test/components/fixtures/fluent-l10n.js | 23 + .../test/components/fixtures/l10n.js | 23 - .../test/components/jest.config.js | 3 +- devtools/client/debugger/.flowconfig | 1 + devtools/client/debugger/babel.config.js | 2 + devtools/client/debugger/bin/try-runner.js | 4 +- .../client/debugger/dist/parser-worker.js | 40 +- .../flow-typed/npm/codemirror_vx.x.x.js | 21 - devtools/client/debugger/jest-test.config.js | 3 + devtools/client/debugger/package.json | 2 + .../devtools-reps/src/reps/document.js | 2 +- .../devtools-reps/src/reps/stubs/document.js | 17 - .../devtools-reps/src/reps/tests/document.js | 20 - devtools/client/debugger/panel.js | 26 +- .../src/actions/breakpoints/syncBreakpoint.js | 2 +- .../src/actions/pause/inlinePreview.js | 14 +- .../debugger/src/actions/pause/mapFrames.js | 6 +- .../src/actions/pause/tests/pause.spec.js | 4 +- .../src/actions/sources/loadSourceText.js | 32 +- .../actions/sources/tests/loadSource.spec.js | 2 +- .../debugger/src/actions/types/index.js | 15 +- .../src/actions/utils/middleware/log.js | 2 +- .../src/actions/utils/middleware/timing.js | 14 +- .../debugger/src/client/firefox/commands.js | 14 +- .../debugger/src/client/firefox/types.js | 1 - .../debugger/src/client/firefox/workers.js | 8 +- .../src/components/Editor/ConditionalPanel.js | 2 +- .../src/components/Editor/DebugLine.js | 52 +- .../debugger/src/components/Editor/Footer.js | 2 +- .../src/components/Editor/SearchBar.js | 2 +- .../debugger/src/components/Editor/index.js | 4 +- .../components/Editor/tests/DebugLine.spec.js | 22 +- .../SecondaryPanes/Breakpoints/Breakpoint.js | 2 +- .../src/components/SecondaryPanes/Scopes.js | 10 +- .../src/components/SecondaryPanes/index.js | 2 +- .../src/components/shared/ManagedTree.js | 2 +- .../debugger/src/reducers/breakpoints.js | 2 +- .../client/debugger/src/reducers/pause.js | 10 +- .../client/debugger/src/reducers/sources.js | 2 +- .../src/selectors/getCallStackFrames.js | 3 +- .../src/selectors/visibleColumnBreakpoints.js | 2 +- .../src/test/fixtures/Services.js} | 11 +- .../client/debugger/src/utils/bootstrap.js | 5 +- devtools/client/debugger/src/utils/dbg.js | 2 +- .../src/utils/pause/frames/collapseFrames.js | 4 +- .../src/utils/pause/frames/displayName.js | 5 +- .../src/utils/pause/frames/getFrameUrl.js | 4 +- .../findGeneratedBindingFromPosition.js | 2 +- .../src/utils/pause/mapScopes/index.js | 4 +- .../src/utils/pause/scopes/getScope.js | 2 +- .../client/debugger/src/utils/pause/why.js | 4 +- devtools/client/debugger/src/utils/prefs.js | 3 +- devtools/client/debugger/src/utils/ui.js | 2 +- devtools/client/debugger/src/utils/utils.js | 4 +- .../workers/parser/findOutOfScopeLocations.js | 3 +- .../debugger/src/workers/parser/getSymbols.js | 13 +- .../src/workers/parser/utils/helpers.js | 4 +- .../debugger/test/mochitest/browser.ini | 4 +- .../mochitest/browser_dbg-chrome-debugging.js | 5 +- .../browser_dbg-inspector-integration.js | 5 +- .../mochitest/browser_dbg-wasm-sourcemaps.js | 27 +- .../mochitest/browser_dbg-worker-exception.js | 18 + .../mochitest/browser_dbg-worker-scopes.js | 4 +- .../examples/doc-worker-exception.html | 10 + .../mochitest/examples/worker-exception.js | 3 + devtools/client/debugger/yarn.lock | 92 +- devtools/client/definitions.js | 61 +- .../client/dom/content/components/DomTree.js | 35 +- devtools/client/dom/panel.js | 16 +- .../dom/test/browser_dom_nodes_highlight.js | 11 +- devtools/client/dom/test/head.js | 37 +- devtools/client/framework/ToolboxProcess.jsm | 5 +- .../actions/dom-mutation-breakpoints.js | 12 +- devtools/client/framework/browser-menus.js | 22 +- .../framework/components/ToolboxToolbar.js | 7 +- devtools/client/framework/devtools-browser.js | 51 +- devtools/client/framework/devtools.js | 170 +- devtools/client/framework/gDevTools.jsm | 149 - devtools/client/framework/moz.build | 1 - devtools/client/framework/selection.js | 49 +- devtools/client/framework/target-from-url.js | 6 +- .../allocations/browser_allocations_target.js | 5 +- devtools/client/framework/test/browser.ini | 2 +- .../test/browser_front_parentFront.js | 42 + .../framework/test/browser_keybindings_01.js | 4 +- .../client/framework/test/browser_menu_api.js | 10 +- .../test/browser_target_get-front.js | 15 + .../test/browser_target_server_compartment.js | 5 +- .../test/browser_toolbox_custom_host.js | 2 +- .../browser_toolbox_options_multiple_tabs.js | 2 +- .../test/browser_toolbox_sidebar_tool.xul | 2 +- .../framework/test/browser_toolbox_toggle.js | 4 +- ..._toolbar_reorder_with_secondary_toolbox.js | 6 + .../test/browser_toolbox_view_source_04.js | 40 - .../browser_toolbox_window_reload_target.js | 23 +- devtools/client/framework/test/head.js | 21 - .../test/metrics/browser_metrics_debugger.js | 2 +- .../test/metrics/browser_metrics_inspector.js | 2 +- .../metrics/browser_metrics_netmonitor.js | 2 +- .../metrics/browser_metrics_webconsole.js | 2 +- .../client/framework/toolbox-context-menu.js | 14 +- devtools/client/framework/toolbox-hosts.js | 2 +- .../framework/toolbox-process-window.html | 2 +- ...oolbox-window.xul => toolbox-window.xhtml} | 2 +- devtools/client/framework/toolbox.js | 521 +- .../framework/{toolbox.xul => toolbox.xhtml} | 4 +- .../client/inspector/animation/animation.js | 10 +- .../components/AnimatedPropertyName.js | 4 +- ...er_animation_animation-target_highlight.js | 9 +- .../client/inspector/boxmodel/box-model.js | 15 +- .../boxmodel/components/BoxModelMain.js | 4 +- .../boxmodel/test/browser_boxmodel_guides.js | 4 +- .../client/inspector/boxmodel/test/head.js | 5 +- .../inspector/changes/reducers/changes.js | 4 +- .../client/inspector/computed/computed.js | 2 +- .../inspector/extensions/extension-sidebar.js | 20 +- .../browser_inspector_extension_sidebar.js | 8 +- .../browser_flexbox_container_element_rep.js | 4 +- .../inspector/fonts/components/FontEditor.js | 21 +- .../inspector/fonts/utils/font-utils.js | 2 +- .../inspector/grids/components/GridOutline.js | 11 +- .../client/inspector/grids/grid-inspector.js | 23 - .../browser_grids_grid-list-element-rep.js | 4 +- devtools/client/inspector/inspector.js | 162 +- .../inspector/layout/components/Accordion.css | 2 +- .../inspector/layout/components/Accordion.js | 53 +- devtools/client/inspector/markup/markup.js | 24 +- .../client/inspector/markup/test/browser.ini | 5 +- .../test/browser_markup_anonymous_02.js | 30 - .../test/browser_markup_anonymous_03.js | 4 +- .../test/browser_markup_anonymous_04.js | 2 - .../browser_markup_events_click_to_close.js | 4 +- .../browser_markup_remove_xul_attributes.js | 2 +- ...oc_markup_xul.xul => doc_markup_xul.xhtml} | 0 devtools/client/inspector/moz.build | 1 + .../inspector/node-picker.js | 117 +- .../inspector/rules/models/element-style.js | 2 +- .../client/inspector/rules/models/rule.js | 35 + devtools/client/inspector/rules/rules.js | 2 +- .../client/inspector/rules/test/browser.ini | 6 + ...r_rules_add-property-invalid-identifier.js | 33 + ..._rules_colorpicker-hides-element-picker.js | 6 +- .../test/browser_rules_inactive_css_grid.js | 16 + ...wser_rules_inactive_css_split-condition.js | 31 + .../browser_rules_inactive_css_visited.js | 81 + .../test/browser_rules_pseudo-visited.js | 40 +- ...ser_rules_pseudo-visited_in_media-query.js | 23 + .../inspector/rules/test/doc_visited.html | 15 + .../test/doc_visited_in_media_query.html | 18 + .../rules/views/text-property-editor.js | 4 + .../client/inspector/shared/test/browser.ini | 2 +- ...einspector_csslogic-content-stylesheets.js | 2 +- .../browser_styleinspector_output-parser.js | 9 - ...sheet.xul => doc_content_stylesheet.xhtml} | 0 .../inspector/shared/tooltips-overlay.js | 6 +- devtools/client/inspector/test/browser.ini | 13 +- ...r_inspector_breadcrumbs_highlight_hover.js | 8 +- ...ser_inspector_breadcrumbs_keyboard_trap.js | 4 +- .../test/browser_inspector_highlighter-01.js | 4 +- .../test/browser_inspector_highlighter-03.js | 4 +- .../test/browser_inspector_highlighter-07.js | 2 +- .../browser_inspector_highlighter-cancel.js | 2 +- ...r_inspector_highlighter-custom-element.js} | 10 +- ...er_inspector_highlighter-eyedropper-xul.js | 4 +- .../browser_inspector_highlighter-hover_03.js | 4 +- ...rowser_inspector_highlighter-iframes_01.js | 8 +- ...rowser_inspector_highlighter-iframes_02.js | 5 + ...ser_inspector_highlighter-keybinding_01.js | 6 +- ...ser_inspector_highlighter-keybinding_02.js | 16 +- ...ser_inspector_highlighter-keybinding_03.js | 11 +- ...ser_inspector_highlighter-keybinding_04.js | 6 +- ..._highlighter-keybinding_separate-window.js | 100 + .../browser_inspector_iframe-navigation.js | 8 +- .../browser_inspector_inspect_mutated_node.js | 75 + ...ser_inspector_picker-stop-on-eyedropper.js | 46 + ...er_inspector_picker-stop-on-tool-change.js | 4 +- .../test/browser_inspector_reload_iframe.js | 43 + ...er_inspector_reload_missing-iframe-node.js | 60 + .../test/browser_inspector_reload_xul.js | 2 +- ...ector_search_keyboard_shortcut_conflict.js | 59 + ...r_inspector_switch-to-inspector-on-pick.js | 2 +- .../doc_inspector_eyedropper_disabled.xhtml | 3 + ...inspector_highlighter_custom_element.xhtml | 20 + .../test/doc_inspector_highlighter_xbl.xul | 12 - ...xul.xul => doc_inspector_reload_xul.xhtml} | 0 devtools/client/inspector/test/head.js | 16 +- devtools/client/inspector/test/shared-head.js | 2 +- devtools/client/jar.mn | 20 +- .../client/jsonview/converter-observer.js | 20 +- .../client/locales/en-US/VariablesView.dtd | 12 - .../client/locales/en-US/debugger.properties | 4 - .../client/locales/en-US/menus.properties | 13 - .../locales/en-US/netmonitor.properties | 82 +- .../locales/en-US/responsive.properties | 5 - devtools/client/locales/en-US/scratchpad.dtd | 143 - .../locales/en-US/scratchpad.properties | 101 - .../client/locales/en-US/sourceeditor.dtd | 3 +- .../locales/en-US/sourceeditor.properties | 6 +- .../client/locales/en-US/startup.properties | 19 - .../client/locales/en-US/storage.properties | 10 +- .../client/locales/en-US/toolbox.properties | 37 - devtools/client/locales/en-US/tooltips.ftl | 6 + devtools/client/memory/actions/snapshot.js | 8 +- .../memory/components/CensusTreeItem.js | 2 +- devtools/client/memory/components/Heap.js | 5 +- devtools/client/memory/components/TreeMap.js | 2 +- devtools/client/memory/initializer.js | 2 +- devtools/client/memory/panel.js | 27 +- devtools/client/menus.js | 47 +- devtools/client/moz.build | 1 - .../src/assets/styles/NetworkDetailsPanel.css | 17 +- .../src/assets/styles/RequestList.css | 9 +- .../src/assets/styles/websockets.css | 7 +- .../netmonitor/src/components/JSONPreview.js | 2 +- .../RequestListColumnTransferredSize.js | 5 +- .../src/components/StatisticsPanel.js | 3 +- .../netmonitor/src/components/TabboxPanel.js | 6 + .../netmonitor/src/components/Toolbar.js | 33 +- .../src/connector/firefox-data-provider.js | 16 +- .../client/netmonitor/src/har/har-builder.js | 4 +- .../netmonitor/src/middleware/batching.js | 2 +- .../src/utils/firefox/open-request-in-tab.js | 2 +- .../netmonitor/src/utils/request-utils.js | 2 +- devtools/client/netmonitor/test/browser.ini | 2 + .../netmonitor/test/browser_net_block-csp.js | 43 + .../netmonitor/test/browser_net_block.js | 6 +- devtools/client/netmonitor/test/head.js | 9 - .../netmonitor/test/html_csp-test-page.html | 17 + devtools/client/performance-new/browser.js | 214 +- .../client/performance-new/components/Perf.js | 41 +- .../components/RecordingButton.js | 57 +- .../performance-new/components/Settings.js | 15 +- .../client/performance-new/initializer.js | 23 +- devtools/client/performance-new/moz.build | 2 + devtools/client/performance-new/panel.js | 2 +- .../performance-new/popup/background.jsm | 254 +- .../popup/icons/capture-profile-icon.svg | 11 - .../performance-new/popup/initializer.js | 83 +- .../performance-new/popup/menu-button.jsm | 120 +- .../client/performance-new/popup/moz.build | 1 - .../client/performance-new/popup/popup.css | 406 - .../client/performance-new/popup/popup.html | 180 - .../client/performance-new/popup/popup.js | 387 - .../client/performance-new/popup/popup.xhtml | 24 + .../client/performance-new/store/actions.js | 179 +- .../client/performance-new/store/reducers.js | 5 + .../client/performance-new/store/selectors.js | 5 + .../performance-new/test/browser/.eslintrc.js | 9 + .../performance-new/test/browser/browser.ini | 8 + .../browser/browser_popup-end-to-end-click.js | 40 + .../test/browser/fake-frontend.html | 67 + .../performance-new/test/browser/head.js | 215 + .../performance-new/test/chrome/head.js | 3 + .../performance-new/test/xpcshell/head.js | 3 + .../test/xpcshell/test_popup_initial_state.js | 67 + .../test/xpcshell/xpcshell.ini | 6 + .../performance/{index.xul => index.xhtml} | 2 +- .../performance/modules/logic/frame-utils.js | 7 +- .../performance/modules/marker-dom-utils.js | 34 +- .../performance/modules/widgets/tree-view.js | 20 +- .../test/browser_perf-tree-abstract-05.js | 8 +- devtools/client/performance/views/details.js | 7 +- devtools/client/preferences/debugger.js | 3 +- devtools/client/responsive/actions/index.js | 3 + .../client/responsive/actions/viewports.js | 12 + devtools/client/responsive/browser/content.js | 5 +- .../components/ResizableViewport.js | 16 +- devtools/client/responsive/index.js | 17 +- devtools/client/responsive/manager.js | 751 +- devtools/client/responsive/moz.build | 2 +- .../client/responsive/reducers/viewports.js | 19 + .../responsive/setting-onboarding-tooltip.js | 77 - .../test/browser/browser_device_width.js | 74 +- .../browser_viewport_resizing_after_reload.js | 2 +- .../browser_viewport_resizing_fixed_width.js | 2 +- .../test/browser/browser_window_sizing.js | 69 +- .../client/responsive/test/browser/head.js | 27 +- devtools/client/responsive/ui.js | 693 + devtools/client/responsive/utils/window.js | 3 +- devtools/client/scratchpad/index.xul | 411 - devtools/client/scratchpad/panel.js | 51 - .../client/scratchpad/scratchpad-manager.jsm | 175 - devtools/client/scratchpad/scratchpad.js | 2536 --- devtools/client/scratchpad/test/.eslintrc.js | 6 - .../test/NS_ERROR_ILLEGAL_INPUT.txt | 2 - devtools/client/scratchpad/test/browser.ini | 45 - .../test/browser_scratchpad_autocomplete.js | 73 - ..._scratchpad_browser_last_window_closing.js | 70 - .../browser_scratchpad_chrome_context_pref.js | 54 - .../test/browser_scratchpad_close_toolbox.js | 39 - .../test/browser_scratchpad_confirm_close.js | 249 - .../test/browser_scratchpad_contexts.js | 217 - ...wser_scratchpad_disable_view_menu_items.js | 77 - ...scratchpad_display_non_error_exceptions.js | 135 - ...owser_scratchpad_display_outputs_errors.js | 93 - .../browser_scratchpad_edit_ui_updates.js | 224 - .../test/browser_scratchpad_eval_func.js | 92 - .../test/browser_scratchpad_execute_print.js | 134 - .../test/browser_scratchpad_falsy.js | 68 - .../test/browser_scratchpad_files.js | 136 - .../test/browser_scratchpad_goto_line_ui.js | 42 - .../test/browser_scratchpad_help_key.js | 58 - .../test/browser_scratchpad_initialization.js | 56 - .../test/browser_scratchpad_inspect.js | 53 - .../browser_scratchpad_inspect_primitives.js | 59 - .../test/browser_scratchpad_long_string.js | 31 - .../test/browser_scratchpad_menubar.js | 39 - .../test/browser_scratchpad_modeline.js | 103 - .../test/browser_scratchpad_open.js | 128 - .../browser_scratchpad_open_error_console.js | 39 - .../test/browser_scratchpad_recent_files.js | 307 - .../test/browser_scratchpad_reload_and_run.js | 82 - ...rowser_scratchpad_remember_view_options.js | 92 - .../test/browser_scratchpad_reset_undo.js | 183 - .../test/browser_scratchpad_restore.js | 106 - .../browser_scratchpad_revert_to_saved.js | 158 - .../browser_scratchpad_run_error_goto_line.js | 80 - .../test/browser_scratchpad_sessions.js | 80 - .../scratchpad/test/browser_scratchpad_tab.js | 87 - .../test/browser_scratchpad_tab_switch.js | 130 - .../test/browser_scratchpad_throw_output.js | 65 - .../scratchpad/test/browser_scratchpad_ui.js | 76 - .../test/browser_scratchpad_unsaved.js | 137 - .../browser_scratchpad_wrong_window_focus.js | 97 - devtools/client/scratchpad/test/head.js | 219 - devtools/client/shared/SplitView.jsm | 1 - devtools/client/shared/browser-loader.js | 26 +- devtools/client/shared/build/babel.js | 2 +- .../client/shared/build/build-debugger.js | 4 + devtools/client/shared/build/build.js | 69 +- .../shared/build/node-templates.mozbuild | 31 +- .../client/shared/components/Accordion.css | 20 +- devtools/client/shared/components/Frame.js | 9 +- .../client/shared/components/SmartTrace.js | 13 +- .../client/shared/components/StackTrace.js | 12 +- .../shared/components/menu/MenuButton.js | 1 + .../splitter/GridElementResizer.css | 20 +- .../client/shared/components/tabs/Tabs.css | 6 +- .../client/shared/components/tabs/Tabs.js | 6 +- .../test/mochitest/test_frame_01.html | 16 - .../mochitest/test_smart-trace-grouping.html | 1 - .../test_smart-trace-source-maps.html | 3 - .../test/mochitest/test_smart-trace.html | 1 - .../test_stack-trace-source-maps.html | 1 - .../test/mochitest/test_stack-trace.html | 1 - .../client/shared/components/tree/TreeCell.js | 2 +- .../client/shared/components/tree/TreeRow.js | 4 +- .../shared/components/tree/TreeView.css | 2 +- .../client/shared/components/tree/TreeView.js | 2 +- devtools/client/shared/inplace-editor.js | 2 +- devtools/client/shared/link.js | 4 +- devtools/client/shared/moz.build | 1 + devtools/client/shared/output-parser.js | 44 +- devtools/client/shared/source-utils.js | 29 - devtools/client/shared/sourceeditor/README | 25 +- .../shared/sourceeditor/autocomplete.js | 84 +- .../codemirror/addon/edit/closebrackets.js | 6 +- .../codemirror/addon/edit/closetag.js | 45 +- .../codemirror/addon/edit/continuelist.js | 10 + .../codemirror/addon/edit/matchbrackets.js | 15 +- .../codemirror/addon/fold/brace-fold.js | 2 +- .../codemirror/addon/fold/foldgutter.js | 15 +- .../codemirror/addon/hint/show-hint.js | 434 - .../codemirror/addon/search/search.js | 8 +- .../codemirror/addon/tern/tern.css | 87 - .../codemirror/addon/tern/tern.js | 718 - .../sourceeditor/codemirror/cmiframe.html | 6 +- .../codemirror/codemirror.bundle.js | 2 +- .../sourceeditor/codemirror/keymap/emacs.js | 1 + .../sourceeditor/codemirror/keymap/sublime.js | 4 +- .../sourceeditor/codemirror/keymap/vim.js | 264 +- .../codemirror/lib/codemirror.css | 11 +- .../sourceeditor/codemirror/lib/codemirror.js | 18920 ++++++++-------- .../codemirror/mode/clike/clike.js | 133 +- .../codemirror/mode/clojure/clojure.js | 522 +- .../sourceeditor/codemirror/mode/css/css.js | 15 +- .../codemirror/mode/htmlmixed/htmlmixed.js | 4 +- .../codemirror/mode/javascript/javascript.js | 98 +- .../sourceeditor/codemirror/mode/jsx/jsx.js | 4 +- .../sourceeditor/codemirror/mode/rust/rust.js | 72 + .../codemirror/mode/simple/simple.js | 216 + .../sourceeditor/codemirror/mode/xml/xml.js | 11 + .../sourceeditor/codemirror/mozilla.css | 9 + devtools/client/shared/sourceeditor/editor.js | 25 +- devtools/client/shared/sourceeditor/moz.build | 4 - .../client/shared/sourceeditor/tern/README | 13 - .../shared/sourceeditor/tern/browser.js | 2916 --- .../shared/sourceeditor/tern/comment.js | 87 - .../shared/sourceeditor/tern/condense.js | 304 - .../client/shared/sourceeditor/tern/def.js | 656 - .../client/shared/sourceeditor/tern/ecma5.js | 950 - .../client/shared/sourceeditor/tern/infer.js | 2119 -- .../client/shared/sourceeditor/tern/signal.js | 51 - .../client/shared/sourceeditor/tern/tern.js | 1056 - .../sourceeditor/tern/tests/unit/head_tern.js | 2 - .../tern/tests/unit/test_autocompletion.js | 26 - .../tern/tests/unit/test_import_tern.js | 16 - .../sourceeditor/tern/tests/unit/xpcshell.ini | 7 - .../shared/sourceeditor/test/browser.ini | 3 +- .../test/browser_css_autocompletion.js | 2 +- .../test/browser_editor_autocomplete_basic.js | 6 - .../browser_editor_autocomplete_events.js | 2 +- .../test/browser_editor_autocomplete_js.js | 41 - .../test/browser_editor_goto_line.js | 2 +- .../test/codemirror/mode/javascript/test.js | 31 + .../sourceeditor/test/codemirror/mode_test.js | 2 +- .../test/codemirror/multi_test.js | 10 + .../sourceeditor/test/codemirror/test.js | 87 +- .../sourceeditor/test/codemirror/vim_test.js | 362 +- .../client/shared/sourceeditor/test/head.js | 2 +- .../test/{head.xul => head.xhtml} | 0 .../shared/sourceeditor/webpack.config.js | 2 + devtools/client/shared/stylesheet-utils.js | 63 +- devtools/client/shared/telemetry.js | 1 - devtools/client/shared/test/browser.ini | 32 +- .../shared/test/browser_dbg_worker-window.js | 4 +- .../shared/test/browser_filter-editor-01.js | 6 +- .../shared/test/browser_filter-editor-02.js | 6 +- .../shared/test/browser_filter-editor-03.js | 6 +- .../shared/test/browser_filter-editor-04.js | 6 +- .../shared/test/browser_filter-editor-05.js | 7 +- .../shared/test/browser_filter-editor-06.js | 6 +- .../shared/test/browser_filter-editor-07.js | 7 +- .../shared/test/browser_filter-editor-08.js | 6 +- .../shared/test/browser_filter-editor-09.js | 6 +- .../shared/test/browser_filter-editor-10.js | 6 +- .../shared/test/browser_filter-presets-01.js | 6 +- .../shared/test/browser_filter-presets-02.js | 6 +- .../shared/test/browser_filter-presets-03.js | 6 +- .../shared/test/browser_html_tooltip-01.js | 2 +- .../shared/test/browser_html_tooltip-02.js | 36 +- .../shared/test/browser_html_tooltip-03.js | 2 +- .../shared/test/browser_html_tooltip-04.js | 2 +- .../shared/test/browser_html_tooltip-05.js | 2 +- .../test/browser_html_tooltip_arrow-01.js | 2 +- .../test/browser_html_tooltip_arrow-02.js | 2 +- .../browser_html_tooltip_consecutive-show.js | 2 +- .../browser_html_tooltip_doorhanger-01.js | 58 +- .../browser_html_tooltip_doorhanger-02.js | 2 +- .../test/browser_html_tooltip_height-auto.js | 2 +- .../shared/test/browser_html_tooltip_hover.js | 2 +- .../test/browser_html_tooltip_offset.js | 2 +- .../test/browser_html_tooltip_resize.js | 8 +- .../shared/test/browser_html_tooltip_rtl.js | 2 +- .../test/browser_html_tooltip_screen_edge.js | 74 + .../browser_html_tooltip_variable-height.js | 2 +- .../test/browser_html_tooltip_width-auto.js | 2 +- .../test/browser_html_tooltip_xul-wrapper.js | 4 +- .../shared/test/browser_html_tooltip_zoom.js | 2 +- ...wser_inplace-editor_autocomplete_offset.js | 3 +- .../shared/test/browser_outputparser.js | 1 + ...rowser_tableWidget_keyboard_interaction.js | 2 +- .../browser_tableWidget_mouse_interaction.js | 2 +- .../browser_telemetry_button_responsive.js | 3 +- .../browser_telemetry_button_scratchpad.js | 115 - devtools/client/shared/test/browser_theme.js | 2 +- ...oltip-02.xul => doc_html_tooltip-02.xhtml} | 0 ...oltip-03.xul => doc_html_tooltip-03.xhtml} | 7 +- ...oltip-04.xul => doc_html_tooltip-04.xhtml} | 0 ...oltip-05.xul => doc_html_tooltip-05.xhtml} | 0 ...tml_tooltip.xul => doc_html_tooltip.xhtml} | 0 ...01.xul => doc_html_tooltip_arrow-01.xhtml} | 0 ...02.xul => doc_html_tooltip_arrow-02.xhtml} | 0 .../test/doc_html_tooltip_doorhanger-01.xhtml | 73 + .../test/doc_html_tooltip_doorhanger-01.xul | 48 - ...l => doc_html_tooltip_doorhanger-02.xhtml} | 0 ...hover.xul => doc_html_tooltip_hover.xhtml} | 0 ...tip_rtl.xul => doc_html_tooltip_rtl.xhtml} | 0 ..._inplace-editor_autocomplete_offset.xhtml} | 0 ...ptions-view.xul => doc_options-view.xhtml} | 2 +- ...oc_tableWidget_keyboard_interaction.xhtml} | 0 ...> doc_tableWidget_mouse_interaction.xhtml} | 0 devtools/client/shared/test/head.js | 2 +- devtools/client/shared/test/leakhunt.js | 1 - devtools/client/shared/test/shared-head.js | 8 +- .../client/shared/test/test-actor-registry.js | 10 +- devtools/client/shared/test/test-actor.js | 21 +- .../shared/test/unit/test_source-utils.js | 16 - devtools/client/shared/thread-utils.js | 28 + devtools/client/shared/view-source.js | 37 - .../shared/widgets/AbstractTreeItem.jsm | 4 +- .../client/shared/widgets/FilterWidget.js | 8 +- .../shared/widgets/ShapesInContextEditor.js | 7 + devtools/client/shared/widgets/TableWidget.js | 11 +- devtools/client/shared/widgets/TreeWidget.js | 2 +- .../client/shared/widgets/VariablesView.jsm | 47 +- .../client/shared/widgets/VariablesView.xul | 17 - .../shared/widgets/tooltip/HTMLTooltip.js | 61 +- .../tooltip/SwatchColorPickerTooltip.js | 8 +- .../widgets/tooltip/SwatchFilterTooltip.js | 8 +- .../tooltip/inactive-css-tooltip-helper.js | 6 +- .../client/shared/widgets/view-helpers.js | 2 +- .../client/storage/{index.xul => index.xhtml} | 10 +- devtools/client/storage/panel.js | 28 +- ...er_storage_dynamic_updates_localStorage.js | 63 +- .../client/storage/test/storage-updates.html | 16 +- devtools/client/storage/ui.js | 55 +- devtools/client/styleeditor/StyleEditorUI.jsm | 23 +- devtools/client/styleeditor/index.xhtml | 162 + devtools/client/styleeditor/index.xul | 159 - devtools/client/styleeditor/panel.js | 28 +- devtools/client/styleeditor/test/browser.ini | 2 +- ...ser_styleeditor_bug_851132_middle_click.js | 2 +- .../test/browser_styleeditor_loading.js | 5 +- .../test/browser_styleeditor_media_sidebar.js | 13 +- ...browser_styleeditor_media_sidebar_links.js | 5 +- .../test/browser_styleeditor_nostyle.js | 10 +- .../test/browser_styleeditor_xul.js | 2 +- .../{doc_xulpage.xul => doc_xulpage.xhtml} | 0 .../client/styleeditor/test/media-rules.css | 2 +- devtools/client/themes/animation.css | 5 +- devtools/client/themes/breadcrumbs.css | 2 +- devtools/client/themes/chart.css | 4 +- devtools/client/themes/common.css | 42 + devtools/client/themes/dark-theme.css | 9 +- .../client/themes/images/tool-scratchpad.svg | 6 - devtools/client/themes/light-theme.css | 7 +- devtools/client/themes/perf.css | 106 +- devtools/client/themes/performance.css | 10 +- devtools/client/themes/rules.css | 9 +- devtools/client/themes/splitters.css | 6 +- devtools/client/themes/storage.css | 16 +- devtools/client/themes/toolbars.css | 7 +- devtools/client/themes/toolbox.css | 4 - devtools/client/themes/tooltips.css | 93 +- devtools/client/themes/variables.css | 234 +- devtools/client/themes/webconsole.css | 25 +- .../client/webconsole/actions/messages.js | 19 +- .../webconsole/browser-console-manager.js | 5 +- devtools/client/webconsole/components/App.css | 12 +- devtools/client/webconsole/components/App.js | 31 +- .../components/FilterBar/FilterButton.js | 5 +- .../webconsole/components/Input/JSTerm.js | 29 +- .../components/Output/ConsoleTable.js | 136 +- .../webconsole/components/Output/Message.js | 12 +- devtools/client/webconsole/constants.js | 1 - .../webconsole/enhancers/net-provider.js | 2 +- devtools/client/webconsole/index.html | 4 +- devtools/client/webconsole/panel.js | 18 +- .../client/webconsole/reducers/messages.js | 76 +- devtools/client/webconsole/reducers/prefs.js | 1 - .../client/webconsole/service-container.js | 28 +- devtools/client/webconsole/store.js | 2 - .../webconsole/test/browser/browser.ini | 3 +- .../test/browser/browser_console.js | 2 +- .../browser_console_context_menu_entries.js | 30 +- ...rm_autocomplete_return_key_no_selection.js | 4 + ...ser_jsterm_await_concurrent_same_result.js | 2 - .../test/browser/browser_jsterm_editor.js | 1 - ...itor_disabled_history_nav_with_keyboard.js | 4 +- .../browser/browser_jsterm_editor_enter.js | 1 - .../browser/browser_jsterm_editor_execute.js | 1 - ...browser_jsterm_editor_execute_selection.js | 1 - .../browser/browser_jsterm_editor_gutter.js | 1 - .../browser/browser_jsterm_editor_resize.js | 1 - ..._jsterm_editor_toggle_keyboard_shortcut.js | 2 - .../browser/browser_jsterm_editor_toolbar.js | 1 - ...ser_jsterm_no_input_and_tab_key_pressed.js | 4 - .../browser_webconsole_console_table.js | 108 +- ...bconsole_console_table_post_alterations.js | 63 + .../browser_webconsole_filters_persist.js | 7 +- ...ser_webconsole_location_scratchpad_link.js | 68 - .../browser_webconsole_network_attach.js | 12 +- ...console_network_message_close_on_escape.js | 5 + ...r_webconsole_network_messages_openinnet.js | 48 + ...browser_webconsole_network_reset_filter.js | 23 +- .../browser_webconsole_nodes_highlight.js | 11 +- .../browser_webconsole_reverse_search.js | 16 +- ...ole_stacktrace_location_scratchpad_link.js | 73 - .../browser_webconsole_stubs_console_api.js | 3 +- .../client/webconsole/test/browser/head.js | 14 +- .../webconsole/test/node/babel.config.js | 12 + .../node/components/filter-button.test.js | 6 +- .../test/node/fixtures/serviceContainer.js | 1 - .../test/node/fixtures/stubs/consoleApi.js | 56 +- .../webconsole/test/node/mocha-test-setup.js | 9 +- .../client/webconsole/test/node/package.json | 4 + .../client/webconsole/test/node/yarn.lock | 3455 +++ devtools/client/webconsole/utils/messages.js | 20 + .../webconsole/utils/object-inspector.js | 8 +- .../webconsole/webconsole-connection-proxy.js | 445 +- devtools/client/webconsole/webconsole-ui.js | 15 +- .../client/webconsole/webconsole-wrapper.js | 26 +- devtools/client/webconsole/webconsole.js | 48 +- devtools/docs/backend/actor-registration.md | 6 +- devtools/docs/backend/client-api.md | 2 - devtools/docs/contributing/css.md | 2 +- devtools/docs/files/adding-files.md | 6 +- devtools/docs/frontend/csp.md | 2 +- .../getting-started/development-profiles.md | 2 +- devtools/server/actors/addon/addons.js | 2 + devtools/server/actors/addon/moz.build | 1 - .../addon/webextension-inspected-window.js | 16 +- devtools/server/actors/addon/webextension.js | 195 - .../server/actors/animation-type-longhand.js | 2 - devtools/server/actors/common.js | 14 + devtools/server/actors/descriptors/moz.build | 2 + .../server/actors/descriptors/webextension.js | 262 + devtools/server/actors/environment.js | 2 +- devtools/server/actors/highlighters.js | 24 +- .../server/actors/highlighters/box-model.js | 1 - .../actors/highlighters/paused-debugger.js | 87 +- .../actors/highlighters/utils/markup.js | 16 +- devtools/server/actors/inspector/css-logic.js | 14 +- .../inspector/custom-element-watcher.js | 3 +- .../actors/inspector/document-walker.js | 4 +- .../actors/inspector/event-collector.js | 11 +- devtools/server/actors/inspector/node.js | 71 +- devtools/server/actors/inspector/utils.js | 19 +- devtools/server/actors/inspector/walker.js | 57 +- devtools/server/actors/moz.build | 1 - .../network-monitor/network-observer.js | 1 - devtools/server/actors/object.js | 251 +- devtools/server/actors/object/long-string.js | 115 - devtools/server/actors/object/moz.build | 1 - .../server/actors/object/property-iterator.js | 2 +- devtools/server/actors/object/utils.js | 15 +- devtools/server/actors/perf.js | 206 +- devtools/server/actors/promises.js | 215 - devtools/server/actors/storage.js | 540 +- devtools/server/actors/styles.js | 91 +- devtools/server/actors/stylesheets.js | 4 +- .../server/actors/targets/browsing-context.js | 8 +- .../server/actors/targets/content-process.js | 13 - devtools/server/actors/targets/moz.build | 1 - .../actors/targets/webextension-proxy.js | 108 - devtools/server/actors/thread.js | 6 +- devtools/server/actors/utils/TabSources.js | 4 +- .../server/actors/utils/actor-registry.js | 5 - .../actors/utils/inactive-property-helper.js | 212 +- devtools/server/actors/webbrowser.js | 25 +- devtools/server/actors/webconsole.js | 97 +- .../actors/webconsole/eval-with-debugger.js | 14 +- .../webconsole/listeners/console-service.js | 6 +- .../connectors/content-process-connector.js | 117 + devtools/server/connectors/frame-connector.js | 307 + .../tern => server/connectors}/moz.build | 13 +- .../server/connectors/worker-connector.js | 171 + devtools/server/debugger-server.js | 546 +- devtools/server/moz.build | 2 + .../gecko-profiler-interface.js | 224 + .../performance-new}/moz.build | 8 +- devtools/server/startup/content-process.jsm | 5 +- devtools/server/startup/frame.js | 5 +- devtools/server/tests/browser/browser.ini | 9 +- .../browser/browser_canvasframe_helper_02.js | 4 +- .../browser_dbg_promises-allocation-stack.js | 70 - ...er_dbg_promises-chrome-allocation-stack.js | 104 - .../browser_dbg_promises-fulfillment-stack.js | 89 - .../browser_dbg_promises-rejection-stack.js | 98 - .../browser/browser_inspector-anonymous.js | 71 +- .../browser_storage_webext_storage_local.js | 38 + .../doc_promise-get-allocation-stack.html | 27 - .../doc_promise-get-fulfillment-stack.html | 26 - .../doc_promise-get-rejection-stack.html | 26 - devtools/server/tests/browser/head.js | 5 +- .../browser/inspector-traversal-data.html | 22 +- .../tests/browser/test-window.xhtml} | 5 +- devtools/server/tests/mochitest/chrome.ini | 2 +- ...er.Source.prototype.introductionType.xhtml | 7 + ...gger.Source.prototype.introductionType.xul | 4 - .../inactive-property-helper/float.js | 51 + ...ger.Source.prototype.introductionType.html | 2 +- .../tests/mochitest/test_css-properties.html | 5 - .../mochitest/test_inspector-changeattrs.html | 2 +- .../mochitest/test_inspector-changevalue.html | 2 +- .../mochitest/test_inspector-dead-nodes.html | 2 +- .../test_inspector-display-type.html | 2 +- .../test_inspector-duplicate-node.html | 2 +- .../tests/mochitest/test_inspector-hide.html | 2 +- ...st_inspector-inactive-property-helper.html | 1 + .../test_inspector-mutations-attr.html | 2 +- .../test_inspector-mutations-events.html | 2 +- .../test_inspector-mutations-value.html | 2 +- .../mochitest/test_inspector-pick-color.html | 2 +- .../test_inspector-pseudoclass-lock.html | 2 +- .../mochitest/test_inspector-reload.html | 2 +- .../mochitest/test_inspector-resize.html | 2 +- .../mochitest/test_inspector-resolve-url.html | 2 +- .../test_inspector-scroll-into-view.html | 2 +- .../test_inspector-search-front.html | 2 +- .../mochitest/test_inspector-template.html | 2 +- ..._inspector_getImageData-wait-for-load.html | 2 +- .../test_inspector_getImageData.html | 2 +- .../test_inspector_getImageDataFromURL.html | 2 +- .../test_inspector_getNodeFromActor.html | 2 +- .../test_inspector_getOffsetParent.html | 2 +- .../tests/mochitest/test_styles-applied.html | 4 +- .../tests/mochitest/test_styles-computed.html | 2 +- .../tests/mochitest/test_styles-layout.html | 2 +- .../tests/mochitest/test_styles-matched.html | 2 +- .../tests/mochitest/test_styles-modify.html | 2 +- .../tests/mochitest/test_styles-svg.html | 2 +- ..._webextension-addon-debugging-connect.html | 8 +- .../tests/mochitest/webextension-helpers.js | 4 +- devtools/server/tests/unit/head_dbg.js | 40 - .../server/tests/unit/test_addons_actor.js | 8 +- .../unit/test_extension_storage_actor.js | 861 + .../server/tests/unit/test_longstringactor.js | 100 - .../server/tests/unit/test_objectgrips-17.js | 5 +- .../server/tests/unit/test_objectgrips-21.js | 2 +- .../tests/unit/test_promises_actor_attach.js | 41 - .../unit/test_promises_actor_list_promises.js | 64 - .../unit/test_promises_actor_onnewpromise.js | 65 - .../test_promises_actor_onpromisesettled.js | 101 - ...st_promises_client_getdependentpromises.js | 112 - .../test_promises_object_creationtimestamp.js | 83 - .../test_promises_object_timetosettle-01.js | 73 - .../test_promises_object_timetosettle-02.js | 71 - devtools/server/tests/unit/xpcshell.ini | 12 +- devtools/shared/DevToolsUtils.js | 2 +- devtools/shared/Loader.jsm | 300 +- devtools/shared/acorn/UPGRADING.md | 2 +- devtools/shared/adb/adb-addon.js | 2 +- devtools/shared/base-loader.js | 2 +- devtools/shared/client/object-client.js | 76 - .../shared/css/generated/properties-db.js | 24 - devtools/shared/event-emitter.js | 6 +- devtools/shared/fronts/accessibility.js | 13 +- devtools/shared/fronts/addon/addons.js | 5 +- .../addon/webextension-inspected-window.js | 5 +- devtools/shared/fronts/animation.js | 9 +- devtools/shared/fronts/changes.js | 4 +- devtools/shared/fronts/css-properties.js | 132 +- devtools/shared/fronts/descriptors/moz.build | 2 + .../addon.js => descriptors/webextension.js} | 76 +- devtools/shared/fronts/device.js | 5 +- devtools/shared/fronts/emulation.js | 5 +- devtools/shared/fronts/framerate.js | 5 +- devtools/shared/fronts/highlighters.js | 9 +- devtools/shared/fronts/inspector.js | 516 +- devtools/shared/fronts/inspector/moz.build | 1 - devtools/shared/fronts/memory.js | 5 +- devtools/shared/fronts/moz.build | 4 +- devtools/shared/fronts/node.js | 33 +- devtools/shared/fronts/perf.js | 5 +- .../shared/fronts/performance-recording.js | 4 +- devtools/shared/fronts/performance.js | 5 +- devtools/shared/fronts/preference.js | 5 +- devtools/shared/fronts/promises.js | 25 - devtools/shared/fronts/reflow.js | 4 +- devtools/shared/fronts/root.js | 4 +- devtools/shared/fronts/screenshot.js | 5 +- devtools/shared/fronts/source.js | 1 - devtools/shared/fronts/storage.js | 5 +- devtools/shared/fronts/styles.js | 9 +- devtools/shared/fronts/stylesheets.js | 13 +- devtools/shared/fronts/targets/moz.build | 1 - .../shared/fronts/targets/target-mixin.js | 24 +- devtools/shared/fronts/thread.js | 10 +- devtools/shared/fronts/walker.js | 522 + devtools/shared/fronts/webconsole.js | 9 +- devtools/shared/fronts/websocket.js | 5 +- devtools/shared/inspector/css-logic.js | 330 +- devtools/shared/layout/utils.js | 125 +- devtools/shared/protocol/Front.js | 18 +- devtools/shared/protocol/types.js | 17 +- devtools/shared/security/auth.js | 2 +- devtools/shared/specs/addon/moz.build | 1 - devtools/shared/specs/descriptors/moz.build | 2 + .../{addon => descriptors}/webextension.js | 11 +- devtools/shared/specs/index.js | 27 +- devtools/shared/specs/inspector.js | 363 +- devtools/shared/specs/moz.build | 2 +- devtools/shared/specs/node.js | 10 + devtools/shared/specs/promises.js | 56 - devtools/shared/specs/root.js | 2 +- devtools/shared/specs/storage.js | 17 + devtools/shared/specs/styles.js | 4 + devtools/shared/specs/targets/addon.js | 46 - devtools/shared/specs/targets/moz.build | 1 - devtools/shared/specs/walker.js | 369 + .../browser_allocation_tracker.js | 7 +- .../browser/browser_l10n_localizeMarkup.js | 17 +- devtools/shared/tests/mochitest/chrome.ini | 1 + .../test_css-logic-findCssSelector.html | 114 + .../tests/unit/test_independent_loaders.js | 4 +- .../tests/unit/test_invisible_loader.js | 18 +- .../shared/tests/unit/test_require_lazy.js | 14 +- .../shared/webconsole/js-property-provider.js | 2 +- devtools/shared/webconsole/network-helper.js | 4 +- .../test/test_bug819670_getter_throws.html | 8 +- .../webconsole/test/test_consoleapi.html | 2 +- .../test/test_consoleapi_innerID.html | 2 +- .../shared/webconsole/test/test_jsterm.html | 2 +- .../webconsole/test/test_object_actor.html | 2 +- .../AboutDevToolsToolboxRegistration.jsm | 2 +- devtools/startup/DevToolsShim.jsm | 88 +- devtools/startup/DevToolsStartup.jsm | 124 +- .../startup/aboutdevtools/aboutdevtools.xhtml | 2 +- .../locales/en-US/key-shortcuts.properties | 4 - .../startup/tests/unit/test_devtools_shim.js | 1 - 807 files changed, 25709 insertions(+), 38536 deletions(-) delete mode 100644 devtools/client/application/test/components/fixtures/PluralForm.js create mode 100644 devtools/client/application/test/components/fixtures/fluent-l10n.js delete mode 100644 devtools/client/application/test/components/fixtures/l10n.js rename devtools/client/{themes/scratchpad.css => debugger/src/test/fixtures/Services.js} (69%) create mode 100644 devtools/client/debugger/test/mochitest/browser_dbg-worker-exception.js create mode 100644 devtools/client/debugger/test/mochitest/examples/doc-worker-exception.html create mode 100644 devtools/client/debugger/test/mochitest/examples/worker-exception.js delete mode 100644 devtools/client/framework/gDevTools.jsm create mode 100644 devtools/client/framework/test/browser_front_parentFront.js delete mode 100644 devtools/client/framework/test/browser_toolbox_view_source_04.js rename devtools/client/framework/{toolbox-window.xul => toolbox-window.xhtml} (90%) rename devtools/client/framework/{toolbox.xul => toolbox.xhtml} (95%) delete mode 100644 devtools/client/inspector/markup/test/browser_markup_anonymous_02.js rename devtools/client/inspector/markup/test/{doc_markup_xul.xul => doc_markup_xul.xhtml} (100%) rename devtools/{shared/fronts => client}/inspector/node-picker.js (51%) create mode 100644 devtools/client/inspector/rules/test/browser_rules_add-property-invalid-identifier.js create mode 100644 devtools/client/inspector/rules/test/browser_rules_inactive_css_split-condition.js create mode 100644 devtools/client/inspector/rules/test/browser_rules_inactive_css_visited.js create mode 100644 devtools/client/inspector/rules/test/browser_rules_pseudo-visited_in_media-query.js create mode 100644 devtools/client/inspector/rules/test/doc_visited.html create mode 100644 devtools/client/inspector/rules/test/doc_visited_in_media_query.html rename devtools/client/inspector/shared/test/{doc_content_stylesheet.xul => doc_content_stylesheet.xhtml} (100%) rename devtools/client/inspector/test/{browser_inspector_highlighter-xbl.js => browser_inspector_highlighter-custom-element.js} (80%) create mode 100644 devtools/client/inspector/test/browser_inspector_highlighter-keybinding_separate-window.js create mode 100644 devtools/client/inspector/test/browser_inspector_inspect_mutated_node.js create mode 100644 devtools/client/inspector/test/browser_inspector_picker-stop-on-eyedropper.js create mode 100644 devtools/client/inspector/test/browser_inspector_reload_iframe.js create mode 100644 devtools/client/inspector/test/browser_inspector_reload_missing-iframe-node.js create mode 100644 devtools/client/inspector/test/browser_inspector_search_keyboard_shortcut_conflict.js create mode 100644 devtools/client/inspector/test/doc_inspector_eyedropper_disabled.xhtml create mode 100644 devtools/client/inspector/test/doc_inspector_highlighter_custom_element.xhtml delete mode 100644 devtools/client/inspector/test/doc_inspector_highlighter_xbl.xul rename devtools/client/inspector/test/{doc_inspector_reload_xul.xul => doc_inspector_reload_xul.xhtml} (100%) delete mode 100644 devtools/client/locales/en-US/VariablesView.dtd delete mode 100644 devtools/client/locales/en-US/scratchpad.dtd delete mode 100644 devtools/client/locales/en-US/scratchpad.properties create mode 100644 devtools/client/netmonitor/test/browser_net_block-csp.js create mode 100644 devtools/client/netmonitor/test/html_csp-test-page.html delete mode 100644 devtools/client/performance-new/popup/icons/capture-profile-icon.svg delete mode 100644 devtools/client/performance-new/popup/popup.css delete mode 100644 devtools/client/performance-new/popup/popup.html delete mode 100644 devtools/client/performance-new/popup/popup.js create mode 100644 devtools/client/performance-new/popup/popup.xhtml create mode 100644 devtools/client/performance-new/test/browser/.eslintrc.js create mode 100644 devtools/client/performance-new/test/browser/browser.ini create mode 100644 devtools/client/performance-new/test/browser/browser_popup-end-to-end-click.js create mode 100644 devtools/client/performance-new/test/browser/fake-frontend.html create mode 100644 devtools/client/performance-new/test/browser/head.js create mode 100644 devtools/client/performance-new/test/xpcshell/head.js create mode 100644 devtools/client/performance-new/test/xpcshell/test_popup_initial_state.js create mode 100644 devtools/client/performance-new/test/xpcshell/xpcshell.ini rename devtools/client/performance/{index.xul => index.xhtml} (99%) delete mode 100644 devtools/client/responsive/setting-onboarding-tooltip.js create mode 100644 devtools/client/responsive/ui.js delete mode 100644 devtools/client/scratchpad/index.xul delete mode 100644 devtools/client/scratchpad/panel.js delete mode 100644 devtools/client/scratchpad/scratchpad-manager.jsm delete mode 100644 devtools/client/scratchpad/scratchpad.js delete mode 100644 devtools/client/scratchpad/test/.eslintrc.js delete mode 100644 devtools/client/scratchpad/test/NS_ERROR_ILLEGAL_INPUT.txt delete mode 100644 devtools/client/scratchpad/test/browser.ini delete mode 100644 devtools/client/scratchpad/test/browser_scratchpad_autocomplete.js delete mode 100644 devtools/client/scratchpad/test/browser_scratchpad_browser_last_window_closing.js delete mode 100644 devtools/client/scratchpad/test/browser_scratchpad_chrome_context_pref.js delete mode 100644 devtools/client/scratchpad/test/browser_scratchpad_close_toolbox.js delete mode 100644 devtools/client/scratchpad/test/browser_scratchpad_confirm_close.js delete mode 100644 devtools/client/scratchpad/test/browser_scratchpad_contexts.js delete mode 100644 devtools/client/scratchpad/test/browser_scratchpad_disable_view_menu_items.js delete mode 100644 devtools/client/scratchpad/test/browser_scratchpad_display_non_error_exceptions.js delete mode 100644 devtools/client/scratchpad/test/browser_scratchpad_display_outputs_errors.js delete mode 100644 devtools/client/scratchpad/test/browser_scratchpad_edit_ui_updates.js delete mode 100644 devtools/client/scratchpad/test/browser_scratchpad_eval_func.js delete mode 100644 devtools/client/scratchpad/test/browser_scratchpad_execute_print.js delete mode 100644 devtools/client/scratchpad/test/browser_scratchpad_falsy.js delete mode 100644 devtools/client/scratchpad/test/browser_scratchpad_files.js delete mode 100644 devtools/client/scratchpad/test/browser_scratchpad_goto_line_ui.js delete mode 100644 devtools/client/scratchpad/test/browser_scratchpad_help_key.js delete mode 100644 devtools/client/scratchpad/test/browser_scratchpad_initialization.js delete mode 100644 devtools/client/scratchpad/test/browser_scratchpad_inspect.js delete mode 100644 devtools/client/scratchpad/test/browser_scratchpad_inspect_primitives.js delete mode 100644 devtools/client/scratchpad/test/browser_scratchpad_long_string.js delete mode 100644 devtools/client/scratchpad/test/browser_scratchpad_menubar.js delete mode 100644 devtools/client/scratchpad/test/browser_scratchpad_modeline.js delete mode 100644 devtools/client/scratchpad/test/browser_scratchpad_open.js delete mode 100644 devtools/client/scratchpad/test/browser_scratchpad_open_error_console.js delete mode 100644 devtools/client/scratchpad/test/browser_scratchpad_recent_files.js delete mode 100644 devtools/client/scratchpad/test/browser_scratchpad_reload_and_run.js delete mode 100644 devtools/client/scratchpad/test/browser_scratchpad_remember_view_options.js delete mode 100644 devtools/client/scratchpad/test/browser_scratchpad_reset_undo.js delete mode 100644 devtools/client/scratchpad/test/browser_scratchpad_restore.js delete mode 100644 devtools/client/scratchpad/test/browser_scratchpad_revert_to_saved.js delete mode 100644 devtools/client/scratchpad/test/browser_scratchpad_run_error_goto_line.js delete mode 100644 devtools/client/scratchpad/test/browser_scratchpad_sessions.js delete mode 100644 devtools/client/scratchpad/test/browser_scratchpad_tab.js delete mode 100644 devtools/client/scratchpad/test/browser_scratchpad_tab_switch.js delete mode 100644 devtools/client/scratchpad/test/browser_scratchpad_throw_output.js delete mode 100644 devtools/client/scratchpad/test/browser_scratchpad_ui.js delete mode 100644 devtools/client/scratchpad/test/browser_scratchpad_unsaved.js delete mode 100644 devtools/client/scratchpad/test/browser_scratchpad_wrong_window_focus.js delete mode 100644 devtools/client/scratchpad/test/head.js delete mode 100644 devtools/client/shared/sourceeditor/codemirror/addon/hint/show-hint.js delete mode 100644 devtools/client/shared/sourceeditor/codemirror/addon/tern/tern.css delete mode 100644 devtools/client/shared/sourceeditor/codemirror/addon/tern/tern.js create mode 100644 devtools/client/shared/sourceeditor/codemirror/mode/rust/rust.js create mode 100644 devtools/client/shared/sourceeditor/codemirror/mode/simple/simple.js delete mode 100644 devtools/client/shared/sourceeditor/tern/README delete mode 100644 devtools/client/shared/sourceeditor/tern/browser.js delete mode 100644 devtools/client/shared/sourceeditor/tern/comment.js delete mode 100644 devtools/client/shared/sourceeditor/tern/condense.js delete mode 100644 devtools/client/shared/sourceeditor/tern/def.js delete mode 100644 devtools/client/shared/sourceeditor/tern/ecma5.js delete mode 100644 devtools/client/shared/sourceeditor/tern/infer.js delete mode 100644 devtools/client/shared/sourceeditor/tern/signal.js delete mode 100644 devtools/client/shared/sourceeditor/tern/tern.js delete mode 100644 devtools/client/shared/sourceeditor/tern/tests/unit/head_tern.js delete mode 100644 devtools/client/shared/sourceeditor/tern/tests/unit/test_autocompletion.js delete mode 100644 devtools/client/shared/sourceeditor/tern/tests/unit/test_import_tern.js delete mode 100644 devtools/client/shared/sourceeditor/tern/tests/unit/xpcshell.ini delete mode 100644 devtools/client/shared/sourceeditor/test/browser_editor_autocomplete_js.js rename devtools/client/shared/sourceeditor/test/{head.xul => head.xhtml} (100%) create mode 100644 devtools/client/shared/test/browser_html_tooltip_screen_edge.js delete mode 100644 devtools/client/shared/test/browser_telemetry_button_scratchpad.js rename devtools/client/shared/test/{doc_html_tooltip-02.xul => doc_html_tooltip-02.xhtml} (100%) rename devtools/client/shared/test/{doc_html_tooltip-03.xul => doc_html_tooltip-03.xhtml} (75%) rename devtools/client/shared/test/{doc_html_tooltip-04.xul => doc_html_tooltip-04.xhtml} (100%) rename devtools/client/shared/test/{doc_html_tooltip-05.xul => doc_html_tooltip-05.xhtml} (100%) rename devtools/client/shared/test/{doc_html_tooltip.xul => doc_html_tooltip.xhtml} (100%) rename devtools/client/shared/test/{doc_html_tooltip_arrow-01.xul => doc_html_tooltip_arrow-01.xhtml} (100%) rename devtools/client/shared/test/{doc_html_tooltip_arrow-02.xul => doc_html_tooltip_arrow-02.xhtml} (100%) create mode 100644 devtools/client/shared/test/doc_html_tooltip_doorhanger-01.xhtml delete mode 100644 devtools/client/shared/test/doc_html_tooltip_doorhanger-01.xul rename devtools/client/shared/test/{doc_html_tooltip_doorhanger-02.xul => doc_html_tooltip_doorhanger-02.xhtml} (100%) rename devtools/client/shared/test/{doc_html_tooltip_hover.xul => doc_html_tooltip_hover.xhtml} (100%) rename devtools/client/shared/test/{doc_html_tooltip_rtl.xul => doc_html_tooltip_rtl.xhtml} (100%) rename devtools/client/shared/test/{doc_inplace-editor_autocomplete_offset.xul => doc_inplace-editor_autocomplete_offset.xhtml} (100%) rename devtools/client/shared/test/{doc_options-view.xul => doc_options-view.xhtml} (93%) rename devtools/client/shared/test/{doc_tableWidget_keyboard_interaction.xul => doc_tableWidget_keyboard_interaction.xhtml} (100%) rename devtools/client/shared/test/{doc_tableWidget_mouse_interaction.xul => doc_tableWidget_mouse_interaction.xhtml} (100%) create mode 100644 devtools/client/shared/thread-utils.js delete mode 100644 devtools/client/shared/widgets/VariablesView.xul rename devtools/client/storage/{index.xul => index.xhtml} (90%) create mode 100644 devtools/client/styleeditor/index.xhtml delete mode 100644 devtools/client/styleeditor/index.xul rename devtools/client/styleeditor/test/{doc_xulpage.xul => doc_xulpage.xhtml} (100%) delete mode 100644 devtools/client/themes/images/tool-scratchpad.svg create mode 100644 devtools/client/webconsole/test/browser/browser_webconsole_console_table_post_alterations.js delete mode 100644 devtools/client/webconsole/test/browser/browser_webconsole_location_scratchpad_link.js delete mode 100644 devtools/client/webconsole/test/browser/browser_webconsole_stacktrace_location_scratchpad_link.js create mode 100644 devtools/client/webconsole/test/node/babel.config.js create mode 100644 devtools/client/webconsole/test/node/yarn.lock delete mode 100644 devtools/server/actors/addon/webextension.js create mode 100644 devtools/server/actors/descriptors/webextension.js delete mode 100644 devtools/server/actors/object/long-string.js delete mode 100644 devtools/server/actors/promises.js delete mode 100644 devtools/server/actors/targets/webextension-proxy.js create mode 100644 devtools/server/connectors/content-process-connector.js create mode 100644 devtools/server/connectors/frame-connector.js rename devtools/{client/shared/sourceeditor/tern => server/connectors}/moz.build (53%) create mode 100644 devtools/server/connectors/worker-connector.js create mode 100644 devtools/server/performance-new/gecko-profiler-interface.js rename devtools/{client/scratchpad => server/performance-new}/moz.build (59%) delete mode 100644 devtools/server/tests/browser/browser_dbg_promises-allocation-stack.js delete mode 100644 devtools/server/tests/browser/browser_dbg_promises-chrome-allocation-stack.js delete mode 100644 devtools/server/tests/browser/browser_dbg_promises-fulfillment-stack.js delete mode 100644 devtools/server/tests/browser/browser_dbg_promises-rejection-stack.js create mode 100644 devtools/server/tests/browser/browser_storage_webext_storage_local.js delete mode 100644 devtools/server/tests/browser/doc_promise-get-allocation-stack.html delete mode 100644 devtools/server/tests/browser/doc_promise-get-fulfillment-stack.html delete mode 100644 devtools/server/tests/browser/doc_promise-get-rejection-stack.html rename devtools/{client/inspector/markup/test/doc_markup_anonymous_xul.xul => server/tests/browser/test-window.xhtml} (65%) create mode 100644 devtools/server/tests/mochitest/doc_Debugger.Source.prototype.introductionType.xhtml delete mode 100644 devtools/server/tests/mochitest/doc_Debugger.Source.prototype.introductionType.xul create mode 100644 devtools/server/tests/mochitest/inactive-property-helper/float.js create mode 100644 devtools/server/tests/unit/test_extension_storage_actor.js delete mode 100644 devtools/server/tests/unit/test_longstringactor.js delete mode 100644 devtools/server/tests/unit/test_promises_actor_attach.js delete mode 100644 devtools/server/tests/unit/test_promises_actor_list_promises.js delete mode 100644 devtools/server/tests/unit/test_promises_actor_onnewpromise.js delete mode 100644 devtools/server/tests/unit/test_promises_actor_onpromisesettled.js delete mode 100644 devtools/server/tests/unit/test_promises_client_getdependentpromises.js delete mode 100644 devtools/server/tests/unit/test_promises_object_creationtimestamp.js delete mode 100644 devtools/server/tests/unit/test_promises_object_timetosettle-01.js delete mode 100644 devtools/server/tests/unit/test_promises_object_timetosettle-02.js rename devtools/shared/fronts/{targets/addon.js => descriptors/webextension.js} (50%) delete mode 100644 devtools/shared/fronts/promises.js create mode 100644 devtools/shared/fronts/walker.js rename devtools/shared/specs/{addon => descriptors}/webextension.js (62%) delete mode 100644 devtools/shared/specs/promises.js delete mode 100644 devtools/shared/specs/targets/addon.js create mode 100644 devtools/shared/specs/walker.js create mode 100644 devtools/shared/tests/mochitest/test_css-logic-findCssSelector.html diff --git a/devtools/.eslintrc.js b/devtools/.eslintrc.js index 666e1b6791..0ade3f7112 100644 --- a/devtools/.eslintrc.js +++ b/devtools/.eslintrc.js @@ -21,8 +21,6 @@ module.exports = { } }, { "files": [ - "client/scratchpad/scratchpad-manager.jsm", - "client/scratchpad/scratchpad.js", "client/shared/*.jsm", ], "rules": { @@ -31,7 +29,6 @@ module.exports = { }, { "files": [ "client/framework/**", - "client/scratchpad/**", "client/shared/*.jsm", "client/shared/widgets/*.jsm", ], @@ -41,23 +38,13 @@ module.exports = { }, { "files": [ "client/framework/**", - "client/scratchpad/**", ], "rules": { "max-nested-callbacks": "off", } - }, { - "files": [ - "client/scratchpad/test/browser_scratchpad_inspect.js", - "client/scratchpad/test/browser_scratchpad_inspect_primitives.js", - ], - "rules": { - "no-labels": "off", - } }, { "files": [ "client/framework/**", - "client/scratchpad/**", "client/shared/*.jsm", "client/shared/widgets/*.jsm", ], @@ -67,7 +54,6 @@ module.exports = { }, { "files": [ "client/framework/test/**", - "client/scratchpad/**", ], "rules": { "mozilla/var-only-at-top-level": "off", @@ -75,7 +61,6 @@ module.exports = { }, { "files": [ "client/framework/**", - "client/scratchpad/**", "client/shared/widgets/*.jsm", ], "rules": { @@ -84,7 +69,6 @@ module.exports = { }, { "files": [ "client/framework/**", - "client/scratchpad/**", ], "rules": { "strict": "off", diff --git a/devtools/client/aboutdebugging/index.html b/devtools/client/aboutdebugging/index.html index 70d9fa7895..4e0f177ec5 100644 --- a/devtools/client/aboutdebugging/index.html +++ b/devtools/client/aboutdebugging/index.html @@ -7,7 +7,7 @@ Debugging + content="default-src chrome: resource:; img-src data: chrome: resource: https:; object-src 'none'" /> diff --git a/devtools/client/aboutdebugging/src/actions/runtimes.js b/devtools/client/aboutdebugging/src/actions/runtimes.js index bbc6cdcedc..5ab3ed096a 100644 --- a/devtools/client/aboutdebugging/src/actions/runtimes.js +++ b/devtools/client/aboutdebugging/src/actions/runtimes.js @@ -447,8 +447,7 @@ function updateRemoteRuntimes(runtimes, type) { runtimes.forEach(runtime => { const existingRuntime = findRuntimeById(runtime.id, getState().runtimes); const isConnectionValid = - existingRuntime && - existingRuntime.runtimeDetails && + existingRuntime?.runtimeDetails && !existingRuntime.runtimeDetails.clientWrapper.isClosed(); runtime.runtimeDetails = isConnectionValid ? existingRuntime.runtimeDetails diff --git a/devtools/client/aboutdebugging/src/components/App.js b/devtools/client/aboutdebugging/src/components/App.js index 973920da85..308d11d567 100644 --- a/devtools/client/aboutdebugging/src/components/App.js +++ b/devtools/client/aboutdebugging/src/components/App.js @@ -90,7 +90,7 @@ class App extends PureComponent { ...this.props.usbRuntimes, ]; const runtime = runtimes.find(x => x.id === id); - return runtime && runtime.runtimeDetails; + return runtime?.runtimeDetails; }; const { dispatch } = this.props; diff --git a/devtools/client/aboutdebugging/src/middleware/event-recording.js b/devtools/client/aboutdebugging/src/middleware/event-recording.js index c34558b04f..9d25a86a2c 100644 --- a/devtools/client/aboutdebugging/src/middleware/event-recording.js +++ b/devtools/client/aboutdebugging/src/middleware/event-recording.js @@ -58,9 +58,9 @@ function getRuntimeEventExtras(runtime) { const { extra, runtimeDetails } = runtime; // deviceName can be undefined for non-usb devices, but we should not log "undefined". - const deviceName = (extra && extra.deviceName) || ""; + const deviceName = extra?.deviceName || ""; const runtimeShortName = runtime.type === RUNTIMES.USB ? runtime.name : ""; - const runtimeName = (runtimeDetails && runtimeDetails.info.name) || ""; + const runtimeName = runtimeDetails?.info.name || ""; return { connection_type: runtime.type, device_name: deviceName, @@ -125,8 +125,7 @@ function onRemoteRuntimesUpdated(action, store) { const oldRuntime = oldRuntimes.find( r => r.extra.deviceName === oldDeviceName ); - const isUnplugged = - newRuntime && newRuntime.isUnplugged && !oldRuntime.isUnplugged; + const isUnplugged = newRuntime?.isUnplugged && !oldRuntime.isUnplugged; if (oldDeviceName && (!newRuntime || isUnplugged)) { recordEvent("device_removed", { connection_type: action.runtimeType, @@ -153,8 +152,7 @@ function onRemoteRuntimesUpdated(action, store) { const oldRuntime = oldRuntimes.find( r => r.extra.deviceName === newDeviceName ); - const isPlugged = - oldRuntime && oldRuntime.isUnplugged && !newRuntime.isUnplugged; + const isPlugged = oldRuntime?.isUnplugged && !newRuntime.isUnplugged; if (newDeviceName && (!oldRuntime || isPlugged)) { recordEvent("device_added", { diff --git a/devtools/client/aboutdebugging/test/browser/browser_aboutdebugging_addons_debug_popup.js b/devtools/client/aboutdebugging/test/browser/browser_aboutdebugging_addons_debug_popup.js index d0baee0f06..9881c18843 100644 --- a/devtools/client/aboutdebugging/test/browser/browser_aboutdebugging_addons_debug_popup.js +++ b/devtools/client/aboutdebugging/test/browser/browser_aboutdebugging_addons_debug_popup.js @@ -257,12 +257,6 @@ async function toolboxTestScript(toolbox, devtoolsTab) { const waitForNavigated = toolbox.target.once("navigate"); popupFrameBtn.click(); - // Clicking the menu item may do highlighting. - await waitUntil(() => toolbox.highlighter); - await Promise.race([ - toolbox.highlighter.once("node-highlight"), - wait(1000), - ]); await waitForNavigated; consoleWrapper.dispatchEvaluateExpression( "myWebExtensionPopupAddonFunction()" diff --git a/devtools/client/aboutdebugging/test/browser/browser_aboutdebugging_addons_warnings.js b/devtools/client/aboutdebugging/test/browser/browser_aboutdebugging_addons_warnings.js index d031891122..6f00208cbb 100644 --- a/devtools/client/aboutdebugging/test/browser/browser_aboutdebugging_addons_warnings.js +++ b/devtools/client/aboutdebugging/test/browser/browser_aboutdebugging_addons_warnings.js @@ -14,6 +14,7 @@ add_task(async function() { const { document, tab, window } = await openAboutDebugging(); await selectThisFirefoxPage(document, window.AboutDebugging.store); + await pushPref("extensions.webextensions.warnings-as-errors", false); await installTemporaryExtensionFromXPI( { id: EXTENSION_ID, @@ -25,6 +26,7 @@ add_task(async function() { }, document ); + await SpecialPowers.popPrefEnv(); info("Wait until a debug target item appears"); await waitUntil(() => findDebugTargetByText(EXTENSION_NAME, document)); diff --git a/devtools/client/aboutdebugging/test/browser/browser_aboutdebugging_message_close.js b/devtools/client/aboutdebugging/test/browser/browser_aboutdebugging_message_close.js index 9df0cb07a8..34a854d111 100644 --- a/devtools/client/aboutdebugging/test/browser/browser_aboutdebugging_message_close.js +++ b/devtools/client/aboutdebugging/test/browser/browser_aboutdebugging_message_close.js @@ -54,6 +54,7 @@ async function testCloseMessageWithButton(warningMessage, doc) { } async function installExtensionWithWarning(doc) { + await pushPref("extensions.webextensions.warnings-as-errors", false); await installTemporaryExtensionFromXPI( { id: EXTENSION_ID, @@ -65,6 +66,7 @@ async function installExtensionWithWarning(doc) { }, doc ); + await SpecialPowers.popPrefEnv(); info("Wait until a debug target item appears"); await waitUntil(() => findDebugTargetByText(EXTENSION_NAME, doc)); diff --git a/devtools/client/accessibility/accessibility-view.js b/devtools/client/accessibility/accessibility-view.js index 8b30aec23e..2de6a77a4d 100644 --- a/devtools/client/accessibility/accessibility-view.js +++ b/devtools/client/accessibility/accessibility-view.js @@ -3,7 +3,7 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ "use strict"; -/* global EVENTS, gToolbox */ +/* global EVENTS */ const nodeConstants = require("devtools/shared/dom-node-constants"); diff --git a/devtools/client/accessibility/accessibility.css b/devtools/client/accessibility/accessibility.css index c1046f1a01..5a4e94f726 100644 --- a/devtools/client/accessibility/accessibility.css +++ b/devtools/client/accessibility/accessibility.css @@ -4,7 +4,6 @@ :root { --accessibility-font-size: 12px; - --accessibility-toolbar-height: 24px; --accessibility-toolbar-height-tall: 35px; --accessibility-toolbar-focus: var(--blue-50); --accessibility-toolbar-focus-alpha30: rgba(10, 132, 255, 0.3); @@ -12,7 +11,12 @@ --accessibility-horizontal-padding: 5px; --accessibility-horizontal-indent: 20px; --accessibility-properties-item-width: calc(100% - var(--accessibility-horizontal-indent)); - --accessibility-tree-height: calc(100vh - var(--accessibility-toolbar-height) * 2 - 1px); + /* The main content can use the full height minus the height of the toolbar + (including 1px border bottom) */ + --accessibility-main-height: calc(100vh - var(--theme-toolbar-height) - 1px); + /* The tree can use the main content height minus the height of the tree + header, which has the same height as the toolbar and a 1px border bottom */ + --accessibility-tree-height: calc(var(--accessibility-main-height) - var(--theme-toolbar-height) - 1px); --accessibility-arrow-horizontal-padding: 4px; --accessibility-tree-row-height: 21px; --accessibility-unfocused-tree-focused-node-background: var(--grey-20); @@ -96,7 +100,7 @@ body { } .split-box.horz { - height: calc(100vh - var(--accessibility-toolbar-height)); + height: var(--accessibility-main-height); } .mainFrame .devtools-button, @@ -369,7 +373,7 @@ body { background: var(--theme-toolbar-background); font: message-box; font-size: var(--accessibility-font-size); - height: var(--accessibility-toolbar-height); + height: var(--theme-toolbar-height); color: var(--theme-toolbar-color); } @@ -452,14 +456,14 @@ body { .split-box:not(.horz) .right-sidebar { position: fixed; width: inherit; - height: calc(100vh - (var(--accessibility-toolbar-height))); + height: var(--accessibility-main-height) } .right-sidebar ._header { background-color: var(--theme-toolbar-background); border-bottom: 1px solid var(--theme-splitter-color); - height: var(--accessibility-toolbar-height); - line-height: var(--accessibility-toolbar-height); + height: var(--theme-toolbar-height); + line-height: var(--theme-toolbar-height); padding-inline-start: 14px; padding-inline-end: var(--accessibility-arrow-horizontal-padding); display: flex; @@ -683,7 +687,7 @@ body { margin: 0; font-weight: bold; font-size: var(--accessibility-font-size); - line-height: var(--accessibility-toolbar-height); + line-height: var(--theme-toolbar-height); } .accessibility-color-contrast-annotation { diff --git a/devtools/client/accessibility/components/Accessible.js b/devtools/client/accessibility/components/Accessible.js index b2d43ca652..72aab581e2 100644 --- a/devtools/client/accessibility/components/Accessible.js +++ b/devtools/client/accessibility/components/Accessible.js @@ -190,20 +190,22 @@ class Accessible extends Component { this.setState({ expanded }); } - showHighlighter(nodeFront) { + async showHighlighter(nodeFront) { if (!gToolbox) { return; } - gToolbox.highlighter.highlight(nodeFront); + const { highlighterFront } = nodeFront; + await highlighterFront.highlight(nodeFront); } - hideHighlighter() { + async hideHighlighter(nodeFront) { if (!gToolbox) { return; } - gToolbox.highlighter.unhighlight(); + const { highlighterFront } = nodeFront; + await highlighterFront.unhighlight(); } showAccessibleHighlighter(accessible) { @@ -286,7 +288,8 @@ class Accessible extends Component { if (isNode(object)) { valueProps.defaultRep = ElementNode; - valueProps.onDOMNodeMouseOut = () => this.hideHighlighter(); + valueProps.onDOMNodeMouseOut = () => + this.hideHighlighter(this.props.DOMNode); valueProps.onDOMNodeMouseOver = () => this.showHighlighter(this.props.DOMNode); valueProps.onInspectIconClick = () => this.selectNode(this.props.DOMNode); diff --git a/devtools/client/accessibility/panel.js b/devtools/client/accessibility/panel.js index a08997c3a3..1b48775634 100644 --- a/devtools/client/accessibility/panel.js +++ b/devtools/client/accessibility/panel.js @@ -248,16 +248,11 @@ AccessibilityPanel.prototype = { return this._toolbox.target; }, - async destroy() { - if (this._destroying) { - await this._destroying; + destroy() { + if (this._destroyed) { return; } - - let resolver; - this._destroying = new Promise(resolve => { - resolver = resolve; - }); + this._destroyed = true; this.target.off("navigate", this.onTabNavigated); this._toolbox.off("select", this.onPanelVisibilityChange); @@ -290,8 +285,6 @@ AccessibilityPanel.prototype = { this.panelWin.gTelemetry = null; this.emit("destroyed"); - - resolver(); }, }; diff --git a/devtools/client/accessibility/reducers/accessibles.js b/devtools/client/accessibility/reducers/accessibles.js index db5572409c..564f4959a5 100644 --- a/devtools/client/accessibility/reducers/accessibles.js +++ b/devtools/client/accessibility/reducers/accessibles.js @@ -38,7 +38,7 @@ function accessibles(state = getInitialState(), action) { } function getActorID(accessible) { - return accessible.actorID || (accessible._form && accessible._form.actor); + return accessible.actorID || accessible._form?.actor; } /** diff --git a/devtools/client/application/panel.js b/devtools/client/application/panel.js index a6c0dda8e5..2165d69f8e 100644 --- a/devtools/client/application/panel.js +++ b/devtools/client/application/panel.js @@ -38,8 +38,6 @@ class ApplicationPanel { this.panelWin = null; this.toolbox = null; this.emit("destroyed"); - - return this; } } diff --git a/devtools/client/application/test/components/fixtures/PluralForm.js b/devtools/client/application/test/components/fixtures/PluralForm.js deleted file mode 100644 index d629820a91..0000000000 --- a/devtools/client/application/test/components/fixtures/PluralForm.js +++ /dev/null @@ -1,11 +0,0 @@ -/* 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"; - -module.exports.PluralForm = { - get(num, str) { - return str; - }, -}; diff --git a/devtools/client/application/test/components/fixtures/fluent-l10n.js b/devtools/client/application/test/components/fixtures/fluent-l10n.js new file mode 100644 index 0000000000..186ca00342 --- /dev/null +++ b/devtools/client/application/test/components/fixtures/fluent-l10n.js @@ -0,0 +1,23 @@ +/* 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"; + +/** + * Mock for devtools/client/shared/modules/fluent-l10n/fluent-l10n + */ +class FluentL10n { + async init() {} + + getBundles() { + return []; + } + + getString(id, args) { + return args ? `${id}__${JSON.stringify(args)}` : id; + } +} + +// Export the class +exports.FluentL10n = FluentL10n; diff --git a/devtools/client/application/test/components/fixtures/l10n.js b/devtools/client/application/test/components/fixtures/l10n.js deleted file mode 100644 index e071465364..0000000000 --- a/devtools/client/application/test/components/fixtures/l10n.js +++ /dev/null @@ -1,23 +0,0 @@ -/* 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"; - -// @TODO Load the actual strings from instead. -class L10N { - getStr(str) { - switch (str) { - default: - return str; - } - } - - getFormatStr(str) { - return this.getStr(str); - } -} - -module.exports = { - L10N: new L10N(), -}; diff --git a/devtools/client/application/test/components/jest.config.js b/devtools/client/application/test/components/jest.config.js index 7a6ba0a327..c2814f249f 100644 --- a/devtools/client/application/test/components/jest.config.js +++ b/devtools/client/application/test/components/jest.config.js @@ -9,11 +9,10 @@ module.exports = { verbose: true, moduleNameMapper: { // Custom name mappers for modules that require m-c specific API. - "^../utils/l10n": `${__dirname}/fixtures/l10n`, "^devtools/client/shared/link": `${__dirname}/fixtures/stub`, - "^devtools/shared/plural-form": `${__dirname}/fixtures/plural-form`, "^chrome": `${__dirname}/fixtures/Chrome`, "^Services": `${__dirname}/fixtures/Services`, + "^devtools/client/shared/fluent-l10n/fluent-l10n$": `${__dirname}/fixtures/fluent-l10n`, "^devtools/client/shared/unicode-url": `${__dirname}/fixtures/unicode-url`, // Map all require("devtools/...") to the real devtools root. "^devtools\\/(.*)": `${__dirname}/../../../../$1`, diff --git a/devtools/client/debugger/.flowconfig b/devtools/client/debugger/.flowconfig index 115ce8fbc8..3860bff958 100644 --- a/devtools/client/debugger/.flowconfig +++ b/devtools/client/debugger/.flowconfig @@ -20,3 +20,4 @@ suppress_comment=\\(.\\|\n\\)*\\$FlowIgnore suppress_comment=\\(.\\|\n\\)*\\$FlowFixMe module.name_mapper='^debugger-html$' -> '/src/types.js' +esproposal.optional_chaining=enable diff --git a/devtools/client/debugger/babel.config.js b/devtools/client/debugger/babel.config.js index 0af5826c0a..38d22d2f6a 100644 --- a/devtools/client/debugger/babel.config.js +++ b/devtools/client/debugger/babel.config.js @@ -32,6 +32,8 @@ module.exports = { plugins: [ "@babel/plugin-transform-flow-strip-types", "@babel/plugin-proposal-class-properties", + "@babel/plugin-proposal-optional-chaining", + "@babel/plugin-proposal-nullish-coalescing-operator", [ "module-resolver", { diff --git a/devtools/client/debugger/bin/try-runner.js b/devtools/client/debugger/bin/try-runner.js index db0042e6b9..b77cc82503 100644 --- a/devtools/client/debugger/bin/try-runner.js +++ b/devtools/client/debugger/bin/try-runner.js @@ -75,8 +75,6 @@ function jest() { const jsonOut = out.substring(out.indexOf("{"), out.lastIndexOf("}") + 1); const results = JSON.parse(jsonOut); - const failed = results.numFailedTests == 0; - // The individual failing tests are in jammed into the same message string :/ const errors = [].concat( ...results.testResults.map(r => @@ -85,7 +83,7 @@ function jest() { ); logErrors("jest", errors); - return failed; + return errors.length == 0; } function stylelint() { diff --git a/devtools/client/debugger/dist/parser-worker.js b/devtools/client/debugger/dist/parser-worker.js index f2a57f0e93..5884640079 100644 --- a/devtools/client/debugger/dist/parser-worker.js +++ b/devtools/client/debugger/dist/parser-worker.js @@ -9175,7 +9175,11 @@ function getSpecifiers(specifiers) { return []; } - return specifiers.map(specifier => specifier.local && specifier.local.name); + return specifiers.map(specifier => { + var _specifier$local; + + return (_specifier$local = specifier.local) === null || _specifier$local === void 0 ? void 0 : _specifier$local.name; + }); } function isComputedExpression(expression) { @@ -9218,10 +9222,14 @@ function getVariables(dec) { // e.g. const [{a, b }] = 2 - return dec.id.elements.filter(element => element).map(element => ({ - name: t.isAssignmentPattern(element) ? element.left.name : element.name || element.argument && element.argument.name, - location: element.loc - })).filter(({ + return dec.id.elements.filter(element => element).map(element => { + var _element$argument; + + return { + name: t.isAssignmentPattern(element) ? element.left.name : element.name || ((_element$argument = element.argument) === null || _element$argument === void 0 ? void 0 : _element$argument.name), + location: element.loc + }; + }).filter(({ name }) => name); } @@ -15806,11 +15814,13 @@ function extractSymbols(sourceId) { } function extendSnippet(name, expression, path, prevPath) { - const computed = path && path.node.computed; - const prevComputed = prevPath && prevPath.node.computed; + var _path$node$property, _path$node$property$e; + + const computed = path === null || path === void 0 ? void 0 : path.node.computed; + const prevComputed = prevPath === null || prevPath === void 0 ? void 0 : prevPath.node.computed; const prevArray = t.isArrayExpression(prevPath); const array = t.isArrayExpression(path); - const value = path && path.node.property && path.node.property.extra && path.node.property.extra.raw || ""; + const value = (path === null || path === void 0 ? void 0 : (_path$node$property = path.node.property) === null || _path$node$property === void 0 ? void 0 : (_path$node$property$e = _path$node$property.extra) === null || _path$node$property$e === void 0 ? void 0 : _path$node$property$e.raw) || ""; if (expression === "") { if (computed) { @@ -15868,6 +15878,8 @@ function getMemberSnippet(node, expression = "") { } function getObjectSnippet(path, prevPath, expression = "") { + var _path$parentPath; + if (!path) { return expression; } @@ -15875,11 +15887,13 @@ function getObjectSnippet(path, prevPath, expression = "") { const name = path.node.key.name; const extendedExpression = extendSnippet(name, expression, path, prevPath); const nextPrevPath = path; - const nextPath = path.parentPath && path.parentPath.parentPath; + const nextPath = (_path$parentPath = path.parentPath) === null || _path$parentPath === void 0 ? void 0 : _path$parentPath.parentPath; return getSnippet(nextPath, nextPrevPath, extendedExpression); } function getArraySnippet(path, prevPath, expression) { + var _path$parentPath2; + if (!prevPath.parentPath) { throw new Error("Assertion failure - path should exist"); } @@ -15887,7 +15901,7 @@ function getArraySnippet(path, prevPath, expression) { const index = `${prevPath.parentPath.containerIndex}`; const extendedExpression = extendSnippet(index, expression, path, prevPath); const nextPrevPath = path; - const nextPath = path.parentPath && path.parentPath.parentPath; + const nextPath = (_path$parentPath2 = path.parentPath) === null || _path$parentPath2 === void 0 ? void 0 : _path$parentPath2.parentPath; return getSnippet(nextPath, nextPrevPath, extendedExpression); } @@ -42469,8 +42483,6 @@ Object.defineProperty(exports, "__esModule", { }); exports.default = void 0; -var _get = _interopRequireDefault(__webpack_require__(161)); - var _findIndex = _interopRequireDefault(__webpack_require__(354)); var _findLastIndex = _interopRequireDefault(__webpack_require__(371)); @@ -42502,11 +42514,13 @@ function findSymbols(source) { function getLocation(func) { + var _func$identifier, _func$identifier$loc; + const location = { ...func.location }; // if the function has an identifier, start the block after it so the // identifier is included in the "scope" of its parent - const identifierEnd = (0, _get.default)(func, "identifier.loc.end"); + const identifierEnd = func === null || func === void 0 ? void 0 : (_func$identifier = func.identifier) === null || _func$identifier === void 0 ? void 0 : (_func$identifier$loc = _func$identifier.loc) === null || _func$identifier$loc === void 0 ? void 0 : _func$identifier$loc.end; if (identifierEnd) { location.start = identifierEnd; diff --git a/devtools/client/debugger/flow-typed/npm/codemirror_vx.x.x.js b/devtools/client/debugger/flow-typed/npm/codemirror_vx.x.x.js index bb6ffa651d..c10d10e760 100644 --- a/devtools/client/debugger/flow-typed/npm/codemirror_vx.x.x.js +++ b/devtools/client/debugger/flow-typed/npm/codemirror_vx.x.x.js @@ -122,10 +122,6 @@ declare module 'codemirror/addon/hint/javascript-hint' { declare module.exports: any; } -declare module 'codemirror/addon/hint/show-hint' { - declare module.exports: any; -} - declare module 'codemirror/addon/hint/sql-hint' { declare module.exports: any; } @@ -246,14 +242,6 @@ declare module 'codemirror/addon/selection/selection-pointer' { declare module.exports: any; } -declare module 'codemirror/addon/tern/tern' { - declare module.exports: any; -} - -declare module 'codemirror/addon/tern/worker' { - declare module.exports: any; -} - declare module 'codemirror/addon/wrap/hardwrap' { declare module.exports: any; } @@ -1090,9 +1078,6 @@ declare module 'codemirror/addon/hint/html-hint.js' { declare module 'codemirror/addon/hint/javascript-hint.js' { declare module.exports: $Exports<'codemirror/addon/hint/javascript-hint'>; } -declare module 'codemirror/addon/hint/show-hint.js' { - declare module.exports: $Exports<'codemirror/addon/hint/show-hint'>; -} declare module 'codemirror/addon/hint/sql-hint.js' { declare module.exports: $Exports<'codemirror/addon/hint/sql-hint'>; } @@ -1183,12 +1168,6 @@ declare module 'codemirror/addon/selection/mark-selection.js' { declare module 'codemirror/addon/selection/selection-pointer.js' { declare module.exports: $Exports<'codemirror/addon/selection/selection-pointer'>; } -declare module 'codemirror/addon/tern/tern.js' { - declare module.exports: $Exports<'codemirror/addon/tern/tern'>; -} -declare module 'codemirror/addon/tern/worker.js' { - declare module.exports: $Exports<'codemirror/addon/tern/worker'>; -} declare module 'codemirror/addon/wrap/hardwrap.js' { declare module.exports: $Exports<'codemirror/addon/wrap/hardwrap'>; } diff --git a/devtools/client/debugger/jest-test.config.js b/devtools/client/debugger/jest-test.config.js index b2efabcc6d..33150e5a87 100644 --- a/devtools/client/debugger/jest-test.config.js +++ b/devtools/client/debugger/jest-test.config.js @@ -40,5 +40,8 @@ module.exports = { moduleNameMapper: { "\\.css$": "/src/test/__mocks__/styleMock.js", "\\.svg$": "/src/test/__mocks__/svgMock.js", + "^Services": "/src/test/fixtures/Services", + // Map all require("devtools/...") to the real devtools root. + "^devtools\\/(.*)": "/../../$1", }, }; diff --git a/devtools/client/debugger/package.json b/devtools/client/debugger/package.json index babf3b18ea..dace19115c 100644 --- a/devtools/client/debugger/package.json +++ b/devtools/client/debugger/package.json @@ -102,6 +102,8 @@ "@babel/plugin-proposal-class-properties": "^7.5.5", "@babel/plugin-transform-flow-strip-types": "^7.4.4", "@babel/plugin-transform-modules-commonjs": "^7.5.0", + "@babel/plugin-proposal-nullish-coalescing-operator": "^7.8.3", + "@babel/plugin-proposal-optional-chaining": "^7.8.3", "@babel/preset-env": "^7.5.5", "@babel/preset-react": "^7.0.0", "@babel/register": "^7.0.0", diff --git a/devtools/client/debugger/packages/devtools-reps/src/reps/document.js b/devtools/client/debugger/packages/devtools-reps/src/reps/document.js index f2d8fb46f6..a2e6ad9a74 100644 --- a/devtools/client/debugger/packages/devtools-reps/src/reps/document.js +++ b/devtools/client/debugger/packages/devtools-reps/src/reps/document.js @@ -55,7 +55,7 @@ function supportsObject(object, noGrip = false) { } const type = getGripType(object, noGrip); - return object.preview && (type === "HTMLDocument" || type === "XULDocument"); + return object.preview && type === "HTMLDocument"; } // Exports from this module diff --git a/devtools/client/debugger/packages/devtools-reps/src/reps/stubs/document.js b/devtools/client/debugger/packages/devtools-reps/src/reps/stubs/document.js index ba7b7c208c..9a898c0dc2 100644 --- a/devtools/client/debugger/packages/devtools-reps/src/reps/stubs/document.js +++ b/devtools/client/debugger/packages/devtools-reps/src/reps/stubs/document.js @@ -34,21 +34,4 @@ stubs.set("Location-less Document", { }, }); -stubs.set("XULDocument", { - type: "object", - actor: "server1.conn0.obj434", - class: "XULDocument", - extensible: true, - frozen: false, - sealed: false, - ownPropertyLength: 1, - preview: { - kind: "DOMNode", - nodeType: 9, - nodeName: "#document", - isConnected: true, - location: "chrome://browser/content/browser.xul", - }, -}); - module.exports = stubs; diff --git a/devtools/client/debugger/packages/devtools-reps/src/reps/tests/document.js b/devtools/client/debugger/packages/devtools-reps/src/reps/tests/document.js index b4e5575ffa..add38dc643 100644 --- a/devtools/client/debugger/packages/devtools-reps/src/reps/tests/document.js +++ b/devtools/client/debugger/packages/devtools-reps/src/reps/tests/document.js @@ -39,23 +39,3 @@ describe("Document", () => { expect(renderedComponent.text()).toEqual("HTMLDocument"); }); }); - -describe("XULDocument", () => { - const stub = stubs.get("XULDocument"); - it("correctly selects Document Rep", () => { - expect(getRep(stub)).toBe(Document.rep); - }); - - it("renders with expected text content", () => { - const renderedComponent = shallow( - Document.rep({ - object: stub, - }) - ); - - expect(renderedComponent.text()).toEqual( - "XULDocument chrome://browser/content/browser.xul" - ); - expectActorAttribute(renderedComponent, stub.actor); - }); -}); diff --git a/devtools/client/debugger/panel.js b/devtools/client/debugger/panel.js index 77c56ee087..483341eaa7 100644 --- a/devtools/client/debugger/panel.js +++ b/devtools/client/debugger/panel.js @@ -27,8 +27,9 @@ async function getNodeFront(gripOrFront, toolbox) { if ("actorID" in gripOrFront) { return new Promise(resolve => resolve(gripOrFront)); } - // Given a grip - return toolbox.walker.gripToNodeFront(gripOrFront); + + const inspectorFront = await toolbox.target.getFront("inspector"); + return inspectorFront.getNodeFrontFromNodeGrip(gripOrFront); } DebuggerPanel.prototype = { @@ -103,7 +104,6 @@ DebuggerPanel.prototype = { }, openElementInInspector: async function(gripOrFront) { - await this.toolbox.initInspector(); const onSelectInspector = this.toolbox.selectTool("inspector"); const onGripNodeToFront = getNodeFront(gripOrFront, this.toolbox); @@ -121,18 +121,18 @@ DebuggerPanel.prototype = { }, highlightDomElement: async function(gripOrFront) { - await this.toolbox.initInspector(); - if (!this.toolbox.highlighter) { - return null; - } - const front = await getNodeFront(gripOrFront, this.toolbox); - return this.toolbox.highlighter.highlight(front); + const nodeFront = await getNodeFront(gripOrFront, this.toolbox); + nodeFront.highlighterFront.highlight(nodeFront); }, - unHighlightDomElement: function() { - return this.toolbox.highlighter - ? this.toolbox.highlighter.unhighlight(false) - : null; + unHighlightDomElement: async function(gripOrFront) { + try { + const nodeFront = await getNodeFront(gripOrFront, this.toolbox); + nodeFront.highlighterFront.unhighlight(); + } catch (e) { + // This call might fail if called asynchrously after the toolbox is finished + // closing. + } }, getFrames: function() { diff --git a/devtools/client/debugger/src/actions/breakpoints/syncBreakpoint.js b/devtools/client/debugger/src/actions/breakpoints/syncBreakpoint.js index 4e12c3863d..857a6c6bdd 100644 --- a/devtools/client/debugger/src/actions/breakpoints/syncBreakpoint.js +++ b/devtools/client/debugger/src/actions/breakpoints/syncBreakpoint.js @@ -42,7 +42,7 @@ async function findBreakpointPosition( ); const position = findPosition(positions, location); - return position && position.generatedLocation; + return position?.generatedLocation; } async function findNewLocation( diff --git a/devtools/client/debugger/src/actions/pause/inlinePreview.js b/devtools/client/debugger/src/actions/pause/inlinePreview.js index f2bc1e24a1..cb3f2c511f 100644 --- a/devtools/client/debugger/src/actions/pause/inlinePreview.js +++ b/devtools/client/debugger/src/actions/pause/inlinePreview.js @@ -56,8 +56,7 @@ export function generateInlinePreview(cx: ThreadContext, frame: ?Frame) { ); let scopes: ?OriginalScope | Scope | null = - (originalFrameScopes && originalFrameScopes.scope) || - (generatedFrameScopes && generatedFrameScopes.scope); + originalFrameScopes?.scope || generatedFrameScopes?.scope; if (!scopes || !scopes.bindings) { return; @@ -144,8 +143,7 @@ function getBindingValues( ): Array { const previews = []; - const binding = - originalAstScopes[curLevel] && originalAstScopes[curLevel].bindings[name]; + const binding = originalAstScopes[curLevel]?.bindings[name]; if (!binding) { return previews; } @@ -211,13 +209,9 @@ function getExpressionNameAndValue( const property: Object = properties.find( prop => prop.name === meta.property ); - displayValue = property && property.contents.value; + displayValue = property?.contents.value; displayName += `.${meta.property}`; - } else if ( - displayValue && - displayValue.preview && - displayValue.preview.ownProperties - ) { + } else if (displayValue?.preview?.ownProperties) { const { ownProperties } = displayValue.preview; Object.keys(ownProperties).forEach(prop => { if (prop === meta.property) { diff --git a/devtools/client/debugger/src/actions/pause/mapFrames.js b/devtools/client/debugger/src/actions/pause/mapFrames.js index 15f31c1ded..022707241f 100644 --- a/devtools/client/debugger/src/actions/pause/mapFrames.js +++ b/devtools/client/debugger/src/actions/pause/mapFrames.js @@ -24,7 +24,7 @@ import SourceMaps, { isGeneratedId } from "devtools-source-map"; function isFrameBlackboxed(state, frame) { const source = getSource(state, frame.location.sourceId); - return source && source.isBlackBoxed; + return source?.isBlackBoxed; } function getSelectedFrameId(state, thread, frames) { @@ -34,7 +34,7 @@ function getSelectedFrameId(state, thread, frames) { } selectedFrame = frames.find(frame => !isFrameBlackboxed(state, frame)); - return selectedFrame && selectedFrame.id; + return selectedFrame?.id; } export function updateFrameLocation( @@ -105,7 +105,7 @@ function isWasmOriginalSourceFrame(frame, getState: () => State): boolean { frame.generatedLocation.sourceId ); - return Boolean(generatedSource && generatedSource.isWasm); + return Boolean(generatedSource?.isWasm); } async function expandFrames( diff --git a/devtools/client/debugger/src/actions/pause/tests/pause.spec.js b/devtools/client/debugger/src/actions/pause/tests/pause.spec.js index 58faa6f7e6..31a21bd970 100644 --- a/devtools/client/debugger/src/actions/pause/tests/pause.spec.js +++ b/devtools/client/debugger/src/actions/pause/tests/pause.spec.js @@ -247,8 +247,8 @@ describe("pause", () => { }, }, }, - mappings: { "1": null }, - original: { "1": { pending: false, scope: null } }, + mappings: { "1": undefined }, + original: { "1": { pending: false, scope: undefined } }, }); expect( diff --git a/devtools/client/debugger/src/actions/sources/loadSourceText.js b/devtools/client/debugger/src/actions/sources/loadSourceText.js index 85af914e7f..87793238a4 100644 --- a/devtools/client/debugger/src/actions/sources/loadSourceText.js +++ b/devtools/client/debugger/src/actions/sources/loadSourceText.js @@ -74,18 +74,32 @@ async function loadSource( return result; } - const actors = getSourceActorsForSource(state, source.id); - if (!actors.length) { - throw new Error("No source actor for loadSource"); + // We only need the source text from one actor, but messages sent to retrieve + // the source might fail if the actor has or is about to shut down. Keep + // trying with different actors until one request succeeds. + let response; + const handledActors = new Set(); + while (true) { + const actors = getSourceActorsForSource(state, source.id); + const actor = actors.find(({ actor: a }) => !handledActors.has(a)); + if (!actor) { + throw new Error("Unknown source"); + } + handledActors.add(actor.actor); + + try { + telemetry.start(loadSourceHistogram, source); + response = await client.sourceContents(actor); + telemetry.finish(loadSourceHistogram, source); + break; + } catch (e) { + console.warn(`sourceContents failed: ${e}`); + } } - telemetry.start(loadSourceHistogram, source); - const response = await client.sourceContents(actors[0]); - telemetry.finish(loadSourceHistogram, source); - return { - text: response.source, - contentType: response.contentType || "text/javascript", + text: (response: any).source, + contentType: (response: any).contentType || "text/javascript", }; } diff --git a/devtools/client/debugger/src/actions/sources/tests/loadSource.spec.js b/devtools/client/debugger/src/actions/sources/tests/loadSource.spec.js index 0e83284d92..de1a0746d3 100644 --- a/devtools/client/debugger/src/actions/sources/tests/loadSource.spec.js +++ b/devtools/client/debugger/src/actions/sources/tests/loadSource.spec.js @@ -268,7 +268,7 @@ describe("loadSourceText", () => { : null; expect( content && isRejected(content) && typeof content.value === "string" - ? content.value.indexOf("unknown source") + ? content.value.indexOf("Unknown source") : -1 ).not.toBe(-1); }); diff --git a/devtools/client/debugger/src/actions/types/index.js b/devtools/client/debugger/src/actions/types/index.js index 14e69fc02e..11383480b2 100644 --- a/devtools/client/debugger/src/actions/types/index.js +++ b/devtools/client/debugger/src/actions/types/index.js @@ -5,7 +5,13 @@ // @flow import typeof SourceMaps from "devtools-source-map"; -import type { WorkerList, MainThread, Context, ThreadId } from "../../types"; +import type { + WorkerList, + MainThread, + Context, + ThreadId, + SourceLocation, +} from "../../types"; import type { State } from "../../reducers/types"; import type { MatchedLocations } from "../../reducers/file-search"; import type { TreeNode } from "../../utils/sources-tree/types"; @@ -152,6 +158,13 @@ export type DebuggeeAction = +type: "SELECT_THREAD", +cx: Context, +thread: ThreadId, + |} + | {| + +type: "PREVIEW_PAUSED_LOCATION", + +location: SourceLocation, + |} + | {| + +type: "CLEAR_PREVIEW_PAUSED_LOCATION", |}; export type { diff --git a/devtools/client/debugger/src/actions/utils/middleware/log.js b/devtools/client/debugger/src/actions/utils/middleware/log.js index 2fc60d672b..010b956795 100644 --- a/devtools/client/debugger/src/actions/utils/middleware/log.js +++ b/devtools/client/debugger/src/actions/utils/middleware/log.js @@ -28,7 +28,7 @@ function cloneAction(action: any) { action = { ...action }; // ADD_TAB, ... - if (action.source && action.source.text) { + if (action.source?.text) { const source = { ...action.source, text: "" }; action.source = source; } diff --git a/devtools/client/debugger/src/actions/utils/middleware/timing.js b/devtools/client/debugger/src/actions/utils/middleware/timing.js index a021ca894c..84661efb57 100644 --- a/devtools/client/debugger/src/actions/utils/middleware/timing.js +++ b/devtools/client/debugger/src/actions/utils/middleware/timing.js @@ -9,15 +9,13 @@ * will appear in performance tooling under the User Timing API */ -const mark = - window.performance && window.performance.mark - ? window.performance.mark.bind(window.performance) - : a => {}; +const mark = window.performance?.mark + ? window.performance.mark.bind(window.performance) + : a => {}; -const measure = - window.performance && window.performance.measure - ? window.performance.measure.bind(window.performance) - : (a, b, c) => {}; +const measure = window.performance?.measure + ? window.performance.measure.bind(window.performance) + : (a, b, c) => {}; export function timing(store: any) { return (next: any) => (action: any) => { diff --git a/devtools/client/debugger/src/client/firefox/commands.js b/devtools/client/debugger/src/client/firefox/commands.js index 4f9fcf484e..1cc08c4939 100644 --- a/devtools/client/debugger/src/client/firefox/commands.js +++ b/devtools/client/debugger/src/client/firefox/commands.js @@ -107,7 +107,7 @@ function lookupThreadFront(thread: string) { } function listWorkerThreadFronts() { - return (Object.values(workerTargets): any).map(target => target.threadFront); + return (Object.values(workerTargets): any).map(target => target.threadFront).filter(t => !!t); } function forEachThread(iteratee) { @@ -452,10 +452,11 @@ async function getSourceActorBreakableLines({ thread, actor, }: SourceActor): Promise> { - const sourceThreadFront = lookupThreadFront(thread); - const sourceFront = sourceThreadFront.source({ actor }); + let sourceFront; let actorLines = []; try { + const sourceThreadFront = lookupThreadFront(thread); + sourceFront = sourceThreadFront.source({ actor }); actorLines = await sourceFront.getBreakableLines(); } catch (e) { // Handle backward compatibility @@ -463,10 +464,11 @@ async function getSourceActorBreakableLines({ e.message && e.message.match(/does not recognize the packet type getBreakableLines/) ) { - const pos = await sourceFront.getBreakpointPositionsCompressed(); + const pos = await (sourceFront: any).getBreakpointPositionsCompressed(); actorLines = Object.keys(pos).map(line => Number(line)); - } else if (!e.message || !e.message.match(/Connection closed/)) { - throw e; + } else { + // Other exceptions could be due to the target thread being shut down. + console.warn(`getSourceActorBreakableLines failed: ${e}`); } } diff --git a/devtools/client/debugger/src/client/firefox/types.js b/devtools/client/debugger/src/client/firefox/types.js index 76340d2e1e..7f72d3e261 100644 --- a/devtools/client/debugger/src/client/firefox/types.js +++ b/devtools/client/debugger/src/client/firefox/types.js @@ -165,7 +165,6 @@ export type TabPayload = { performanceActor: ActorId, performanceEntriesActor: ActorId, profilerActor: ActorId, - promisesActor: ActorId, reflowActor: ActorId, storageActor: ActorId, styleEditorActor: ActorId, diff --git a/devtools/client/debugger/src/client/firefox/workers.js b/devtools/client/debugger/src/client/firefox/workers.js index a8296d347f..1611772c9b 100644 --- a/devtools/client/debugger/src/client/firefox/workers.js +++ b/devtools/client/debugger/src/client/firefox/workers.js @@ -7,6 +7,9 @@ import { addThreadEventListeners } from "./events"; import type { TabTarget } from "./types"; +// $FlowIgnore +const { defaultThreadOptions } = require("devtools/client/shared/thread-utils"); + export function supportsWorkers(tabTarget: TabTarget) { return tabTarget.isBrowsingContext || tabTarget.isContentProcess; } @@ -32,7 +35,10 @@ export async function updateWorkerTargets({ if (workerTargets[threadActorID]) { newWorkerTargets[threadActorID] = workerTargets[threadActorID]; } else { - const [, workerThread] = await workerTargetFront.attachThread(options); + const [, workerThread] = await workerTargetFront.attachThread({ + ...defaultThreadOptions(), + ...args.options, + }); workerThread.resume(); addThreadEventListeners(workerThread); diff --git a/devtools/client/debugger/src/components/Editor/ConditionalPanel.js b/devtools/client/debugger/src/components/Editor/ConditionalPanel.js index 6626f5ed30..789f736ab3 100644 --- a/devtools/client/debugger/src/components/Editor/ConditionalPanel.js +++ b/devtools/client/debugger/src/components/Editor/ConditionalPanel.js @@ -201,7 +201,7 @@ export class ConditionalPanel extends PureComponent { getDefaultValue() { const { breakpoint, log } = this.props; - const options = (breakpoint && breakpoint.options) || {}; + const options = breakpoint?.options || {}; return log ? options.logValue : options.condition; } diff --git a/devtools/client/debugger/src/components/Editor/DebugLine.js b/devtools/client/debugger/src/components/Editor/DebugLine.js index f57b117de5..5c431e182e 100644 --- a/devtools/client/debugger/src/components/Editor/DebugLine.js +++ b/devtools/client/debugger/src/components/Editor/DebugLine.js @@ -22,11 +22,11 @@ import { getCurrentThread, } from "../../selectors"; -import type { Frame, Why, SourceWithContent } from "../../types"; +import type { SourceLocation, Why, SourceWithContent } from "../../types"; type OwnProps = {||}; type Props = { - frame: ?Frame, + location: ?SourceLocation, why: ?Why, source: ?SourceWithContent, }; @@ -36,46 +36,47 @@ type TextClasses = { lineClass: string, }; -function isDocumentReady(source: ?SourceWithContent, frame: ?Frame) { - return ( - frame && source && source.content && hasDocument(frame.location.sourceId) - ); +function isDocumentReady( + source: ?SourceWithContent, + location: ?SourceLocation +) { + return location && source && source.content && hasDocument(location.sourceId); } export class DebugLine extends PureComponent { debugExpression: null; componentDidMount() { - const { why, frame, source } = this.props; - this.setDebugLine(why, frame, source); + const { why, location, source } = this.props; + this.setDebugLine(why, location, source); } componentWillUnmount() { - const { why, frame, source } = this.props; - this.clearDebugLine(why, frame, source); + const { why, location, source } = this.props; + this.clearDebugLine(why, location, source); } componentDidUpdate(prevProps: Props) { - const { why, frame, source } = this.props; + const { why, location, source } = this.props; startOperation(); - this.clearDebugLine(prevProps.why, prevProps.frame, prevProps.source); - this.setDebugLine(why, frame, source); + this.clearDebugLine(prevProps.why, prevProps.location, prevProps.source); + this.setDebugLine(why, location, source); endOperation(); } setDebugLine( why: ?Why, - frame: ?Frame, + location: ?SourceLocation, source: ?SourceWithContent ) { - if (!frame || !isDocumentReady(source, frame)) { + if (!location || !isDocumentReady(source, location)) { return; } - const { sourceId } = frame.location; + const { sourceId } = location; const doc = getDocument(sourceId); - let { line, column } = toEditorPosition(frame.location); + let { line, column } = toEditorPosition(location); let { markTextClass, lineClass } = this.getTextClasses(why); doc.addLineClass(line, "line", lineClass); @@ -97,8 +98,12 @@ export class DebugLine extends PureComponent { ); } - clearDebugLine(why: ?Why, frame: ?Frame, source: ?SourceWithContent) { - if (!frame || !isDocumentReady(source, frame)) { + clearDebugLine( + why: ?Why, + location: ?SourceLocation, + source: ?SourceWithContent + ) { + if (!location || !isDocumentReady(source, location)) { return; } @@ -106,14 +111,14 @@ export class DebugLine extends PureComponent { this.debugExpression.clear(); } - const { line } = toEditorPosition(frame.location); + const { line } = toEditorPosition(location); const doc = getDocument(location.sourceId); const { lineClass } = this.getTextClasses(why); doc.removeLineClass(line, "line", lineClass); } getTextClasses(why: ?Why): TextClasses { - if (isException(why)) { + if (why && isException(why)) { return { markTextClass: "debug-expression-error", lineClass: "new-debug-line-error", @@ -130,9 +135,10 @@ export class DebugLine extends PureComponent { const mapStateToProps = state => { const frame = getVisibleSelectedFrame(state); + const location = frame?.location; return { - frame, - source: frame && getSourceWithContent(state, frame.location.sourceId), + location, + source: location && getSourceWithContent(state, location.sourceId), why: getPauseReason(state, getCurrentThread(state)), }; }; diff --git a/devtools/client/debugger/src/components/Editor/Footer.js b/devtools/client/debugger/src/components/Editor/Footer.js index 8538a5b3aa..77d810ab1f 100644 --- a/devtools/client/debugger/src/components/Editor/Footer.js +++ b/devtools/client/debugger/src/components/Editor/Footer.js @@ -134,7 +134,7 @@ class SourceFooter extends PureComponent { blackBoxButton() { const { cx, selectedSource, toggleBlackBox } = this.props; - const sourceLoaded = selectedSource && selectedSource.content; + const sourceLoaded = selectedSource?.content; if (!selectedSource) { return; diff --git a/devtools/client/debugger/src/components/Editor/SearchBar.js b/devtools/client/debugger/src/components/Editor/SearchBar.js index 6ad5e79af8..db85e8c88f 100644 --- a/devtools/client/debugger/src/components/Editor/SearchBar.js +++ b/devtools/client/debugger/src/components/Editor/SearchBar.js @@ -262,7 +262,7 @@ class SearchBar extends Component { function SearchModBtn({ modVal, className, svgName, tooltip }) { const preppedClass = classnames(className, { - active: modifiers && modifiers[modVal], + active: modifiers?.[modVal], }); return ( - -
-
Overhead:
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - -
-
-
- -
-

Settings

-
-

Interval:

- - - 1 ms - -

Window length:

- - - 20 sec - -

Buffer size:

- - - 90 MB - -
- Threads: -
-
-
-
-
-
-
-
-
-
- Features: -
- -
-
-
-
-
- - - - diff --git a/devtools/client/performance-new/popup/popup.js b/devtools/client/performance-new/popup/popup.js deleted file mode 100644 index fe8b7c23f9..0000000000 --- a/devtools/client/performance-new/popup/popup.js +++ /dev/null @@ -1,387 +0,0 @@ -/* 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 background = - ChromeUtils.import("resource://devtools/client/performance-new/popup/background.jsm"); - -const intervalScale = makeExponentialScale(0.01, 100); -const buffersizeScale = makeExponentialScale(10000, 100000000); -// Window Length accepts a numerical value between 1-N. We also need to put an -// infinite number at the end of the window length slider. Therefore, the max -// value pretends like it's infinite in the slider. -// The maximum value of window length is 300 seconds. For that reason, we are -// treating 400 as infinity. -const infiniteWindowLength = 400; -const windowLengthScale = makeExponentialScale(1, infiniteWindowLength); - -const PROFILE_ENTRY_SIZE = 9; // sizeof(double) + sizeof(char), http://searchfox.org/mozilla-central/rev/e8835f52eff29772a57dca7bcc86a9a312a23729/tools/profiler/core/ProfileEntry.h#73 - -const featurePrefix = "perf-settings-feature-checkbox-"; -const features = [ - "java", - "js", - "leaf", - "mainthreadio", - "privacy", - "responsiveness", - "screenshots", - "seqstyle", - "stackwalk", - "tasktracer", - "jstracer", - "jsallocations", - "trackopts", -]; -const threadPrefix = "perf-settings-thread-checkbox-"; -// A map of element ID suffixes to their corresponding profile thread name, for -// creating ID -> name mappings, e.g.`$threadPrefix}dom-worker` - DOM Worker. -const threadMap = { - compositor: "Compositor", - "dns-resolver": "DNS Resolver", - "dom-worker": "DOM Worker", - "gecko-main": "GeckoMain", - "img-decoder": "ImgDecoder", - "paint-worker": "PaintWorker", - "render-backend": "RenderBackend", - renderer: "Renderer", - "socket-thread": "Socket Thread", - "stream-trans": "StreamTrans", - "style-thread": "StyleThread", -}; - -function initializePopup() { - const updateIsRunning = () => { - renderState(background.state); - }; - background.isRunningObserver.addObserver(updateIsRunning); - - window.addEventListener("unload", () => { - background.isRunningObserver.removeObserver(updateIsRunning); - }); - - for (const name of features) { - setupFeatureCheckbox(name); - } - - for (const name in threadMap) { - setupThreadCheckbox(name); - } - - document - .querySelector("#perf-settings-thread-text") - .addEventListener("change", async e => { - background.adjustState({ threads: e.target.value }); - renderState(background.state); - }); - - document - .querySelector(".settings-apply-button") - .addEventListener("click", async () => { - background.restartProfiler(); - }); - - document.querySelector(".button-start").addEventListener("click", async () => { - background.startProfiler(); - background.adjustState({ isRunning: true }); - renderState(background.state); - }); - - document.querySelector(".button-cancel").addEventListener("click", async () => { - background.stopProfiler(); - background.adjustState({ isRunning: false }); - renderState(background.state); - }); - - document - .querySelector("#button-capture") - .addEventListener("click", async () => { - if (document.documentElement.classList.contains("status-running")) { - await background.captureProfile(); - window.gClosePopup(); - } - }); - - document - .querySelector("#settings-label") - .addEventListener("click", async () => { - const { settingsOpen } = background.state; - background.adjustState({ - settingsOpen: !settingsOpen, - }); - renderState(background.state); - }); - - document.querySelector(".interval-range").addEventListener("input", async e => { - const frac = e.target.value / 100; - background.adjustState({ - interval: intervalScale.fromFractionToSingleDigitValue(frac), - }); - renderState(background.state); - }); - - document - .querySelector(".buffersize-range") - .addEventListener("input", async e => { - const frac = e.target.value / 100; - background.adjustState({ - buffersize: buffersizeScale.fromFractionToSingleDigitValue(frac), - }); - renderState(background.state); - }); - - document - .querySelector(".windowlength-range") - .addEventListener("input", async e => { - const frac = e.target.value / 100; - background.adjustState({ - windowLength: windowLengthScale.fromFractionToSingleDigitValue(frac), - }); - renderState(background.state); - }); - - window.onload = () => { - // Letting the background script know how the infiniteWindowLength is represented. - background.adjustState({ - infiniteWindowLength, - }); - }; - - renderState(background.state); -} - -function renderState(state) { - const { isRunning, settingsOpen, interval, buffersize, windowLength } = state; - document.documentElement.classList.toggle("status-running", isRunning); - document.documentElement.classList.toggle("status-stopped", !isRunning); - document.querySelector(".settings").classList.toggle("open", settingsOpen); - document.querySelector(".interval-value").textContent = `${interval} ms`; - document.querySelector(".buffersize-value").textContent = prettyBytes( - buffersize * PROFILE_ENTRY_SIZE - ); - document.querySelector(".windowlength-value").textContent = windowLength === - infiniteWindowLength - ? `∞` - : `${windowLength} sec`; - const overhead = calculateOverhead(state); - const overheadDiscreteContainer = document.querySelector(".discrete-level"); - for (let i = 0; i < overheadDiscreteContainer.children.length; i++) { - const discreteLevelNotch = overheadDiscreteContainer.children[i]; - const isActive = - i <= - Math.round(overhead * (overheadDiscreteContainer.children.length - 1)); - discreteLevelNotch.classList.toggle("active", isActive); - discreteLevelNotch.classList.toggle("inactive", !isActive); - } - - renderControls(state); - - window.requestAnimationFrame(() => { - if (window.gResizePopup) { - window.gResizePopup(document.body.clientHeight); - } - }); -} - -function renderControls(state) { - document.querySelector(".interval-range").value = - intervalScale.fromValueToFraction(state.interval) * 100; - document.querySelector(".buffersize-range").value = - buffersizeScale.fromValueToFraction(state.buffersize) * 100; - document.querySelector(".windowlength-range").value = - windowLengthScale.fromValueToFraction(state.windowLength) * 100; - - for (const name of features) { - document.getElementById(featurePrefix + name).value = state[name]; - } - - for (const name in threadMap) { - document.getElementById( - threadPrefix + name - ).checked = state.threads.includes(threadMap[name]); - } - - document.querySelector("#perf-settings-thread-text").value = state.threads; -} - -function clamp(val, min, max) { - return Math.max(min, Math.min(max, val)); -} - -function lerp(frac, rangeStart, rangeEnd) { - return (1 - frac) * rangeStart + frac * rangeEnd; -} - -function scaleRangeWithClamping( - val, - sourceRangeStart, - sourceRangeEnd, - destRangeStart, - destRangeEnd -) { - const frac = clamp( - (val - sourceRangeStart) / (sourceRangeEnd - sourceRangeStart), - 0, - 1 - ); - return lerp(frac, destRangeStart, destRangeEnd); -} - -function calculateOverhead(state) { - const overheadFromSampling = - scaleRangeWithClamping( - Math.log(state.interval), - Math.log(0.05), - Math.log(1), - 1, - 0 - ) + - scaleRangeWithClamping( - Math.log(state.interval), - Math.log(1), - Math.log(100), - 0.1, - 0 - ); - const overheadFromBuffersize = scaleRangeWithClamping( - Math.log(state.buffersize), - Math.log(10), - Math.log(1000000), - 0, - 0.1 - ); - const overheadFromStackwalk = state.stackwalk ? 0.05 : 0; - const overheadFromResponsiveness = state.responsiveness ? 0.05 : 0; - const overheadFromJavaScrpt = state.js ? 0.05 : 0; - const overheadFromSeqStyle = state.seqstyle ? 0.05 : 0; - const overheadFromTaskTracer = state.tasktracer ? 0.05 : 0; - const overheadFromJSTracer = state.jstracer ? 0.05 : 0; - const overheadFromJSAllocations = state.jsallocations ? 0.05 : 0; - return clamp( - overheadFromSampling + - overheadFromBuffersize + - overheadFromStackwalk + - overheadFromResponsiveness + - overheadFromJavaScrpt + - overheadFromSeqStyle + - overheadFromTaskTracer + - overheadFromJSTracer + - overheadFromJSAllocations, - 0, - 1 - ); -} - -/** - * This helper initializes and adds listeners to the features checkboxes that - * will adjust the profiler state when changed. - */ -async function setupFeatureCheckbox(name) { - // Java profiling is only meaningful on android. - if (name == "java") { - if (background.platform !== "android") { - document.querySelector("#java").style.display = "none"; - return; - } - } - - const checkbox = document.querySelector(`#${featurePrefix}${name}`); - checkbox.checked = background.state.features[name]; - - checkbox.addEventListener("change", async e => { - const newFeatures = Object.assign({}, background.state.features); - newFeatures[name] = e.target.checked; - background.adjustState({ features: newFeatures }); - renderState(background.state); - }); -} - -/** - * This helper initializes and adds listeners to the threads checkboxes that - * will adjust the profiler state when changed. - */ -async function setupThreadCheckbox(name) { - const checkbox = document.querySelector(`#${threadPrefix}${name}`); - checkbox.checked = background.state.threads.includes(threadMap[name]); - - checkbox.addEventListener("change", async e => { - let threads = background.state.threads; - if (e.target.checked) { - threads += "," + e.target.value; - } else { - threads = threadTextToList(threads) - .filter(thread => thread !== e.target.value) - .join(","); - } - background.adjustState({ threads }); - renderState(background.state); - }); -} - -/** - * Clean up the thread list string into a list of values. - * @param string threads, comma separated values. - * @return Array list of thread names - */ -function threadTextToList(threads) { - return ( - threads - // Split on commas - .split(",") - // Clean up any extraneous whitespace - .map(string => string.trim()) - // Filter out any blank strings - .filter(string => string) - ); -} - -function makeExponentialScale(rangeStart, rangeEnd) { - const startExp = Math.log(rangeStart); - const endExp = Math.log(rangeEnd); - const fromFractionToValue = frac => - Math.exp((1 - frac) * startExp + frac * endExp); - const fromValueToFraction = value => - (Math.log(value) - startExp) / (endExp - startExp); - const fromFractionToSingleDigitValue = frac => { - return +fromFractionToValue(frac).toPrecision(1); - }; - return { - fromFractionToValue, - fromValueToFraction, - fromFractionToSingleDigitValue, - }; -} - -const UNITS = ["B", "kB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"]; - -function prettyBytes(num) { - if (!Number.isFinite(num)) { - throw new TypeError( - `Expected a finite number, got ${typeof num}: ${num}` - ); - } - - const neg = num < 0; - - if (neg) { - num = -num; - } - - if (num < 1) { - return (neg ? "-" : "") + num + " B"; - } - - const exponent = Math.min( - Math.floor(Math.log(num) / Math.log(1000)), - UNITS.length - 1 - ); - const numStr = Number((num / Math.pow(1000, exponent)).toPrecision(3)); - const unit = UNITS[exponent]; - - return (neg ? "-" : "") + numStr + " " + unit; -} - -module.exports = { - initializePopup, -}; diff --git a/devtools/client/performance-new/popup/popup.xhtml b/devtools/client/performance-new/popup/popup.xhtml new file mode 100644 index 0000000000..cc12354f21 --- /dev/null +++ b/devtools/client/performance-new/popup/popup.xhtml @@ -0,0 +1,24 @@ + + + %htmlDTD; +]> + + + + + + + + +
+ + + + diff --git a/devtools/client/performance-new/store/actions.js b/devtools/client/performance-new/store/actions.js index 69f3bb9d94..f6262ce52d 100644 --- a/devtools/client/performance-new/store/actions.js +++ b/devtools/client/performance-new/store/actions.js @@ -12,10 +12,6 @@ const { REQUEST_TO_STOP_PROFILER, }, } = require("devtools/client/performance-new/utils"); -const { OS } = require("resource://gre/modules/osfile.jsm"); -const { - ProfilerGetSymbols, -} = require("resource://gre/modules/ProfilerGetSymbols.jsm"); /** * The recording state manages the current state of the recording panel. @@ -131,181 +127,34 @@ exports.startRecording = () => { return (dispatch, getState) => { const recordingSettings = selectors.getRecordingSettings(getState()); const perfFront = selectors.getPerfFront(getState()); - perfFront.startProfiler(recordingSettings); + // In the case of the profiler popup, the startProfiler can be synchronous. + // In order to properly allow the React components to handle the state changes + // make sure and change the recording state first, then start the profiler. dispatch(changeRecordingState(REQUEST_TO_START_RECORDING)); + perfFront.startProfiler(recordingSettings); }; }; -/** - * Returns a function getDebugPathFor(debugName, breakpadId) => Library which - * resolves a (debugName, breakpadId) pair to the library's information, which - * contains the absolute paths on the file system where the binary and its - * optional pdb file are stored. - * - * This is needed for the following reason: - * - In order to obtain a symbol table for a system library, we need to know - * the library's absolute path on the file system. On Windows, we - * additionally need to know the absolute path to the library's PDB file, - * which we call the binary's "debugPath". - * - Symbol tables are requested asynchronously, by the profiler UI, after the - * profile itself has been obtained. - * - When the symbol tables are requested, we don't want the profiler UI to - * pass us arbitrary absolute file paths, as an extra defense against - * potential information leaks. - * - Instead, when the UI requests symbol tables, it identifies the library - * with a (debugName, breakpadId) pair. We need to map that pair back to the - * absolute paths. - * - We get the "trusted" paths from the "libs" sections of the profile. We - * trust these paths because we just obtained the profile directly from - * Gecko. - * - This function builds the (debugName, breakpadId) => Library mapping and - * retains it on the returned closure so that it can be consulted after the - * profile has been passed to the UI. - * - * @param {object} profile - The profile JSON object - */ -function createLibraryMap(profile) { - const map = new Map(); - function fillMapForProcessRecursive(processProfile) { - for (const lib of processProfile.libs) { - const { debugName, breakpadId } = lib; - const key = [debugName, breakpadId].join(":"); - map.set(key, lib); - } - for (const subprocess of processProfile.processes) { - fillMapForProcessRecursive(subprocess); - } - } - - fillMapForProcessRecursive(profile); - return function getLibraryFor(debugName, breakpadId) { - const key = [debugName, breakpadId].join(":"); - return map.get(key); - }; -} - -async function getSymbolTableFromDebuggee(perfFront, path, breakpadId) { - const [addresses, index, buffer] = await perfFront.getSymbolTable( - path, - breakpadId - ); - // The protocol transmits these arrays as plain JavaScript arrays of - // numbers, but we want to pass them on as typed arrays. Convert them now. - return [ - new Uint32Array(addresses), - new Uint32Array(index), - new Uint8Array(buffer), - ]; -} - -async function doesFileExistAtPath(path) { - try { - const result = await OS.File.stat(path); - return !result.isDir; - } catch (e) { - if (e instanceof OS.File.Error && e.becauseNoSuchFile) { - return false; - } - throw e; - } -} - -/** - * Retrieve a symbol table from a binary on the host machine, by looking up - * relevant build artifacts in the specified objdirs. - * This is needed if the debuggee is a build running on a remote machine that - * was compiled by the developer on *this* machine (the "host machine"). In - * that case, the objdir will contain the compiled binary with full symbol and - * debug information, whereas the binary on the device may not exist in - * uncompressed form or may have been stripped of debug information and some - * symbol information. - * An objdir, or "object directory", is a directory on the host machine that's - * used to store build artifacts ("object files") from the compilation process. - * - * @param {array of string} objdirs An array of objdir paths on the host machine - * that should be searched for relevant build artifacts. - * @param {string} filename The file name of the binary. - * @param {string} breakpadId The breakpad ID of the binary. - * @returns {Promise} The symbol table of the first encountered binary with a - * matching breakpad ID, in SymbolTableAsTuple format. An exception is thrown (the - * promise is rejected) if nothing was found. - */ -async function getSymbolTableFromLocalBinary(objdirs, filename, breakpadId) { - const candidatePaths = []; - for (const objdirPath of objdirs) { - // Binaries are usually expected to exist at objdir/dist/bin/filename. - candidatePaths.push(OS.Path.join(objdirPath, "dist", "bin", filename)); - // Also search in the "objdir" directory itself (not just in dist/bin). - // If, for some unforeseen reason, the relevant binary is not inside the - // objdirs dist/bin/ directory, this provides a way out because it lets the - // user specify the actual location. - candidatePaths.push(OS.Path.join(objdirPath, filename)); - } - - for (const path of candidatePaths) { - if (await doesFileExistAtPath(path)) { - try { - return await ProfilerGetSymbols.getSymbolTable(path, path, breakpadId); - } catch (e) { - // ProfilerGetSymbols.getSymbolTable was unsuccessful. So either the - // file wasn't parseable or its contents didn't match the specified - // breakpadId, or some other error occurred. - // Advance to the next candidate path. - } - } - } - throw new Error("Could not find any matching binary."); -} - /** * Stops the profiler, and opens the profile in a new window. + * @param {object} window - The current window for the page. */ -exports.getProfileAndStopProfiler = () => { +exports.getProfileAndStopProfiler = window => { return async (dispatch, getState) => { const perfFront = selectors.getPerfFront(getState()); dispatch(changeRecordingState(REQUEST_TO_GET_PROFILE_AND_STOP_PROFILER)); const profile = await perfFront.getProfileAndStopProfiler(); - const libraryGetter = createLibraryMap(profile); - async function getSymbolTable(debugName, breakpadId) { - const { name, path, debugPath } = libraryGetter(debugName, breakpadId); - if (await doesFileExistAtPath(path)) { - // This profile was obtained from this machine, and not from a - // different device (e.g. an Android phone). Dump symbols from the file - // on this machine directly. - return ProfilerGetSymbols.getSymbolTable(path, debugPath, breakpadId); - } - // The file does not exist, which probably indicates that the profile was - // obtained on a different machine, i.e. the debuggee is truly remote - // (e.g. on an Android phone). - try { - // First, try to find a binary with a matching file name and breakpadId - // on the host machine. This works if the profiled build is a developer - // build that has been compiled on this machine, and if the binary is - // one of the Gecko binaries and not a system library. - // The other place where we could obtain symbols is the debuggee device; - // that's handled in the catch branch below. - // We check the host machine first, because if this is a developer - // build, then the objdir files will contain more symbol information - // than the files that get pushed to the device. - const objdirs = selectors.getObjdirs(getState()); - return await getSymbolTableFromLocalBinary(objdirs, name, breakpadId); - } catch (e) { - // No matching file was found on the host machine. - // Try to obtain the symbol table on the debuggee. We get into this - // branch in the following cases: - // - Android system libraries - // - Firefox binaries that have no matching equivalent on the host - // machine, for example because the user didn't point us at the - // corresponding objdir, or if the build was compiled somewhere - // else, or if the build on the device is outdated. - // For now, this path is not used on Windows, which is why we don't - // need to pass the library's debugPath. - return getSymbolTableFromDebuggee(perfFront, path, breakpadId); - } + if (window.gClosePopup) { + // The close popup function only exists when we are in the popup. + window.gClosePopup(); } - selectors.getReceiveProfileFn(getState())(profile, getSymbolTable); + const getSymbolTable = selectors.getSymbolTableGetter(getState())(profile); + const receiveProfile = selectors.getReceiveProfileFn(getState()); + + receiveProfile(profile, getSymbolTable); + dispatch(changeRecordingState(AVAILABLE_TO_RECORD)); }; }; diff --git a/devtools/client/performance-new/store/reducers.js b/devtools/client/performance-new/store/reducers.js index 751cb3c469..34c9728ae8 100644 --- a/devtools/client/performance-new/store/reducers.js +++ b/devtools/client/performance-new/store/reducers.js @@ -137,6 +137,9 @@ function objdirs(state = [], action) { * perfFront - The current Front to the Perf actor. * receiveProfile - A function to receive the profile and open it into a new window. * setRecordingPreferences - A function to set the recording settings. + * isPopup - A boolean value that sets lets the UI know if it is in the popup window + * or inside of devtools. + * getSymbolTableGetter - Run this function to get the getSymbolTable function. * } */ function initializedValues(state = null, action) { @@ -146,6 +149,8 @@ function initializedValues(state = null, action) { perfFront: action.perfFront, receiveProfile: action.receiveProfile, setRecordingPreferences: action.setRecordingPreferences, + isPopup: Boolean(action.isPopup), + getSymbolTableGetter: action.getSymbolTableGetter, }; default: return state; diff --git a/devtools/client/performance-new/store/selectors.js b/devtools/client/performance-new/store/selectors.js index 587ac09c0b..e21d1b8e29 100644 --- a/devtools/client/performance-new/store/selectors.js +++ b/devtools/client/performance-new/store/selectors.js @@ -36,6 +36,9 @@ const getPerfFront = state => getInitializedValues(state).perfFront; const getReceiveProfileFn = state => getInitializedValues(state).receiveProfile; const getSetRecordingPreferencesFn = state => getInitializedValues(state).setRecordingPreferences; +const getIsPopup = state => getInitializedValues(state).isPopup; +const getSymbolTableGetter = state => + getInitializedValues(state).getSymbolTableGetter; module.exports = { getRecordingState, @@ -52,4 +55,6 @@ module.exports = { getPerfFront, getReceiveProfileFn, getSetRecordingPreferencesFn, + getIsPopup, + getSymbolTableGetter, }; diff --git a/devtools/client/performance-new/test/browser/.eslintrc.js b/devtools/client/performance-new/test/browser/.eslintrc.js new file mode 100644 index 0000000000..db91f10466 --- /dev/null +++ b/devtools/client/performance-new/test/browser/.eslintrc.js @@ -0,0 +1,9 @@ +/* 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 = { + // Extend from the shared list of defined globals for mochitests. + 'extends': '../../../../.eslintrc.mochitests.js' +}; diff --git a/devtools/client/performance-new/test/browser/browser.ini b/devtools/client/performance-new/test/browser/browser.ini new file mode 100644 index 0000000000..bdd57968e3 --- /dev/null +++ b/devtools/client/performance-new/test/browser/browser.ini @@ -0,0 +1,8 @@ +[DEFAULT] +tags = devtools devtools-performance +subsuite = devtools +support-files = + head.js + fake-frontend.html + +[browser_popup-end-to-end-click.js] diff --git a/devtools/client/performance-new/test/browser/browser_popup-end-to-end-click.js b/devtools/client/performance-new/test/browser/browser_popup-end-to-end-click.js new file mode 100644 index 0000000000..61f4b2fad7 --- /dev/null +++ b/devtools/client/performance-new/test/browser/browser_popup-end-to-end-click.js @@ -0,0 +1,40 @@ +/* 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"; + +add_task(async function test() { + info( + "Test that the profiler pop-up works end to end with profile recording and " + + "capture using the mouse and hitting buttons." + ); + await setProfilerFrontendUrl( + "http://example.com/browser/devtools/client/performance-new/test/browser/fake-frontend.html" + ); + await makeSureProfilerPopupIsEnabled(); + toggleOpenProfilerPopup(); + + { + const button = await getElementFromPopupByText("Start recording"); + info("Click the button to start recording."); + button.click(); + } + + { + const button = await getElementFromPopupByText("Capture recording"); + info("Click the button to capture the recording."); + button.click(); + } + + info( + "If the profiler successfully injects a profile into the page, then the " + + "fake frontend will rename the title of the page." + ); + + await checkTabLoadedProfile({ + initialTitle: "Waiting on the profile", + successTitle: "Profile received", + errorTitle: "Error", + }); +}); diff --git a/devtools/client/performance-new/test/browser/fake-frontend.html b/devtools/client/performance-new/test/browser/fake-frontend.html new file mode 100644 index 0000000000..3839d1b442 --- /dev/null +++ b/devtools/client/performance-new/test/browser/fake-frontend.html @@ -0,0 +1,67 @@ + + + + + + + + + + + diff --git a/devtools/client/performance-new/test/browser/head.js b/devtools/client/performance-new/test/browser/head.js new file mode 100644 index 0000000000..6dac97b1a2 --- /dev/null +++ b/devtools/client/performance-new/test/browser/head.js @@ -0,0 +1,215 @@ +/* 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"; + +/** + * Wait for a single requestAnimationFrame tick. + */ +function tick() { + return new Promise(resolve => requestAnimationFrame(resolve)); +} + +/** + * It can be confusing when waiting for something asynchronously. This function + * logs out a message periodically (every 1 second) in order to create helpful + * log messages. + * @param {string} message + * @returns {Function} + */ +function createPeriodicLogger() { + let startTime = Date.now(); + let lastCount = 0; + let lastMessage = null; + + return message => { + if (lastMessage === message) { + // The messages are the same, check if we should log them. + const now = Date.now(); + const count = Math.floor((now - startTime) / 1000); + if (count !== lastCount) { + info( + `${message} (After ${count} ${count === 1 ? "second" : "seconds"})` + ); + lastCount = count; + } + } else { + // The messages are different, log them now, and reset the waiting time. + info(message); + startTime = Date.now(); + lastCount = 0; + lastMessage = message; + } + }; +} + +/** + * Wait until a condition is fullfilled. + * @param {Function} condition + * @param {string?} logMessage + * @return The truthy result of the condition. + */ +async function waitUntil(condition, message) { + const logPeriodically = createPeriodicLogger(); + + // Loop through the condition. + while (true) { + if (message) { + logPeriodically(message); + } + const result = condition(); + if (result) { + return result; + } + + await tick(); + } +} + +/** + * This function will select a node from the XPath. + * @returns {HTMLElement?} + */ +function getElementByXPath(document, path) { + return document.evaluate( + path, + document, + null, + XPathResult.FIRST_ORDERED_NODE_TYPE, + null + ).singleNodeValue; +} + +/** + * This function looks inside of the profiler popup's iframe for some element + * that contains some text. It runs in a loop every requestAnimationFrame until + * it finds an element. If it doesn't find the element it throws an error. + * It also doesn't assume the popup will be visible yet, as this popup showing + * is an async event. + * @param {string} text + * @param {number} maxTicks (optional) + */ +async function getElementFromPopupByText(text) { + const xpath = `//*[contains(text(), '${text}')]`; + return waitUntil(() => { + const iframe = document.getElementById("PanelUI-profilerIframe"); + if (iframe) { + return getElementByXPath(iframe.contentDocument, xpath); + } + return null; + }, `Trying to find the element with the text "${text}".`); +} + +/** + * Make sure the profiler popup is enabled. + */ +async function makeSureProfilerPopupIsEnabled() { + info("Make sure the profiler popup is enabled."); + + info("> Load the profiler menu button."); + const { ProfilerMenuButton } = ChromeUtils.import( + "resource://devtools/client/performance-new/popup/menu-button.jsm" + ); + + if (!ProfilerMenuButton.isEnabled()) { + info("> The menu button is not enabled, turn it on."); + ProfilerMenuButton.toggle(document); + + await waitUntil( + () => gBrowser.ownerDocument.getElementById("profiler-button"), + "> Waiting until the profiler button is added to the browser." + ); + + await SimpleTest.promiseFocus(gBrowser.ownerGlobal); + + registerCleanupFunction(() => { + info( + "Clean up after the test by disabling the profiler popup menu button." + ); + if (!ProfilerMenuButton.isEnabled()) { + throw new Error( + "Expected the profiler popup to still be enabled during the test cleanup." + ); + } + ProfilerMenuButton.toggle(document); + }); + } else { + info("> The menu button was already enabled."); + } +} + +/** + * This function toggles the profiler menu button, and then uses user gestures + * to click it open. + */ +function toggleOpenProfilerPopup() { + info("Toggle open the profiler popup."); + + info("> Find the profiler menu button."); + const profilerButton = document.getElementById("profiler-button"); + if (!profilerButton) { + throw new Error("Could not find the profiler button in the menu."); + } + + info("> Trigger a click on the profiler menu button."); + profilerButton.click(); +} + +/** + * This function overwrites the default profiler.firefox.com URL for tests. This + * ensures that the tests do not attempt to access external URLs. + * @param {string} url + * @returns {Promise} + */ +function setProfilerFrontendUrl(url) { + info("Setting the profiler URL to the fake frontend."); + return SpecialPowers.pushPrefEnv({ + set: [ + // Make sure observer and testing function run in the same process + ["devtools.performance.recording.ui-base-url", url], + ["devtools.performance.recording.ui-base-url-path", ""], + ], + }); +} + +/** + * This function checks the document title of a tab to see what the state is. + * This creates a simple messaging mechanism between the content page and the + * test harness. This function runs in a loop every requestAnimationFrame, and + * checks for a sucess title. In addition, an "initialTitle" and "errorTitle" + * can be specified for nicer test output. + * @param {Object} + * { + * initialTitle: string, + * successTitle: string, + * errorTitle: string + * } + */ +async function checkTabLoadedProfile({ + initialTitle, + successTitle, + errorTitle, +}) { + const logPeriodically = createPeriodicLogger(); + + info("Attempting to see if the selected tab can receive a profile."); + + return waitUntil(() => { + switch (gBrowser.selectedTab.textContent) { + case initialTitle: + logPeriodically(`> Waiting for the profile to be received.`); + return false; + case successTitle: + ok(true, "The profile was successfully injected to the page"); + BrowserTestUtils.removeTab(gBrowser.selectedTab); + return true; + case errorTitle: + throw new Error( + "The fake frontend indicated that there was an error injecting the profile." + ); + default: + logPeriodically(`> Waiting for the fake frontend tab to be loaded.`); + return false; + } + }); +} diff --git a/devtools/client/performance-new/test/chrome/head.js b/devtools/client/performance-new/test/chrome/head.js index 95d0b616f4..c69095d140 100644 --- a/devtools/client/performance-new/test/chrome/head.js +++ b/devtools/client/performance-new/test/chrome/head.js @@ -202,6 +202,8 @@ function createPerfComponent() { recordingPreferencesCalls.push(settings); } + const noop = () => {}; + function mountComponent() { store.dispatch( actions.initializeStore({ @@ -211,6 +213,7 @@ function createPerfComponent() { store.getState() ), setRecordingPreferences: recordingPreferencesMock, + getSymbolTableGetter: () => noop, }) ); diff --git a/devtools/client/performance-new/test/xpcshell/head.js b/devtools/client/performance-new/test/xpcshell/head.js new file mode 100644 index 0000000000..e0032240a4 --- /dev/null +++ b/devtools/client/performance-new/test/xpcshell/head.js @@ -0,0 +1,3 @@ +/* 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/. */ diff --git a/devtools/client/performance-new/test/xpcshell/test_popup_initial_state.js b/devtools/client/performance-new/test/xpcshell/test_popup_initial_state.js new file mode 100644 index 0000000000..cc7839069e --- /dev/null +++ b/devtools/client/performance-new/test/xpcshell/test_popup_initial_state.js @@ -0,0 +1,67 @@ +/* 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"; + +/** + * Tests the initial state of the background script for the popup. + */ + +function setupBackgroundJsm() { + const background = ChromeUtils.import( + "resource://devtools/client/performance-new/popup/background.jsm" + ); + return background; +} + +add_task(function test() { + info("Test that we get the default values from state."); + const { + getState, + revertPrefs, + DEFAULT_BUFFER_SIZE, + DEFAULT_STACKWALK_FEATURE, + } = setupBackgroundJsm().forTestsOnly; + + Assert.equal( + getState().buffersize, + DEFAULT_BUFFER_SIZE, + "The initial state has the default buffersize." + ); + Assert.equal( + getState().features.stackwalk, + DEFAULT_STACKWALK_FEATURE, + "The stackwalk feature is initialized to the default." + ); + revertPrefs(); +}); + +add_task(function test() { + info("Test that the state and features are properly validated."); + const { + getState, + adjustState, + revertPrefs, + initializeState, + DEFAULT_STACKWALK_FEATURE, + } = setupBackgroundJsm().forTestsOnly; + + info("Manipulate the state."); + const state = getState(); + state.features.stackwalk = !DEFAULT_STACKWALK_FEATURE; + state.features.UNKNOWN_FEATURE_FOR_TESTS = true; + adjustState(state); + adjustState(initializeState()); + + Assert.equal( + getState().features.UNKNOWN_FEATURE_FOR_TESTS, + undefined, + "The unknown feature is removed." + ); + Assert.equal( + getState().features.stackwalk, + !DEFAULT_STACKWALK_FEATURE, + "The stackwalk preference is still flipped from the default." + ); + revertPrefs(); +}); diff --git a/devtools/client/performance-new/test/xpcshell/xpcshell.ini b/devtools/client/performance-new/test/xpcshell/xpcshell.ini new file mode 100644 index 0000000000..98a7c4c0fe --- /dev/null +++ b/devtools/client/performance-new/test/xpcshell/xpcshell.ini @@ -0,0 +1,6 @@ +[DEFAULT] +tags = devtools +head = head.js +firefox-appdir = browser + +[test_popup_initial_state.js] diff --git a/devtools/client/performance/index.xul b/devtools/client/performance/index.xhtml similarity index 99% rename from devtools/client/performance/index.xul rename to devtools/client/performance/index.xhtml index c833260d0e..1d48e034fd 100644 --- a/devtools/client/performance/index.xul +++ b/devtools/client/performance/index.xhtml @@ -2,7 +2,7 @@ - + diff --git a/devtools/client/performance/modules/logic/frame-utils.js b/devtools/client/performance/modules/logic/frame-utils.js index f928839cf2..5cfc2b1a9d 100644 --- a/devtools/client/performance/modules/logic/frame-utils.js +++ b/devtools/client/performance/modules/logic/frame-utils.js @@ -381,8 +381,7 @@ function isNumeric(c) { function shouldDemangle(name) { return ( - name && - name.charCodeAt && + name?.charCodeAt && name.charCodeAt(0) === CHAR_CODE_UNDERSCORE && name.charCodeAt(1) === CHAR_CODE_UNDERSCORE && name.charCodeAt(2) === CHAR_CODE_CAP_Z @@ -450,7 +449,7 @@ function getFrameInfo(node, options) { // if it does not. const totalSamples = options.root.samples; const totalDuration = options.root.duration; - if (options && options.root && !data.COSTS_CALCULATED) { + if (options?.root && !data.COSTS_CALCULATED) { data.selfDuration = (node.youngestFrameSamples / totalSamples) * totalDuration; data.selfPercentage = (node.youngestFrameSamples / totalSamples) * 100; @@ -459,7 +458,7 @@ function getFrameInfo(node, options) { data.COSTS_CALCULATED = true; } - if (options && options.allocations && !data.ALLOCATION_DATA_CALCULATED) { + if (options?.allocations && !data.ALLOCATION_DATA_CALCULATED) { const totalBytes = options.root.byteSize; data.selfCount = node.youngestFrameSamples; data.totalCount = node.samples; diff --git a/devtools/client/performance/modules/marker-dom-utils.js b/devtools/client/performance/modules/marker-dom-utils.js index 3ed220ddf5..275c4aeed0 100644 --- a/devtools/client/performance/modules/marker-dom-utils.js +++ b/devtools/client/performance/modules/marker-dom-utils.js @@ -43,16 +43,16 @@ exports.MarkerDOMUtils = { buildTitle: function(doc, marker) { const blueprint = MarkerBlueprintUtils.getBlueprintFor(marker); - const hbox = doc.createElement("hbox"); + const hbox = doc.createXULElement("hbox"); hbox.setAttribute("align", "center"); - const bullet = doc.createElement("hbox"); + const bullet = doc.createXULElement("hbox"); bullet.className = `marker-details-bullet marker-color-${ blueprint.colorName }`; const title = MarkerBlueprintUtils.getMarkerLabel(marker); - const label = doc.createElement("label"); + const label = doc.createXULElement("label"); label.className = "marker-details-type"; label.setAttribute("value", title); @@ -95,15 +95,15 @@ exports.MarkerDOMUtils = { * @return Node */ buildNameValueLabel: function(doc, field, value) { - const hbox = doc.createElement("hbox"); + const hbox = doc.createXULElement("hbox"); hbox.className = "marker-details-labelcontainer"; - const nameLabel = doc.createElement("label"); + const nameLabel = doc.createXULElement("label"); nameLabel.className = "plain marker-details-name-label"; nameLabel.setAttribute("value", field); hbox.appendChild(nameLabel); - const valueLabel = doc.createElement("label"); + const valueLabel = doc.createXULElement("label"); valueLabel.className = "plain marker-details-value-label"; valueLabel.setAttribute("value", value); hbox.appendChild(valueLabel); @@ -127,7 +127,7 @@ exports.MarkerDOMUtils = { container.className = "marker-details-stack"; container.setAttribute("type", type); - const nameLabel = doc.createElement("label"); + const nameLabel = doc.createXULElement("label"); nameLabel.className = "plain marker-details-name-label"; nameLabel.setAttribute("value", L10N.getStr(`marker.field.${type}`)); container.appendChild(nameLabel); @@ -155,8 +155,8 @@ exports.MarkerDOMUtils = { "marker.field.asyncStack", frame.asyncCause ); - const asyncBox = doc.createElement("hbox"); - const asyncLabel = doc.createElement("label"); + const asyncBox = doc.createXULElement("hbox"); + const asyncLabel = doc.createXULElement("label"); asyncLabel.className = "devtools-monospace"; asyncLabel.setAttribute("value", asyncStr); asyncBox.appendChild(asyncLabel); @@ -164,28 +164,28 @@ exports.MarkerDOMUtils = { wasAsyncParent = false; } - const hbox = doc.createElement("hbox"); + const hbox = doc.createXULElement("hbox"); if (displayName) { - const functionLabel = doc.createElement("label"); + const functionLabel = doc.createXULElement("label"); functionLabel.className = "devtools-monospace"; functionLabel.setAttribute("value", displayName); hbox.appendChild(functionLabel); } if (url) { - const linkNode = doc.createElement("a"); + const linkNode = doc.createXULElement("a"); linkNode.className = "waterfall-marker-location devtools-source-link"; linkNode.href = url; linkNode.draggable = false; linkNode.setAttribute("title", url); - const urlLabel = doc.createElement("label"); + const urlLabel = doc.createXULElement("label"); urlLabel.className = "filename"; urlLabel.setAttribute("value", getSourceNames(url).short); linkNode.appendChild(urlLabel); - const lineLabel = doc.createElement("label"); + const lineLabel = doc.createXULElement("label"); lineLabel.className = "line-number"; lineLabel.setAttribute("value", `:${line}`); linkNode.appendChild(lineLabel); @@ -205,7 +205,7 @@ exports.MarkerDOMUtils = { } if (!displayName && !url) { - const unknownLabel = doc.createElement("label"); + const unknownLabel = doc.createXULElement("label"); unknownLabel.setAttribute( "value", L10N.getStr("marker.value.unknownFrame") @@ -238,10 +238,10 @@ exports.MarkerDOMUtils = { const elements = []; if (options.allocations && shouldShowAllocationsTrigger(marker)) { - const hbox = doc.createElement("hbox"); + const hbox = doc.createXULElement("hbox"); hbox.className = "marker-details-customcontainer"; - const label = doc.createElement("label"); + const label = doc.createXULElement("label"); label.className = "custom-button"; label.setAttribute("value", "Show allocation triggers"); label.setAttribute("type", "show-allocations"); diff --git a/devtools/client/performance/modules/widgets/tree-view.js b/devtools/client/performance/modules/widgets/tree-view.js index 457321a6b4..79ad8229ff 100644 --- a/devtools/client/performance/modules/widgets/tree-view.js +++ b/devtools/client/performance/modules/widgets/tree-view.js @@ -237,7 +237,7 @@ CallView.prototype = extend(AbstractTreeItem.prototype, { ); } - const targetNode = document.createElement("hbox"); + const targetNode = document.createXULElement("hbox"); targetNode.className = "call-tree-item"; targetNode.setAttribute( "origin", @@ -286,7 +286,7 @@ CallView.prototype = extend(AbstractTreeItem.prototype, { * Invoked by `_displaySelf`. */ _createCell: function(doc, value, type) { - const cell = doc.createElement("description"); + const cell = doc.createXULElement("description"); cell.className = "plain call-tree-cell"; cell.setAttribute("type", type); cell.setAttribute("crop", "end"); @@ -302,7 +302,7 @@ CallView.prototype = extend(AbstractTreeItem.prototype, { frameInfo, frameLevel ) { - const cell = doc.createElement("hbox"); + const cell = doc.createXULElement("hbox"); cell.className = "call-tree-cell"; cell.style.marginInlineStart = frameLevel * CALL_TREE_INDENTATION + "px"; cell.setAttribute("type", "function"); @@ -314,7 +314,7 @@ CallView.prototype = extend(AbstractTreeItem.prototype, { frameInfo.hasOptimizations && !frameInfo.isMetaCategory ) { - const icon = doc.createElement("description"); + const icon = doc.createXULElement("description"); icon.setAttribute("tooltiptext", VIEW_OPTIMIZATIONS_TOOLTIP); icon.className = "opt-icon"; cell.appendChild(icon); @@ -323,7 +323,7 @@ CallView.prototype = extend(AbstractTreeItem.prototype, { // Don't render a name label node if there's no function name. A different // location label node will be rendered instead. if (frameName) { - const nameNode = doc.createElement("description"); + const nameNode = doc.createXULElement("description"); nameNode.className = "plain call-tree-name"; nameNode.textContent = frameName; cell.appendChild(nameNode); @@ -357,7 +357,7 @@ CallView.prototype = extend(AbstractTreeItem.prototype, { _appendFunctionDetailsCells: function(doc, cell, frameInfo) { if (frameInfo.fileName) { - const urlNode = doc.createElement("description"); + const urlNode = doc.createXULElement("description"); urlNode.className = "plain call-tree-url"; urlNode.textContent = frameInfo.fileName; urlNode.setAttribute( @@ -369,28 +369,28 @@ CallView.prototype = extend(AbstractTreeItem.prototype, { } if (frameInfo.line) { - const lineNode = doc.createElement("description"); + const lineNode = doc.createXULElement("description"); lineNode.className = "plain call-tree-line"; lineNode.textContent = ":" + frameInfo.line; cell.appendChild(lineNode); } if (frameInfo.column) { - const columnNode = doc.createElement("description"); + const columnNode = doc.createXULElement("description"); columnNode.className = "plain call-tree-column"; columnNode.textContent = ":" + frameInfo.column; cell.appendChild(columnNode); } if (frameInfo.host) { - const hostNode = doc.createElement("description"); + const hostNode = doc.createXULElement("description"); hostNode.className = "plain call-tree-host"; hostNode.textContent = frameInfo.host; cell.appendChild(hostNode); } if (frameInfo.categoryData.label) { - const categoryNode = doc.createElement("description"); + const categoryNode = doc.createXULElement("description"); categoryNode.className = "plain call-tree-category"; categoryNode.style.color = frameInfo.categoryData.color; categoryNode.textContent = frameInfo.categoryData.label; diff --git a/devtools/client/performance/test/browser_perf-tree-abstract-05.js b/devtools/client/performance/test/browser_perf-tree-abstract-05.js index 54203f6e8d..55cdb39224 100644 --- a/devtools/client/performance/test/browser_perf-tree-abstract-05.js +++ b/devtools/client/performance/test/browser_perf-tree-abstract-05.js @@ -20,7 +20,13 @@ add_task(async function() { const container = document.createXULElement("vbox"); container.style.height = "100%"; container.style.overflow = "scroll"; - await appendAndWaitForPaint(gBrowser.selectedBrowser.parentNode, container); + const browserStack = gBrowser.selectedBrowser.parentNode; + // Allow the browser stack to shrink since it will have really long content + browserStack.style.minHeight = "0"; + registerCleanupFunction(() => { + browserStack.style.removeProperty("min-height"); + }); + await appendAndWaitForPaint(browserStack, container); const myDataSrc = { label: "root", diff --git a/devtools/client/performance/views/details.js b/devtools/client/performance/views/details.js index 0640f65de5..918bc39b92 100644 --- a/devtools/client/performance/views/details.js +++ b/devtools/client/performance/views/details.js @@ -158,10 +158,9 @@ const DetailsView = { return false; } - const prefSupported = - prefs && prefs.length - ? prefs.every(p => PerformanceController.getPref(p)) - : true; + const prefSupported = prefs?.length + ? prefs.every(p => PerformanceController.getPref(p)) + : true; return PerformanceController.isFeatureSupported(features) && prefSupported; }, diff --git a/devtools/client/preferences/debugger.js b/devtools/client/preferences/debugger.js index 6616d44cb0..c74704195a 100644 --- a/devtools/client/preferences/debugger.js +++ b/devtools/client/preferences/debugger.js @@ -84,7 +84,6 @@ pref("devtools.debugger.features.windowless-workers", true); pref("devtools.debugger.features.event-listeners-breakpoints", true); pref("devtools.debugger.features.dom-mutation-breakpoints", true); pref("devtools.debugger.features.log-points", true); -pref("devtools.debugger.features.overlay-step-buttons", false); -pref("devtools.debugger.features.overlay-step-buttons", true); +pref("devtools.debugger.features.overlay", true); pref("devtools.debugger.features.inline-preview", true); pref("devtools.debugger.features.watchpoints", false); diff --git a/devtools/client/responsive/actions/index.js b/devtools/client/responsive/actions/index.js index 3c2c5b3cc4..95da022751 100644 --- a/devtools/client/responsive/actions/index.js +++ b/devtools/client/responsive/actions/index.js @@ -101,6 +101,9 @@ createEnum( // Update the device modal state. "UPDATE_DEVICE_MODAL", + + // Zoom the viewport. + "ZOOM_VIEWPORT", ], module.exports ); diff --git a/devtools/client/responsive/actions/viewports.js b/devtools/client/responsive/actions/viewports.js index 4357f13cf5..52e81269d2 100644 --- a/devtools/client/responsive/actions/viewports.js +++ b/devtools/client/responsive/actions/viewports.js @@ -16,6 +16,7 @@ const { REMOVE_DEVICE_ASSOCIATION, RESIZE_VIEWPORT, ROTATE_VIEWPORT, + ZOOM_VIEWPORT, } = require("./index"); const { post } = require("../utils/message"); @@ -111,4 +112,15 @@ module.exports = { id, }; }, + + /** + * Zoom the viewport. + */ + zoomViewport(id, zoom) { + return { + type: ZOOM_VIEWPORT, + id, + zoom, + }; + }, }; diff --git a/devtools/client/responsive/browser/content.js b/devtools/client/responsive/browser/content.js index fe8d678eb4..bfccd692b6 100644 --- a/devtools/client/responsive/browser/content.js +++ b/devtools/client/responsive/browser/content.js @@ -81,9 +81,8 @@ var global = this; height, }); - const zoom = content.windowUtils.getResolution(); - width = content.innerWidth * zoom; - height = content.innerHeight * zoom; + width = content.innerWidth; + height = content.innerHeight; debug(`EMIT RESIZEVIEWPORT: ${width} x ${height}`); sendAsyncMessage("ResponsiveMode:OnResizeViewport", { width, diff --git a/devtools/client/responsive/components/ResizableViewport.js b/devtools/client/responsive/components/ResizableViewport.js index e1134d9c9f..dc6ba1a169 100644 --- a/devtools/client/responsive/components/ResizableViewport.js +++ b/devtools/client/responsive/components/ResizableViewport.js @@ -66,8 +66,8 @@ class ResizableViewport extends PureComponent { } let { lastClientX, lastClientY, ignoreX, ignoreY } = this.state; - let deltaX = clientX - lastClientX; - let deltaY = clientY - lastClientY; + let deltaX = (clientX - lastClientX) / this.props.viewport.zoom; + let deltaY = (clientY - lastClientY) / this.props.viewport.zoom; if (!this.props.leftAlignmentEnabled) { // The viewport is centered horizontally, so horizontal resize resizes @@ -82,18 +82,18 @@ class ResizableViewport extends PureComponent { deltaY = 0; } - let width = this.props.viewport.width + deltaX; - let height = this.props.viewport.height + deltaY; + let width = Math.round(this.props.viewport.width + deltaX); + let height = Math.round(this.props.viewport.height + deltaY); if (width < VIEWPORT_MIN_WIDTH) { width = VIEWPORT_MIN_WIDTH; - } else { + } else if (width != this.props.viewport.width) { lastClientX = clientX; } if (height < VIEWPORT_MIN_HEIGHT) { height = VIEWPORT_MIN_HEIGHT; - } else { + } else if (height != this.props.viewport.height) { lastClientY = clientY; } @@ -171,8 +171,8 @@ class ResizableViewport extends PureComponent { { className: contentClass, style: { - width: viewport.width + "px", - height: viewport.height + "px", + width: Math.round(viewport.width * viewport.zoom) + "px", + height: Math.round(viewport.height * viewport.zoom) + "px", }, }, Browser({ diff --git a/devtools/client/responsive/index.js b/devtools/client/responsive/index.js index 5de9a07bf5..801fb772ef 100644 --- a/devtools/client/responsive/index.js +++ b/devtools/client/responsive/index.js @@ -26,7 +26,11 @@ const message = require("./utils/message"); const App = createFactory(require("./components/App")); const Store = require("./store"); const { loadDevices, restoreDeviceState } = require("./actions/devices"); -const { addViewport, resizeViewport } = require("./actions/viewports"); +const { + addViewport, + resizeViewport, + zoomViewport, +} = require("./actions/viewports"); const { changeDisplayPixelRatio } = require("./actions/ui"); // Exposed for use by tests @@ -176,3 +180,14 @@ window.getViewportBrowser = () => { } return browser; }; + +/** + * Called by manager.js to zoom the viewport. + */ +window.setViewportZoom = zoom => { + try { + bootstrap.dispatch(zoomViewport(0, zoom)); + } catch (e) { + console.error(e); + } +}; diff --git a/devtools/client/responsive/manager.js b/devtools/client/responsive/manager.js index cb000f4604..0b9163f2f9 100644 --- a/devtools/client/responsive/manager.js +++ b/devtools/client/responsive/manager.js @@ -4,51 +4,16 @@ "use strict"; -const { Ci } = require("chrome"); const promise = require("promise"); -const Services = require("Services"); const EventEmitter = require("devtools/shared/event-emitter"); -const { getOrientation } = require("./utils/orientation"); -loader.lazyRequireGetter( - this, - "DebuggerClient", - "devtools/shared/client/debugger-client", - true -); -loader.lazyRequireGetter( - this, - "DebuggerServer", - "devtools/server/debugger-server", - true -); -loader.lazyRequireGetter( - this, - "throttlingProfiles", - "devtools/client/shared/components/throttling/profiles" -); -loader.lazyRequireGetter( - this, - "SettingOnboardingTooltip", - "devtools/client/responsive/setting-onboarding-tooltip" -); -loader.lazyRequireGetter( - this, - "swapToInnerBrowser", - "devtools/client/responsive/browser/swap", - true -); +loader.lazyRequireGetter(this, "ResponsiveUI", "devtools/client/responsive/ui"); loader.lazyRequireGetter( this, "startup", "devtools/client/responsive/utils/window", true ); -loader.lazyRequireGetter( - this, - "message", - "devtools/client/responsive/utils/message" -); loader.lazyRequireGetter( this, "showNotification", @@ -81,27 +46,27 @@ loader.lazyRequireGetter( true ); loader.lazyRequireGetter(this, "Telemetry", "devtools/client/shared/telemetry"); -loader.lazyRequireGetter(this, "asyncStorage", "devtools/shared/async-storage"); - -const TOOL_URL = "chrome://devtools/content/responsive/index.xhtml"; - -const RELOAD_CONDITION_PREF_PREFIX = "devtools.responsive.reloadConditions."; -const RELOAD_NOTIFICATION_PREF = - "devtools.responsive.reloadNotification.enabled"; -const SHOW_SETTING_TOOLTIP_PREF = "devtools.responsive.show-setting-tooltip"; - -function debug(msg) { - // console.log(`RDM manager: ${msg}`); -} /** * ResponsiveUIManager is the external API for the browser UI, etc. to use when * opening and closing the responsive UI. */ -const ResponsiveUIManager = (exports.ResponsiveUIManager = { - _telemetry: new Telemetry(), +class ResponsiveUIManager { + constructor() { + this.activeTabs = new Map(); - activeTabs: new Map(), + this.handleMenuCheck = this.handleMenuCheck.bind(this); + + EventEmitter.decorate(this); + } + + get telemetry() { + if (!this._telemetry) { + this._telemetry = new Telemetry(); + } + + return this._telemetry; + } /** * Toggle the responsive UI for a tab. @@ -126,7 +91,7 @@ const ResponsiveUIManager = (exports.ResponsiveUIManager = { const completed = this[action + "IfNeeded"](window, tab, options); completed.catch(console.error); return completed; - }, + } /** * Opens the responsive UI, if not already open. @@ -153,7 +118,7 @@ const ResponsiveUIManager = (exports.ResponsiveUIManager = { if (!this.isActiveForTab(tab)) { this.initMenuCheckListenerFor(window); - const ui = new ResponsiveUI(window, tab); + const ui = new ResponsiveUI(this, window, tab); this.activeTabs.set(tab, ui); // Explicitly not await on telemetry to avoid delaying RDM opening @@ -165,7 +130,7 @@ const ResponsiveUIManager = (exports.ResponsiveUIManager = { } return this.getResponsiveUIForTab(tab); - }, + } /** * Record all telemetry probes related to RDM opening. @@ -180,12 +145,12 @@ const ResponsiveUIManager = (exports.ResponsiveUIManager = { } const hostType = toolbox ? toolbox.hostType : "none"; const hasToolbox = !!toolbox; - const tel = this._telemetry; + if (hasToolbox) { - tel.scalarAdd("devtools.responsive.toolbox_opened_first", 1); + this.telemetry.scalarAdd("devtools.responsive.toolbox_opened_first", 1); } - tel.recordEvent("activate", "responsive_design", null, { + this.telemetry.recordEvent("activate", "responsive_design", null, { host: hostType, width: Math.ceil(window.outerWidth / 50) * 50, session_id: toolbox ? toolbox.sessionId : -1, @@ -196,8 +161,12 @@ const ResponsiveUIManager = (exports.ResponsiveUIManager = { if (!trigger) { trigger = "unknown"; } - tel.keyedScalarAdd("devtools.responsive.open_trigger", trigger, 1); - }, + this.telemetry.keyedScalarAdd( + "devtools.responsive.open_trigger", + trigger, + 1 + ); + } /** * Closes the responsive UI, if not already closed. @@ -236,7 +205,7 @@ const ResponsiveUIManager = (exports.ResponsiveUIManager = { // Explicitly not await on telemetry to avoid delaying RDM closing this.recordTelemetryClose(window, tab); } - }, + } async recordTelemetryClose(window, tab) { const isKnownTab = TargetFactory.isKnownTab(tab); @@ -247,13 +216,13 @@ const ResponsiveUIManager = (exports.ResponsiveUIManager = { } const hostType = toolbox ? toolbox.hostType : "none"; - const t = this._telemetry; - t.recordEvent("deactivate", "responsive_design", null, { + + this.telemetry.recordEvent("deactivate", "responsive_design", null, { host: hostType, width: Math.ceil(window.outerWidth / 50) * 50, session_id: toolbox ? toolbox.sessionId : -1, }); - }, + } /** * Returns true if responsive UI is active for a given tab. @@ -264,7 +233,7 @@ const ResponsiveUIManager = (exports.ResponsiveUIManager = { */ isActiveForTab(tab) { return this.activeTabs.has(tab); - }, + } /** * Returns true if responsive UI is active in any tab in the given window. @@ -275,7 +244,7 @@ const ResponsiveUIManager = (exports.ResponsiveUIManager = { */ isActiveForWindow(window) { return [...this.activeTabs.keys()].some(t => t.ownerGlobal === window); - }, + } /** * Return the responsive UI controller for a tab. @@ -287,23 +256,23 @@ const ResponsiveUIManager = (exports.ResponsiveUIManager = { */ getResponsiveUIForTab(tab) { return this.activeTabs.get(tab); - }, + } handleMenuCheck({ target }) { - ResponsiveUIManager.setMenuCheckFor(target); - }, + this.setMenuCheckFor(target); + } initMenuCheckListenerFor(window) { const { tabContainer } = window.gBrowser; tabContainer.addEventListener("TabSelect", this.handleMenuCheck); - }, + } removeMenuCheckListenerFor(window) { if (window && window.gBrowser && window.gBrowser.tabContainer) { const { tabContainer } = window.gBrowser; tabContainer.removeEventListener("TabSelect", this.handleMenuCheck); } - }, + } async setMenuCheckFor(tab, window = tab.ownerGlobal) { await startup(window); @@ -312,7 +281,7 @@ const ResponsiveUIManager = (exports.ResponsiveUIManager = { if (menu) { menu.setAttribute("checked", this.isActiveForTab(tab)); } - }, + } showRemoteOnlyNotification(window, tab, { trigger } = {}) { return showNotification(window, tab, { @@ -320,643 +289,7 @@ const ResponsiveUIManager = (exports.ResponsiveUIManager = { msg: l10n.getStr("responsive.remoteOnly"), priority: PriorityLevels.PRIORITY_CRITICAL_MEDIUM, }); - }, -}); - -EventEmitter.decorate(ResponsiveUIManager); - -/** - * ResponsiveUI manages the responsive design tool for a specific tab. The - * actual tool itself lives in a separate chrome:// document that is loaded into - * the tab upon opening responsive design. This object acts a helper to - * integrate the tool into the surrounding browser UI as needed. - */ -function ResponsiveUI(window, tab) { - this.browserWindow = window; - this.tab = tab; - this.inited = this.init(); + } } -ResponsiveUI.prototype = { - /** - * The main browser chrome window (that holds many tabs). - */ - browserWindow: null, - - /** - * The specific browser tab this responsive instance is for. - */ - tab: null, - - /** - * Promise resovled when the UI init has completed. - */ - inited: null, - - /** - * Flag set when destruction has begun. - */ - destroying: false, - - /** - * Flag set when destruction has ended. - */ - destroyed: false, - - /** - * A window reference for the chrome:// document that displays the responsive - * design tool. It is safe to reference this window directly even with e10s, - * as the tool UI is always loaded in the parent process. The web content - * contained *within* the tool UI on the other hand is loaded in the child - * process. - */ - toolWindow: null, - - /** - * Open RDM while preserving the state of the page. We use `swapFrameLoaders` - * to ensure all in-page state is preserved, just like when you move a tab to - * a new window. - * - * For more details, see /devtools/docs/responsive-design-mode.md. - */ - async init() { - debug("Init start"); - - const ui = this; - - // Watch for tab close and window close so we can clean up RDM synchronously - this.tab.addEventListener("TabClose", this); - this.browserWindow.addEventListener("unload", this); - - // Swap page content from the current tab into a viewport within RDM - debug("Create browser swapper"); - this.swap = swapToInnerBrowser({ - tab: this.tab, - containerURL: TOOL_URL, - async getInnerBrowser(containerBrowser) { - const toolWindow = (ui.toolWindow = containerBrowser.contentWindow); - toolWindow.addEventListener("message", ui); - debug("Wait until init from inner"); - await message.request(toolWindow, "init"); - toolWindow.addInitialViewport({ - uri: "about:blank", - userContextId: ui.tab.userContextId, - }); - debug("Wait until browser mounted"); - await message.wait(toolWindow, "browser-mounted"); - return ui.getViewportBrowser(); - }, - }); - debug("Wait until swap start"); - await this.swap.start(); - - // Set the ui toolWindow to fullZoom and textZoom of 100%. Directly change - // the zoom levels of the toolwindow docshell. That doesn't affect the zoom - // of the RDM content, but it does send events that confuse the Zoom UI. - // So before we adjust the zoom levels of the toolWindow, we first cache - // the reported zoom levels of the RDM content, because we'll have to - // re-apply them to re-sync the Zoom UI. - - // Cache the values now and we'll re-apply them near the end of this function. - // This is important since other steps here can also cause the Zoom UI update - // event to be sent for other browsers, and this means that the changes from - // our Zoom UI update event would be overwritten. After this function, future - // changes to zoom levels will send Zoom UI update events in an order that - // keeps the Zoom UI synchronized with the RDM content zoom levels. - const fullZoom = this.tab.linkedBrowser.fullZoom; - const textZoom = this.tab.linkedBrowser.textZoom; - - ui.toolWindow.docShell.contentViewer.fullZoom = 1; - ui.toolWindow.docShell.contentViewer.textZoom = 1; - - this.tab.addEventListener("BeforeTabRemotenessChange", this); - - // Notify the inner browser to start the frame script - debug("Wait until start frame script"); - await message.request(this.toolWindow, "start-frame-script"); - - // Get the protocol ready to speak with emulation actor - debug("Wait until RDP server connect"); - await this.connectToServer(); - - // Restore the previous state of RDM. - await this.restoreState(); - - // Show the settings onboarding tooltip - if (Services.prefs.getBoolPref(SHOW_SETTING_TOOLTIP_PREF)) { - this.settingOnboardingTooltip = new SettingOnboardingTooltip( - ui.toolWindow.document - ); - } - - // Re-apply our cached zoom levels. Other Zoom UI update events have finished - // by now. - this.tab.linkedBrowser.fullZoom = fullZoom; - this.tab.linkedBrowser.textZoom = textZoom; - - // Non-blocking message to tool UI to start any delayed init activities - message.post(this.toolWindow, "post-init"); - - debug("Init done"); - }, - - /** - * Close RDM and restore page content back into a regular tab. - * - * @param object - * Destroy options, which currently includes a `reason` string. - * @return boolean - * Whether this call is actually destroying. False means destruction - * was already in progress. - */ - async destroy(options) { - if (this.destroying) { - return false; - } - this.destroying = true; - - // If our tab is about to be closed, there's not enough time to exit - // gracefully, but that shouldn't be a problem since the tab will go away. - // So, skip any waiting when we're about to close the tab. - const isTabDestroyed = !this.tab.linkedBrowser; - const isWindowClosing = - (options && options.reason === "unload") || isTabDestroyed; - const isTabContentDestroying = - isWindowClosing || - (options && - (options.reason === "TabClose" || - options.reason === "BeforeTabRemotenessChange")); - - // Ensure init has finished before starting destroy - if (!isTabContentDestroying) { - await this.inited; - } - - this.tab.removeEventListener("TabClose", this); - this.tab.removeEventListener("BeforeTabRemotenessChange", this); - this.browserWindow.removeEventListener("unload", this); - this.toolWindow.removeEventListener("message", this); - - if (!isTabContentDestroying) { - // Notify the inner browser to stop the frame script - await message.request(this.toolWindow, "stop-frame-script"); - } - - // Ensure the tab is reloaded if required when exiting RDM so that no emulated - // settings are left in a customized state. - if (!isTabContentDestroying) { - let reloadNeeded = false; - await this.updateDPPX(); - await this.updateNetworkThrottling(); - reloadNeeded |= - (await this.updateUserAgent()) && this.reloadOnChange("userAgent"); - reloadNeeded |= - (await this.updateTouchSimulation()) && - this.reloadOnChange("touchSimulation"); - if (reloadNeeded) { - this.getViewportBrowser().reload(); - } - } - - if (this.settingOnboardingTooltip) { - this.settingOnboardingTooltip.destroy(); - this.settingOnboardingTooltip = null; - } - - // Destroy local state - const swap = this.swap; - this.browserWindow = null; - this.tab = null; - this.inited = null; - this.toolWindow = null; - this.swap = null; - - // Close the debugger client used to speak with emulation actor. - // The actor handles clearing any overrides itself, so it's not necessary to clear - // anything on shutdown client side. - const clientClosed = this.client.close(); - if (!isTabContentDestroying) { - await clientClosed; - } - this.client = this.emulationFront = null; - - if (!isWindowClosing) { - // Undo the swap and return the content back to a normal tab - swap.stop(); - } - - this.destroyed = true; - - return true; - }, - - async connectToServer() { - // The client being instantiated here is separate from the toolbox. It is being used - // separately and has a life cycle that doesn't correspond to the toolbox. As a - // result, it does not have a target, so we are not using `target.getFront` here. See - // also the implementation for about:debugging - DebuggerServer.init(); - DebuggerServer.registerAllActors(); - this.client = new DebuggerClient(DebuggerServer.connectPipe()); - await this.client.connect(); - const targetFront = await this.client.mainRoot.getTab(); - this.emulationFront = new EmulationFront(this.client); - // Because we are not using getFront (see previous comment), we have to do what it - // does and manually set the front's actor ID here. - // Once bug 1465635 is resolved, we will be able to use getFront from here and remove - // this. - this.emulationFront.actorID = - targetFront.targetForm[this.emulationFront.formAttributeName]; - this.emulationFront.manage(this.emulationFront); - }, - - /** - * Show one-time notification about reloads for emulation. - */ - showReloadNotification() { - if (Services.prefs.getBoolPref(RELOAD_NOTIFICATION_PREF, false)) { - showNotification(this.browserWindow, this.tab, { - msg: l10n.getFormatStr("responsive.reloadNotification.description2"), - }); - Services.prefs.setBoolPref(RELOAD_NOTIFICATION_PREF, false); - } - }, - - reloadOnChange(id) { - this.showReloadNotification(); - const pref = RELOAD_CONDITION_PREF_PREFIX + id; - return Services.prefs.getBoolPref(pref, false); - }, - - handleEvent(event) { - const { browserWindow, tab } = this; - - switch (event.type) { - case "message": - this.handleMessage(event); - break; - case "BeforeTabRemotenessChange": - case "TabClose": - case "unload": - ResponsiveUIManager.closeIfNeeded(browserWindow, tab, { - reason: event.type, - }); - break; - } - }, - - handleMessage(event) { - if (event.origin !== "chrome://devtools") { - return; - } - - switch (event.data.type) { - case "change-device": - this.onChangeDevice(event); - break; - case "change-network-throttling": - this.onChangeNetworkThrottling(event); - break; - case "change-pixel-ratio": - this.onChangePixelRatio(event); - break; - case "change-touch-simulation": - this.onChangeTouchSimulation(event); - break; - case "change-user-agent": - this.onChangeUserAgent(event); - break; - case "content-resize": - this.onContentResize(event); - break; - case "exit": - this.onExit(); - break; - case "remove-device-association": - this.onRemoveDeviceAssociation(); - break; - case "viewport-orientation-change": - this.onRotateViewport(event); - break; - case "viewport-resize": - this.onResizeViewport(event); - break; - } - }, - - async onChangeDevice(event) { - const { pixelRatio, touch, userAgent } = event.data.device; - let reloadNeeded = false; - await this.updateDPPX(pixelRatio); - - // Get the orientation values of the device we are changing to and update. - const { device, viewport } = event.data; - const { type, angle } = getOrientation(device, viewport); - await this.updateScreenOrientation(type, angle); - - reloadNeeded |= - (await this.updateUserAgent(userAgent)) && - this.reloadOnChange("userAgent"); - reloadNeeded |= - (await this.updateTouchSimulation(touch)) && - this.reloadOnChange("touchSimulation"); - if (reloadNeeded) { - this.getViewportBrowser().reload(); - } - // Used by tests - this.emit("device-changed"); - }, - - async onChangeNetworkThrottling(event) { - const { enabled, profile } = event.data; - await this.updateNetworkThrottling(enabled, profile); - // Used by tests - this.emit("network-throttling-changed"); - }, - - onChangePixelRatio(event) { - const { pixelRatio } = event.data; - this.updateDPPX(pixelRatio); - }, - - async onChangeTouchSimulation(event) { - const { enabled } = event.data; - const reloadNeeded = - (await this.updateTouchSimulation(enabled)) && - this.reloadOnChange("touchSimulation"); - if (reloadNeeded) { - this.getViewportBrowser().reload(); - } - // Used by tests - this.emit("touch-simulation-changed"); - }, - - async onChangeUserAgent(event) { - const { userAgent } = event.data; - const reloadNeeded = - (await this.updateUserAgent(userAgent)) && - this.reloadOnChange("userAgent"); - if (reloadNeeded) { - this.getViewportBrowser().reload(); - } - this.emit("user-agent-changed"); - }, - - onContentResize(event) { - const { width, height } = event.data; - this.emit("content-resize", { - width, - height, - }); - }, - - onExit() { - const { browserWindow, tab } = this; - ResponsiveUIManager.closeIfNeeded(browserWindow, tab); - }, - - async onRemoveDeviceAssociation() { - let reloadNeeded = false; - await this.updateDPPX(); - reloadNeeded |= - (await this.updateUserAgent()) && this.reloadOnChange("userAgent"); - reloadNeeded |= - (await this.updateTouchSimulation()) && - this.reloadOnChange("touchSimulation"); - if (reloadNeeded) { - this.getViewportBrowser().reload(); - } - // Used by tests - this.emit("device-association-removed"); - }, - - onResizeViewport(event) { - const { width, height } = event.data; - this.emit("viewport-resize", { - width, - height, - }); - }, - - async onRotateViewport(event) { - const { orientationType: type, angle, isViewportRotated } = event.data; - await this.updateScreenOrientation(type, angle, isViewportRotated); - }, - - /** - * Restores the previous state of RDM. - */ - async restoreState() { - const deviceState = await asyncStorage.getItem( - "devtools.responsive.deviceState" - ); - if (deviceState) { - // Return if there is a device state to restore, this will be done when the - // device list is loaded after the post-init. - return; - } - - const height = Services.prefs.getIntPref( - "devtools.responsive.viewport.height", - 0 - ); - const pixelRatio = Services.prefs.getIntPref( - "devtools.responsive.viewport.pixelRatio", - 0 - ); - const touchSimulationEnabled = Services.prefs.getBoolPref( - "devtools.responsive.touchSimulation.enabled", - false - ); - const userAgent = Services.prefs.getCharPref( - "devtools.responsive.userAgent", - "" - ); - const width = Services.prefs.getIntPref( - "devtools.responsive.viewport.width", - 0 - ); - - let reloadNeeded = false; - const { type, angle } = this.getInitialViewportOrientation({ - width, - height, - }); - - await this.updateDPPX(pixelRatio); - await this.updateScreenOrientation(type, angle); - - if (touchSimulationEnabled) { - reloadNeeded |= - (await this.updateTouchSimulation(touchSimulationEnabled)) && - this.reloadOnChange("touchSimulation"); - } - if (userAgent) { - reloadNeeded |= - (await this.updateUserAgent(userAgent)) && - this.reloadOnChange("userAgent"); - } - if (reloadNeeded) { - this.getViewportBrowser().reload(); - } - }, - - /** - * Set or clear the emulated device pixel ratio. - * - * @return boolean - * Whether a reload is needed to apply the change. - * (This is always immediate, so it's always false.) - */ - async updateDPPX(dppx) { - if (!dppx) { - await this.emulationFront.clearDPPXOverride(); - return false; - } - await this.emulationFront.setDPPXOverride(dppx); - return false; - }, - - /** - * Set or clear network throttling. - * - * @return boolean - * Whether a reload is needed to apply the change. - * (This is always immediate, so it's always false.) - */ - async updateNetworkThrottling(enabled, profile) { - if (!enabled) { - await this.emulationFront.clearNetworkThrottling(); - return false; - } - const data = throttlingProfiles.find(({ id }) => id == profile); - const { download, upload, latency } = data; - await this.emulationFront.setNetworkThrottling({ - downloadThroughput: download, - uploadThroughput: upload, - latency, - }); - return false; - }, - - /** - * Set or clear the emulated user agent. - * - * @return boolean - * Whether a reload is needed to apply the change. - */ - updateUserAgent(userAgent) { - if (!userAgent) { - return this.emulationFront.clearUserAgentOverride(); - } - return this.emulationFront.setUserAgentOverride(userAgent); - }, - - /** - * Set or clear touch simulation. When setting to true, this method will - * additionally set meta viewport override if the pref - * "devtools.responsive.metaViewport.enabled" is true. When setting to - * false, this method will clear all touch simulation and meta viewport - * overrides, returning to default behavior for both settings. - * - * @return boolean - * Whether a reload is needed to apply the override change(s). - */ - async updateTouchSimulation(enabled) { - let reloadNeeded; - if (enabled) { - const metaViewportEnabled = Services.prefs.getBoolPref( - "devtools.responsive.metaViewport.enabled", - false - ); - - reloadNeeded = await this.emulationFront.setTouchEventsOverride( - Ci.nsIDocShell.TOUCHEVENTS_OVERRIDE_ENABLED - ); - - if (metaViewportEnabled) { - reloadNeeded |= await this.emulationFront.setMetaViewportOverride( - Ci.nsIDocShell.META_VIEWPORT_OVERRIDE_ENABLED - ); - } - } else { - reloadNeeded = await this.emulationFront.clearTouchEventsOverride(); - reloadNeeded |= await this.emulationFront.clearMetaViewportOverride(); - } - return reloadNeeded; - }, - - /** - * Sets the screen orientation values of the simulated device. - * - * @param {String} type - * The orientation type to update the current device screen to. - * @param {Number} angle - * The rotation angle to update the current device screen to. - * @param {Boolean} isViewportRotated - * Whether or not the reason for updating the screen orientation is a result - * of actually rotating the device via the RDM toolbar. If true, then an - * "orientationchange" event is simulated. Otherwise, the screen orientation is - * updated because of changing devices, opening RDM, or the page has been - * reloaded/navigated to, so we should not be simulating "orientationchange". - */ - async updateScreenOrientation(type, angle, isViewportRotated = false) { - const targetFront = await this.client.mainRoot.getTab(); - const simulateOrientationChangeSupported = await targetFront.actorHasMethod( - "emulation", - "simulateScreenOrientationChange" - ); - - // Ensure that simulateScreenOrientationChange is supported. - if (simulateOrientationChangeSupported) { - await this.emulationFront.simulateScreenOrientationChange( - type, - angle, - isViewportRotated - ); - } - - // Used by tests. - if (!isViewportRotated) { - this.emit("only-viewport-orientation-changed"); - } - }, - - /** - * Helper for tests. Assumes a single viewport for now. - */ - getViewportSize() { - return this.toolWindow.getViewportSize(); - }, - - /** - * Helper for tests, etc. Assumes a single viewport for now. - */ - async setViewportSize(size) { - await this.inited; - this.toolWindow.setViewportSize(size); - }, - - /** - * Helper for tests/reloading the viewport. Assumes a single viewport for now. - */ - getViewportBrowser() { - return this.toolWindow.getViewportBrowser(); - }, - - /** - * Helper for contacting the viewport content. Assumes a single viewport for now. - */ - getViewportMessageManager() { - return this.getViewportBrowser().messageManager; - }, - - /** - * Helper for getting the initial viewport orientation. - */ - getInitialViewportOrientation(viewport) { - return getOrientation(viewport, viewport); - }, -}; - -EventEmitter.decorate(ResponsiveUI.prototype); +module.exports = new ResponsiveUIManager(); diff --git a/devtools/client/responsive/moz.build b/devtools/client/responsive/moz.build index ad3a785566..93629c153e 100644 --- a/devtools/client/responsive/moz.build +++ b/devtools/client/responsive/moz.build @@ -17,9 +17,9 @@ DevToolsModules( 'index.js', 'manager.js', 'reducers.js', - 'setting-onboarding-tooltip.js', 'store.js', 'types.js', + 'ui.js', ) XPCSHELL_TESTS_MANIFESTS += ['test/unit/xpcshell.ini'] diff --git a/devtools/client/responsive/reducers/viewports.js b/devtools/client/responsive/reducers/viewports.js index f2532e2976..2a69f8e5f2 100644 --- a/devtools/client/responsive/reducers/viewports.js +++ b/devtools/client/responsive/reducers/viewports.js @@ -15,6 +15,7 @@ const { REMOVE_DEVICE_ASSOCIATION, RESIZE_VIEWPORT, ROTATE_VIEWPORT, + ZOOM_VIEWPORT, } = require("../actions/index"); const VIEWPORT_WIDTH_PREF = "devtools.responsive.viewport.width"; @@ -34,6 +35,7 @@ const INITIAL_VIEWPORT = { width: Services.prefs.getIntPref(VIEWPORT_WIDTH_PREF, 320), pixelRatio: Services.prefs.getIntPref(VIEWPORT_PIXEL_RATIO_PREF, 0), userContextId: 0, + zoom: 1, }; const reducers = { @@ -184,6 +186,23 @@ const reducers = { }; }); }, + + [ZOOM_VIEWPORT](viewports, { id, zoom }) { + return viewports.map(viewport => { + if (viewport.id !== id) { + return viewport; + } + + if (!zoom) { + zoom = viewport.zoom; + } + + return { + ...viewport, + zoom, + }; + }); + }, }; module.exports = function(viewports = INITIAL_VIEWPORTS, action) { diff --git a/devtools/client/responsive/setting-onboarding-tooltip.js b/devtools/client/responsive/setting-onboarding-tooltip.js deleted file mode 100644 index 21a2f5d733..0000000000 --- a/devtools/client/responsive/setting-onboarding-tooltip.js +++ /dev/null @@ -1,77 +0,0 @@ -/* 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 Services = require("Services"); -const { - HTMLTooltip, -} = require("devtools/client/shared/widgets/tooltip/HTMLTooltip"); - -const { getStr } = require("./utils/l10n"); - -const SHOW_SETTING_TOOLTIP_PREF = "devtools.responsive.show-setting-tooltip"; - -const CONTAINER_WIDTH = 270; - -/** - * Setting onboarding tooltip that is shown on the setting menu button in the RDM toolbar - * when the pref is on. - */ -class SettingOnboardingTooltip { - constructor(doc) { - this.doc = doc; - this.tooltip = new HTMLTooltip(this.doc, { - consumeOutsideClicks: false, - type: "arrow", - }); - - this.onCloseButtonClick = this.onCloseButtonClick.bind(this); - - const container = doc.createElement("div"); - container.className = "onboarding-container"; - - const icon = doc.createElement("span"); - icon.className = "onboarding-icon"; - container.appendChild(icon); - - const content = doc.createElement("div"); - content.className = "onboarding-content"; - content.textContent = getStr("responsive.settingOnboarding.content"); - container.appendChild(content); - - this.closeButton = doc.createElement("button"); - this.closeButton.className = "onboarding-close-button devtools-button"; - container.appendChild(this.closeButton); - - this.closeButton.addEventListener("click", this.onCloseButtonClick); - - this.tooltip.panel.appendChild(container); - this.tooltip.setContentSize({ width: CONTAINER_WIDTH }); - this.tooltip.show(this.doc.getElementById("settings-button"), { - position: "bottom", - }); - } - - destroy() { - this.closeButton.removeEventListener("click", this.onCloseButtonClick); - - this.tooltip.destroy(); - - this.closeButton = null; - this.doc = null; - this.tooltip = null; - } - - /** - * Handler for the "click" event on the close button. Hides the onboarding tooltip - * and sets the show three pane onboarding tooltip pref to false. - */ - onCloseButtonClick() { - Services.prefs.setBoolPref(SHOW_SETTING_TOOLTIP_PREF, false); - this.tooltip.hide(); - } -} - -module.exports = SettingOnboardingTooltip; diff --git a/devtools/client/responsive/test/browser/browser_device_width.js b/devtools/client/responsive/test/browser/browser_device_width.js index 5cf0091f3d..0e4623321d 100644 --- a/devtools/client/responsive/test/browser/browser_device_width.js +++ b/devtools/client/responsive/test/browser/browser_device_width.js @@ -3,30 +3,46 @@ http://creativecommons.org/publicdomain/zero/1.0/ */ "use strict"; -const TEST_URL = "data:text/html;charset=utf-8,"; +const TEST_URL = + 'data:text/html;charset=utf-8,'; addRDMTask(TEST_URL, async function({ ui, manager }) { + await pushPref("devtools.responsive.metaViewport.enabled", true); + ok(ui, "An instance of the RDM should be attached to the tab."); - await setViewportSize(ui, manager, 110, 500); + await setViewportSizeAndAwaitReflow(ui, manager, 110, 500); info("Checking initial width/height properties."); - await doInitialChecks(ui); + await doInitialChecks(ui, 110); + + info("Checking initial width/height with meta viewport on"); + await setTouchAndMetaViewportSupport(ui, true); + await doInitialChecks(ui, 980); + await setTouchAndMetaViewportSupport(ui, false); info("Changing the RDM size"); - await setViewportSize(ui, manager, 90, 500); + await setViewportSizeAndAwaitReflow(ui, manager, 90, 500); info("Checking for screen props"); await checkScreenProps(ui); + info("Checking for screen props with meta viewport on"); + await setTouchAndMetaViewportSupport(ui, true); + await checkScreenProps(ui); + await setTouchAndMetaViewportSupport(ui, false); + + info("Checking for subframe props"); + await checkSubframeProps(ui); + + info("Checking for subframe props with meta viewport on"); + await setTouchAndMetaViewportSupport(ui, true); + await checkSubframeProps(ui); + await setTouchAndMetaViewportSupport(ui, false); + info("Changing the RDM size using input keys"); await setViewportSizeWithInputKeys(ui); - info("Setting docShell.deviceSizeIsPageSize to false"); - await ContentTask.spawn(ui.getViewportBrowser(), {}, async function() { - const docShell = content.docShell; - docShell.deviceSizeIsPageSize = false; - }); - info("Checking for screen props once again."); await checkScreenProps2(ui); }); @@ -71,14 +87,14 @@ async function setViewportSizeWithInputKeys(ui) { await resized; } -async function doInitialChecks(ui) { +async function doInitialChecks(ui, expectedInnerWidth) { const { innerWidth, matchesMedia, outerHeight, outerWidth, } = await grabContentInfo(ui); - is(innerWidth, 110, "initial width should be 110px"); + is(innerWidth, expectedInnerWidth, "inner width should be as expected"); is(outerWidth, 110, "device's outerWidth should be 110px"); is(outerHeight, 500, "device's outerHeight should be 500px"); isnot( @@ -107,15 +123,24 @@ async function checkScreenProps(ui) { } async function checkScreenProps2(ui) { - const { matchesMedia, screen } = await grabContentInfo(ui); - ok(!matchesMedia, "media query should be re-evaluated."); - is( + const { screen } = await grabContentInfo(ui); + isnot( window.screen.width, screen.width, - "screen.width should be the size of the screen." + "screen.width should not be the size of the screen." ); } +async function checkSubframeProps(ui) { + const { outerWidth, matchesMedia, screen } = await grabContentSubframeInfo( + ui + ); + is(outerWidth, 90, "subframe outerWidth should be 90px"); + ok(matchesMedia, "subframe media query should match"); + is(screen.width, 90, "subframe screen.width should be the page width"); + is(screen.height, 500, "subframe screen.height should be the page height"); +} + function grabContentInfo(ui) { return ContentTask.spawn(ui.getViewportBrowser(), {}, async function() { return { @@ -130,3 +155,20 @@ function grabContentInfo(ui) { }; }); } + +function grabContentSubframeInfo(ui) { + return ContentTask.spawn(ui.getViewportBrowser(), {}, async function() { + const subframe = content.document.getElementById("subframe"); + const win = subframe.contentWindow; + return { + screen: { + width: win.screen.width, + height: win.screen.height, + }, + innerWidth: win.innerWidth, + matchesMedia: win.matchMedia("(max-device-width:100px)").matches, + outerHeight: win.outerHeight, + outerWidth: win.outerWidth, + }; + }); +} diff --git a/devtools/client/responsive/test/browser/browser_viewport_resizing_after_reload.js b/devtools/client/responsive/test/browser/browser_viewport_resizing_after_reload.js index 189833f89f..d04fc8e1bb 100644 --- a/devtools/client/responsive/test/browser/browser_viewport_resizing_after_reload.js +++ b/devtools/client/responsive/test/browser/browser_viewport_resizing_after_reload.js @@ -38,7 +38,7 @@ addRDMTask(TEST_URL, async function({ ui, manager }) { }, { metaSupport: true, - before: [0.5, 600, 1200], + before: [0.5, 300, 600], after: [1.0, 600, 300], }, ]; diff --git a/devtools/client/responsive/test/browser/browser_viewport_resizing_fixed_width.js b/devtools/client/responsive/test/browser/browser_viewport_resizing_fixed_width.js index e52c7a6916..32086631b4 100644 --- a/devtools/client/responsive/test/browser/browser_viewport_resizing_fixed_width.js +++ b/devtools/client/responsive/test/browser/browser_viewport_resizing_fixed_width.js @@ -35,7 +35,7 @@ addRDMTask(TEST_URL, async function({ ui, manager }) { { metaSupport: true, before: [2.0, 300, 150], - after: [0.25, 200, 200], // This checks that min-zoom is active. + after: [0.25, 300, 300], // This checks that min-zoom is active. }, ]; diff --git a/devtools/client/responsive/test/browser/browser_window_sizing.js b/devtools/client/responsive/test/browser/browser_window_sizing.js index 2524e1862a..e05891449a 100644 --- a/devtools/client/responsive/test/browser/browser_window_sizing.js +++ b/devtools/client/responsive/test/browser/browser_window_sizing.js @@ -12,6 +12,14 @@ const WIDTH = 375; const HEIGHT = 450; const ZOOM_LEVELS = [0.3, 0.5, 0.9, 1, 1.5, 2, 2.4]; +function promiseContentReflow(ui) { + return ContentTask.spawn(ui.getViewportBrowser(), {}, async function() { + return new Promise(resolve => { + content.window.requestAnimationFrame(resolve); + }); + }); +} + add_task(async function() { const tab = await addTab(TEST_URL); const browser = tab.linkedBrowser; @@ -24,27 +32,40 @@ add_task(async function() { info(`Setting zoom level to ${ZOOM_LEVELS[i]}`); ZoomManager.setZoomForBrowser(browser, ZOOM_LEVELS[i]); - await checkWindowOuterSize(ui); - await checkWindowScreenSize(ui); + // We need to ensure that the RDM pane has had time to both change size and + // change the zoom level. This is currently not an atomic operation. The event + // timing is this: + // 1) Pane changes size, content reflows. + // 2) Pane changes zoom, content reflows. + // So to wait for the post-zoom reflow to be complete, we have two wait on TWO + // reflows. + await promiseContentReflow(ui); + await promiseContentReflow(ui); + + await checkWindowOuterSize(ui, ZOOM_LEVELS[i]); + await checkWindowScreenSize(ui, ZOOM_LEVELS[i]); } }); -async function checkWindowOuterSize(ui) { +async function checkWindowOuterSize(ui, zoom_level) { return ContentTask.spawn( ui.getViewportBrowser(), - { width: WIDTH, height: HEIGHT }, - async function({ width, height }) { + { width: WIDTH, height: HEIGHT, zoom: zoom_level }, + async function({ width, height, zoom }) { // Approximate the outer size value returned on the window content with the expected - // value. We should expect, at the very most, a 1px difference between the two due + // value. We should expect, at the very most, a 2px difference between the two due // to floating point rounding errors that occur when scaling from inner size CSS // integer values to outer size CSS integer values. See Part 1 of Bug 1107456. + // Some of the drift is also due to full zoom scaling effects; see Bug 1577775. ok( - Math.abs(content.outerWidth - width) <= 1, - `window.outerWidth should be ${width} and we got ${content.outerWidth}.` + Math.abs(content.outerWidth - width) <= 2, + `window.outerWidth zoom ${zoom} should be ${width} and we got ${ + content.outerWidth + }.` ); ok( - Math.abs(content.outerHeight - height) <= 1, - `window.outerHeight should be ${height} and we got ${ + Math.abs(content.outerHeight - height) <= 2, + `window.outerHeight zoom ${zoom} should be ${height} and we got ${ content.outerHeight }.` ); @@ -52,33 +73,39 @@ async function checkWindowOuterSize(ui) { ); } -async function checkWindowScreenSize(ui) { +async function checkWindowScreenSize(ui, zoom_level) { return ContentTask.spawn( ui.getViewportBrowser(), - { width: WIDTH, height: HEIGHT }, - async function({ width, height }) { + { width: WIDTH, height: HEIGHT, zoom: zoom_level }, + async function({ width, height, zoom }) { const { screen } = content; ok( - Math.abs(screen.availWidth - width) <= 1, - `screen.availWidth should be ${width} and we got ${screen.availWidth}.` + Math.abs(screen.availWidth - width) <= 2, + `screen.availWidth zoom ${zoom} should be ${width} and we got ${ + screen.availWidth + }.` ); ok( - Math.abs(screen.availHeight - height) <= 1, - `screen.availHeight should be ${height} and we got ${ + Math.abs(screen.availHeight - height) <= 2, + `screen.availHeight zoom ${zoom} should be ${height} and we got ${ screen.availHeight }.` ); ok( - Math.abs(screen.width - width) <= 1, - `screen.width should be ${width} and we got ${screen.width}.` + Math.abs(screen.width - width) <= 2, + `screen.width zoom " ${zoom} should be ${width} and we got ${ + screen.width + }.` ); ok( - Math.abs(screen.height - height) <= 1, - `screen.height should be ${height} and we got ${screen.height}.` + Math.abs(screen.height - height) <= 2, + `screen.height zoom " ${zoom} should be ${height} and we got ${ + screen.height + }.` ); } ); diff --git a/devtools/client/responsive/test/browser/head.js b/devtools/client/responsive/test/browser/head.js index 2244871780..02a8ebaddc 100644 --- a/devtools/client/responsive/test/browser/head.js +++ b/devtools/client/responsive/test/browser/head.js @@ -47,8 +47,7 @@ const asyncStorage = require("devtools/shared/async-storage"); loader.lazyRequireGetter( this, "ResponsiveUIManager", - "devtools/client/responsive/manager", - true + "devtools/client/responsive/manager" ); const E10S_MULTI_ENABLED = @@ -195,26 +194,26 @@ function waitForViewportResizeTo(ui, width, height) { // See bug 1302879. const browser = ui.getViewportBrowser(); - const onResizeViewport = data => { + const onContentResize = data => { if (!isSizeMatching(data)) { return; } - ui.off("viewport-resize", onResizeViewport); + ui.off("content-resize", onContentResize); browser.removeEventListener("mozbrowserloadend", onBrowserLoadEnd); - info(`Got viewport-resize to ${width} x ${height}`); + info(`Got content-resize to ${width} x ${height}`); resolve(); }; const onBrowserLoadEnd = async function() { const data = ui.getViewportSize(ui); - onResizeViewport(data); + onContentResize(data); }; info(`Waiting for viewport-resize to ${width} x ${height}`); // We're changing the viewport size, which may also change the content // size. We wait on the viewport resize event, and check for the // desired size. - ui.on("viewport-resize", onResizeViewport); + ui.on("content-resize", onContentResize); browser.addEventListener("mozbrowserloadend", onBrowserLoadEnd, { once: true, }); @@ -723,24 +722,24 @@ async function testViewportZoomWidthAndHeight( } if (typeof width !== "undefined" || typeof height !== "undefined") { - const layoutSize = await spawnViewportTask(ui, {}, function() { + const innerSize = await spawnViewportTask(ui, {}, function() { return { - width: content.screen.width, - height: content.screen.height, + width: content.innerWidth, + height: content.innerHeight, }; }); if (typeof width !== "undefined") { is( - layoutSize.width, + innerSize.width, width, - message + " should have expected layout width." + message + " should have expected inner width." ); } if (typeof height !== "undefined") { is( - layoutSize.height, + innerSize.height, height, - message + " should have expected layout height." + message + " should have expected inner height." ); } } diff --git a/devtools/client/responsive/ui.js b/devtools/client/responsive/ui.js new file mode 100644 index 0000000000..5bc9bf514e --- /dev/null +++ b/devtools/client/responsive/ui.js @@ -0,0 +1,693 @@ +/* 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 { Ci } = require("chrome"); +const Services = require("Services"); +const EventEmitter = require("devtools/shared/event-emitter"); +const { getOrientation } = require("./utils/orientation"); + +loader.lazyRequireGetter( + this, + "DebuggerClient", + "devtools/shared/client/debugger-client", + true +); +loader.lazyRequireGetter( + this, + "DebuggerServer", + "devtools/server/debugger-server", + true +); +loader.lazyRequireGetter( + this, + "throttlingProfiles", + "devtools/client/shared/components/throttling/profiles" +); +loader.lazyRequireGetter( + this, + "swapToInnerBrowser", + "devtools/client/responsive/browser/swap", + true +); +loader.lazyRequireGetter( + this, + "message", + "devtools/client/responsive/utils/message" +); +loader.lazyRequireGetter( + this, + "showNotification", + "devtools/client/responsive/utils/notification", + true +); +loader.lazyRequireGetter(this, "l10n", "devtools/client/responsive/utils/l10n"); +loader.lazyRequireGetter( + this, + "EmulationFront", + "devtools/shared/fronts/emulation", + true +); +loader.lazyRequireGetter(this, "asyncStorage", "devtools/shared/async-storage"); + +const TOOL_URL = "chrome://devtools/content/responsive/index.xhtml"; + +const RELOAD_CONDITION_PREF_PREFIX = "devtools.responsive.reloadConditions."; +const RELOAD_NOTIFICATION_PREF = + "devtools.responsive.reloadNotification.enabled"; + +function debug(msg) { + // console.log(`RDM manager: ${msg}`); +} + +/** + * ResponsiveUI manages the responsive design tool for a specific tab. The + * actual tool itself lives in a separate chrome:// document that is loaded into + * the tab upon opening responsive design. This object acts a helper to + * integrate the tool into the surrounding browser UI as needed. + */ +class ResponsiveUI { + /** + * @param {ResponsiveUIManager} manager + * The ResponsiveUIManager instance. + * @param {ChromeWindow} window + * The main browser chrome window (that holds many tabs). + * @param {Tab} tab + * The specific browser element this responsive instance is for. + */ + constructor(manager, window, tab) { + this.manager = manager; + // The main browser chrome window (that holds many tabs). + this.browserWindow = window; + // The specific browser tab this responsive instance is for. + this.tab = tab; + + // Flag set when destruction has begun. + this.destroying = false; + // Flag set when destruction has ended. + this.destroyed = false; + /** + * A window reference for the chrome:// document that displays the responsive + * design tool. It is safe to reference this window directly even with e10s, + * as the tool UI is always loaded in the parent process. The web content + * contained *within* the tool UI on the other hand is loaded in the child + * process. + */ + this.toolWindow = null; + + // Promise resovled when the UI init has completed. + this.inited = this.init(); + + EventEmitter.decorate(this); + } + + /** + * Open RDM while preserving the state of the page. We use `swapFrameLoaders` + * to ensure all in-page state is preserved, just like when you move a tab to + * a new window. + * + * For more details, see /devtools/docs/responsive-design-mode.md. + */ + async init() { + debug("Init start"); + + const ui = this; + + // Watch for tab close and window close so we can clean up RDM synchronously + this.tab.addEventListener("TabClose", this); + this.browserWindow.addEventListener("unload", this); + + // Swap page content from the current tab into a viewport within RDM + debug("Create browser swapper"); + this.swap = swapToInnerBrowser({ + tab: this.tab, + containerURL: TOOL_URL, + async getInnerBrowser(containerBrowser) { + const toolWindow = (ui.toolWindow = containerBrowser.contentWindow); + toolWindow.addEventListener("message", ui); + debug("Wait until init from inner"); + await message.request(toolWindow, "init"); + toolWindow.addInitialViewport({ + uri: "about:blank", + userContextId: ui.tab.userContextId, + }); + debug("Wait until browser mounted"); + await message.wait(toolWindow, "browser-mounted"); + return ui.getViewportBrowser(); + }, + }); + debug("Wait until swap start"); + await this.swap.start(); + + // Set the ui toolWindow to fullZoom and textZoom of 100%. Directly change + // the zoom levels of the toolwindow docshell. That doesn't affect the zoom + // of the RDM content, but it does send events that confuse the Zoom UI. + // So before we adjust the zoom levels of the toolWindow, we first cache + // the reported zoom levels of the RDM content, because we'll have to + // re-apply them to re-sync the Zoom UI. + + // Cache the values now and we'll re-apply them near the end of this function. + // This is important since other steps here can also cause the Zoom UI update + // event to be sent for other browsers, and this means that the changes from + // our Zoom UI update event would be overwritten. After this function, future + // changes to zoom levels will send Zoom UI update events in an order that + // keeps the Zoom UI synchronized with the RDM content zoom levels. + const rdmContent = this.tab.linkedBrowser; + const rdmViewport = ui.toolWindow; + + const fullZoom = rdmContent.fullZoom; + const textZoom = rdmContent.textZoom; + + rdmViewport.docShell.contentViewer.fullZoom = 1; + rdmViewport.docShell.contentViewer.textZoom = 1; + + // Listen to FullZoomChange events coming from the linkedBrowser, + // so that we can zoom the size of the viewport by the same amount. + rdmContent.addEventListener("FullZoomChange", this); + + this.tab.addEventListener("BeforeTabRemotenessChange", this); + + // Notify the inner browser to start the frame script + debug("Wait until start frame script"); + await message.request(this.toolWindow, "start-frame-script"); + + // Get the protocol ready to speak with emulation actor + debug("Wait until RDP server connect"); + await this.connectToServer(); + + // Restore the previous state of RDM. + await this.restoreState(); + + // Re-apply our cached zoom levels. Other Zoom UI update events have finished + // by now. + rdmContent.fullZoom = fullZoom; + rdmContent.textZoom = textZoom; + + // Non-blocking message to tool UI to start any delayed init activities + message.post(this.toolWindow, "post-init"); + + debug("Init done"); + } + + /** + * Close RDM and restore page content back into a regular tab. + * + * @param object + * Destroy options, which currently includes a `reason` string. + * @return boolean + * Whether this call is actually destroying. False means destruction + * was already in progress. + */ + async destroy(options) { + if (this.destroying) { + return false; + } + this.destroying = true; + + // If our tab is about to be closed, there's not enough time to exit + // gracefully, but that shouldn't be a problem since the tab will go away. + // So, skip any waiting when we're about to close the tab. + const isTabDestroyed = !this.tab.linkedBrowser; + const isWindowClosing = + (options && options.reason === "unload") || isTabDestroyed; + const isTabContentDestroying = + isWindowClosing || + (options && + (options.reason === "TabClose" || + options.reason === "BeforeTabRemotenessChange")); + + // Ensure init has finished before starting destroy + if (!isTabContentDestroying) { + await this.inited; + } + + this.tab.linkedBrowser.removeEventListener("FullZoomChange", this); + this.tab.removeEventListener("TabClose", this); + this.tab.removeEventListener("BeforeTabRemotenessChange", this); + this.browserWindow.removeEventListener("unload", this); + this.toolWindow.removeEventListener("message", this); + + if (!isTabContentDestroying) { + // Notify the inner browser to stop the frame script + await message.request(this.toolWindow, "stop-frame-script"); + } + + // Ensure the tab is reloaded if required when exiting RDM so that no emulated + // settings are left in a customized state. + if (!isTabContentDestroying) { + let reloadNeeded = false; + await this.updateDPPX(); + await this.updateNetworkThrottling(); + reloadNeeded |= + (await this.updateUserAgent()) && this.reloadOnChange("userAgent"); + reloadNeeded |= + (await this.updateTouchSimulation()) && + this.reloadOnChange("touchSimulation"); + if (reloadNeeded) { + this.getViewportBrowser().reload(); + } + } + + // Destroy local state + const swap = this.swap; + this.browserWindow = null; + this.tab = null; + this.inited = null; + this.toolWindow = null; + this.swap = null; + + // Close the debugger client used to speak with emulation actor. + // The actor handles clearing any overrides itself, so it's not necessary to clear + // anything on shutdown client side. + const clientClosed = this.client.close(); + if (!isTabContentDestroying) { + await clientClosed; + } + this.client = this.emulationFront = null; + + if (!isWindowClosing) { + // Undo the swap and return the content back to a normal tab + swap.stop(); + } + + this.destroyed = true; + + return true; + } + + async connectToServer() { + // The client being instantiated here is separate from the toolbox. It is being used + // separately and has a life cycle that doesn't correspond to the toolbox. As a + // result, it does not have a target, so we are not using `target.getFront` here. See + // also the implementation for about:debugging + DebuggerServer.init(); + DebuggerServer.registerAllActors(); + this.client = new DebuggerClient(DebuggerServer.connectPipe()); + await this.client.connect(); + const targetFront = await this.client.mainRoot.getTab(); + this.emulationFront = new EmulationFront(this.client); + // Because we are not using getFront (see previous comment), we have to do what it + // does and manually set the front's actor ID here. + // Once bug 1465635 is resolved, we will be able to use getFront from here and remove + // this. + this.emulationFront.actorID = + targetFront.targetForm[this.emulationFront.formAttributeName]; + this.emulationFront.manage(this.emulationFront); + } + + /** + * Show one-time notification about reloads for emulation. + */ + showReloadNotification() { + if (Services.prefs.getBoolPref(RELOAD_NOTIFICATION_PREF, false)) { + showNotification(this.browserWindow, this.tab, { + msg: l10n.getFormatStr("responsive.reloadNotification.description2"), + }); + Services.prefs.setBoolPref(RELOAD_NOTIFICATION_PREF, false); + } + } + + reloadOnChange(id) { + this.showReloadNotification(); + const pref = RELOAD_CONDITION_PREF_PREFIX + id; + return Services.prefs.getBoolPref(pref, false); + } + + handleEvent(event) { + const { browserWindow, tab, toolWindow } = this; + + switch (event.type) { + case "message": + this.handleMessage(event); + break; + case "FullZoomChange": + const zoom = tab.linkedBrowser.fullZoom; + toolWindow.setViewportZoom(zoom); + break; + case "BeforeTabRemotenessChange": + case "TabClose": + case "unload": + this.manager.closeIfNeeded(browserWindow, tab, { + reason: event.type, + }); + break; + } + } + + handleMessage(event) { + if (event.origin !== "chrome://devtools") { + return; + } + + switch (event.data.type) { + case "change-device": + this.onChangeDevice(event); + break; + case "change-network-throttling": + this.onChangeNetworkThrottling(event); + break; + case "change-pixel-ratio": + this.onChangePixelRatio(event); + break; + case "change-touch-simulation": + this.onChangeTouchSimulation(event); + break; + case "change-user-agent": + this.onChangeUserAgent(event); + break; + case "content-resize": + this.onContentResize(event); + break; + case "exit": + this.onExit(); + break; + case "remove-device-association": + this.onRemoveDeviceAssociation(); + break; + case "viewport-orientation-change": + this.onRotateViewport(event); + break; + case "viewport-resize": + this.onResizeViewport(event); + break; + } + } + + async onChangeDevice(event) { + const { pixelRatio, touch, userAgent } = event.data.device; + let reloadNeeded = false; + await this.updateDPPX(pixelRatio); + + // Get the orientation values of the device we are changing to and update. + const { device, viewport } = event.data; + const { type, angle } = getOrientation(device, viewport); + await this.updateScreenOrientation(type, angle); + + reloadNeeded |= + (await this.updateUserAgent(userAgent)) && + this.reloadOnChange("userAgent"); + reloadNeeded |= + (await this.updateTouchSimulation(touch)) && + this.reloadOnChange("touchSimulation"); + if (reloadNeeded) { + this.getViewportBrowser().reload(); + } + // Used by tests + this.emit("device-changed"); + } + + async onChangeNetworkThrottling(event) { + const { enabled, profile } = event.data; + await this.updateNetworkThrottling(enabled, profile); + // Used by tests + this.emit("network-throttling-changed"); + } + + onChangePixelRatio(event) { + const { pixelRatio } = event.data; + this.updateDPPX(pixelRatio); + } + + async onChangeTouchSimulation(event) { + const { enabled } = event.data; + const reloadNeeded = + (await this.updateTouchSimulation(enabled)) && + this.reloadOnChange("touchSimulation"); + if (reloadNeeded) { + this.getViewportBrowser().reload(); + } + // Used by tests + this.emit("touch-simulation-changed"); + } + + async onChangeUserAgent(event) { + const { userAgent } = event.data; + const reloadNeeded = + (await this.updateUserAgent(userAgent)) && + this.reloadOnChange("userAgent"); + if (reloadNeeded) { + this.getViewportBrowser().reload(); + } + this.emit("user-agent-changed"); + } + + onContentResize(event) { + const { width, height } = event.data; + this.emit("content-resize", { + width, + height, + }); + } + + onExit() { + const { browserWindow, tab } = this; + this.manager.closeIfNeeded(browserWindow, tab); + } + + async onRemoveDeviceAssociation() { + let reloadNeeded = false; + await this.updateDPPX(); + reloadNeeded |= + (await this.updateUserAgent()) && this.reloadOnChange("userAgent"); + reloadNeeded |= + (await this.updateTouchSimulation()) && + this.reloadOnChange("touchSimulation"); + if (reloadNeeded) { + this.getViewportBrowser().reload(); + } + // Used by tests + this.emit("device-association-removed"); + } + + onResizeViewport(event) { + const { width, height } = event.data; + this.emit("viewport-resize", { + width, + height, + }); + } + + async onRotateViewport(event) { + const { orientationType: type, angle, isViewportRotated } = event.data; + await this.updateScreenOrientation(type, angle, isViewportRotated); + } + + /** + * Restores the previous state of RDM. + */ + async restoreState() { + const deviceState = await asyncStorage.getItem( + "devtools.responsive.deviceState" + ); + if (deviceState) { + // Return if there is a device state to restore, this will be done when the + // device list is loaded after the post-init. + return; + } + + const height = Services.prefs.getIntPref( + "devtools.responsive.viewport.height", + 0 + ); + const pixelRatio = Services.prefs.getIntPref( + "devtools.responsive.viewport.pixelRatio", + 0 + ); + const touchSimulationEnabled = Services.prefs.getBoolPref( + "devtools.responsive.touchSimulation.enabled", + false + ); + const userAgent = Services.prefs.getCharPref( + "devtools.responsive.userAgent", + "" + ); + const width = Services.prefs.getIntPref( + "devtools.responsive.viewport.width", + 0 + ); + + let reloadNeeded = false; + const { type, angle } = this.getInitialViewportOrientation({ + width, + height, + }); + + await this.updateDPPX(pixelRatio); + await this.updateScreenOrientation(type, angle); + + if (touchSimulationEnabled) { + reloadNeeded |= + (await this.updateTouchSimulation(touchSimulationEnabled)) && + this.reloadOnChange("touchSimulation"); + } + if (userAgent) { + reloadNeeded |= + (await this.updateUserAgent(userAgent)) && + this.reloadOnChange("userAgent"); + } + if (reloadNeeded) { + this.getViewportBrowser().reload(); + } + } + + /** + * Set or clear the emulated device pixel ratio. + * + * @return boolean + * Whether a reload is needed to apply the change. + * (This is always immediate, so it's always false.) + */ + async updateDPPX(dppx) { + if (!dppx) { + await this.emulationFront.clearDPPXOverride(); + return false; + } + await this.emulationFront.setDPPXOverride(dppx); + return false; + } + + /** + * Set or clear network throttling. + * + * @return boolean + * Whether a reload is needed to apply the change. + * (This is always immediate, so it's always false.) + */ + async updateNetworkThrottling(enabled, profile) { + if (!enabled) { + await this.emulationFront.clearNetworkThrottling(); + return false; + } + const data = throttlingProfiles.find(({ id }) => id == profile); + const { download, upload, latency } = data; + await this.emulationFront.setNetworkThrottling({ + downloadThroughput: download, + uploadThroughput: upload, + latency, + }); + return false; + } + + /** + * Set or clear the emulated user agent. + * + * @return boolean + * Whether a reload is needed to apply the change. + */ + updateUserAgent(userAgent) { + if (!userAgent) { + return this.emulationFront.clearUserAgentOverride(); + } + return this.emulationFront.setUserAgentOverride(userAgent); + } + + /** + * Set or clear touch simulation. When setting to true, this method will + * additionally set meta viewport override if the pref + * "devtools.responsive.metaViewport.enabled" is true. When setting to + * false, this method will clear all touch simulation and meta viewport + * overrides, returning to default behavior for both settings. + * + * @return boolean + * Whether a reload is needed to apply the override change(s). + */ + async updateTouchSimulation(enabled) { + let reloadNeeded; + if (enabled) { + const metaViewportEnabled = Services.prefs.getBoolPref( + "devtools.responsive.metaViewport.enabled", + false + ); + + reloadNeeded = await this.emulationFront.setTouchEventsOverride( + Ci.nsIDocShell.TOUCHEVENTS_OVERRIDE_ENABLED + ); + + if (metaViewportEnabled) { + reloadNeeded |= await this.emulationFront.setMetaViewportOverride( + Ci.nsIDocShell.META_VIEWPORT_OVERRIDE_ENABLED + ); + } + } else { + reloadNeeded = await this.emulationFront.clearTouchEventsOverride(); + reloadNeeded |= await this.emulationFront.clearMetaViewportOverride(); + } + return reloadNeeded; + } + + /** + * Sets the screen orientation values of the simulated device. + * + * @param {String} type + * The orientation type to update the current device screen to. + * @param {Number} angle + * The rotation angle to update the current device screen to. + * @param {Boolean} isViewportRotated + * Whether or not the reason for updating the screen orientation is a result + * of actually rotating the device via the RDM toolbar. If true, then an + * "orientationchange" event is simulated. Otherwise, the screen orientation is + * updated because of changing devices, opening RDM, or the page has been + * reloaded/navigated to, so we should not be simulating "orientationchange". + */ + async updateScreenOrientation(type, angle, isViewportRotated = false) { + const targetFront = await this.client.mainRoot.getTab(); + const simulateOrientationChangeSupported = await targetFront.actorHasMethod( + "emulation", + "simulateScreenOrientationChange" + ); + + // Ensure that simulateScreenOrientationChange is supported. + if (simulateOrientationChangeSupported) { + await this.emulationFront.simulateScreenOrientationChange( + type, + angle, + isViewportRotated + ); + } + + // Used by tests. + if (!isViewportRotated) { + this.emit("only-viewport-orientation-changed"); + } + } + + /** + * Helper for tests. Assumes a single viewport for now. + */ + getViewportSize() { + return this.toolWindow.getViewportSize(); + } + + /** + * Helper for tests, etc. Assumes a single viewport for now. + */ + async setViewportSize(size) { + await this.inited; + this.toolWindow.setViewportSize(size); + } + + /** + * Helper for tests/reloading the viewport. Assumes a single viewport for now. + */ + getViewportBrowser() { + return this.toolWindow.getViewportBrowser(); + } + + /** + * Helper for contacting the viewport content. Assumes a single viewport for now. + */ + getViewportMessageManager() { + return this.getViewportBrowser().messageManager; + } + + /** + * Helper for getting the initial viewport orientation. + */ + getInitialViewportOrientation(viewport) { + return getOrientation(viewport, viewport); + } +} + +module.exports = ResponsiveUI; diff --git a/devtools/client/responsive/utils/window.js b/devtools/client/responsive/utils/window.js index 200609fcb6..3435216b48 100644 --- a/devtools/client/responsive/utils/window.js +++ b/devtools/client/responsive/utils/window.js @@ -23,8 +23,7 @@ exports.getDOMWindowUtils = getDOMWindowUtils; * Check if the given browser window has finished the startup. * @params {nsIDOMWindow} window */ -const isStartupFinished = window => - window.gBrowserInit && window.gBrowserInit.delayedStartupFinished; +const isStartupFinished = window => window.gBrowserInit?.delayedStartupFinished; function startup(window) { return new Promise(resolve => { diff --git a/devtools/client/scratchpad/index.xul b/devtools/client/scratchpad/index.xul deleted file mode 100644 index 9750f941c5..0000000000 --- a/devtools/client/scratchpad/index.xul +++ /dev/null @@ -1,411 +0,0 @@ - - - - - - %scratchpadDTD; - -%editMenuStrings; - -%sourceEditorStrings; -]> - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + &noStyleSheet.label; + &noStyleSheet-tip-start.label; + &noStyleSheet-tip-action.label; + &noStyleSheet-tip-end.label; + + + + + + + + + diff --git a/devtools/client/styleeditor/index.xul b/devtools/client/styleeditor/index.xul deleted file mode 100644 index 0a4cb4ab5a..0000000000 --- a/devtools/client/styleeditor/index.xul +++ /dev/null @@ -1,159 +0,0 @@ - - - - %styleEditorDTD; - - %editMenuStrings; - - %sourceEditorStrings; -]> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - &noStyleSheet.label; - &noStyleSheet-tip-start.label; - &noStyleSheet-tip-action.label; - &noStyleSheet-tip-end.label; - - - - - - - - - - - - diff --git a/devtools/client/styleeditor/panel.js b/devtools/client/styleeditor/panel.js index 6006b5b8b6..1ac81ec157 100644 --- a/devtools/client/styleeditor/panel.js +++ b/devtools/client/styleeditor/panel.js @@ -5,7 +5,6 @@ "use strict"; var Services = require("Services"); -var promise = require("promise"); var { XPCOMUtils } = require("resource://gre/modules/XPCOMUtils.jsm"); var EventEmitter = require("devtools/shared/event-emitter"); @@ -131,22 +130,21 @@ StyleEditorPanel.prototype = { * Destroy the style editor. */ destroy: function() { - if (!this._destroyed) { - this._destroyed = true; - - this._target.off("close", this.destroy); - this._target = null; - this._toolbox = null; - this._panelWin = null; - this._panelDoc = null; - this._debuggee.destroy(); - this._debuggee = null; - - this.UI.destroy(); - this.UI = null; + if (this._destroyed) { + return; } + this._destroyed = true; - return promise.resolve(null); + this._target.off("close", this.destroy); + this._target = null; + this._toolbox = null; + this._panelWin = null; + this._panelDoc = null; + this._debuggee.destroy(); + this._debuggee = null; + + this.UI.destroy(); + this.UI = null; }, }; diff --git a/devtools/client/styleeditor/test/browser.ini b/devtools/client/styleeditor/test/browser.ini index 9e7baa1aa3..36947e1676 100644 --- a/devtools/client/styleeditor/test/browser.ini +++ b/devtools/client/styleeditor/test/browser.ini @@ -62,7 +62,7 @@ support-files = doc_long_string.css doc_long.css doc_short_string.css - doc_xulpage.xul + doc_xulpage.xhtml sync.html utf-16.css !/devtools/client/inspector/shared/test/head.js diff --git a/devtools/client/styleeditor/test/browser_styleeditor_bug_851132_middle_click.js b/devtools/client/styleeditor/test/browser_styleeditor_bug_851132_middle_click.js index 4f31102eba..8b40da3e2b 100644 --- a/devtools/client/styleeditor/test/browser_styleeditor_bug_851132_middle_click.js +++ b/devtools/client/styleeditor/test/browser_styleeditor_bug_851132_middle_click.js @@ -2,7 +2,7 @@ http://creativecommons.org/publicdomain/zero/1.0/ */ "use strict"; -// Test that middle click on style sheet doesn't open index.xul in a new +// Test that middle click on style sheet doesn't open index.xhtml in a new // tab (bug 851132). const TESTCASE_URI = TEST_BASE_HTTP + "four.html"; diff --git a/devtools/client/styleeditor/test/browser_styleeditor_loading.js b/devtools/client/styleeditor/test/browser_styleeditor_loading.js index 560b34c5c0..4ad0cf59e2 100644 --- a/devtools/client/styleeditor/test/browser_styleeditor_loading.js +++ b/devtools/client/styleeditor/test/browser_styleeditor_loading.js @@ -19,11 +19,10 @@ add_task(async function() { const toolbox = gDevTools.getToolbox(target); const panel = toolbox.getPanel("styleeditor"); - const { panelWindow } = panel; + const { panelWindow, UI: ui } = panel; - const root = panelWindow.document.querySelector(".splitview-root"); ok( - !root.classList.contains("loading"), + !ui._root.classList.contains("loading"), "style editor root element does not have 'loading' class name anymore" ); diff --git a/devtools/client/styleeditor/test/browser_styleeditor_media_sidebar.js b/devtools/client/styleeditor/test/browser_styleeditor_media_sidebar.js index ae1840a409..25186c7f36 100644 --- a/devtools/client/styleeditor/test/browser_styleeditor_media_sidebar.js +++ b/devtools/client/styleeditor/test/browser_styleeditor_media_sidebar.js @@ -7,16 +7,17 @@ const TESTCASE_URI = TEST_BASE_HTTPS + "media-rules.html"; const MEDIA_PREF = "devtools.styleeditor.showMediaSidebar"; -const RESIZE = 300; +const RESIZE_W = 300; +const RESIZE_H = 450; const LABELS = [ "not all", "all", - "(max-width: 400px)", + "(max-width: 550px)", "(min-height: 300px) and (max-height: 320px)", - "(max-width: 600px)", + "(max-width: 750px)", ]; const LINE_NOS = [1, 7, 19, 25, 31]; -const NEW_RULE = "\n@media (max-width: 600px) { div { color: blue; } }"; +const NEW_RULE = "\n@media (max-width: 750px) { div { color: blue; } }"; waitForExplicitFinish(); @@ -46,7 +47,7 @@ add_task(async function() { const originalHeight = window.outerHeight; const onMatchesChange = listenForMediaChange(ui); - window.resizeTo(RESIZE, RESIZE); + window.resizeTo(RESIZE_W, RESIZE_H); await onMatchesChange; testMediaMatchChanged(mediaEditor); @@ -78,7 +79,7 @@ function testMediaMatchChanged(editor) { const cond = sidebar.querySelectorAll(".media-rule-condition")[2]; is( cond.textContent, - "(max-width: 400px)", + "(max-width: 550px)", "third rule condition text is correct" ); ok( diff --git a/devtools/client/styleeditor/test/browser_styleeditor_media_sidebar_links.js b/devtools/client/styleeditor/test/browser_styleeditor_media_sidebar_links.js index 8ea26fdbe7..0c4f5552b4 100644 --- a/devtools/client/styleeditor/test/browser_styleeditor_media_sidebar_links.js +++ b/devtools/client/styleeditor/test/browser_styleeditor_media_sidebar_links.js @@ -20,8 +20,7 @@ registerCleanupFunction(() => { loader.lazyRequireGetter( this, "ResponsiveUIManager", - "devtools/client/responsive/manager", - true + "devtools/client/responsive/manager" ); const TESTCASE_URI = TEST_BASE_HTTPS + "media-rules.html"; @@ -35,7 +34,7 @@ add_task(async function() { const tab = gBrowser.selectedTab; testNumberOfLinks(editor); - await testMediaLink(editor, tab, ui, 2, "width", 400); + await testMediaLink(editor, tab, ui, 2, "width", 550); await testMediaLink(editor, tab, ui, 3, "height", 300); await closeRDM(tab, ui); diff --git a/devtools/client/styleeditor/test/browser_styleeditor_nostyle.js b/devtools/client/styleeditor/test/browser_styleeditor_nostyle.js index c1cc196bd1..acb153c919 100644 --- a/devtools/client/styleeditor/test/browser_styleeditor_nostyle.js +++ b/devtools/client/styleeditor/test/browser_styleeditor_nostyle.js @@ -8,16 +8,18 @@ const TESTCASE_URI = TEST_BASE_HTTP + "nostyle.html"; add_task(async function() { - const { panel } = await openStyleEditorForURL(TESTCASE_URI); + const { panel, ui } = await openStyleEditorForURL(TESTCASE_URI); const { panelWindow } = panel; - const root = panelWindow.document.querySelector(".splitview-root"); ok( - !root.classList.contains("loading"), + !ui._root.classList.contains("loading"), "style editor root element does not have 'loading' class name anymore" ); - ok(root.querySelector(".empty.placeholder"), "showing 'no style' indicator"); + ok( + ui._root.querySelector(".empty.placeholder"), + "showing 'no style' indicator" + ); let button = panelWindow.document.querySelector(".style-editor-newButton"); ok(!button.hasAttribute("disabled"), "new style sheet button is enabled"); diff --git a/devtools/client/styleeditor/test/browser_styleeditor_xul.js b/devtools/client/styleeditor/test/browser_styleeditor_xul.js index 927ddf47f0..92545e96b8 100644 --- a/devtools/client/styleeditor/test/browser_styleeditor_xul.js +++ b/devtools/client/styleeditor/test/browser_styleeditor_xul.js @@ -7,7 +7,7 @@ waitForExplicitFinish(); -const TEST_URL = TEST_BASE + "doc_xulpage.xul"; +const TEST_URL = TEST_BASE + "doc_xulpage.xhtml"; add_task(async function() { const tab = await addTab(TEST_URL); diff --git a/devtools/client/styleeditor/test/doc_xulpage.xul b/devtools/client/styleeditor/test/doc_xulpage.xhtml similarity index 100% rename from devtools/client/styleeditor/test/doc_xulpage.xul rename to devtools/client/styleeditor/test/doc_xulpage.xhtml diff --git a/devtools/client/styleeditor/test/media-rules.css b/devtools/client/styleeditor/test/media-rules.css index 5eea7c380a..1adce84c4b 100644 --- a/devtools/client/styleeditor/test/media-rules.css +++ b/devtools/client/styleeditor/test/media-rules.css @@ -16,7 +16,7 @@ div { background-color: ghostwhite; } -@media (max-width: 400px) { +@media (max-width: 550px) { div { color: green; } diff --git a/devtools/client/themes/animation.css b/devtools/client/themes/animation.css index 24be4a5423..ac0c6eb31e 100644 --- a/devtools/client/themes/animation.css +++ b/devtools/client/themes/animation.css @@ -9,7 +9,6 @@ --animation-item-selected-color: rgba(215, 215, 219, 0.9); --cssanimation-color: var(--purple-50); --csstransition-color: var(--blue-55); - --devtools-toolbar-height: 24px; --fast-track-image: url("images/animation-fast-track.svg"); --graph-height: 30px; --graph-right-offset: 10px; @@ -75,7 +74,7 @@ select.playback-rate-selector.devtools-button { text-align: center; } -select.playback-rate-selector.devtools-button:not(:empty):not(:disabled):not(.checked):hover { +select.playback-rate-selector.devtools-button:not(:empty, :disabled, .checked):hover { background: none; background-color: var(--toolbarbutton-background); background-image: url("chrome://devtools/skin/images/dropmarker.svg"); @@ -110,7 +109,7 @@ select.playback-rate-selector.devtools-button:not(:empty):not(:disabled):not(.ch .current-time-scrubber-area::before { content: ""; cursor: col-resize; - height: var(--devtools-toolbar-height); + height: var(--theme-toolbar-height); pointer-events: auto; position: absolute; /* In order to click on edge of current-time-scrubber-controller element */ diff --git a/devtools/client/themes/breadcrumbs.css b/devtools/client/themes/breadcrumbs.css index 2012e1e556..b49e3c91a2 100644 --- a/devtools/client/themes/breadcrumbs.css +++ b/devtools/client/themes/breadcrumbs.css @@ -95,7 +95,7 @@ border: none; margin-inline-start: 10px; margin-inline-end: 1px; - padding: 0 0 2px 0; + padding: 0; } .breadcrumbs-widget-item > .button-box { diff --git a/devtools/client/themes/chart.css b/devtools/client/themes/chart.css index 0239f85c09..6486a90459 100644 --- a/devtools/client/themes/chart.css +++ b/devtools/client/themes/chart.css @@ -69,8 +69,8 @@ transition: all 0.1s ease-out; } -.pie-chart-slice:not(:hover):not([focused]), -.pie-chart-slice:not(:hover):not([focused]) + .pie-chart-label { +.pie-chart-slice:not(:hover, [focused]), +.pie-chart-slice:not(:hover, [focused]) + .pie-chart-label { transform: none !important; } diff --git a/devtools/client/themes/common.css b/devtools/client/themes/common.css index 1f5e56a67e..0208913c53 100644 --- a/devtools/client/themes/common.css +++ b/devtools/client/themes/common.css @@ -78,6 +78,13 @@ white-space: nowrap; } +/** + * Override global.css input styles + */ +html|input { + margin: revert; +} + /** * Override wrong system font from forms.css * Bug 1458224: buttons use a wrong default font-size on Linux @@ -292,6 +299,7 @@ checkbox:-moz-focusring { /* Toolbar buttons */ .devtools-toolbarbutton, +.devtools-togglebutton, .devtools-button { appearance: none; background: transparent; @@ -308,6 +316,10 @@ checkbox:-moz-focusring { white-space: nowrap; } +.devtools-togglebutton { + padding: 1px 6px; +} + /* Remove system form border from devtools-button. */ .devtools-button::-moz-focus-inner { border: 0; @@ -411,6 +423,15 @@ checkbox:-moz-focusring { color: var(--toolbarbutton-focus-color); } +.devtools-togglebutton:not([aria-pressed="true"]) { + background: var(--theme-toolbarbutton-background); + color: var(--theme-toolbarbutton-color); +} + +.devtools-togglebutton:not([aria-pressed="true"]):hover { + background-color: var(--theme-toolbarbutton-hover-background); +} + /* Selectable button which is checked. */ .devtools-toolbarbutton:not([disabled])[label][checked=true], @@ -426,6 +447,27 @@ checkbox:-moz-focusring { background-color: var(--toolbarbutton-checked-focus-background); } +.devtools-togglebutton[aria-pressed="true"], +.devtools-togglebutton:focus { + background-color: var(--theme-toolbarbutton-checked-background); + color: var(--theme-toolbarbutton-checked-color); +} + +.devtools-togglebutton[aria-pressed="true"]:hover { + background: var(--theme-toolbarbutton-checked-hover-background); + color: var(--theme-toolbarbutton-checked-hover-color); +} + +.devtools-togglebutton:active, +.devtools-togglebutton:hover:active { + background: var(--theme-toolbarbutton-active-background); + color: var(--theme-toolbarbutton-active-color); +} + +.devtools-togglebutton::-moz-focus-inner { + border-color: transparent; +} + /* Icons */ .devtools-button.devtools-clear-icon::before { diff --git a/devtools/client/themes/dark-theme.css b/devtools/client/themes/dark-theme.css index 103ac151ed..450bb15874 100644 --- a/devtools/client/themes/dark-theme.css +++ b/devtools/client/themes/dark-theme.css @@ -181,7 +181,9 @@ body { background: transparent; } -.CodeMirror.cm-s-mozilla pre { +.CodeMirror.cm-s-mozilla pre, +.CodeMirror.cm-s-mozilla pre.CodeMirror-line, +.CodeMirror.cm-s-mozilla pre.CodeMirror-line-like { color: var(--theme-text-color-strong); } @@ -242,11 +244,6 @@ div.CodeMirror span.eval-text { background-color: var(--theme-sidebar-background); } -.cm-s-markup-view pre { - line-height: 1.4em; - min-height: 1.4em; -} - .CodeMirror-Tern-fname { color: #f7f7f7; } diff --git a/devtools/client/themes/images/tool-scratchpad.svg b/devtools/client/themes/images/tool-scratchpad.svg deleted file mode 100644 index 2c9cf3d462..0000000000 --- a/devtools/client/themes/images/tool-scratchpad.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - diff --git a/devtools/client/themes/light-theme.css b/devtools/client/themes/light-theme.css index 7e87eddb59..0efb880abd 100644 --- a/devtools/client/themes/light-theme.css +++ b/devtools/client/themes/light-theme.css @@ -179,6 +179,8 @@ body { } .CodeMirror.cm-s-mozilla pre, +.CodeMirror.cm-s-mozilla pre.CodeMirror-line, +.CodeMirror.cm-s-mozilla pre.CodeMirror-line-like, .cm-s-mozilla .cm-variable-3, .cm-s-mozilla .cm-operator, .cm-s-mozilla .cm-special { @@ -233,11 +235,6 @@ div.CodeMirror span.eval-text { background-color: var(--theme-sidebar-background); } -.cm-s-markup-view pre { - line-height: 1.4em; - min-height: 1.4em; -} - .CodeMirror-hints, .CodeMirror-Tern-tooltip { box-shadow: 0 0 4px rgba(128, 128, 128, .5); diff --git a/devtools/client/themes/perf.css b/devtools/client/themes/perf.css index 9ee14421a6..eaadd511e0 100644 --- a/devtools/client/themes/perf.css +++ b/devtools/client/themes/perf.css @@ -3,18 +3,19 @@ * You can obtain one at http://mozilla.org/MPL/2.0/. */ .perf { - width: 100%; - height: 100%; - position: absolute; display: flex; flex-direction: column; align-items: center; } -.devtools-button.perf-button { - padding: 5px; - margin: auto; - font-size: 120%; +.perf-popup { + padding: 7px 16px; +} + +.perf-devtools { + width: 100%; + height: 100%; + position: absolute; } .perf-button-image { @@ -28,9 +29,12 @@ align-items: center; } +.perf-devtools .perf-button-container { + margin-top: 65px; +} + .perf-additional-message { margin: 10px; - margin-top: 65px; } .perf > * { @@ -56,6 +60,9 @@ .perf-settings { width: 100%; +} + +.perf-devtools .perf-settings { margin: 50px 0 25px; } @@ -116,7 +123,7 @@ .perf-settings-notch { margin-right: 1px; flex: 1; - border: 1px solid rgba(0,0,0,0.2); + border: 1px solid rgba(0, 0, 0, 0.2); border-radius: 2px; } @@ -157,7 +164,7 @@ opacity: 0; transform: translateY(-100px); transition-duration: 250ms; - transition-timing-function: cubic-bezier(.07,.95,0,1); + transition-timing-function: cubic-bezier(0.07, 0.95, 0, 1); transition-property: transform, opacity; } @@ -167,7 +174,13 @@ } .perf-settings-summary { - height: 30px; + /* This is a little bit odd, but in order to match the heights of the elements and + * have a fairly large hit area for the summary element, make the element position + * relative, and adjust the position up by a few pixels. This maintains positioning, + * but also provides a larger hit area for the dropdown with the padding property. */ + position: relative; + top: -4px; + padding: 8px 0; cursor: default; user-select: none; } @@ -245,3 +258,74 @@ .perf-settings-dir-list-button-group { padding: 4px 2px; } + +/* See https://design.firefox.com/photon/components/buttons.html for the spec */ +.perf-photon-button { + --blue-50-a30: rgba(10, 132, 255, 0.3); + padding: 0 8px; + border: none; + margin: 0; + + /* reset default styles */ + background: none; + + /* photon styles */ + background-color: var(--grey-90-a10); + border-radius: 2px; + color: var(--grey-90); + font: inherit; +} + +/* This is a Firefox-specific style because Firefox adds a focusring at a bad + * position. We're adding our own below. */ +.perf-photon-button::-moz-focus-inner { + border: none; +} + +.perf-photon-button:hover:not([disabled]) { + background-color: var(--grey-90-a20); +} + +.perf-photon-button:hover:active:not([disabled]) { + background-color: var(--grey-90-a30); +} + +.perf-photon-button-primary, +.perf-photon-button-default { + min-width: 132px; + height: 32px; + padding: 0 8px; + font-size: 13px; +} + +.perf-photon-button-primary { + background-color: var(--blue-60); + color: #fff; +} + +.perf-photon-button-primary:hover:not([disabled]) { + background-color: var(--blue-70); +} + +.perf-photon-button-primary:hover:active:not([disabled]) { + background-color: var(--blue-80); +} + +.perf-photon-button[disabled] { + opacity: 0.4; +} + +.perf-photon-button.perf-button { + margin: 10px; +} + +button.perf-photon-button:focus { + box-shadow: 0 0 0 1px var(--blue-50) inset, 0 0 0 1px var(--blue-50), + 0 0 0 4px var(--blue-50-a30); + outline: 0; +} + +a.perf-photon-button:focus { + box-shadow: 0 0 0 2px var(--blue-50), 0 0 0 6px var(--blue-50-a30); + outline: 0; +} diff --git a/devtools/client/themes/performance.css b/devtools/client/themes/performance.css index aeac028723..3c8dd29dc1 100644 --- a/devtools/client/themes/performance.css +++ b/devtools/client/themes/performance.css @@ -135,6 +135,10 @@ line-height: 0; } +#recordings-pane .devtools-toolbar .devtools-button { + height: calc(var(--theme-toolbar-height) - 2px); +} + .theme-sidebar { position: relative; } @@ -438,14 +442,14 @@ */ #waterfall-tree { - /* DE-XUL: convert this to display: flex once index.xul is converted to HTML */ + /* DE-XUL: convert this to display: flex once index.xhtml is converted to HTML */ display: -moz-box; -moz-box-orient: vertical; -moz-box-flex: 1; } .waterfall-markers { - /* DE-XUL: convert this to display: flex once index.xul is converted to HTML */ + /* DE-XUL: convert this to display: flex once index.xhtml is converted to HTML */ display: -moz-box; -moz-box-orient: vertical; -moz-box-flex: 1; @@ -491,7 +495,7 @@ */ .waterfall-markers .tree { - /* DE-XUL: convert this to display: flex once index.xul is converted to HTML */ + /* DE-XUL: convert this to display: flex once index.xhtml is converted to HTML */ display: -moz-box; -moz-box-orient: vertical; -moz-box-flex: 1; diff --git a/devtools/client/themes/rules.css b/devtools/client/themes/rules.css index 1e8b04fa82..28209d3da5 100644 --- a/devtools/client/themes/rules.css +++ b/devtools/client/themes/rules.css @@ -49,8 +49,7 @@ /* Rule View Toolbar */ #ruleview-toolbar-container { - /* @TODO: Bug 1535956 - Use the right CSS variable for this */ - line-height: calc(24px - 2px); + line-height: var(--theme-toolbar-height); } #ruleview-command-toolbar { @@ -68,6 +67,11 @@ display: none; } +.ruleview-reveal-panel .add-class { + height: var(--theme-toolbar-height); + line-height: normal; +} + .ruleview-reveal-panel label { user-select: none; flex-grow: 1; @@ -231,6 +235,7 @@ border-bottom: 1px solid var(--theme-splitter-color); color: var(--theme-toolbar-color); font-size: 12px; + line-height: 16px; padding: 4px; width: 100%; align-items: center; diff --git a/devtools/client/themes/splitters.css b/devtools/client/themes/splitters.css index bf58341b60..c38bd7ee96 100644 --- a/devtools/client/themes/splitters.css +++ b/devtools/client/themes/splitters.css @@ -2,8 +2,8 @@ * 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/. */ -/* This file is loaded by both browser.xul and toolbox.xul. Therefore, rules - defined here can not rely on toolbox.xul variables. */ +/* This file is loaded by both browser.xhtml and toolbox.xhtml. Therefore, rules + defined here can not rely on toolbox.xhtml variables. */ /* Splitters */ @@ -35,7 +35,7 @@ } #appcontent[devtoolstheme="light"] { - /* These variables are used in browser.xul but inside the toolbox they are overridden by --theme-splitter-color */ + /* These variables are used in browser.xhtml but inside the toolbox they are overridden by --theme-splitter-color */ --devtools-splitter-color: #dde1e4; } diff --git a/devtools/client/themes/storage.css b/devtools/client/themes/storage.css index 739187eb4d..5d14ab1cbb 100644 --- a/devtools/client/themes/storage.css +++ b/devtools/client/themes/storage.css @@ -51,18 +51,22 @@ } /* Text input in storage table */ -#storage-table textbox { +#storage-table input { appearance: none; + -moz-box-flex: 1; /* make sure the outline is not cut off */ position: relative; - max-width: calc(100% - 1px); - padding: 3px 3px; + box-sizing: border-box; + padding: 0 2px; font: inherit; - line-height: 14px; color: var(--theme-text-color-strong); background-color: var(--theme-body-background); } +#storage-table input:focus { + outline: 1px solid var(--blue-50); +} + /* Variables View Sidebar */ #storage-sidebar { @@ -80,6 +84,10 @@ display: -moz-box; } +#storage-searchbox { + -moz-box-flex: 1; +} + #storage-toolbar .add-button::before { background-image: url("chrome://devtools/skin/images/add.svg"); -moz-user-focus: normal; diff --git a/devtools/client/themes/toolbars.css b/devtools/client/themes/toolbars.css index 56e206bef1..dc1bc3d68b 100644 --- a/devtools/client/themes/toolbars.css +++ b/devtools/client/themes/toolbars.css @@ -10,8 +10,11 @@ border-width: 0; border-bottom-width: 1px; border-style: solid; - height: 24px; - line-height: 24px; + /* Reserve 1px for the border */ + height: calc(var(--theme-toolbar-height) + 1px); + /* Line-height based vertical centering technique used in panels + which don't use Flexbox (mostly XUL panels). */ + line-height: var(--theme-toolbar-height); box-sizing: border-box; } diff --git a/devtools/client/themes/toolbox.css b/devtools/client/themes/toolbox.css index 995514ca90..82b1027025 100644 --- a/devtools/client/themes/toolbox.css +++ b/devtools/client/themes/toolbox.css @@ -303,10 +303,6 @@ fill-opacity: 0.25; } -#command-button-scratchpad::before { - background-image: url("chrome://devtools/skin/images/tool-scratchpad.svg"); -} - #command-button-rulers::before { background-image: url("chrome://devtools/skin/images/command-rulers.svg"); } diff --git a/devtools/client/themes/tooltips.css b/devtools/client/themes/tooltips.css index 469800a0a0..545c141f48 100644 --- a/devtools/client/themes/tooltips.css +++ b/devtools/client/themes/tooltips.css @@ -12,13 +12,10 @@ .theme-dark { --bezier-diagonal-color: #eee; --bezier-grid-color: rgba(0, 0, 0, 0.2); - --onboarding-link-color: var(--theme-highlight-blue); - --onboarding-link-active-color: var(--blue-40); /* Tooltips */ --theme-tooltip-color: var(--theme-text-color-strong); - --theme-tooltip-border: #434850; - --theme-tooltip-background: rgba(19, 28, 38, .9); + --theme-tooltip-background: var(--theme-popup-background); --theme-tooltip-shadow: rgba(25, 25, 25, 0.76); /* Doorhangers */ @@ -36,14 +33,11 @@ .theme-light { --bezier-diagonal-color: rgba(0, 0, 0, 0.2); --bezier-grid-color: rgba(0, 0, 0, 0.05); - --onboarding-link-color: var(--blue-60); - --onboarding-link-active-color: var(--blue-70); /* Tooltips */ --theme-tooltip-color: var(--theme-body-color); - --theme-tooltip-border: #d9e1e8; - --theme-tooltip-background: rgba(255, 255, 255, .9); - --theme-tooltip-shadow: rgba(155, 155, 155, 0.26); + --theme-tooltip-background: rgb(255, 255, 255); + --theme-tooltip-shadow: var(--grey-90-a10); /* Doorhangers */ /* These colors are based on the colors used for doorhangers elsewhere in @@ -125,7 +119,7 @@ strong { background: transparent; pointer-events: none; overflow: hidden; - filter: drop-shadow(0 3px 4px var(--theme-tooltip-shadow)); + filter: drop-shadow(0 2px 8px var(--theme-tooltip-shadow)); } .tooltip-xul-wrapper { @@ -182,8 +176,8 @@ strong { /* The arrow image is hidden because the panel is opened using openPopupAtScreen(). */ -/* Remove all decorations on .panel-arrowcontent is the tooltip content container. */ -.tooltip-xul-wrapper[type="arrow"] .panel-arrowcontent { +/* The arrow content is styled on the HTML, so we don't need the styling on the XUL element */ +.tooltip-xul-wrapper[type="arrow"]::part(arrowcontent) { margin: 0; padding: 0; background: transparent; @@ -203,9 +197,7 @@ strong { min-height: 10px; box-sizing: border-box; width: 100%; - - border: 3px solid var(--theme-tooltip-border); - border-radius: 5px; + border-radius: var(--theme-arrowpanel-border-radius); } .tooltip-top[type="arrow"] .tooltip-panel { @@ -234,11 +226,11 @@ strong { } .tooltip-top .tooltip-arrow { - margin-top: -3px; + margin-top: -1px; } .tooltip-bottom .tooltip-arrow { - margin-bottom: -3px; + margin-bottom: -1px; } .tooltip-arrow:before { @@ -249,15 +241,12 @@ strong { margin-left: 4px; background: linear-gradient(-45deg, var(--theme-tooltip-background) 50%, transparent 50%); - border-color: var(--theme-tooltip-border); - border-style: solid; - border-width: 0px 3px 3px 0px; - border-radius: 3px; + border: 0 none; pointer-events: all; } .tooltip-bottom .tooltip-arrow:before { - margin-top: 4px; + margin-top: 5px; transform: rotate(225deg); } @@ -266,6 +255,12 @@ strong { transform: rotate(45deg); } +/* XUL panels have a default border, but pure HTML tooltips don't have one. */ +.tooltip-container[type="arrow"]:not(.tooltip-container-xul) > .tooltip-panel, +.tooltip-container[type="arrow"]:not(.tooltip-container-xul) > .tooltip-arrow::before { + border: 1px solid var(--theme-arrowpanel-border-color); +} + /* Tooltip : doorhanger style */ .tooltip-container[type="doorhanger"] > .tooltip-panel { @@ -492,11 +487,12 @@ strong { /* Tooltip: Events */ .devtools-tooltip-events-container { + border-radius: var(--theme-arrowpanel-border-radius); height: 100%; overflow-y: auto; } -.event-header { +.devtools-tooltip-events-container .event-header { display: flex; align-items: center; box-sizing: content-box; @@ -506,6 +502,7 @@ strong { cursor: pointer; overflow: hidden; color: var(--theme-tooltip-color); + background-color: var(--theme-tooltip-background); } .devtools-tooltip-events-container .event-header:first-child { @@ -697,56 +694,6 @@ strong { padding: 7px; } -/* Tooltip: Onboarding Tooltip */ - -.onboarding-container { - align-items: center; - background-color: var(--theme-toolbar-background); - box-sizing: border-box; - color: var(--theme-body-color); - display: flex; - font-size: 12px; - padding: 7px; - width: 100%; - user-select: none; -} - -.onboarding-icon { - display: inline-block; - background-size: 21px; - width: 21px; - height: 21px; - margin: 8px; - background-image: url("chrome://devtools/skin/images/fox-smiling.svg"); -} - -.onboarding-content { - flex: 1; - padding-inline-start: 5px; -} - -.onboarding-link { - color: var(--onboarding-link-color); - cursor: pointer; -} - -.onboarding-link:hover { - text-decoration: underline; -} - -.onboarding-link:active { - color: var(--onboarding-link-active-color); -} - -.onboarding-close-button { - align-self: flex-start; -} - -.onboarding-close-button::before { - background-image: url("chrome://devtools/skin/images/close.svg"); - margin: -6px 0 0 -6px; -} - /* Tooltip: Invoke getter confirm Tooltip */ .invoke-confirm { diff --git a/devtools/client/themes/variables.css b/devtools/client/themes/variables.css index 693b66bf52..4ed1b55e19 100644 --- a/devtools/client/themes/variables.css +++ b/devtools/client/themes/variables.css @@ -2,17 +2,105 @@ * 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/. */ -/* Variable declarations for light and dark devtools themes. - * Colors are taken from: - * https://developer.mozilla.org/en-US/docs/Tools/DevToolsColors. - */ - /* * IMPORTANT NOTE * This file is parsed in js (see client/shared/theme.js) * so the formatting should be consistent (i.e. no '}' inside a rule). */ +:root { + --theme-focus-border-color-textbox: #0675d3; + --theme-textbox-box-shadow: rgba(97, 181, 255, 0.75); + + /* Text sizes */ + --theme-body-font-size: 11px; + --theme-code-font-size: 11px; + --theme-code-line-height: calc(15 / 11); + + /* Toolbar size (excluding borders) */ + --theme-toolbar-height: 24px; + + /* For accessibility purposes we want to enhance the focus styling. This + * should improve keyboard navigation usability. */ + --theme-focus-outline: 1px dotted var(--theme-focus-outline-color); + --theme-focus-box-shadow-textbox: 0 0 0 1px var(--theme-textbox-box-shadow); + + --toolbarbutton-focus-background: var(--theme-selection-focus-background); + --toolbarbutton-focus-color: var(--theme-selection-focus-color); + --toolbarbutton-checked-background: var(--theme-selection-background); + --toolbarbutton-checked-color: var(--theme-selection-color); + --toolbarbutton-checked-focus-background: var(--blue-60); + + /* The photon animation curve */ + --animation-curve: cubic-bezier(0.07, 0.95, 0, 1); + + /* + * Photon Colors CSS Variables v3.3.2 + * - Colors are taken from https://github.com/FirefoxUX/photon-colors/blob/master/photon-colors.css + * - We only add Photon color variables that we are actually using; unused + * variables will fail browser/base/content/test/static/browser_parsable_css.js + * - We added a few unofficial colors: a few intermediary values (e.g. Blue 45), + * and lighter variants for the dark theme (e.g. Red 20, Red 40). + */ + --magenta-50: #ff1ad9; + --magenta-65: #dd00a9; + --magenta-70: #b5007f; + + --purple-50: #9400ff; + --purple-60: #8000d7; + + --blue-30: #75baff; + --blue-40: #45a1ff; + --blue-50: #0a84ff; + --blue-55: #0074e8; + --blue-60: #0060df; + --blue-70: #003eaa; + --blue-80: #002275; + + --teal-60: #00c8d7; + --teal-70: #008ea4; + + --green-50: #30e60b; + --green-60: #12bc00; + --green-70: #058b00; + + --yellow-50: #ffe900; + --yellow-60: #d7b600; + --yellow-65: #be9b00; + --yellow-70: #a47f00; + --yellow-80: #715100; + + --red-20: #ffb3d2; + --red-40: #ff3b6b; + --red-50: #ff0039; + --red-60: #d70022; + --red-70: #a4000f; + + --grey-10: #f9f9fa; + --grey-10-a15: rgba(249, 249, 250, 0.15); + --grey-10-a20: rgba(249, 249, 250, 0.2); + --grey-10-a25: rgba(249, 249, 250, 0.25); + --grey-10-a30: rgba(249, 249, 250, 0.3); + --grey-20: #ededf0; + --grey-25: #e0e0e2; + --grey-30: #d7d7db; + --grey-40: #b1b1b3; + --grey-43: #a4a4a4; + --grey-45: #939395; + --grey-50: #737373; + --grey-55: #5c5c5f; + --grey-60: #4a4a4f; + --grey-70: #38383d; + --grey-80: #2a2a2e; + --grey-85: #1b1b1d; + --grey-90: #0c0c0d; + --grey-90-a05: rgba(12, 12, 13, 0.05); + --grey-90-a10: rgba(12, 12, 13, 0.1); + --grey-90-a15: rgba(12, 12, 13, 0.15); + --grey-90-a20: rgba(12, 12, 13, 0.2); + --grey-90-a30: rgba(12, 12, 13, 0.3); +} + :root.theme-light { --theme-body-background: white; --theme-sidebar-background: white; @@ -30,13 +118,17 @@ --theme-toolbar-hover-active: var(--grey-20); --theme-toolbar-separator: var(--grey-90-a10); + /* Toolbar buttons */ + --toolbarbutton-background: var(--theme-toolbar-hover); + --toolbarbutton-hover-background: var(--theme-toolbar-hover); + /* Buttons */ --theme-button-background: rgba(12, 12, 13, 0.05); --theme-button-active-background: rgba(12, 12, 13, 0.1); /* Selection */ --theme-selection-background: var(--blue-55); - --theme-selection-background-hover: #F0F9FE; + --theme-selection-background-hover: #f0f9fe; --theme-selection-focus-background: var(--toolbarbutton-hover-background); --theme-selection-focus-color: var(--grey-70); --theme-selection-color: #ffffff; @@ -64,7 +156,7 @@ --theme-highlight-blue: var(--blue-55); --theme-highlight-purple: var(--blue-70); --theme-highlight-red: var(--magenta-65); - --theme-highlight-yellow: #FFF89E; + --theme-highlight-yellow: #fff89e; /* These theme-highlight color variables have not been photonized. */ --theme-highlight-bluegrey: #0072ab; @@ -93,10 +185,21 @@ --theme-popup-background: -moz-field; --theme-popup-color: -moz-fieldText; --theme-popup-border-color: ThreeDShadow; - --theme-popup-dimmed: hsla(0,0%,80%,.3); + --theme-popup-dimmed: hsla(0, 0%, 80%, 0.3); + + /* Styling for devtool buttons */ + --theme-toolbarbutton-background: none; + --theme-toolbarbutton-color: var(--grey-70); + --theme-toolbarbutton-hover-background: var(--grey-90-a05); + --theme-toolbarbutton-checked-background: var(--grey-90-a10); + --theme-toolbarbutton-checked-color: var(--grey-90); + --theme-toolbarbutton-checked-hover-background: var(--grey-90-a15); + --theme-toolbarbutton-checked-hover-color: var(--grey-90); + --theme-toolbarbutton-active-background: var(--grey-90-a20); + --theme-toolbarbutton-active-color: var(--grey-90); /* Element Flash background color */ - --theme-bg-yellow: #FFF697; + --theme-bg-yellow: #fff697; } /* @@ -104,8 +207,8 @@ * system colors. */ :root[platform="mac"].theme-light { - --theme-popup-color: hsl(0,0%,10%); - --theme-popup-border-color: hsla(210,4%,10%,.05); + --theme-popup-color: hsl(0, 0%, 10%); + --theme-popup-border-color: var(--grey-90-a20); } :root.theme-dark { @@ -125,12 +228,16 @@ --theme-toolbar-hover-active: #252526; --theme-toolbar-separator: var(--grey-10-a20); + /* Toolbar buttons */ + --toolbarbutton-background: var(--grey-70); + --toolbarbutton-hover-background: var(--grey-70); + /* Buttons */ --theme-button-background: rgba(249, 249, 250, 0.1); --theme-button-active-background: rgba(249, 249, 250, 0.15); /* Selection */ - --theme-selection-background: #204E8A; + --theme-selection-background: #204e8a; --theme-selection-background-hover: #303844; --theme-selection-focus-background: var(--grey-60); --theme-selection-focus-color: var(--grey-30); @@ -155,11 +262,11 @@ --theme-text-color-inactive: var(--grey-50); --theme-text-color-strong: var(--grey-30); - --theme-highlight-green: #86DE74; - --theme-highlight-blue: #75BFFF; - --theme-highlight-purple: #B98EFF; - --theme-highlight-red: #FF7DE9; - --theme-highlight-yellow: #FFF89E; + --theme-highlight-green: #86de74; + --theme-highlight-blue: #75bfff; + --theme-highlight-purple: #b98eff; + --theme-highlight-red: #ff7de9; + --theme-highlight-yellow: #fff89e; /* These theme-highlight color variables have not been photonized. */ --theme-highlight-bluegrey: #5e88b0; @@ -186,88 +293,21 @@ /* Common popup styles(used by HTMLTooltip and autocomplete) */ --theme-popup-background: var(--grey-60); - --theme-popup-color: rgb(249,249,250); + --theme-popup-color: rgb(249, 249, 250); --theme-popup-border-color: #27272b; - --theme-popup-dimmed: rgba(249,249,250,.1); + --theme-popup-dimmed: rgba(249, 249, 250, 0.1); + + /* Styling for devtool buttons */ + --theme-toolbarbutton-background: none; + --theme-toolbarbutton-color: var(--grey-40); + --theme-toolbarbutton-hover-background: var(--grey-10-a15); + --theme-toolbarbutton-checked-background: var(--grey-10-a20); + --theme-toolbarbutton-checked-color: var(--grey-30); + --theme-toolbarbutton-checked-hover-background: var(--grey-10-a25); + --theme-toolbarbutton-checked-hover-color: var(--grey-30); + --theme-toolbarbutton-active-background: var(--grey-10-a30); + --theme-toolbarbutton-active-color: var(--grey-30); /* Element Flash background color */ --theme-bg-yellow: #605913; } - -:root { - --theme-focus-border-color-textbox: #0675d3; - --theme-textbox-box-shadow: rgba(97,181,255,.75); - - /* Text sizes */ - --theme-body-font-size: 11px; - --theme-code-font-size: 11px; - - /* For accessibility purposes we want to enhance the focus styling. This - * should improve keyboard navigation usability. */ - --theme-focus-outline: 1px dotted var(--theme-focus-outline-color); - --theme-focus-box-shadow-textbox: 0 0 0 1px var(--theme-textbox-box-shadow); - - --toolbarbutton-background: var(--theme-toolbar-hover); - --toolbarbutton-hover-background: var(--theme-toolbar-hover); - --toolbarbutton-focus-background: var(--theme-selection-focus-background); - --toolbarbutton-focus-color: var(--theme-selection-focus-color); - --toolbarbutton-checked-background: var(--theme-selection-background); - --toolbarbutton-checked-color: var(--theme-selection-color); - --toolbarbutton-checked-focus-background: var(--blue-60); - - /* The photon animation curve */ - --animation-curve: cubic-bezier(.07,.95,0,1); - - /* Firefox Colors CSS Variables v1.0.3 - * Colors are taken from: https://github.com/FirefoxUX/design-tokens - * Some intermediate colors were added (names ending in '5'). - */ - --magenta-50: #ff1ad9; - --magenta-65: #dd00a9; - --magenta-70: #b5007f; - - --purple-50: #9400ff; - --purple-60: #8000d7; - - --blue-30: #75baff; - --blue-40: #45a1ff; - --blue-50: #0a84ff; - --blue-55: #0074e8; - --blue-60: #0060df; - --blue-70: #003eaa; - --blue-80: #002275; - - --teal-60: #00c8d7; - --teal-70: #008ea4; - - --red-20: #ffb3d2; - --red-40: #ff3b6b; - --red-50: #ff0039; - --red-60: #d70022; - --red-70: #a4000f; - - --green-50: #30e60b; - --green-60: #12bc00; - --green-70: #058b00; - - --yellow-50: #ffe900; - --yellow-60: #d7b600; - --yellow-65: #be9b00; - --yellow-70: #a47f00; - --yellow-80: #715100; - - --grey-10: #f9f9fa; - --grey-20: #ededf0; - --grey-25: #e0e0e2; - --grey-30: #d7d7db; - --grey-40: #b1b1b3; - --grey-43: #a4a4a4; - --grey-45: #939395; - --grey-50: #737373; - --grey-55: #5c5c5f; - --grey-60: #4a4a4f; - --grey-70: #38383d; - --grey-80: #2a2a2e; - --grey-85: #1b1b1d; - --grey-90: #0c0c0d; -} diff --git a/devtools/client/themes/webconsole.css b/devtools/client/themes/webconsole.css index ab7b78d4bf..e953360404 100644 --- a/devtools/client/themes/webconsole.css +++ b/devtools/client/themes/webconsole.css @@ -67,7 +67,8 @@ a { /* Workaround for Bug 575675 - FindChildWithRules aRelevantLinkVisited * assertion when loading HTML page with links in XUL iframe */ -*:visited { } +*:visited { +} * { box-sizing: border-box; @@ -236,7 +237,7 @@ a { } /* Center first level indent within the left gutter */ -.message:not(.startGroup):not(.startGroupCollapsed) > .indent[data-indent="1"] { +.message:not(.startGroup, .startGroupCollapsed) > .indent[data-indent="1"] { margin-inline-start: calc(1px + var(--console-icon-horizontal-offset)); margin-inline-end: calc(11px - var(--console-icon-horizontal-offset)); } @@ -467,6 +468,9 @@ html #webconsole-notificationbox { line-height: var(--console-output-line-height); /* aim for a 32px left space (a descendent has 4px padding) */ padding-inline-start: calc(var(--console-inline-start-gutter) - 4px); + /* Create a new stacking context */ + position: relative; + z-index: 0; /* Keep below column resizers */ /* input icon */ background-image: url(chrome://devtools/skin/images/webconsole/input.svg); background-position-x: calc(10px + var(--console-icon-horizontal-offset)); @@ -545,7 +549,7 @@ html #webconsole-notificationbox { } a.learn-more-link.webconsole-learn-more-link { - font-style: normal; + font-style: normal; } /* @@ -816,16 +820,17 @@ a.learn-more-link.webconsole-learn-more-link { padding: 5px 4px; font-weight: inherit; z-index: 1; + text-overflow: ellipsis; } -.new-consoletable > [role=gridcell] { +.new-consoletable > [role="gridcell"] { background-color: var(--theme-body-background); - padding: 3px 4px; - min-width: 100px; color: var(--theme-body-color); + padding: 3px 4px; + text-overflow: ellipsis; } -.new-consoletable > [role=gridcell].even { +.new-consoletable > [role="gridcell"].even { background-color: var(--table-zebra-background); } @@ -899,7 +904,9 @@ a.learn-more-link.webconsole-learn-more-link { /* Use a bigger arrow that is visually similar to other icons (10px) */ .message.network > .collapse-button::before, .message.startGroup > .indent[data-indent="0"] ~ .collapse-button::before, -.message.startGroupCollapsed > .indent[data-indent="0"] ~ .collapse-button::before { +.message.startGroupCollapsed + > .indent[data-indent="0"] + ~ .collapse-button::before { width: 100%; background-image: url("chrome://devtools/skin/images/arrow-big.svg"); fill: var(--theme-icon-dimmed-color); @@ -926,7 +933,7 @@ a.learn-more-link.webconsole-learn-more-link { .webconsole-app .tree.focused .arrow, .webconsole-app .object-inspector .tree-node.focused .arrow { - fill: currentColor + fill: currentColor; } /** Utils **/ diff --git a/devtools/client/webconsole/actions/messages.js b/devtools/client/webconsole/actions/messages.js index c8bccb0a8f..c0a2fa3e89 100644 --- a/devtools/client/webconsole/actions/messages.js +++ b/devtools/client/webconsole/actions/messages.js @@ -4,10 +4,7 @@ "use strict"; -const { - prepareMessage, - getArrayTypeNames, -} = require("devtools/client/webconsole/utils/messages"); +const { prepareMessage } = require("devtools/client/webconsole/utils/messages"); const { IdGenerator, } = require("devtools/client/webconsole/utils/id-generator"); @@ -119,19 +116,6 @@ function messageGetMatchingElements(id, cssSelectors) { }; } -function messageGetTableData(id, grip, dataType) { - return async ({ dispatch, services }) => { - const needEntries = ["Map", "WeakMap", "Set", "WeakSet"].includes(dataType); - const enumIndexedPropertiesOnly = getArrayTypeNames().includes(dataType); - - const results = await (needEntries - ? services.fetchObjectEntries(grip) - : services.fetchObjectProperties(grip, enumIndexedPropertiesOnly)); - - dispatch(messageUpdatePayload(id, results)); - }; -} - /** * Associate additional data with a message without mutating the original message object. * @@ -177,7 +161,6 @@ module.exports = { messageOpen, messageClose, messageGetMatchingElements, - messageGetTableData, messageUpdatePayload, networkMessageUpdate, networkUpdateRequest, diff --git a/devtools/client/webconsole/browser-console-manager.js b/devtools/client/webconsole/browser-console-manager.js index c85322eb7b..bb5bc7eee0 100644 --- a/devtools/client/webconsole/browser-console-manager.js +++ b/devtools/client/webconsole/browser-console-manager.js @@ -86,8 +86,9 @@ class BrowserConsoleManager { const { DevToolsLoader } = ChromeUtils.import( "resource://devtools/shared/Loader.jsm" ); - const loader = new DevToolsLoader(); - loader.freshCompartment = true; + const loader = new DevToolsLoader({ + freshCompartment: true, + }); const { DebuggerServer } = loader.require( "devtools/server/debugger-server" ); diff --git a/devtools/client/webconsole/components/App.css b/devtools/client/webconsole/components/App.css index fe63bca227..0e47d8bf51 100644 --- a/devtools/client/webconsole/components/App.css +++ b/devtools/client/webconsole/components/App.css @@ -132,6 +132,7 @@ body { grid-column: 1 / 2; grid-row: -1 / -2; border-top: 1px solid var(--theme-splitter-color); + border-inline-end: 1px solid var(--theme-splitter-color); } .sidebar { @@ -139,6 +140,7 @@ body { grid-row: 1 / -1; grid-column: -1 / -2; grid-template-rows: subgrid; + border-inline-start: 1px solid var(--theme-splitter-color); background-color: var(--theme-sidebar-background); width: 200px; min-width: 150px; @@ -146,7 +148,6 @@ body { } .sidebar-resizer { - /* We want the splitter to cover the whole column (minus self-xss message) */ grid-row: 1 / -1; grid-column: -1 / -2; } @@ -230,6 +231,7 @@ body { .jsterm-editor .webconsole-editor-toolbar { grid-column: 1 / 2; grid-row: 2 / 3; + border-inline-end: 1px solid var(--theme-splitter-color); display: grid; /* We're going to have the run button, the history nav and the close button */ grid-template-columns: auto 1fr auto auto auto; @@ -277,18 +279,22 @@ body { grid-column: 1 / 2; grid-row: 3 / 5; width: 30vw; - min-width: 150px; + /* Don't allow the input to be narrower than the grid-column it's in */ + min-width: 100%; border-top: none; + border-inline-end: 1px solid var(--theme-splitter-color); display: block; } .jsterm-editor #webconsole-notificationbox { - grid-row: 1 / 2; grid-column: -1 / 1; + grid-row: 1 / 2; } .jsterm-editor .jsterm-input-container > .CodeMirror { padding-inline-start: 0; + font-size: var(--theme-code-font-size); + line-height: var(--theme-code-line-height); background-image: none; } diff --git a/devtools/client/webconsole/components/App.js b/devtools/client/webconsole/components/App.js index e7516a0765..d72527b633 100644 --- a/devtools/client/webconsole/components/App.js +++ b/devtools/client/webconsole/components/App.js @@ -91,7 +91,6 @@ class App extends Component { filterBarDisplayMode: PropTypes.oneOf([ ...Object.values(FILTERBAR_DISPLAY_MODES), ]).isRequired, - editorFeatureEnabled: PropTypes.bool.isRequired, }; } @@ -113,7 +112,7 @@ class App extends Component { } onKeyDown(event) { - const { dispatch, webConsoleUI, editorFeatureEnabled } = this.props; + const { dispatch, webConsoleUI } = this.props; if ( (!isMacOS && event.key === "F9") || @@ -126,7 +125,6 @@ class App extends Component { } if ( - editorFeatureEnabled && event.key.toLowerCase() === "b" && ((isMacOS && event.metaKey) || (!isMacOS && event.ctrlKey)) ) { @@ -182,7 +180,7 @@ class App extends Component { return; } - if (webConsoleUI && webConsoleUI.jsterm) { + if (webConsoleUI?.jsterm) { webConsoleUI.jsterm.focus(); } } @@ -278,7 +276,6 @@ class App extends Component { autocomplete, editorMode, editorWidth, - editorFeatureEnabled, } = this.props; return JSTerm({ @@ -287,9 +284,8 @@ class App extends Component { serviceContainer, onPaste: this.onPaste, autocomplete, - editorMode: editorMode && editorFeatureEnabled, + editorMode, editorWidth, - editorFeatureEnabled, }); } @@ -314,13 +310,13 @@ class App extends Component { } renderNotificationBox() { - const { notifications, editorMode, editorFeatureEnabled } = this.props; + const { notifications, editorMode } = this.props; return NotificationBox({ id: "webconsole-notificationbox", key: "notification-box", - displayBorderTop: !(editorMode && editorFeatureEnabled), - displayBorderBottom: editorMode && editorFeatureEnabled, + displayBorderTop: !editorMode, + displayBorderBottom: editorMode, wrapping: true, notifications, }); @@ -337,10 +333,10 @@ class App extends Component { } renderRootElement(children) { - const { editorMode, editorFeatureEnabled } = this.props; + const { editorMode } = this.props; const classNames = ["webconsole-app"]; - if (editorMode && editorFeatureEnabled) { + if (editorMode) { classNames.push("jsterm-editor"); } @@ -358,12 +354,7 @@ class App extends Component { } render() { - const { - webConsoleUI, - editorMode, - editorFeatureEnabled, - dispatch, - } = this.props; + const { webConsoleUI, editorMode, dispatch } = this.props; const filterBar = this.renderFilterBar(); const consoleOutput = this.renderConsoleOutput(); @@ -375,7 +366,7 @@ class App extends Component { return this.renderRootElement([ filterBar, - editorFeatureEnabled && editorMode + editorMode ? EditorToolbar({ dispatch, editorMode, @@ -390,7 +381,7 @@ class App extends Component { ), GridElementWidthResizer({ key: "editor-resizer", - enabled: editorFeatureEnabled && editorMode, + enabled: editorMode, position: "end", className: "editor-resizer", getControlledElementNode: () => webConsoleUI.jsterm.node, diff --git a/devtools/client/webconsole/components/FilterBar/FilterButton.js b/devtools/client/webconsole/components/FilterBar/FilterButton.js index 88969bf2d6..b539be8500 100644 --- a/devtools/client/webconsole/components/FilterBar/FilterButton.js +++ b/devtools/client/webconsole/components/FilterBar/FilterButton.js @@ -19,10 +19,7 @@ FilterButton.propTypes = { function FilterButton(props) { const { active, label, filterKey, dispatch, title } = props; - const classList = ["devtools-button", filterKey]; - if (active) { - classList.push("checked"); - } + const classList = ["devtools-togglebutton", filterKey]; return dom.button( { diff --git a/devtools/client/webconsole/components/Input/JSTerm.js b/devtools/client/webconsole/components/Input/JSTerm.js index 562336bd4a..4d8a91f5b0 100644 --- a/devtools/client/webconsole/components/Input/JSTerm.js +++ b/devtools/client/webconsole/components/Input/JSTerm.js @@ -33,7 +33,7 @@ loader.lazyRequireGetter( ); loader.lazyRequireGetter( this, - "focusableSelector", + "getFocusableElements", "devtools/client/shared/focus", true ); @@ -95,8 +95,6 @@ class JSTerm extends Component { autocompleteData: PropTypes.object.isRequired, // Toggle the editor mode. editorToggle: PropTypes.func.isRequired, - // Is the editor feature enabled - editorFeatureEnabled: PropTypes.bool, // Is the input in editor mode. editorMode: PropTypes.bool, editorWidth: PropTypes.number, @@ -537,7 +535,12 @@ class JSTerm extends Component { return null; } - const items = Array.from(el.querySelectorAll(focusableSelector)); + // We only want to get visible focusable element, and for that we can assert that + // the offsetParent isn't null. We can do that because we don't have fixed position + // element in the console. + const items = getFocusableElements(el).filter( + ({ offsetParent }) => offsetParent !== null + ); const inputIndex = items.indexOf(inputField); if (items.length === 0 || (inputIndex > -1 && items.length === 1)) { @@ -1053,16 +1056,14 @@ class JSTerm extends Component { return null; } - const openEditorButton = this.props.editorFeatureEnabled - ? dom.button({ - className: "devtools-button webconsole-input-openEditorButton", - title: l10n.getFormatStr( - "webconsole.input.openEditorButton.tooltip", - [isMacOS ? "Cmd + B" : "Ctrl + B"] - ), - onClick: this.props.editorToggle, - }) - : undefined; + const openEditorButton = dom.button({ + className: "devtools-button webconsole-input-openEditorButton", + title: l10n.getFormatStr( + "webconsole.input.openEditorButton.tooltip", + [isMacOS ? "Cmd + B" : "Ctrl + B"] + ), + onClick: this.props.editorToggle, + }); return dom.div( { diff --git a/devtools/client/webconsole/components/Output/ConsoleTable.js b/devtools/client/webconsole/components/Output/ConsoleTable.js index 1c223b3985..4c6b068bde 100644 --- a/devtools/client/webconsole/components/Output/ConsoleTable.js +++ b/devtools/client/webconsole/components/Output/ConsoleTable.js @@ -8,10 +8,10 @@ const { createFactory, } = require("devtools/client/shared/vendor/react"); const dom = require("devtools/client/shared/vendor/react-dom-factories"); -const actions = require("devtools/client/webconsole/actions/messages"); const { l10n, getArrayTypeNames, + getDescriptorValue, } = require("devtools/client/webconsole/utils/messages"); loader.lazyGetter(this, "MODE", function() { return require("devtools/client/shared/components/reps/reps").MODE; @@ -28,7 +28,8 @@ loader.lazyRequireGetter( ); const TABLE_ROW_MAX_ITEMS = 1000; -const TABLE_COLUMN_MAX_ITEMS = 10; +// Match Chrome max column number. +const TABLE_COLUMN_MAX_ITEMS = 21; class ConsoleTable extends Component { static get propTypes() { @@ -37,7 +38,6 @@ class ConsoleTable extends Component { parameters: PropTypes.array.isRequired, serviceContainer: PropTypes.object.isRequired, id: PropTypes.string.isRequired, - tableData: PropTypes.object, }; } @@ -47,23 +47,6 @@ class ConsoleTable extends Component { this.getRows = this.getRows.bind(this); } - componentWillMount() { - const { id, dispatch, parameters } = this.props; - - if (!Array.isArray(parameters) || parameters.length === 0) { - return; - } - - // Get all the object properties. - dispatch( - actions.messageGetTableData( - id, - parameters[0], - getParametersDataType(parameters) - ) - ); - } - getHeaders(columns) { const headerItems = []; columns.forEach((value, key) => @@ -73,6 +56,7 @@ class ConsoleTable extends Component { className: "new-consoletable-header", role: "columnheader", key, + title: value, }, value ) @@ -86,21 +70,29 @@ class ConsoleTable extends Component { return items.map((item, index) => { const cells = []; + const className = index % 2 ? "odd" : "even"; + columns.forEach((value, key) => { + const cellValue = item[key]; + const cellContent = + typeof cellValue === "undefined" + ? "" + : GripMessageBody({ + grip: cellValue, + mode: MODE.SHORT, + useQuotes: false, + serviceContainer, + dispatch, + }); + cells.push( dom.div( { role: "gridcell", - className: index % 2 ? "odd" : "even", + className, key, }, - GripMessageBody({ - grip: item[key], - mode: MODE.SHORT, - useQuotes: false, - serviceContainer, - dispatch, - }) + cellContent ) ); }); @@ -109,28 +101,29 @@ class ConsoleTable extends Component { } render() { - const { parameters, tableData } = this.props; - const headersGrip = parameters[1]; + const { parameters } = this.props; + const [valueGrip, headersGrip] = parameters; const headers = headersGrip && headersGrip.preview ? headersGrip.preview.items : null; - // if tableData is nullable, we don't show anything. - if (!tableData) { + const data = valueGrip && valueGrip.ownProperties; + + // if we don't have any data, don't show anything. + if (!data) { return null; } - const { columns, items } = getTableItems( - tableData, - getParametersDataType(parameters), - headers - ); + const dataType = getParametersDataType(parameters); + const { columns, items } = getTableItems(data, dataType, headers); return dom.div( { className: "new-consoletable", role: "grid", style: { - gridTemplateColumns: `repeat(${columns.size}, auto)`, + gridTemplateColumns: `repeat(${columns.size}, calc(100% / ${ + columns.size + }))`, }, }, this.getHeaders(columns), @@ -146,10 +139,11 @@ function getParametersDataType(parameters = null) { return parameters[0].class; } -function getTableItems(data = {}, type, headers = null) { - const INDEX_NAME = "_index"; - const VALUE_NAME = "_value"; - const namedIndexes = { +const INDEX_NAME = "_index"; +const VALUE_NAME = "_value"; + +function getNamedIndexes(type) { + return { [INDEX_NAME]: getArrayTypeNames() .concat("Object") .includes(type) @@ -158,6 +152,19 @@ function getTableItems(data = {}, type, headers = null) { [VALUE_NAME]: l10n.getStr("table.value"), key: l10n.getStr("table.key"), }; +} + +function hasValidCustomHeaders(headers) { + return ( + Array.isArray(headers) && + headers.every( + header => typeof header === "string" || Number.isInteger(Number(header)) + ) + ); +} + +function getTableItems(data = {}, type, headers = null) { + const namedIndexes = getNamedIndexes(type); let columns = new Map(); const items = []; @@ -167,15 +174,16 @@ function getTableItems(data = {}, type, headers = null) { Object.keys(item).forEach(key => addColumn(key)); }; + const validCustomHeaders = hasValidCustomHeaders(headers); + const addColumn = function(columnIndex) { const columnExists = columns.has(columnIndex); const hasMaxColumns = columns.size == TABLE_COLUMN_MAX_ITEMS; - const hasCustomHeaders = Array.isArray(headers); if ( !columnExists && !hasMaxColumns && - (!hasCustomHeaders || + (!validCustomHeaders || headers.includes(columnIndex) || columnIndex === INDEX_NAME) ) { @@ -183,7 +191,7 @@ function getTableItems(data = {}, type, headers = null) { } }; - for (let index of Object.keys(data)) { + for (let [index, property] of Object.entries(data)) { if (type !== "Object" && index == parseInt(index, 10)) { index = parseInt(index, 10); } @@ -192,32 +200,22 @@ function getTableItems(data = {}, type, headers = null) { [INDEX_NAME]: index, }; - const property = data[index] ? data[index].value : undefined; + const propertyValue = getDescriptorValue(property); - if (property && property.preview) { - const { preview } = property; - const entries = preview.ownProperties || preview.items; - if (entries) { - for (const [key, entry] of Object.entries(entries)) { - item[key] = - entry && Object.prototype.hasOwnProperty.call(entry, "value") - ? entry.value - : entry; - } - } else { - if (preview.key) { - item.key = preview.key; - } - - item[VALUE_NAME] = Object.prototype.hasOwnProperty.call( - preview, - "value" - ) - ? preview.value - : property; + if (propertyValue && propertyValue.ownProperties) { + const entries = propertyValue.ownProperties; + for (const [key, entry] of Object.entries(entries)) { + item[key] = getDescriptorValue(entry); } + } else if ( + propertyValue && + propertyValue.preview && + (type === "Map" || type === "WeakMap") + ) { + item.key = propertyValue.preview.key; + item[VALUE_NAME] = propertyValue.preview.value; } else { - item[VALUE_NAME] = property; + item[VALUE_NAME] = propertyValue; } addItem(item); @@ -229,7 +227,7 @@ function getTableItems(data = {}, type, headers = null) { // Some headers might not be present in the items, so we make sure to // return all the headers set by the user. - if (Array.isArray(headers)) { + if (validCustomHeaders) { headers.forEach(header => addColumn(header)); } diff --git a/devtools/client/webconsole/components/Output/Message.js b/devtools/client/webconsole/components/Output/Message.js index ec324ce33e..7f693511df 100644 --- a/devtools/client/webconsole/components/Output/Message.js +++ b/devtools/client/webconsole/components/Output/Message.js @@ -82,14 +82,13 @@ class Message extends Component { emitNewMessage: PropTypes.func.isRequired, onViewSource: PropTypes.func.isRequired, onViewSourceInDebugger: PropTypes.func, - onViewSourceInScratchpad: PropTypes.func, onViewSourceInStyleEditor: PropTypes.func, openContextMenu: PropTypes.func.isRequired, openLink: PropTypes.func.isRequired, sourceMapService: PropTypes.any, canRewind: PropTypes.func.isRequired, - jumpToExecutionPoint: PropTypes.func.isRequired, - onMessageHover: PropTypes.func.isRequired, + jumpToExecutionPoint: PropTypes.func, + onMessageHover: PropTypes.func, }), notes: PropTypes.arrayOf( PropTypes.shape({ @@ -324,9 +323,6 @@ class Message extends Component { onViewSourceInDebugger: serviceContainer.onViewSourceInDebugger || serviceContainer.onViewSource, - onViewSourceInScratchpad: - serviceContainer.onViewSourceInScratchpad || - serviceContainer.onViewSource, onViewSource: serviceContainer.onViewSource, onReady: this.props.maybeScrollToBottom, sourceMapService: serviceContainer.sourceMapService, @@ -386,10 +382,6 @@ class Message extends Component { onFrameClick = serviceContainer.onViewSourceInStyleEditor || serviceContainer.onViewSource; - } else if (/^Scratchpad\/\d+$/.test(frame.source)) { - onFrameClick = - serviceContainer.onViewSourceInScratchpad || - serviceContainer.onViewSource; } else { // Point everything else to debugger, if source not available, // it will fall back to view-source. diff --git a/devtools/client/webconsole/constants.js b/devtools/client/webconsole/constants.js index f90d940fde..8d939c99be 100644 --- a/devtools/client/webconsole/constants.js +++ b/devtools/client/webconsole/constants.js @@ -82,7 +82,6 @@ const prefs = { SIDEBAR_TOGGLE: "devtools.webconsole.sidebarToggle", AUTOCOMPLETE: "devtools.webconsole.input.autocomplete", GROUP_WARNINGS: "devtools.webconsole.groupWarningMessages", - EDITOR: "devtools.webconsole.features.editor", }, }, }; diff --git a/devtools/client/webconsole/enhancers/net-provider.js b/devtools/client/webconsole/enhancers/net-provider.js index 6da944156a..c678b2f9d5 100644 --- a/devtools/client/webconsole/enhancers/net-provider.js +++ b/devtools/client/webconsole/enhancers/net-provider.js @@ -39,7 +39,7 @@ function enableNetProvider(webConsoleUI) { const actions = { updateRequest: (id, data, batch) => { - proxy.dispatchRequestUpdate(id, data); + return proxy.dispatchRequestUpdate(id, data); }, }; diff --git a/devtools/client/webconsole/index.html b/devtools/client/webconsole/index.html index b01309f166..5ecb218385 100644 --- a/devtools/client/webconsole/index.html +++ b/devtools/client/webconsole/index.html @@ -9,8 +9,8 @@ persist="screenX screenY width height sizemode"> - - + + diff --git a/devtools/client/webconsole/panel.js b/devtools/client/webconsole/panel.js index 76760cacdb..23d033e7e1 100644 --- a/devtools/client/webconsole/panel.js +++ b/devtools/client/webconsole/panel.js @@ -95,17 +95,13 @@ WebConsolePanel.prototype = { }, destroy: function() { - if (this._destroyer) { - return this._destroyer; + if (!this._toolbox) { + return; } - - this._destroyer = this.hud.destroy(); - this._destroyer.then(() => { - this._frameWindow = null; - this._toolbox = null; - this.emit("destroyed"); - }); - - return this._destroyer; + this.hud.destroy(); + this.hud = null; + this._frameWindow = null; + this._toolbox = null; + this.emit("destroyed"); }, }; diff --git a/devtools/client/webconsole/reducers/messages.js b/devtools/client/webconsole/reducers/messages.js index 5f4fdcca14..9026ff2951 100644 --- a/devtools/client/webconsole/reducers/messages.js +++ b/devtools/client/webconsole/reducers/messages.js @@ -47,6 +47,12 @@ loader.lazyRequireGetter( "devtools/client/webconsole/utils/messages", true ); +loader.lazyRequireGetter( + this, + "getDescriptorValue", + "devtools/client/webconsole/utils/messages", + true +); loader.lazyRequireGetter( this, "getParentWarningGroupMessageId", @@ -1255,10 +1261,50 @@ function isTextInParameters(text, regex, parameters) { return false; } - return getAllProps(parameters).some(prop => { - const str = prop + ""; - return regex ? regex.test(str) : str.toLocaleLowerCase().includes(text); - }); + return parameters.some(parameter => + isTextInParameter(text, regex, parameter) + ); +} + +/** + * Returns true if given text is included in provided parameter. + */ +function isTextInParameter(text, regex, parameter) { + const matchStr = str => + regex ? regex.test(str) : str.toLocaleLowerCase().includes(text); + + if (parameter && parameter.class && matchStr(parameter.class)) { + return true; + } + + const parameterType = typeof parameter; + if (parameterType !== "object" && parameterType !== "undefined") { + const str = parameter + ""; + if (matchStr(str)) { + return true; + } + } + + const previewItems = getGripPreviewItems(parameter); + for (const item of previewItems) { + if (isTextInParameter(text, regex, item)) { + return true; + } + } + + if (parameter && parameter.ownProperties) { + for (const [key, desc] of Object.entries(parameter.ownProperties)) { + if (matchStr(key)) { + return true; + } + + if (isTextInParameter(text, regex, getDescriptorValue(desc))) { + return true; + } + } + } + + return false; } /** @@ -1354,28 +1400,6 @@ function isTextInPrefix(text, regex, prefix) { return regex ? regex.test(str) : str.toLocaleLowerCase().includes(text); } -/** - * Get a flat array of all the grips and their properties. - * - * @param {Array} Grips - * @return {Array} Flat array of the grips and their properties. - */ -function getAllProps(grips) { - let result = grips.reduce((res, grip) => { - const previewItems = getGripPreviewItems(grip); - const allProps = previewItems.length > 0 ? getAllProps(previewItems) : []; - return [...res, grip, grip.class, ...allProps]; - }, []); - - // We are interested only in primitive props (to search for) - // not in objects and undefined previews. - result = result.filter( - grip => typeof grip != "object" && typeof grip != "undefined" - ); - - return [...new Set(result)]; -} - function getDefaultFiltersCounter() { const count = DEFAULT_FILTERS.reduce((res, filter) => { res[filter] = 0; diff --git a/devtools/client/webconsole/reducers/prefs.js b/devtools/client/webconsole/reducers/prefs.js index 5fd4ba4d31..1a161a471b 100644 --- a/devtools/client/webconsole/reducers/prefs.js +++ b/devtools/client/webconsole/reducers/prefs.js @@ -15,7 +15,6 @@ const PrefState = overrides => sidebarToggle: false, groupWarnings: false, historyCount: 50, - editor: false, }, overrides ) diff --git a/devtools/client/webconsole/service-container.js b/devtools/client/webconsole/service-container.js index 8e0651e625..906a9125b9 100644 --- a/devtools/client/webconsole/service-container.js +++ b/devtools/client/webconsole/service-container.js @@ -278,7 +278,7 @@ function setupServiceContainer({ }; if (toolbox) { - const { highlight, unhighlight } = toolbox.getHighlighter(true); + const { highlight, unhighlight } = toolbox.getHighlighter(); Object.assign(serviceContainer, { onViewSourceInDebugger: frame => { @@ -301,17 +301,6 @@ function setupServiceContainer({ webconsoleWrapper.webConsoleUI.emit("source-in-debugger-opened"); }); }, - onViewSourceInScratchpad: frame => - toolbox.viewSourceInScratchpad(frame.url, frame.line).then(() => { - webconsoleWrapper.telemetry.recordEvent( - "jump_to_source", - "webconsole", - null, - { - session_id: toolbox.sessionId, - } - ); - }), onViewSourceInStyleEditor: frame => toolbox .viewSourceInStyleEditor(frame.url, frame.line, frame.column) @@ -339,19 +328,22 @@ function setupServiceContainer({ highlightDomElement: highlight, unHighlightDomElement: unhighlight, openNodeInInspector: async grip => { - await this.toolbox.initInspector(); const onSelectInspector = toolbox.selectTool( "inspector", "inspect_dom" ); - const onGripNodeToFront = this.toolbox.walker.gripToNodeFront(grip); - const [front, inspector] = await Promise.all([ - onGripNodeToFront, + + const onNodeFront = this.toolbox.target + .getFront("inspector") + .then(inspectorFront => inspectorFront.getNodeFrontFromNodeGrip(grip)); + + const [nodeFront, inspectorPanel] = await Promise.all([ + onNodeFront, onSelectInspector, ]); - const onInspectorUpdated = inspector.once("inspector-updated"); - const onNodeFrontSet = toolbox.selection.setNodeFront(front, { + const onInspectorUpdated = inspectorPanel.once("inspector-updated"); + const onNodeFrontSet = toolbox.selection.setNodeFront(nodeFront, { reason: "console", }); diff --git a/devtools/client/webconsole/store.js b/devtools/client/webconsole/store.js index 22956d9339..c0803fc65d 100644 --- a/devtools/client/webconsole/store.js +++ b/devtools/client/webconsole/store.js @@ -49,7 +49,6 @@ function configureStore(webConsoleUI, options = {}) { const sidebarToggle = getBoolPref(PREFS.FEATURES.SIDEBAR_TOGGLE); const autocomplete = getBoolPref(PREFS.FEATURES.AUTOCOMPLETE); const groupWarnings = getBoolPref(PREFS.FEATURES.GROUP_WARNINGS); - const editor = getBoolPref(PREFS.FEATURES.EDITOR); const historyCount = getIntPref(PREFS.UI.INPUT_HISTORY_COUNT); const initialState = { @@ -59,7 +58,6 @@ function configureStore(webConsoleUI, options = {}) { autocomplete, historyCount, groupWarnings, - editor, }), filters: FilterState({ error: getBoolPref(PREFS.FILTER.ERROR), diff --git a/devtools/client/webconsole/test/browser/browser.ini b/devtools/client/webconsole/test/browser/browser.ini index 30c68d2872..28b47dc913 100644 --- a/devtools/client/webconsole/test/browser/browser.ini +++ b/devtools/client/webconsole/test/browser/browser.ini @@ -309,6 +309,7 @@ skip-if = true # Bug XXX # Bug 1405250 [browser_webconsole_console_group.js] [browser_webconsole_console_logging_workers_api.js] skip-if = e10s # SharedWorkers console events are not received on the current process because they could run on any process. +[browser_webconsole_console_table_post_alterations.js] [browser_webconsole_console_table.js] [browser_webconsole_console_timeStamp.js] [browser_webconsole_console_trace_distinct.js] @@ -365,7 +366,6 @@ skip-if = true # Bug XXX # Bug 1404382 [browser_webconsole_keyboard_accessibility.js] [browser_webconsole_location_debugger_link.js] [browser_webconsole_location_logpoint_debugger_link.js] -[browser_webconsole_location_scratchpad_link.js] [browser_webconsole_location_styleeditor_link.js] [browser_webconsole_logErrorInPage.js] [browser_webconsole_loglimit.js] @@ -440,7 +440,6 @@ skip-if = verify [browser_webconsole_split_focus.js] [browser_webconsole_split_persist.js] [browser_webconsole_stacktrace_location_debugger_link.js] -[browser_webconsole_stacktrace_location_scratchpad_link.js] [browser_webconsole_stacktrace_mapped_location_debugger_link.js] [browser_webconsole_strict_mode_errors.js] [browser_webconsole_string.js] diff --git a/devtools/client/webconsole/test/browser/browser_console.js b/devtools/client/webconsole/test/browser/browser_console.js index 2c754e7219..0fd2a7cdbd 100644 --- a/devtools/client/webconsole/test/browser/browser_console.js +++ b/devtools/client/webconsole/test/browser/browser_console.js @@ -110,7 +110,7 @@ async function testMessages(hud) { ); await checkMessageExists(hud, "error from nuked globals"); await checkMessageExists(hud, "message from content window"); - await checkMessageExists(hud, "browser.xul"); + await checkMessageExists(hud, "browser.xhtml"); await checkMessageExists(hud, "framescript-eval"); await checkMessageExists(hud, "framescript-message"); await checkMessageExists(hud, "foobarException"); diff --git a/devtools/client/webconsole/test/browser/browser_console_context_menu_entries.js b/devtools/client/webconsole/test/browser/browser_console_context_menu_entries.js index 46b01c65d1..b74d908057 100644 --- a/devtools/client/webconsole/test/browser/browser_console_context_menu_entries.js +++ b/devtools/client/webconsole/test/browser/browser_console_context_menu_entries.js @@ -78,14 +78,17 @@ add_task(async function() { 6, "The context menu has the right number of entries." ); - is(actualEntries[0], "#editmenu-undo (editmenu-undo) [disabled]"); - is(actualEntries[1], "#editmenu-cut (editmenu-cut) [disabled]"); - is(actualEntries[2], "#editmenu-copy (editmenu-copy) [disabled]"); + is(actualEntries[0], "#editmenu-undo (text-action-undo) [disabled]"); + is(actualEntries[1], "#editmenu-cut (text-action-cut) [disabled]"); + is(actualEntries[2], "#editmenu-copy (text-action-copy) [disabled]"); // Paste may or may not be enabled depending on what ran before this. // If emptyClipboard is fixed (666254) we could assert if it's enabled/disabled. - ok(actualEntries[3].startsWith("#editmenu-paste (editmenu-paste)")); - is(actualEntries[4], "#editmenu-delete (editmenu-delete) [disabled]"); - is(actualEntries[5], "#editmenu-selectAll (editmenu-select-all) [disabled]"); + ok(actualEntries[3].startsWith("#editmenu-paste (text-action-paste)")); + is(actualEntries[4], "#editmenu-delete (text-action-delete) [disabled]"); + is( + actualEntries[5], + "#editmenu-selectAll (text-action-select-all) [disabled]" + ); const node = hud.jsterm.node; const inputContainer = node.closest(".jsterm-input-container"); @@ -97,14 +100,17 @@ add_task(async function() { 6, "The context menu has the right number of entries." ); - is(actualEntries[0], "#editmenu-undo (editmenu-undo) [disabled]"); - is(actualEntries[1], "#editmenu-cut (editmenu-cut) [disabled]"); - is(actualEntries[2], "#editmenu-copy (editmenu-copy) [disabled]"); + is(actualEntries[0], "#editmenu-undo (text-action-undo) [disabled]"); + is(actualEntries[1], "#editmenu-cut (text-action-cut) [disabled]"); + is(actualEntries[2], "#editmenu-copy (text-action-copy) [disabled]"); // Paste may or may not be enabled depending on what ran before this. // If emptyClipboard is fixed (666254) we could assert if it's enabled/disabled. - ok(actualEntries[3].startsWith("#editmenu-paste (editmenu-paste)")); - is(actualEntries[4], "#editmenu-delete (editmenu-delete) [disabled]"); - is(actualEntries[5], "#editmenu-selectAll (editmenu-select-all) [disabled]"); + ok(actualEntries[3].startsWith("#editmenu-paste (text-action-paste)")); + is(actualEntries[4], "#editmenu-delete (text-action-delete) [disabled]"); + is( + actualEntries[5], + "#editmenu-selectAll (text-action-select-all) [disabled]" + ); await hideContextMenu(hud); // Close the browser console. diff --git a/devtools/client/webconsole/test/browser/browser_jsterm_autocomplete_return_key_no_selection.js b/devtools/client/webconsole/test/browser/browser_jsterm_autocomplete_return_key_no_selection.js index 22848b37b8..22281284fa 100644 --- a/devtools/client/webconsole/test/browser/browser_jsterm_autocomplete_return_key_no_selection.js +++ b/devtools/client/webconsole/test/browser/browser_jsterm_autocomplete_return_key_no_selection.js @@ -49,6 +49,7 @@ add_task(async function() { ); ok(!getInputCompletionValue(hud), "completeNode is empty"); + const onMessage = waitForMessage(hud, "hello world"); EventUtils.synthesizeKey("KEY_Enter"); is(getInputValue(hud), "", "input is empty after KEY_Enter"); @@ -59,4 +60,7 @@ add_task(async function() { "window.testBugA", "jsterm history is correct" ); + + info("Wait for the execution value to appear"); + await onMessage; }); diff --git a/devtools/client/webconsole/test/browser/browser_jsterm_await_concurrent_same_result.js b/devtools/client/webconsole/test/browser/browser_jsterm_await_concurrent_same_result.js index bd32882852..1dbe0e0230 100644 --- a/devtools/client/webconsole/test/browser/browser_jsterm_await_concurrent_same_result.js +++ b/devtools/client/webconsole/test/browser/browser_jsterm_await_concurrent_same_result.js @@ -11,7 +11,6 @@ const TEST_URI = add_task(async function() { // Enable editor mode as we'll be able to quicly trigger multiple evaluations. - await pushPref("devtools.webconsole.features.editor", true); await pushPref("devtools.webconsole.input.editor", true); const hud = await openNewTabAndConsole(TEST_URI); @@ -36,6 +35,5 @@ add_task(async function() { ); ok(true, "There are as many results as commands"); - Services.prefs.clearUserPref("devtools.webconsole.features.editor"); Services.prefs.clearUserPref("devtools.webconsole.input.editor"); }); diff --git a/devtools/client/webconsole/test/browser/browser_jsterm_editor.js b/devtools/client/webconsole/test/browser/browser_jsterm_editor.js index 8f2c85cca1..d89f2741f0 100644 --- a/devtools/client/webconsole/test/browser/browser_jsterm_editor.js +++ b/devtools/client/webconsole/test/browser/browser_jsterm_editor.js @@ -8,7 +8,6 @@ const TEST_URI = "data:text/html;charset=utf8,

Test editor"; add_task(async function() { - await pushPref("devtools.webconsole.features.editor", true); await pushPref("devtools.webconsole.input.editor", false); const tab = await addTab(TEST_URI); diff --git a/devtools/client/webconsole/test/browser/browser_jsterm_editor_disabled_history_nav_with_keyboard.js b/devtools/client/webconsole/test/browser/browser_jsterm_editor_disabled_history_nav_with_keyboard.js index 45109625f5..3c11f0e463 100644 --- a/devtools/client/webconsole/test/browser/browser_jsterm_editor_disabled_history_nav_with_keyboard.js +++ b/devtools/client/webconsole/test/browser/browser_jsterm_editor_disabled_history_nav_with_keyboard.js @@ -11,7 +11,6 @@ const TEST_URI = "data:text/html;charset=utf-8,Web Console test for bug 1519313"; add_task(async function() { - await pushPref("devtools.webconsole.features.editor", true); await pushPref("devtools.webconsole.input.editor", true); const hud = await openNewTabAndConsole(TEST_URI); @@ -28,7 +27,8 @@ add_task(async function() { info("Executing a bunch of non-sense JS expression"); for (const expression of testExpressions) { - await executeAndWaitForMessage(hud, expression, expression); + // Wait until we get the result of the command. + await executeAndWaitForMessage(hud, expression, "", ".result"); ok(true, `JS expression executed successfully: ${expression} `); } diff --git a/devtools/client/webconsole/test/browser/browser_jsterm_editor_enter.js b/devtools/client/webconsole/test/browser/browser_jsterm_editor_enter.js index a41237fbb4..6583c22c80 100644 --- a/devtools/client/webconsole/test/browser/browser_jsterm_editor_enter.js +++ b/devtools/client/webconsole/test/browser/browser_jsterm_editor_enter.js @@ -11,7 +11,6 @@ const TEST_URI = "data:text/html;charset=utf-8,Web Console test for bug 1519314"; add_task(async function() { - await pushPref("devtools.webconsole.features.editor", true); await pushPref("devtools.webconsole.input.editor", true); await performEditorEnabledTests(); }); diff --git a/devtools/client/webconsole/test/browser/browser_jsterm_editor_execute.js b/devtools/client/webconsole/test/browser/browser_jsterm_editor_execute.js index 1652720c62..61e2ab93b4 100644 --- a/devtools/client/webconsole/test/browser/browser_jsterm_editor_execute.js +++ b/devtools/client/webconsole/test/browser/browser_jsterm_editor_execute.js @@ -11,7 +11,6 @@ const TEST_URI = "data:text/html;charset=utf-8,Web Console test for bug 1519313"; add_task(async function() { - await pushPref("devtools.webconsole.features.editor", true); await pushPref("devtools.webconsole.input.editor", true); const hud = await openNewTabAndConsole(TEST_URI); diff --git a/devtools/client/webconsole/test/browser/browser_jsterm_editor_execute_selection.js b/devtools/client/webconsole/test/browser/browser_jsterm_editor_execute_selection.js index c25918e118..46538ba766 100644 --- a/devtools/client/webconsole/test/browser/browser_jsterm_editor_execute_selection.js +++ b/devtools/client/webconsole/test/browser/browser_jsterm_editor_execute_selection.js @@ -11,7 +11,6 @@ const TEST_URI = "data:text/html;charset=utf-8,Web Console test for executing input selection"; add_task(async function() { - await pushPref("devtools.webconsole.features.editor", true); await pushPref("devtools.webconsole.input.editor", true); const hud = await openNewTabAndConsole(TEST_URI); diff --git a/devtools/client/webconsole/test/browser/browser_jsterm_editor_gutter.js b/devtools/client/webconsole/test/browser/browser_jsterm_editor_gutter.js index 752fd9ea1c..179dc9dd17 100644 --- a/devtools/client/webconsole/test/browser/browser_jsterm_editor_gutter.js +++ b/devtools/client/webconsole/test/browser/browser_jsterm_editor_gutter.js @@ -10,7 +10,6 @@ const TEST_URI = "data:text/html;charset=utf-8,Test JsTerm editor line gutters"; add_task(async function() { - await pushPref("devtools.webconsole.features.editor", true); await pushPref("devtools.webconsole.input.editor", true); const hud = await openNewTabAndConsole(TEST_URI); diff --git a/devtools/client/webconsole/test/browser/browser_jsterm_editor_resize.js b/devtools/client/webconsole/test/browser/browser_jsterm_editor_resize.js index e7533ceb78..c0e505f268 100644 --- a/devtools/client/webconsole/test/browser/browser_jsterm_editor_resize.js +++ b/devtools/client/webconsole/test/browser/browser_jsterm_editor_resize.js @@ -9,7 +9,6 @@ const TEST_URI = "data:text/html;charset=utf-8,Web Console test for editor resize"; add_task(async function() { - await pushPref("devtools.webconsole.features.editor", true); await pushPref("devtools.webconsole.input.editor", true); // Reset editorWidth pref so we have steady results when running multiple times. diff --git a/devtools/client/webconsole/test/browser/browser_jsterm_editor_toggle_keyboard_shortcut.js b/devtools/client/webconsole/test/browser/browser_jsterm_editor_toggle_keyboard_shortcut.js index 37cdceca1f..d2824d6c6d 100644 --- a/devtools/client/webconsole/test/browser/browser_jsterm_editor_toggle_keyboard_shortcut.js +++ b/devtools/client/webconsole/test/browser/browser_jsterm_editor_toggle_keyboard_shortcut.js @@ -11,8 +11,6 @@ const TEST_URI = const EDITOR_PREF = "devtools.webconsole.input.editor"; add_task(async function() { - await pushPref("devtools.webconsole.features.editor", true); - // Start with the editor turned off await pushPref(EDITOR_PREF, false); let hud = await openNewTabAndConsole(TEST_URI); diff --git a/devtools/client/webconsole/test/browser/browser_jsterm_editor_toolbar.js b/devtools/client/webconsole/test/browser/browser_jsterm_editor_toolbar.js index ed10ecc13c..dd52839842 100644 --- a/devtools/client/webconsole/test/browser/browser_jsterm_editor_toolbar.js +++ b/devtools/client/webconsole/test/browser/browser_jsterm_editor_toolbar.js @@ -8,7 +8,6 @@ const TEST_URI = "data:text/html;charset=utf8,

Test editor toolbar"; add_task(async function() { - await pushPref("devtools.webconsole.features.editor", true); await pushPref("devtools.webconsole.input.editor", false); const tab = await addTab(TEST_URI); diff --git a/devtools/client/webconsole/test/browser/browser_jsterm_no_input_and_tab_key_pressed.js b/devtools/client/webconsole/test/browser/browser_jsterm_no_input_and_tab_key_pressed.js index 6ef16a38cb..63ecc81e1a 100644 --- a/devtools/client/webconsole/test/browser/browser_jsterm_no_input_and_tab_key_pressed.js +++ b/devtools/client/webconsole/test/browser/browser_jsterm_no_input_and_tab_key_pressed.js @@ -9,10 +9,6 @@ const TEST_URI = "data:text/html,Testing jsterm with no input"; add_task(async function() { - // For now, let's disable editor as we don't know what the final placement of the - // open editor button (which may impact this test). - await pushPref("devtools.webconsole.features.editor", false); - const hud = await openNewTabAndConsole(TEST_URI); const jsterm = hud.jsterm; diff --git a/devtools/client/webconsole/test/browser/browser_webconsole_console_table.js b/devtools/client/webconsole/test/browser/browser_webconsole_console_table.js index 1dbffd46d8..16f2bf94a6 100644 --- a/devtools/client/webconsole/test/browser/browser_webconsole_console_table.js +++ b/devtools/client/webconsole/test/browser/browser_webconsole_console_table.js @@ -38,12 +38,12 @@ add_task(async function() { expected: { columns: ["(index)", "Values"], rows: [ - ["0", "undefined"], + ["0", ""], ["1", "apples"], - ["2", "undefined"], + ["2", ""], ["3", "oranges"], - ["4", "undefined"], - ["5", "undefined"], + ["4", ""], + ["5", ""], ["6", "bananas"], ], }, @@ -54,7 +54,7 @@ add_task(async function() { input: [[1, , 2]], expected: { columns: ["(index)", "0", "1", "2"], - rows: [["0", "1", "undefined", "2"]], + rows: [["0", "1", "", "2"]], }, }, { @@ -230,8 +230,8 @@ add_task(async function() { expected: { columns: ["(index)", "a", "b", "c", "d", "e"], rows: [ - ["0", "null", "false", "undefined", "0", "undefined"], - ["1", "undefined", "null", "false", "undefined", "0"], + ["0", "null", "false", "undefined", "0", ""], + ["1", "", "null", "false", "undefined", "0"], ], }, }, @@ -258,7 +258,7 @@ add_task(async function() { input: [{ a: { b: 34 } }], expected: { columns: ["(index)", "a"], - rows: [["0", "Object { … }"]], + rows: [["0", "Object { b: 34 }"]], }, additionalTest: async function(node) { info("Check that object in a cell can be expanded"); @@ -270,6 +270,49 @@ add_task(async function() { ok(nodes[2].textContent.includes("")); }, }, + { + info: "Testing max columns", + input: [ + Array.from({ length: 30 }).reduce((acc, _, i) => { + return { + ...acc, + ["item" + i]: i, + }; + }, {}), + ], + expected: { + // We show 21 columns at most + columns: [ + "(index)", + ...Array.from({ length: 20 }, (_, i) => `item${i}`), + ], + rows: [[0, ...Array.from({ length: 20 }, (_, i) => i)]], + }, + }, + { + info: "Testing performance entries", + input: "PERFORMANCE_ENTRIES", + headers: [ + "name", + "entryType", + "initiatorType", + "connectStart", + "connectEnd", + "fetchStart", + ], + expected: { + columns: [ + "(index)", + "initiatorType", + "fetchStart", + "connectStart", + "connectEnd", + "name", + "entryType", + ], + rows: [[0, "navigation", /\d+/, /\d+/, /\d+/, TEST_URI, "navigation"]], + }, + }, ]; await ContentTask.spawn( @@ -277,7 +320,13 @@ add_task(async function() { testCases.map(({ input, headers }) => ({ input, headers })), function(tests) { tests.forEach(test => { - content.wrappedJSObject.doConsoleTable(test.input, test.headers); + let { input, headers } = test; + if (input === "PERFORMANCE_ENTRIES") { + input = content.wrappedJSObject.performance.getEntriesByType( + "navigation" + ); + } + content.wrappedJSObject.doConsoleTable(input, headers); }); } ); @@ -310,25 +359,54 @@ async function testItem(testCase, node) { const cells = Array.from(node.querySelectorAll("[role=gridcell]")); is( - JSON.stringify(testCase.expected.columns), JSON.stringify(columns.map(column => column.textContent)), - "table has the expected columns" + JSON.stringify(testCase.expected.columns), + `${testCase.info} | table has the expected columns` ); // We don't really have rows since we are using a CSS grid in order to have a sticky // header on the table. So we check the "rows" by dividing the number of cells by the // number of columns. is( - testCase.expected.rows.length, cells.length / columnsNumber, - "table has the expected number of rows" + testCase.expected.rows.length, + `${testCase.info} | table has the expected number of rows` ); testCase.expected.rows.forEach((expectedRow, rowIndex) => { const startIndex = rowIndex * columnsNumber; // Slicing the cells array so we can get the current "row". - const rowCells = cells.slice(startIndex, startIndex + columnsNumber); - is(rowCells.map(x => x.textContent).join(" | "), expectedRow.join(" | ")); + const rowCells = cells + .slice(startIndex, startIndex + columnsNumber) + .map(x => x.textContent); + + const isRegex = x => x && x.constructor.name === "RegExp"; + const hasRegExp = expectedRow.find(isRegex); + if (hasRegExp) { + is( + rowCells.length, + expectedRow.length, + `${testCase.info} | row ${rowIndex} has the expected number of cell` + ); + rowCells.forEach((cell, i) => { + const expected = expectedRow[i]; + const info = `${ + testCase.info + } | row ${rowIndex} cell ${i} has the expected content`; + + if (isRegex(expected)) { + ok(expected.test(cell), info); + } else { + is(cell, expected, info); + } + }); + } else { + is( + rowCells.join(" | "), + expectedRow.join(" | "), + `${testCase.info} | row has the expected content` + ); + } }); if (testCase.expected.overflow) { diff --git a/devtools/client/webconsole/test/browser/browser_webconsole_console_table_post_alterations.js b/devtools/client/webconsole/test/browser/browser_webconsole_console_table_post_alterations.js new file mode 100644 index 0000000000..95394753e3 --- /dev/null +++ b/devtools/client/webconsole/test/browser/browser_webconsole_console_table_post_alterations.js @@ -0,0 +1,63 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Check that calling console.table on a variable which is modified after the +// console.table call only shows data for when the variable was logged. + +const TEST_URI = `data:text/html,Test console.table with modified variable`; + +add_task(async function() { + const hud = await openNewTabAndConsole(TEST_URI); + + await ContentTask.spawn(gBrowser.selectedBrowser, null, () => { + const x = ["a", "b"]; + content.wrappedJSObject.console.table(x); + x.push("c"); + content.wrappedJSObject.console.table(x); + x.sort((a, b) => b - a); + content.wrappedJSObject.console.table(x); + }); + + const [table1, table2, table3] = await waitFor(() => { + const res = hud.ui.outputNode.querySelectorAll( + ".message .new-consoletable" + ); + if (res.length === 3) { + return res; + } + return null; + }); + + info("Check the rows of the first table"); + checkTable(table1, [[0, "a"], [1, "b"]]); + + info("Check the rows of the table after adding an element to the array"); + checkTable(table2, [[0, "a"], [1, "b"], [2, "c"]]); + + info("Check the rows of the table after sorting the array"); + checkTable(table3, [[0, "c"], [1, "b"], [2, "a"]]); +}); + +function checkTable(node, expectedRows) { + const columns = Array.from(node.querySelectorAll("[role=columnheader]")); + const columnsNumber = columns.length; + const cells = Array.from(node.querySelectorAll("[role=gridcell]")); + + // We don't really have rows since we are using a CSS grid in order to have a sticky + // header on the table. So we check the "rows" by dividing the number of cells by the + // number of columns. + is( + cells.length / columnsNumber, + expectedRows.length, + "table has the expected number of rows" + ); + + expectedRows.forEach((expectedRow, rowIndex) => { + const startIndex = rowIndex * columnsNumber; + // Slicing the cells array so we can get the current "row". + const rowCells = cells.slice(startIndex, startIndex + columnsNumber); + is(rowCells.map(x => x.textContent).join(" | "), expectedRow.join(" | ")); + }); +} diff --git a/devtools/client/webconsole/test/browser/browser_webconsole_filters_persist.js b/devtools/client/webconsole/test/browser/browser_webconsole_filters_persist.js index ac87b9328c..dc2778402d 100644 --- a/devtools/client/webconsole/test/browser/browser_webconsole_filters_persist.js +++ b/devtools/client/webconsole/test/browser/browser_webconsole_filters_persist.js @@ -31,6 +31,9 @@ add_task(async function() { filterButton.click(); }); + // Wait for the CSS warning to be displayed so we don't have a pending promise. + await waitFor(() => findMessage(hud, "Expected color but found ‘blouge’")); + info("Close and re-open the console"); await closeTabAndToolbox(); hud = await openNewTabAndConsole(TEST_URI); @@ -53,9 +56,9 @@ async function getFilterButtons(hud) { }); ok(filterBar, "Filter bar is shown when filter icon is clicked."); - return filterBar.querySelectorAll(".devtools-button"); + return filterBar.querySelectorAll(".devtools-togglebutton"); } function filterIsEnabled(button) { - return button.classList.contains("checked"); + return button.getAttribute("aria-pressed") === "true"; } diff --git a/devtools/client/webconsole/test/browser/browser_webconsole_location_scratchpad_link.js b/devtools/client/webconsole/test/browser/browser_webconsole_location_scratchpad_link.js deleted file mode 100644 index 885647b3df..0000000000 --- a/devtools/client/webconsole/test/browser/browser_webconsole_location_scratchpad_link.js +++ /dev/null @@ -1,68 +0,0 @@ -/* Any copyright is dedicated to the Public Domain. - * http://creativecommons.org/publicdomain/zero/1.0/ */ - -"use strict"; - -const TEST_URI = - "data:text/html;charset=utf8,

test Scratchpad panel linking

"; - -add_task(async function() { - await pushPref("devtools.scratchpad.enabled", true); - await openNewTabAndToolbox(TEST_URI); - - info("Opening toolbox with Scratchpad panel"); - - const target = await TargetFactory.forTab(gBrowser.selectedTab); - const toolbox = await gDevTools.showToolbox(target, "scratchpad", "window"); - - const scratchpadPanel = toolbox.getPanel("scratchpad"); - const { scratchpad } = scratchpadPanel; - is( - toolbox.getCurrentPanel(), - scratchpadPanel, - "Scratchpad is currently selected panel" - ); - - info("Switching to webconsole panel"); - - const webconsolePanel = await toolbox.selectTool("webconsole"); - const { hud } = webconsolePanel; - is( - toolbox.getCurrentPanel(), - webconsolePanel, - "Webconsole is currently selected panel" - ); - - info("console.log()ing from Scratchpad"); - - const messageText = "foobar-from-scratchpad"; - scratchpad.setText(`console.log('${messageText}')`); - scratchpad.run(); - const message = await waitFor(() => findMessage(hud, messageText)); - - info("Clicking link to switch to and focus Scratchpad"); - - ok(message, "Found logged message from Scratchpad"); - const anchor = message.querySelector( - ".message-location .frame-link-filename" - ); - - const onScratchpadSelected = new Promise(resolve => { - toolbox.once("scratchpad-selected", resolve); - }); - - EventUtils.synthesizeMouse(anchor, 2, 2, {}, hud.iframeWindow); - await onScratchpadSelected; - - is( - toolbox.getCurrentPanel(), - scratchpadPanel, - "Clicking link switches to Scratchpad panel" - ); - - is( - Services.ww.activeWindow, - toolbox.topWindow, - "Scratchpad's toolbox is focused" - ); -}); diff --git a/devtools/client/webconsole/test/browser/browser_webconsole_network_attach.js b/devtools/client/webconsole/test/browser/browser_webconsole_network_attach.js index d768b103b0..f7b26b5b2f 100644 --- a/devtools/client/webconsole/test/browser/browser_webconsole_network_attach.js +++ b/devtools/client/webconsole/test/browser/browser_webconsole_network_attach.js @@ -56,15 +56,9 @@ async function testNetworkMessage(messageNode) { ok(headersTab, "Headers tab is available"); // Headers tab should be selected by default, so just check its content. - let headersContent; - await waitUntil(() => { - headersContent = messageNode.querySelector( - "#headers-panel .headers-overview" - ); - return headersContent; - }); - - ok(headersContent, "Headers content is available"); + await waitUntil(() => + messageNode.querySelector("#headers-panel .headers-overview") + ); } /** diff --git a/devtools/client/webconsole/test/browser/browser_webconsole_network_message_close_on_escape.js b/devtools/client/webconsole/test/browser/browser_webconsole_network_message_close_on_escape.js index 995775c595..4d12b3a4d8 100644 --- a/devtools/client/webconsole/test/browser/browser_webconsole_network_message_close_on_escape.js +++ b/devtools/client/webconsole/test/browser/browser_webconsole_network_message_close_on_escape.js @@ -35,6 +35,11 @@ add_task(async function task() { ); ok(headersTab, "Headers tab is available"); + // Wait for all network RDP request to be finished and have updated the UI + await waitUntil(() => + messageNode.querySelector("#headers-panel .headers-overview") + ); + info("Focus header tab and hit Escape"); headersTab.focus(); EventUtils.sendKey("ESCAPE", toolbox.win); diff --git a/devtools/client/webconsole/test/browser/browser_webconsole_network_messages_openinnet.js b/devtools/client/webconsole/test/browser/browser_webconsole_network_messages_openinnet.js index b690099866..19e427a4bf 100644 --- a/devtools/client/webconsole/test/browser/browser_webconsole_network_messages_openinnet.js +++ b/devtools/client/webconsole/test/browser/browser_webconsole_network_messages_openinnet.js @@ -35,6 +35,21 @@ add_task(async function task() { await openMessageInNetmonitor(toolbox, hud, documentUrl); + info( + "Wait for the netmonitor headers panel to appear as it spawn RDP requests" + ); + const netmonitor = toolbox.getCurrentPanel(); + await waitUntil(() => + netmonitor.panelWin.document.querySelector( + "#headers-panel .headers-overview" + ) + ); + + info( + "Wait for the event timings request which do not necessarily update the UI as timings may be undefined for cached requests" + ); + await waitForRequestData(netmonitor.panelWin.store, ["eventTimings"], 0); + // Go back to console. await toolbox.selectTool("webconsole"); info("console panel open again."); @@ -46,4 +61,37 @@ add_task(async function task() { const jsonUrl = TEST_PATH + JSON_TEST_URL; await openMessageInNetmonitor(toolbox, hud, jsonUrl); + + info( + "Wait for the netmonitor headers panel to appear as it spawn RDP requests" + ); + await waitUntil(() => + netmonitor.panelWin.document.querySelector( + "#headers-panel .headers-overview" + ) + ); + + info( + "Wait for the event timings request which do not necessarily update the UI as timings may be undefined for cached requests" + ); + await waitForRequestData(netmonitor.panelWin.store, ["eventTimings"], 1); }); + +const { + getSortedRequests, +} = require("devtools/client/netmonitor/src/selectors/index"); + +function waitForRequestData(store, fields, i) { + return waitUntil(() => { + const item = getSortedRequests(store.getState()).get(i); + if (!item) { + return false; + } + for (const field of fields) { + if (!item[field]) { + return false; + } + } + return true; + }); +} diff --git a/devtools/client/webconsole/test/browser/browser_webconsole_network_reset_filter.js b/devtools/client/webconsole/test/browser/browser_webconsole_network_reset_filter.js index 6179cf30b8..707c6f004f 100644 --- a/devtools/client/webconsole/test/browser/browser_webconsole_network_reset_filter.js +++ b/devtools/client/webconsole/test/browser/browser_webconsole_network_reset_filter.js @@ -38,9 +38,19 @@ add_task(async function() { info("Open the testscript.js request in the network monitor"); await openMessageInNetmonitor(toolbox, hud, url, shortUrl); + const netmonitor = toolbox.getCurrentPanel(); + + info( + "Wait for the netmonitor headers panel to appear as it spawn RDP requests" + ); + await waitUntil(() => + netmonitor.panelWin.document.querySelector( + "#headers-panel .headers-overview" + ) + ); + info("Filter out the current request"); - const panel = toolbox.getCurrentPanel(); - const { store, windowRequire } = panel.panelWin; + const { store, windowRequire } = netmonitor.panelWin; const Actions = windowRequire("devtools/client/netmonitor/src/actions/index"); store.dispatch(Actions.toggleRequestFilterType("js")); @@ -50,4 +60,13 @@ add_task(async function() { info("Open the testscript.js request again in the network monitor"); await openMessageInNetmonitor(toolbox, hud, url, shortUrl); + + info( + "Wait for the netmonitor headers panel to appear as it spawn RDP requests" + ); + await waitUntil(() => + netmonitor.panelWin.document.querySelector( + "#headers-panel .headers-overview" + ) + ); }); diff --git a/devtools/client/webconsole/test/browser/browser_webconsole_nodes_highlight.js b/devtools/client/webconsole/test/browser/browser_webconsole_nodes_highlight.js index d340515436..864e4e699f 100644 --- a/devtools/client/webconsole/test/browser/browser_webconsole_nodes_highlight.js +++ b/devtools/client/webconsole/test/browser/browser_webconsole_nodes_highlight.js @@ -50,9 +50,8 @@ add_task(async function() { // the inspector should be initialized first and then the node should // highlight after the hover effect. - let onNodeHighlight = toolbox.target - .once("inspector") - .then(inspector => inspector.highlighter.once("node-highlight")); + const inspectorFront = await toolbox.target.getFront("inspector"); + let onNodeHighlight = inspectorFront.highlighter.once("node-highlight"); EventUtils.synthesizeMouseAtCenter(node, { type: "mousemove" }, view); @@ -62,7 +61,7 @@ add_task(async function() { ok(isVisible, "Highlighter is displayed"); info("Unhighlight the node by moving away from the node"); - let onNodeUnhighlight = toolbox.highlighter.once("node-unhighlight"); + let onNodeUnhighlight = inspectorFront.highlighter.once("node-unhighlight"); EventUtils.synthesizeMouseAtCenter( nonHighlightEl, { type: "mousemove" }, @@ -73,8 +72,8 @@ add_task(async function() { ok(true, "node-unhighlight event was fired when moving away from the node"); info("Check we don't have zombie highlighters when briefly hovering a node"); - onNodeHighlight = toolbox.highlighter.once("node-highlight"); - onNodeUnhighlight = toolbox.highlighter.once("node-unhighlight"); + onNodeHighlight = inspectorFront.highlighter.once("node-highlight"); + onNodeUnhighlight = inspectorFront.highlighter.once("node-unhighlight"); // Move hover the node and then right after move out. EventUtils.synthesizeMouseAtCenter(node, { type: "mousemove" }, view); EventUtils.synthesizeMouseAtCenter( diff --git a/devtools/client/webconsole/test/browser/browser_webconsole_reverse_search.js b/devtools/client/webconsole/test/browser/browser_webconsole_reverse_search.js index 2e79943a0e..26ca027fd8 100644 --- a/devtools/client/webconsole/test/browser/browser_webconsole_reverse_search.js +++ b/devtools/client/webconsole/test/browser/browser_webconsole_reverse_search.js @@ -19,10 +19,15 @@ add_task(async function() { .querySelectorAll("*") .forEach(console.log)`, `document`, - `"😎"`, + `"a" + "😎"`, ]; - const onLastMessage = waitForMessage(hud, `"😎"`); + // We have to wait for the same message twice in order to wait for the evaluation line + // as well as the result line + const onLastMessage = waitForMessages({ + hud, + messages: [{ text: `"a" + "😎"` }, { text: `"a😎"` }], + }); for (const input of jstermHistory) { execute(hud, input); } @@ -156,7 +161,12 @@ add_task(async function() { ); info("Check that Enter evaluates the JsTerm and closes the UI"); - const onMessage = waitForMessage(hud, `"😎"`); + // We have to wait for the same message twice in order to wait for the evaluation line + // as well as the result line + const onMessage = waitForMessages({ + hud, + messages: [{ text: `"a" + "😎"` }, { text: `"a😎"` }], + }); const onReverseSearchClose = waitFor(() => !getReverseSearchElement(hud)); EventUtils.synthesizeKey("KEY_Enter"); await Promise.all([onMessage, onReverseSearchClose]); diff --git a/devtools/client/webconsole/test/browser/browser_webconsole_stacktrace_location_scratchpad_link.js b/devtools/client/webconsole/test/browser/browser_webconsole_stacktrace_location_scratchpad_link.js deleted file mode 100644 index df38416eac..0000000000 --- a/devtools/client/webconsole/test/browser/browser_webconsole_stacktrace_location_scratchpad_link.js +++ /dev/null @@ -1,73 +0,0 @@ -/* Any copyright is dedicated to the Public Domain. - * http://creativecommons.org/publicdomain/zero/1.0/ */ - -"use strict"; - -const TEST_URI = - "data:text/html;charset=utf8,

test stacktrace scratchpad linking

"; - -add_task(async function() { - await pushPref("devtools.scratchpad.enabled", true); - await openNewTabAndToolbox(TEST_URI); - - info("Opening toolbox with Scratchpad panel"); - - const target = await TargetFactory.forTab(gBrowser.selectedTab); - const toolbox = await gDevTools.showToolbox(target, "scratchpad", "window"); - - const scratchpadPanel = toolbox.getPanel("scratchpad"); - const { scratchpad } = scratchpadPanel; - is( - toolbox.getCurrentPanel(), - scratchpadPanel, - "Scratchpad is currently selected panel" - ); - - info("Switching to webconsole panel"); - - const webconsolePanel = await toolbox.selectTool("webconsole"); - const { hud } = webconsolePanel; - is( - toolbox.getCurrentPanel(), - webconsolePanel, - "Webconsole is currently selected panel" - ); - - info("console.trace()ing from Scratchpad"); - - scratchpad.setText(` - function foo() { - bar(); - } - - function bar() { - console.trace(); - } - - foo(); - `); - scratchpad.run(); - const message = await waitFor(() => findMessage(hud, "console.trace()")); - ok(message, "Found console.trace message from Scratchpad"); - - info("Clicking link to switch to and focus Scratchpad"); - const anchor = await waitFor(() => - message.querySelector(".stacktrace .frame") - ); - const onScratchpadSelected = toolbox.once("scratchpad-selected"); - - EventUtils.sendMouseEvent({ type: "mousedown" }, anchor); - await onScratchpadSelected; - - is( - toolbox.getCurrentPanel(), - scratchpadPanel, - "Clicking link in stacktrace switches to Scratchpad panel" - ); - - is( - Services.ww.activeWindow, - toolbox.topWindow, - "Scratchpad's toolbox is focused" - ); -}); diff --git a/devtools/client/webconsole/test/browser/browser_webconsole_stubs_console_api.js b/devtools/client/webconsole/test/browser/browser_webconsole_stubs_console_api.js index f3c2e0ca39..d48289d5ca 100644 --- a/devtools/client/webconsole/test/browser/browser_webconsole_stubs_console_api.js +++ b/devtools/client/webconsole/test/browser/browser_webconsole_stubs_console_api.js @@ -63,7 +63,8 @@ add_task(async function() { async function generateConsoleApiStubs() { const { PREFS } = require("devtools/client/webconsole/constants"); // Hiding log messages so we don't get unwanted client/server communication. - Services.prefs.setBoolPref(PREFS.FILTER.LOG, false); + const { getPrefsService } = require("devtools/client/webconsole/utils/prefs"); + getPrefsService({}).setBoolPref(PREFS.FILTER.LOG, false); const stubs = new Map(); diff --git a/devtools/client/webconsole/test/browser/head.js b/devtools/client/webconsole/test/browser/head.js index b256ab1b90..787dac00e6 100644 --- a/devtools/client/webconsole/test/browser/head.js +++ b/devtools/client/webconsole/test/browser/head.js @@ -923,6 +923,13 @@ async function openMessageInNetmonitor(toolbox, hud, url, urlInConsole) { }); ok(true, "The attached url is correct."); + + info( + "Wait for the netmonitor headers panel to appear as it spawn RDP requests" + ); + await waitUntil(() => + panelWin.document.querySelector("#headers-panel .headers-overview") + ); } function selectNode(hud, node) { @@ -1129,16 +1136,15 @@ function isReverseSearchInputFocused(hud) { */ async function selectNodeWithPicker(toolbox, testActor, selector) { const inspector = toolbox.getPanel("inspector"); - const inspectorFront = inspector.inspectorFront; - const onPickerStarted = inspectorFront.nodePicker.once("picker-started"); - inspectorFront.nodePicker.start(); + const onPickerStarted = toolbox.nodePicker.once("picker-started"); + toolbox.nodePicker.start(); await onPickerStarted; info( `Picker mode started, now clicking on "${selector}" to select that node` ); - const onPickerStopped = inspectorFront.nodePicker.once("picker-stopped"); + const onPickerStopped = toolbox.nodePicker.once("picker-stopped"); const onInspectorUpdated = inspector.once("inspector-updated"); testActor.synthesizeMouse({ diff --git a/devtools/client/webconsole/test/node/babel.config.js b/devtools/client/webconsole/test/node/babel.config.js new file mode 100644 index 0000000000..3fa8391507 --- /dev/null +++ b/devtools/client/webconsole/test/node/babel.config.js @@ -0,0 +1,12 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +module.exports = { + plugins: [ + "@babel/plugin-proposal-optional-chaining", + "@babel/plugin-proposal-nullish-coalescing-operator", + ], +}; diff --git a/devtools/client/webconsole/test/node/components/filter-button.test.js b/devtools/client/webconsole/test/node/components/filter-button.test.js index 0648eabddc..cfbb12f7cb 100644 --- a/devtools/client/webconsole/test/node/components/filter-button.test.js +++ b/devtools/client/webconsole/test/node/components/filter-button.test.js @@ -22,9 +22,8 @@ describe("FilterButton component:", () => { it("displays as active when turned on", () => { const wrapper = render(FilterButton(props)); expect(wrapper.is("button")).toBe(true); - expect(wrapper.hasClass("devtools-button")).toBe(true); + expect(wrapper.hasClass("devtools-togglebutton")).toBe(true); expect(wrapper.hasClass("error")).toBe(true); - expect(wrapper.hasClass("checked")).toBe(true); expect(wrapper.attr("aria-pressed")).toBe("true"); expect(wrapper.text()).toBe("Error"); }); @@ -32,9 +31,8 @@ describe("FilterButton component:", () => { it("displays as inactive when turned off", () => { const wrapper = render(FilterButton({ ...props, active: false })); expect(wrapper.is("button")).toBe(true); - expect(wrapper.hasClass("devtools-button")).toBe(true); + expect(wrapper.hasClass("devtools-togglebutton")).toBe(true); expect(wrapper.hasClass("error")).toBe(true); - expect(wrapper.hasClass("checked")).toBe(false); expect(wrapper.attr("aria-pressed")).toBe("false"); expect(wrapper.text()).toBe("Error"); }); diff --git a/devtools/client/webconsole/test/node/fixtures/serviceContainer.js b/devtools/client/webconsole/test/node/fixtures/serviceContainer.js index 197e5f6aae..773750aa58 100644 --- a/devtools/client/webconsole/test/node/fixtures/serviceContainer.js +++ b/devtools/client/webconsole/test/node/fixtures/serviceContainer.js @@ -13,7 +13,6 @@ module.exports = { }, onViewSourceInDebugger: () => {}, onViewSourceInStyleEditor: () => {}, - onViewSourceInScratchpad: () => {}, openNetworkPanel: () => {}, resendNetworkRequest: () => {}, sourceMapService: { diff --git a/devtools/client/webconsole/test/node/fixtures/stubs/consoleApi.js b/devtools/client/webconsole/test/node/fixtures/stubs/consoleApi.js index e1b893656d..d64efe855f 100644 --- a/devtools/client/webconsole/test/node/fixtures/stubs/consoleApi.js +++ b/devtools/client/webconsole/test/node/fixtures/stubs/consoleApi.js @@ -458,14 +458,26 @@ stubPackets.set(`console.table(['red', 'green', 'blue']);`, { "extensible": true, "frozen": false, "sealed": false, - "preview": { - "kind": "ArrayLike", - "length": 3, - "items": [ - "red", - "green", - "blue" - ] + "preview": null, + "ownProperties": { + "0": { + "configurable": true, + "enumerable": true, + "writable": true, + "value": "red" + }, + "1": { + "configurable": true, + "enumerable": true, + "writable": true, + "value": "green" + }, + "2": { + "configurable": true, + "enumerable": true, + "writable": true, + "value": "blue" + } } } ], @@ -1150,14 +1162,26 @@ stubPackets.set(`console.table(['a', 'b', 'c'])`, { "extensible": true, "frozen": false, "sealed": false, - "preview": { - "kind": "ArrayLike", - "length": 3, - "items": [ - "a", - "b", - "c" - ] + "preview": null, + "ownProperties": { + "0": { + "configurable": true, + "enumerable": true, + "writable": true, + "value": "a" + }, + "1": { + "configurable": true, + "enumerable": true, + "writable": true, + "value": "b" + }, + "2": { + "configurable": true, + "enumerable": true, + "writable": true, + "value": "c" + } } } ], diff --git a/devtools/client/webconsole/test/node/mocha-test-setup.js b/devtools/client/webconsole/test/node/mocha-test-setup.js index 61423c1532..5cfe9dd0c7 100644 --- a/devtools/client/webconsole/test/node/mocha-test-setup.js +++ b/devtools/client/webconsole/test/node/mocha-test-setup.js @@ -5,8 +5,14 @@ "use strict"; +require("@babel/register")({ + // by default everything is ignored + ignore: [/node_modules/], +}); + const mcRoot = `${__dirname}/../../../../../`; -const getModule = mcPath => `module.exports = require("${mcRoot}${mcPath}");`; +const getModule = mcPath => + `module.exports = require("${(mcRoot + mcPath).replace(/\\/gi, "/")}");`; const { Services: { pref }, @@ -29,7 +35,6 @@ pref("devtools.webconsole.groupWarningMessages", false); pref("devtools.webconsole.input.editor", false); pref("devtools.webconsole.input.autocomplete", true); pref("devtools.browserconsole.contentMessages", true); -pref("devtools.webconsole.features.editor", true); pref("devtools.webconsole.input.editorWidth", 800); global.loader = { diff --git a/devtools/client/webconsole/test/node/package.json b/devtools/client/webconsole/test/node/package.json index d56eb1885a..9aa93280df 100644 --- a/devtools/client/webconsole/test/node/package.json +++ b/devtools/client/webconsole/test/node/package.json @@ -15,6 +15,10 @@ "test-ci": "mocha \"./{,!(node_modules)/**}/*.test.js\" -r mock-local-storage -r jsdom-global/register -r ./mocha-test-setup.js --reporter json" }, "dependencies": { + "@babel/core": "^7.8.7", + "@babel/plugin-proposal-nullish-coalescing-operator": "^7.8.3", + "@babel/plugin-proposal-optional-chaining": "^7.8.3", + "@babel/register": "^7.8.6", "devtools-modules": "0.0.37", "enzyme": "^3.3.0", "enzyme-adapter-react-16": "^1.1.1", diff --git a/devtools/client/webconsole/test/node/yarn.lock b/devtools/client/webconsole/test/node/yarn.lock new file mode 100644 index 0000000000..22115923ba --- /dev/null +++ b/devtools/client/webconsole/test/node/yarn.lock @@ -0,0 +1,3455 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +"@babel/code-frame@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.8.3.tgz#33e25903d7481181534e12ec0a25f16b6fcf419e" + integrity sha512-a9gxpmdXtZEInkCSHUJDLHZVBgb1QS0jhss4cPP93EW7s+uC5bikET2twEF3KV+7rDblJcmNvTR7VJejqd2C2g== + dependencies: + "@babel/highlight" "^7.8.3" + +"@babel/core@^7.8.7": + version "7.8.7" + resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.8.7.tgz#b69017d221ccdeb203145ae9da269d72cf102f3b" + integrity sha512-rBlqF3Yko9cynC5CCFy6+K/w2N+Sq/ff2BPy+Krp7rHlABIr5epbA7OxVeKoMHB39LZOp1UY5SuLjy6uWi35yA== + dependencies: + "@babel/code-frame" "^7.8.3" + "@babel/generator" "^7.8.7" + "@babel/helpers" "^7.8.4" + "@babel/parser" "^7.8.7" + "@babel/template" "^7.8.6" + "@babel/traverse" "^7.8.6" + "@babel/types" "^7.8.7" + convert-source-map "^1.7.0" + debug "^4.1.0" + gensync "^1.0.0-beta.1" + json5 "^2.1.0" + lodash "^4.17.13" + resolve "^1.3.2" + semver "^5.4.1" + source-map "^0.5.0" + +"@babel/generator@^7.8.6", "@babel/generator@^7.8.7": + version "7.8.8" + resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.8.8.tgz#cdcd58caab730834cee9eeadb729e833b625da3e" + integrity sha512-HKyUVu69cZoclptr8t8U5b6sx6zoWjh8jiUhnuj3MpZuKT2dJ8zPTuiy31luq32swhI0SpwItCIlU8XW7BZeJg== + dependencies: + "@babel/types" "^7.8.7" + jsesc "^2.5.1" + lodash "^4.17.13" + source-map "^0.5.0" + +"@babel/helper-function-name@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.8.3.tgz#eeeb665a01b1f11068e9fb86ad56a1cb1a824cca" + integrity sha512-BCxgX1BC2hD/oBlIFUgOCQDOPV8nSINxCwM3o93xP4P9Fq6aV5sgv2cOOITDMtCfQ+3PvHp3l689XZvAM9QyOA== + dependencies: + "@babel/helper-get-function-arity" "^7.8.3" + "@babel/template" "^7.8.3" + "@babel/types" "^7.8.3" + +"@babel/helper-get-function-arity@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/helper-get-function-arity/-/helper-get-function-arity-7.8.3.tgz#b894b947bd004381ce63ea1db9f08547e920abd5" + integrity sha512-FVDR+Gd9iLjUMY1fzE2SR0IuaJToR4RkCDARVfsBBPSP53GEqSFjD8gNyxg246VUyc/ALRxFaAK8rVG7UT7xRA== + dependencies: + "@babel/types" "^7.8.3" + +"@babel/helper-plugin-utils@^7.8.0", "@babel/helper-plugin-utils@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.8.3.tgz#9ea293be19babc0f52ff8ca88b34c3611b208670" + integrity sha512-j+fq49Xds2smCUNYmEHF9kGNkhbet6yVIBp4e6oeQpH1RUs/Ir06xUKzDjDkGcaaokPiTNs2JBWHjaE4csUkZQ== + +"@babel/helper-split-export-declaration@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.8.3.tgz#31a9f30070f91368a7182cf05f831781065fc7a9" + integrity sha512-3x3yOeyBhW851hroze7ElzdkeRXQYQbFIb7gLK1WQYsw2GWDay5gAJNw1sWJ0VFP6z5J1whqeXH/WCdCjZv6dA== + dependencies: + "@babel/types" "^7.8.3" + +"@babel/helpers@^7.8.4": + version "7.8.4" + resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.8.4.tgz#754eb3ee727c165e0a240d6c207de7c455f36f73" + integrity sha512-VPbe7wcQ4chu4TDQjimHv/5tj73qz88o12EPkO2ValS2QiQS/1F2SsjyIGNnAD0vF/nZS6Cf9i+vW6HIlnaR8w== + dependencies: + "@babel/template" "^7.8.3" + "@babel/traverse" "^7.8.4" + "@babel/types" "^7.8.3" + +"@babel/highlight@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.8.3.tgz#28f173d04223eaaa59bc1d439a3836e6d1265797" + integrity sha512-PX4y5xQUvy0fnEVHrYOarRPXVWafSjTW9T0Hab8gVIawpl2Sj0ORyrygANq+KjcNlSSTw0YCLSNA8OyZ1I4yEg== + dependencies: + chalk "^2.0.0" + esutils "^2.0.2" + js-tokens "^4.0.0" + +"@babel/parser@^7.8.6", "@babel/parser@^7.8.7": + version "7.8.8" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.8.8.tgz#4c3b7ce36db37e0629be1f0d50a571d2f86f6cd4" + integrity sha512-mO5GWzBPsPf6865iIbzNE0AvkKF3NE+2S3eRUpE+FE07BOAkXh6G+GW/Pj01hhXjve1WScbaIO4UlY1JKeqCcA== + +"@babel/plugin-proposal-nullish-coalescing-operator@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-nullish-coalescing-operator/-/plugin-proposal-nullish-coalescing-operator-7.8.3.tgz#e4572253fdeed65cddeecfdab3f928afeb2fd5d2" + integrity sha512-TS9MlfzXpXKt6YYomudb/KU7nQI6/xnapG6in1uZxoxDghuSMZsPb6D2fyUwNYSAp4l1iR7QtFOjkqcRYcUsfw== + dependencies: + "@babel/helper-plugin-utils" "^7.8.3" + "@babel/plugin-syntax-nullish-coalescing-operator" "^7.8.0" + +"@babel/plugin-proposal-optional-chaining@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.8.3.tgz#ae10b3214cb25f7adb1f3bc87ba42ca10b7e2543" + integrity sha512-QIoIR9abkVn+seDE3OjA08jWcs3eZ9+wJCKSRgo3WdEU2csFYgdScb+8qHB3+WXsGJD55u+5hWCISI7ejXS+kg== + dependencies: + "@babel/helper-plugin-utils" "^7.8.3" + "@babel/plugin-syntax-optional-chaining" "^7.8.0" + +"@babel/plugin-syntax-nullish-coalescing-operator@^7.8.0": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz#167ed70368886081f74b5c36c65a88c03b66d1a9" + integrity sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-optional-chaining@^7.8.0": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz#4f69c2ab95167e0180cd5336613f8c5788f7d48a" + integrity sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/register@^7.8.6": + version "7.8.6" + resolved "https://registry.yarnpkg.com/@babel/register/-/register-7.8.6.tgz#a1066aa6168a73a70c35ef28cc5865ccc087ea69" + integrity sha512-7IDO93fuRsbyml7bAafBQb3RcBGlCpU4hh5wADA2LJEEcYk92WkwFZ0pHyIi2fb5Auoz1714abETdZKCOxN0CQ== + dependencies: + find-cache-dir "^2.0.0" + lodash "^4.17.13" + make-dir "^2.1.0" + pirates "^4.0.0" + source-map-support "^0.5.16" + +"@babel/template@^7.8.3", "@babel/template@^7.8.6": + version "7.8.6" + resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.8.6.tgz#86b22af15f828dfb086474f964dcc3e39c43ce2b" + integrity sha512-zbMsPMy/v0PWFZEhQJ66bqjhH+z0JgMoBWuikXybgG3Gkd/3t5oQ1Rw2WQhnSrsOmsKXnZOx15tkC4qON/+JPg== + dependencies: + "@babel/code-frame" "^7.8.3" + "@babel/parser" "^7.8.6" + "@babel/types" "^7.8.6" + +"@babel/traverse@^7.8.4", "@babel/traverse@^7.8.6": + version "7.8.6" + resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.8.6.tgz#acfe0c64e1cd991b3e32eae813a6eb564954b5ff" + integrity sha512-2B8l0db/DPi8iinITKuo7cbPznLCEk0kCxDoB9/N6gGNg/gxOXiR/IcymAFPiBwk5w6TtQ27w4wpElgp9btR9A== + dependencies: + "@babel/code-frame" "^7.8.3" + "@babel/generator" "^7.8.6" + "@babel/helper-function-name" "^7.8.3" + "@babel/helper-split-export-declaration" "^7.8.3" + "@babel/parser" "^7.8.6" + "@babel/types" "^7.8.6" + debug "^4.1.0" + globals "^11.1.0" + lodash "^4.17.13" + +"@babel/types@^7.8.3", "@babel/types@^7.8.6", "@babel/types@^7.8.7": + version "7.8.7" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.8.7.tgz#1fc9729e1acbb2337d5b6977a63979b4819f5d1d" + integrity sha512-k2TreEHxFA4CjGkL+GYjRyx35W0Mr7DP5+9q6WMkyKXB+904bYmG40syjMFV0oLlhhFCwWl0vA0DyzTDkwAiJw== + dependencies: + esutils "^2.0.2" + lodash "^4.17.13" + to-fast-properties "^2.0.0" + +"@types/node@*": + version "13.9.2" + resolved "https://registry.yarnpkg.com/@types/node/-/node-13.9.2.tgz#ace1880c03594cc3e80206d96847157d8e7fa349" + integrity sha512-bnoqK579sAYrQbp73wwglccjJ4sfRdKU7WNEZ5FW4K2U6Kc0/eZ5kvXG0JKsEKFB50zrFmfFt52/cvBbZa7eXg== + +abab@^1.0.3: + version "1.0.4" + resolved "https://registry.yarnpkg.com/abab/-/abab-1.0.4.tgz#5faad9c2c07f60dd76770f71cf025b62a63cfd4e" + integrity sha1-X6rZwsB/YN12dw9xzwJbYqY8/U4= + +acorn-globals@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/acorn-globals/-/acorn-globals-3.1.0.tgz#fd8270f71fbb4996b004fa880ee5d46573a731bf" + integrity sha1-/YJw9x+7SZawBPqIDuXUZXOnMb8= + dependencies: + acorn "^4.0.4" + +acorn@^4.0.4: + version "4.0.13" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-4.0.13.tgz#105495ae5361d697bd195c825192e1ad7f253787" + integrity sha1-EFSVrlNh1pe9GVyCUZLhrX8lN4c= + +airbnb-prop-types@^2.15.0: + version "2.15.0" + resolved "https://registry.yarnpkg.com/airbnb-prop-types/-/airbnb-prop-types-2.15.0.tgz#5287820043af1eb469f5b0af0d6f70da6c52aaef" + integrity sha512-jUh2/hfKsRjNFC4XONQrxo/n/3GG4Tn6Hl0WlFQN5PY9OMC9loSCoAYKnZsWaP8wEfd5xcrPloK0Zg6iS1xwVA== + dependencies: + array.prototype.find "^2.1.0" + function.prototype.name "^1.1.1" + has "^1.0.3" + is-regex "^1.0.4" + object-is "^1.0.1" + object.assign "^4.1.0" + object.entries "^1.1.0" + prop-types "^15.7.2" + prop-types-exact "^1.2.0" + react-is "^16.9.0" + +ajv@^6.5.5: + version "6.12.0" + resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.0.tgz#06d60b96d87b8454a5adaba86e7854da629db4b7" + integrity sha512-D6gFiFA0RRLyUbvijN74DWAjXSFxWKaWP7mldxkVhyhAV3+SWA9HEJPHQ2c9soIeTFJqcSdFDGFgdqs1iUU2Hw== + dependencies: + fast-deep-equal "^3.1.1" + fast-json-stable-stringify "^2.0.0" + json-schema-traverse "^0.4.1" + uri-js "^4.2.2" + +ansi-escapes@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-1.4.0.tgz#d3a8a83b319aa67793662b13e761c7911422306e" + integrity sha1-06ioOzGapneTZisT52HHkRQiMG4= + +ansi-regex@^2.0.0, ansi-regex@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-2.1.1.tgz#c3b33ab5ee360d86e0e628f0468ae7ef27d654df" + integrity sha1-w7M6te42DYbg5ijwRorn7yfWVN8= + +ansi-styles@^2.2.1: + version "2.2.1" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-2.2.1.tgz#b432dd3358b634cf75e1e4664368240533c1ddbe" + integrity sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4= + +ansi-styles@^3.0.0, ansi-styles@^3.2.1: + version "3.2.1" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d" + integrity sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA== + dependencies: + color-convert "^1.9.0" + +anymatch@^1.3.0: + version "1.3.2" + resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-1.3.2.tgz#553dcb8f91e3c889845dfdba34c77721b90b9d7a" + integrity sha512-0XNayC8lTHQ2OI8aljNCN3sSx6hsr/1+rlcDAotXJR7C1oZZHCNsfpbKwMjRA3Uqb5tF1Rae2oloTr4xpq+WjA== + dependencies: + micromatch "^2.1.5" + normalize-path "^2.0.0" + +append-transform@^0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/append-transform/-/append-transform-0.4.0.tgz#d76ebf8ca94d276e247a36bad44a4b74ab611991" + integrity sha1-126/jKlNJ24keja61EpLdKthGZE= + dependencies: + default-require-extensions "^1.0.0" + +argparse@^1.0.7: + version "1.0.10" + resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.10.tgz#bcd6791ea5ae09725e17e5ad988134cd40b3d911" + integrity sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg== + dependencies: + sprintf-js "~1.0.2" + +arr-diff@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/arr-diff/-/arr-diff-2.0.0.tgz#8f3b827f955a8bd669697e4a4256ac3ceae356cf" + integrity sha1-jzuCf5Vai9ZpaX5KQlasPOrjVs8= + dependencies: + arr-flatten "^1.0.1" + +arr-flatten@^1.0.1: + version "1.1.0" + resolved "https://registry.yarnpkg.com/arr-flatten/-/arr-flatten-1.1.0.tgz#36048bbff4e7b47e136644316c99669ea5ae91f1" + integrity sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg== + +array-equal@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/array-equal/-/array-equal-1.0.0.tgz#8c2a5ef2472fd9ea742b04c77a75093ba2757c93" + integrity sha1-jCpe8kcv2ep0KwTHenUJO6J1fJM= + +array-filter@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/array-filter/-/array-filter-1.0.0.tgz#baf79e62e6ef4c2a4c0b831232daffec251f9d83" + integrity sha1-uveeYubvTCpMC4MSMtr/7CUfnYM= + +array-unique@^0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/array-unique/-/array-unique-0.2.1.tgz#a1d97ccafcbc2625cc70fadceb36a50c58b01a53" + integrity sha1-odl8yvy8JiXMcPrc6zalDFiwGlM= + +array.prototype.find@^2.1.0: + version "2.1.1" + resolved "https://registry.yarnpkg.com/array.prototype.find/-/array.prototype.find-2.1.1.tgz#3baca26108ca7affb08db06bf0be6cb3115a969c" + integrity sha512-mi+MYNJYLTx2eNYy+Yh6raoQacCsNeeMUaspFPh9Y141lFSsWxxB8V9mM2ye+eqiRs917J6/pJ4M9ZPzenWckA== + dependencies: + define-properties "^1.1.3" + es-abstract "^1.17.4" + +array.prototype.flat@^1.2.3: + version "1.2.3" + resolved "https://registry.yarnpkg.com/array.prototype.flat/-/array.prototype.flat-1.2.3.tgz#0de82b426b0318dbfdb940089e38b043d37f6c7b" + integrity sha512-gBlRZV0VSmfPIeWfuuy56XZMvbVfbEUnOXUvt3F/eUUUSyzlgLxhEX4YAEpxNAogRGehPSnfXyPtYyKAhkzQhQ== + dependencies: + define-properties "^1.1.3" + es-abstract "^1.17.0-next.1" + +arrify@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/arrify/-/arrify-1.0.1.tgz#898508da2226f380df904728456849c1501a4b0d" + integrity sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0= + +asn1@~0.2.3: + version "0.2.4" + resolved "https://registry.yarnpkg.com/asn1/-/asn1-0.2.4.tgz#8d2475dfab553bb33e77b54e59e880bb8ce23136" + integrity sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg== + dependencies: + safer-buffer "~2.1.0" + +assert-plus@1.0.0, assert-plus@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/assert-plus/-/assert-plus-1.0.0.tgz#f12e0f3c5d77b0b1cdd9146942e4e96c1e4dd525" + integrity sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU= + +async@^2.1.4: + version "2.6.3" + resolved "https://registry.yarnpkg.com/async/-/async-2.6.3.tgz#d72625e2344a3656e3a3ad4fa749fa83299d82ff" + integrity sha512-zflvls11DCy+dQWzTW2dzuilv8Z5X/pjfmZOWba6TNIVDm+2UDaJmXSOXlasHKfNBs8oo3M0aT50fDEWfKZjXg== + dependencies: + lodash "^4.17.14" + +asynckit@^0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" + integrity sha1-x57Zf380y48robyXkLzDZkdLS3k= + +aws-sign2@~0.7.0: + version "0.7.0" + resolved "https://registry.yarnpkg.com/aws-sign2/-/aws-sign2-0.7.0.tgz#b46e890934a9591f2d2f6f86d7e6a9f1b3fe76a8" + integrity sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg= + +aws4@^1.8.0: + version "1.9.1" + resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.9.1.tgz#7e33d8f7d449b3f673cd72deb9abdc552dbe528e" + integrity sha512-wMHVg2EOHaMRxbzgFJ9gtjOOCrI80OHLG14rxi28XwOW8ux6IiEbRCGGGqCtdAIg4FQCbW20k9RsT4y3gJlFug== + +babel-code-frame@^6.26.0: + version "6.26.0" + resolved "https://registry.yarnpkg.com/babel-code-frame/-/babel-code-frame-6.26.0.tgz#63fd43f7dc1e3bb7ce35947db8fe369a3f58c74b" + integrity sha1-Y/1D99weO7fONZR9uP42mj9Yx0s= + dependencies: + chalk "^1.1.3" + esutils "^2.0.2" + js-tokens "^3.0.2" + +babel-core@^6.0.0, babel-core@^6.26.0: + version "6.26.3" + resolved "https://registry.yarnpkg.com/babel-core/-/babel-core-6.26.3.tgz#b2e2f09e342d0f0c88e2f02e067794125e75c207" + integrity sha512-6jyFLuDmeidKmUEb3NM+/yawG0M2bDZ9Z1qbZP59cyHLz8kYGKYwpJP0UwUKKUiTRNvxfLesJnTedqczP7cTDA== + dependencies: + babel-code-frame "^6.26.0" + babel-generator "^6.26.0" + babel-helpers "^6.24.1" + babel-messages "^6.23.0" + babel-register "^6.26.0" + babel-runtime "^6.26.0" + babel-template "^6.26.0" + babel-traverse "^6.26.0" + babel-types "^6.26.0" + babylon "^6.18.0" + convert-source-map "^1.5.1" + debug "^2.6.9" + json5 "^0.5.1" + lodash "^4.17.4" + minimatch "^3.0.4" + path-is-absolute "^1.0.1" + private "^0.1.8" + slash "^1.0.0" + source-map "^0.5.7" + +babel-generator@^6.18.0, babel-generator@^6.26.0: + version "6.26.1" + resolved "https://registry.yarnpkg.com/babel-generator/-/babel-generator-6.26.1.tgz#1844408d3b8f0d35a404ea7ac180f087a601bd90" + integrity sha512-HyfwY6ApZj7BYTcJURpM5tznulaBvyio7/0d4zFOeMPUmfxkCjHocCuoLa2SAGzBI8AREcH3eP3758F672DppA== + dependencies: + babel-messages "^6.23.0" + babel-runtime "^6.26.0" + babel-types "^6.26.0" + detect-indent "^4.0.0" + jsesc "^1.3.0" + lodash "^4.17.4" + source-map "^0.5.7" + trim-right "^1.0.1" + +babel-helpers@^6.24.1: + version "6.24.1" + resolved "https://registry.yarnpkg.com/babel-helpers/-/babel-helpers-6.24.1.tgz#3471de9caec388e5c850e597e58a26ddf37602b2" + integrity sha1-NHHenK7DiOXIUOWX5Yom3fN2ArI= + dependencies: + babel-runtime "^6.22.0" + babel-template "^6.24.1" + +babel-jest@^20.0.3: + version "20.0.3" + resolved "https://registry.yarnpkg.com/babel-jest/-/babel-jest-20.0.3.tgz#e4a03b13dc10389e140fc645d09ffc4ced301671" + integrity sha1-5KA7E9wQOJ4UD8ZF0J/8TO0wFnE= + dependencies: + babel-core "^6.0.0" + babel-plugin-istanbul "^4.0.0" + babel-preset-jest "^20.0.3" + +babel-messages@^6.23.0: + version "6.23.0" + resolved "https://registry.yarnpkg.com/babel-messages/-/babel-messages-6.23.0.tgz#f3cdf4703858035b2a2951c6ec5edf6c62f2630e" + integrity sha1-8830cDhYA1sqKVHG7F7fbGLyYw4= + dependencies: + babel-runtime "^6.22.0" + +babel-plugin-istanbul@^4.0.0: + version "4.1.6" + resolved "https://registry.yarnpkg.com/babel-plugin-istanbul/-/babel-plugin-istanbul-4.1.6.tgz#36c59b2192efce81c5b378321b74175add1c9a45" + integrity sha512-PWP9FQ1AhZhS01T/4qLSKoHGY/xvkZdVBGlKM/HuxxS3+sC66HhTNR7+MpbO/so/cz/wY94MeSWJuP1hXIPfwQ== + dependencies: + babel-plugin-syntax-object-rest-spread "^6.13.0" + find-up "^2.1.0" + istanbul-lib-instrument "^1.10.1" + test-exclude "^4.2.1" + +babel-plugin-jest-hoist@^20.0.3: + version "20.0.3" + resolved "https://registry.yarnpkg.com/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-20.0.3.tgz#afedc853bd3f8dc3548ea671fbe69d03cc2c1767" + integrity sha1-r+3IU70/jcNUjqZx++adA8wsF2c= + +babel-plugin-syntax-object-rest-spread@^6.13.0: + version "6.13.0" + resolved "https://registry.yarnpkg.com/babel-plugin-syntax-object-rest-spread/-/babel-plugin-syntax-object-rest-spread-6.13.0.tgz#fd6536f2bce13836ffa3a5458c4903a597bb3bf5" + integrity sha1-/WU28rzhODb/o6VFjEkDpZe7O/U= + +babel-preset-jest@^20.0.3: + version "20.0.3" + resolved "https://registry.yarnpkg.com/babel-preset-jest/-/babel-preset-jest-20.0.3.tgz#cbacaadecb5d689ca1e1de1360ebfc66862c178a" + integrity sha1-y6yq3stdaJyh4d4TYOv8ZoYsF4o= + dependencies: + babel-plugin-jest-hoist "^20.0.3" + +babel-register@^6.26.0: + version "6.26.0" + resolved "https://registry.yarnpkg.com/babel-register/-/babel-register-6.26.0.tgz#6ed021173e2fcb486d7acb45c6009a856f647071" + integrity sha1-btAhFz4vy0htestFxgCahW9kcHE= + dependencies: + babel-core "^6.26.0" + babel-runtime "^6.26.0" + core-js "^2.5.0" + home-or-tmp "^2.0.0" + lodash "^4.17.4" + mkdirp "^0.5.1" + source-map-support "^0.4.15" + +babel-runtime@^6.22.0, babel-runtime@^6.26.0, babel-runtime@^6.6.1: + version "6.26.0" + resolved "https://registry.yarnpkg.com/babel-runtime/-/babel-runtime-6.26.0.tgz#965c7058668e82b55d7bfe04ff2337bc8b5647fe" + integrity sha1-llxwWGaOgrVde/4E/yM3vItWR/4= + dependencies: + core-js "^2.4.0" + regenerator-runtime "^0.11.0" + +babel-template@^6.16.0, babel-template@^6.24.1, babel-template@^6.26.0: + version "6.26.0" + resolved "https://registry.yarnpkg.com/babel-template/-/babel-template-6.26.0.tgz#de03e2d16396b069f46dd9fff8521fb1a0e35e02" + integrity sha1-3gPi0WOWsGn0bdn/+FIfsaDjXgI= + dependencies: + babel-runtime "^6.26.0" + babel-traverse "^6.26.0" + babel-types "^6.26.0" + babylon "^6.18.0" + lodash "^4.17.4" + +babel-traverse@^6.18.0, babel-traverse@^6.26.0: + version "6.26.0" + resolved "https://registry.yarnpkg.com/babel-traverse/-/babel-traverse-6.26.0.tgz#46a9cbd7edcc62c8e5c064e2d2d8d0f4035766ee" + integrity sha1-RqnL1+3MYsjlwGTi0tjQ9ANXZu4= + dependencies: + babel-code-frame "^6.26.0" + babel-messages "^6.23.0" + babel-runtime "^6.26.0" + babel-types "^6.26.0" + babylon "^6.18.0" + debug "^2.6.8" + globals "^9.18.0" + invariant "^2.2.2" + lodash "^4.17.4" + +babel-types@^6.18.0, babel-types@^6.26.0: + version "6.26.0" + resolved "https://registry.yarnpkg.com/babel-types/-/babel-types-6.26.0.tgz#a3b073f94ab49eb6fa55cd65227a334380632497" + integrity sha1-o7Bz+Uq0nrb6Vc1lInozQ4BjJJc= + dependencies: + babel-runtime "^6.26.0" + esutils "^2.0.2" + lodash "^4.17.4" + to-fast-properties "^1.0.3" + +babylon@^6.18.0: + version "6.18.0" + resolved "https://registry.yarnpkg.com/babylon/-/babylon-6.18.0.tgz#af2f3b88fa6f5c1e4c634d1a0f8eac4f55b395e3" + integrity sha512-q/UEjfGJ2Cm3oKV71DJz9d25TPnq5rhBVL2Q4fA5wcC3jcrdn7+SssEybFIxwAvvP+YCsCYNKughoF33GxgycQ== + +balanced-match@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767" + integrity sha1-ibTRmasr7kneFk6gK4nORi1xt2c= + +bcrypt-pbkdf@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz#a4301d389b6a43f9b67ff3ca11a3f6637e360e9e" + integrity sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4= + dependencies: + tweetnacl "^0.14.3" + +boolbase@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/boolbase/-/boolbase-1.0.0.tgz#68dff5fbe60c51eb37725ea9e3ed310dcc1e776e" + integrity sha1-aN/1++YMUes3cl6p4+0xDcwed24= + +brace-expansion@^1.1.7: + version "1.1.11" + resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" + integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA== + dependencies: + balanced-match "^1.0.0" + concat-map "0.0.1" + +braces@^1.8.2: + version "1.8.5" + resolved "https://registry.yarnpkg.com/braces/-/braces-1.8.5.tgz#ba77962e12dff969d6b76711e914b737857bf6a7" + integrity sha1-uneWLhLf+WnWt2cR6RS3N4V79qc= + dependencies: + expand-range "^1.8.1" + preserve "^0.2.0" + repeat-element "^1.1.2" + +browser-resolve@^1.11.2: + version "1.11.3" + resolved "https://registry.yarnpkg.com/browser-resolve/-/browser-resolve-1.11.3.tgz#9b7cbb3d0f510e4cb86bdbd796124d28b5890af6" + integrity sha512-exDi1BYWB/6raKHmDTCicQfTkqwN5fioMFV4j8BsfMU4R2DK/QfZfK7kOVkmWCNANf0snkBzqGqAJBao9gZMdQ== + dependencies: + resolve "1.1.7" + +browser-stdout@1.3.1: + version "1.3.1" + resolved "https://registry.yarnpkg.com/browser-stdout/-/browser-stdout-1.3.1.tgz#baa559ee14ced73452229bad7326467c61fabd60" + integrity sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw== + +bser@1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/bser/-/bser-1.0.2.tgz#381116970b2a6deea5646dd15dd7278444b56169" + integrity sha1-OBEWlwsqbe6lZG3RXdcnhES1YWk= + dependencies: + node-int64 "^0.4.0" + +bser@2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/bser/-/bser-2.1.1.tgz#e6787da20ece9d07998533cfd9de6f5c38f4bc05" + integrity sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ== + dependencies: + node-int64 "^0.4.0" + +buffer-from@^1.0.0: + version "1.1.1" + resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.1.tgz#32713bc028f75c02fdb710d7c7bcec1f2c6070ef" + integrity sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A== + +builtin-modules@^1.0.0: + version "1.1.1" + resolved "https://registry.yarnpkg.com/builtin-modules/-/builtin-modules-1.1.1.tgz#270f076c5a72c02f5b65a47df94c5fe3a278892f" + integrity sha1-Jw8HbFpywC9bZaR9+Uxf46J4iS8= + +callsites@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/callsites/-/callsites-2.0.0.tgz#06eb84f00eea413da86affefacbffb36093b3c50" + integrity sha1-BuuE8A7qQT2oav/vrL/7Ngk7PFA= + +camelcase@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-3.0.0.tgz#32fc4b9fcdaf845fcdf7e73bb97cac2261f0ab0a" + integrity sha1-MvxLn82vhF/N9+c7uXysImHwqwo= + +caseless@~0.12.0: + version "0.12.0" + resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.12.0.tgz#1b681c21ff84033c826543090689420d187151dc" + integrity sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw= + +chalk@^1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-1.1.3.tgz#a8115c55e4a702fe4d150abd3872822a7e09fc98" + integrity sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg= + dependencies: + ansi-styles "^2.2.1" + escape-string-regexp "^1.0.2" + has-ansi "^2.0.0" + strip-ansi "^3.0.0" + supports-color "^2.0.0" + +chalk@^2.0.0: + version "2.4.2" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" + integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ== + dependencies: + ansi-styles "^3.2.1" + escape-string-regexp "^1.0.5" + supports-color "^5.3.0" + +cheerio@^1.0.0-rc.3: + version "1.0.0-rc.3" + resolved "https://registry.yarnpkg.com/cheerio/-/cheerio-1.0.0-rc.3.tgz#094636d425b2e9c0f4eb91a46c05630c9a1a8bf6" + integrity sha512-0td5ijfUPuubwLUu0OBoe98gZj8C/AA+RW3v67GPlGOrvxWjZmBXiBCRU+I8VEiNyJzjth40POfHiz2RB3gImA== + dependencies: + css-select "~1.2.0" + dom-serializer "~0.1.1" + entities "~1.1.1" + htmlparser2 "^3.9.1" + lodash "^4.15.0" + parse5 "^3.0.1" + +ci-info@^1.5.0: + version "1.6.0" + resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-1.6.0.tgz#2ca20dbb9ceb32d4524a683303313f0304b1e497" + integrity sha512-vsGdkwSCDpWmP80ncATX7iea5DWQemg1UgCW5J8tqjU3lYw4FBYuj89J0CTVomA7BEfvSZd84GmHko+MxFQU2A== + +cliui@^3.2.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/cliui/-/cliui-3.2.0.tgz#120601537a916d29940f934da3b48d585a39213d" + integrity sha1-EgYBU3qRbSmUD5NNo7SNWFo5IT0= + dependencies: + string-width "^1.0.1" + strip-ansi "^3.0.1" + wrap-ansi "^2.0.0" + +code-point-at@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/code-point-at/-/code-point-at-1.1.0.tgz#0d070b4d043a5bea33a2f1a40e2edb3d9a4ccf77" + integrity sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c= + +color-convert@^1.9.0: + version "1.9.3" + resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8" + integrity sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg== + dependencies: + color-name "1.1.3" + +color-name@1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25" + integrity sha1-p9BVi9icQveV3UIyj3QIMcpTvCU= + +colors@^1.1.2: + version "1.4.0" + resolved "https://registry.yarnpkg.com/colors/-/colors-1.4.0.tgz#c50491479d4c1bdaed2c9ced32cf7c7dc2360f78" + integrity sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA== + +combined-stream@^1.0.6, combined-stream@~1.0.6: + version "1.0.8" + resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f" + integrity sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg== + dependencies: + delayed-stream "~1.0.0" + +commander@2.15.1: + version "2.15.1" + resolved "https://registry.yarnpkg.com/commander/-/commander-2.15.1.tgz#df46e867d0fc2aec66a34662b406a9ccafff5b0f" + integrity sha512-VlfT9F3V0v+jr4yxPc5gg9s62/fIVWsd2Bk2iD435um1NlGMYdVCq+MjcXnhYq2icNOizHr1kK+5TI6H0Hy0ag== + +commander@^2.19.0, commander@~2.20.3: + version "2.20.3" + resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33" + integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ== + +commondir@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/commondir/-/commondir-1.0.1.tgz#ddd800da0c66127393cca5950ea968a3aaf1253b" + integrity sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs= + +concat-map@0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" + integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s= + +content-type-parser@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/content-type-parser/-/content-type-parser-1.0.2.tgz#caabe80623e63638b2502fd4c7f12ff4ce2352e7" + integrity sha512-lM4l4CnMEwOLHAHr/P6MEZwZFPJFtAAKgL6pogbXmVZggIqXhdB6RbBtPOTsw2FcXwYhehRGERJmRrjOiIB8pQ== + +convert-source-map@^1.4.0, convert-source-map@^1.5.1, convert-source-map@^1.7.0: + version "1.7.0" + resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.7.0.tgz#17a2cb882d7f77d3490585e2ce6c524424a3a442" + integrity sha512-4FJkXzKXEDB1snCFZlLP4gpC3JILicCpGbzG9f9G7tGqGCzETQ2hWPrcinA9oU4wtf2biUaEH5065UnMeR33oA== + dependencies: + safe-buffer "~5.1.1" + +core-js@^0.8.3: + version "0.8.4" + resolved "https://registry.yarnpkg.com/core-js/-/core-js-0.8.4.tgz#c22665f1e0d1b9c3c5e1b08dabd1f108695e4fcf" + integrity sha1-wiZl8eDRucPF4bCNq9HxCGleT88= + +core-js@^2.4.0, core-js@^2.5.0: + version "2.6.11" + resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.6.11.tgz#38831469f9922bded8ee21c9dc46985e0399308c" + integrity sha512-5wjnpaT/3dV+XB4borEsnAYQchn00XSgTAWKDkEqv+K8KevjbzmofK6hfJ9TZIlpj2N0xQpazy7PiRQiWHqzWg== + +core-util-is@1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7" + integrity sha1-tf1UIgqivFq1eqtxQMlAdUUDwac= + +css-select@~1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/css-select/-/css-select-1.2.0.tgz#2b3a110539c5355f1cd8d314623e870b121ec858" + integrity sha1-KzoRBTnFNV8c2NMUYj6HCxIeyFg= + dependencies: + boolbase "~1.0.0" + css-what "2.1" + domutils "1.5.1" + nth-check "~1.0.1" + +css-what@2.1: + version "2.1.3" + resolved "https://registry.yarnpkg.com/css-what/-/css-what-2.1.3.tgz#a6d7604573365fe74686c3f311c56513d88285f2" + integrity sha512-a+EPoD+uZiNfh+5fxw2nO9QwFa6nJe2Or35fGY6Ipw1R3R4AGz1d1TEZrCegvw2YTmZ0jXirGYlzxxpYSHwpEg== + +cssom@0.3.x, "cssom@>= 0.3.2 < 0.4.0": + version "0.3.8" + resolved "https://registry.yarnpkg.com/cssom/-/cssom-0.3.8.tgz#9f1276f5b2b463f2114d3f2c75250af8c1a36f4a" + integrity sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg== + +"cssstyle@>= 0.2.37 < 0.3.0": + version "0.2.37" + resolved "https://registry.yarnpkg.com/cssstyle/-/cssstyle-0.2.37.tgz#541097234cb2513c83ceed3acddc27ff27987d54" + integrity sha1-VBCXI0yyUTyDzu06zdwn/yeYfVQ= + dependencies: + cssom "0.3.x" + +dashdash@^1.12.0: + version "1.14.1" + resolved "https://registry.yarnpkg.com/dashdash/-/dashdash-1.14.1.tgz#853cfa0f7cbe2fed5de20326b8dd581035f6e2f0" + integrity sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA= + dependencies: + assert-plus "^1.0.0" + +debug@3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/debug/-/debug-3.1.0.tgz#5bb5a0672628b64149566ba16819e61518c67261" + integrity sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g== + dependencies: + ms "2.0.0" + +debug@^2.6.8, debug@^2.6.9: + version "2.6.9" + resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" + integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA== + dependencies: + ms "2.0.0" + +debug@^3.1.0: + version "3.2.6" + resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.6.tgz#e83d17de16d8a7efb7717edbe5fb10135eee629b" + integrity sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ== + dependencies: + ms "^2.1.1" + +debug@^4.1.0: + version "4.1.1" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.1.1.tgz#3b72260255109c6b589cee050f1d516139664791" + integrity sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw== + dependencies: + ms "^2.1.1" + +decamelize@^1.1.1: + version "1.2.0" + resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290" + integrity sha1-9lNNFRSCabIDUue+4m9QH5oZEpA= + +deep-is@~0.1.3: + version "0.1.3" + resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.3.tgz#b369d6fb5dbc13eecf524f91b070feedc357cf34" + integrity sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ= + +default-require-extensions@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/default-require-extensions/-/default-require-extensions-1.0.0.tgz#f37ea15d3e13ffd9b437d33e1a75b5fb97874cb8" + integrity sha1-836hXT4T/9m0N9M+GnW1+5eHTLg= + dependencies: + strip-bom "^2.0.0" + +define-properties@^1.1.2, define-properties@^1.1.3, define-properties@~1.1.2: + version "1.1.3" + resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.1.3.tgz#cf88da6cbee26fe6db7094f61d870cbd84cee9f1" + integrity sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ== + dependencies: + object-keys "^1.0.12" + +delayed-stream@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" + integrity sha1-3zrhmayt+31ECqrgsp4icrJOxhk= + +detect-indent@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/detect-indent/-/detect-indent-4.0.0.tgz#f76d064352cdf43a1cb6ce619c4ee3a9475de208" + integrity sha1-920GQ1LN9Docts5hnE7jqUdd4gg= + dependencies: + repeating "^2.0.0" + +devtools-modules@0.0.37: + version "0.0.37" + resolved "https://registry.yarnpkg.com/devtools-modules/-/devtools-modules-0.0.37.tgz#29b0041e444fe8b08aae3833b5433ab004d012b3" + integrity sha512-60yjoRsmBSwaHc2vbwxgo+NKgzAZYRppLI1Q5x3sQ8VArGD2nUDVNRBCtR2SihjodNSQuMB4S9owIiE6fSJ2ow== + dependencies: + jest "^20.0.4" + punycode "^2.1.0" + +devtools-services@^0.0.3: + version "0.0.3" + resolved "https://registry.yarnpkg.com/devtools-services/-/devtools-services-0.0.3.tgz#69f1c5653f26b45b4cb0eed41b182a7bb74b98ad" + integrity sha512-O3GGocQwd1q7yUzEFMpHc/aPS2IVOltjlWFlmJXedi1VoX0mbDpjkJHU0svQfiJzEqL7ySMS4qMn+MlIUXySNA== + +diff@3.5.0, diff@^3.2.0: + version "3.5.0" + resolved "https://registry.yarnpkg.com/diff/-/diff-3.5.0.tgz#800c0dd1e0a8bfbc95835c202ad220fe317e5a12" + integrity sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA== + +discontinuous-range@1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/discontinuous-range/-/discontinuous-range-1.0.0.tgz#e38331f0844bba49b9a9cb71c771585aab1bc65a" + integrity sha1-44Mx8IRLukm5qctxx3FYWqsbxlo= + +dom-serializer@0: + version "0.2.2" + resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-0.2.2.tgz#1afb81f533717175d478655debc5e332d9f9bb51" + integrity sha512-2/xPb3ORsQ42nHYiSunXkDjPLBaEj/xTwUO4B7XCZQTRk7EBtTOPaygh10YAAh2OI1Qrp6NWfpAhzswj0ydt9g== + dependencies: + domelementtype "^2.0.1" + entities "^2.0.0" + +dom-serializer@~0.1.1: + version "0.1.1" + resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-0.1.1.tgz#1ec4059e284babed36eec2941d4a970a189ce7c0" + integrity sha512-l0IU0pPzLWSHBcieZbpOKgkIn3ts3vAh7ZuFyXNwJxJXk/c4Gwj9xaTJwIDVQCXawWD0qb3IzMGH5rglQaO0XA== + dependencies: + domelementtype "^1.3.0" + entities "^1.1.1" + +dom-walk@^0.1.0: + version "0.1.1" + resolved "https://registry.yarnpkg.com/dom-walk/-/dom-walk-0.1.1.tgz#672226dc74c8f799ad35307df936aba11acd6018" + integrity sha1-ZyIm3HTI95mtNTB9+TaroRrNYBg= + +domelementtype@1, domelementtype@^1.3.0, domelementtype@^1.3.1: + version "1.3.1" + resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-1.3.1.tgz#d048c44b37b0d10a7f2a3d5fee3f4333d790481f" + integrity sha512-BSKB+TSpMpFI/HOxCNr1O8aMOTZ8hT3pM3GQ0w/mWRmkhEDSFJkkyzz4XQsBV44BChwGkrDfMyjVD0eA2aFV3w== + +domelementtype@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-2.0.1.tgz#1f8bdfe91f5a78063274e803b4bdcedf6e94f94d" + integrity sha512-5HOHUDsYZWV8FGWN0Njbr/Rn7f/eWSQi1v7+HsUVwXgn8nWWlL64zKDkS0n8ZmQ3mlWOMuXOnR+7Nx/5tMO5AQ== + +domhandler@^2.3.0: + version "2.4.2" + resolved "https://registry.yarnpkg.com/domhandler/-/domhandler-2.4.2.tgz#8805097e933d65e85546f726d60f5eb88b44f803" + integrity sha512-JiK04h0Ht5u/80fdLMCEmV4zkNh2BcoMFBmZ/91WtYZ8qVXSKjiw7fXMgFPnHcSZgOo3XdinHvmnDUeMf5R4wA== + dependencies: + domelementtype "1" + +domutils@1.5.1: + version "1.5.1" + resolved "https://registry.yarnpkg.com/domutils/-/domutils-1.5.1.tgz#dcd8488a26f563d61079e48c9f7b7e32373682cf" + integrity sha1-3NhIiib1Y9YQeeSMn3t+Mjc2gs8= + dependencies: + dom-serializer "0" + domelementtype "1" + +domutils@^1.5.1: + version "1.7.0" + resolved "https://registry.yarnpkg.com/domutils/-/domutils-1.7.0.tgz#56ea341e834e06e6748af7a1cb25da67ea9f8c2a" + integrity sha512-Lgd2XcJ/NjEw+7tFvfKxOzCYKZsdct5lczQ2ZaQY8Djz7pfAD3Gbp8ySJWtreII/vDlMVmxwa6pHmdxIYgttDg== + dependencies: + dom-serializer "0" + domelementtype "1" + +ecc-jsbn@~0.1.1: + version "0.1.2" + resolved "https://registry.yarnpkg.com/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz#3a83a904e54353287874c564b7549386849a98c9" + integrity sha1-OoOpBOVDUyh4dMVkt1SThoSamMk= + dependencies: + jsbn "~0.1.0" + safer-buffer "^2.1.0" + +entities@^1.1.1, entities@~1.1.1: + version "1.1.2" + resolved "https://registry.yarnpkg.com/entities/-/entities-1.1.2.tgz#bdfa735299664dfafd34529ed4f8522a275fea56" + integrity sha512-f2LZMYl1Fzu7YSBKg+RoROelpOaNrcGmE9AZubeDfrCEia483oW4MI4VyFd5VNHIgQ/7qm1I0wUHK1eJnn2y2w== + +entities@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/entities/-/entities-2.0.0.tgz#68d6084cab1b079767540d80e56a39b423e4abf4" + integrity sha512-D9f7V0JSRwIxlRI2mjMqufDrRDnx8p+eEOz7aUM9SuvF8gsBzra0/6tbjl1m8eQHrZlYj6PxqE00hZ1SAIKPLw== + +enzyme-adapter-react-16@^1.1.1: + version "1.15.2" + resolved "https://registry.yarnpkg.com/enzyme-adapter-react-16/-/enzyme-adapter-react-16-1.15.2.tgz#b16db2f0ea424d58a808f9df86ab6212895a4501" + integrity sha512-SkvDrb8xU3lSxID8Qic9rB8pvevDbLybxPK6D/vW7PrT0s2Cl/zJYuXvsd1EBTz0q4o3iqG3FJhpYz3nUNpM2Q== + dependencies: + enzyme-adapter-utils "^1.13.0" + enzyme-shallow-equal "^1.0.1" + has "^1.0.3" + object.assign "^4.1.0" + object.values "^1.1.1" + prop-types "^15.7.2" + react-is "^16.12.0" + react-test-renderer "^16.0.0-0" + semver "^5.7.0" + +enzyme-adapter-utils@^1.13.0: + version "1.13.0" + resolved "https://registry.yarnpkg.com/enzyme-adapter-utils/-/enzyme-adapter-utils-1.13.0.tgz#01c885dde2114b4690bf741f8dc94cee3060eb78" + integrity sha512-YuEtfQp76Lj5TG1NvtP2eGJnFKogk/zT70fyYHXK2j3v6CtuHqc8YmgH/vaiBfL8K1SgVVbQXtTcgQZFwzTVyQ== + dependencies: + airbnb-prop-types "^2.15.0" + function.prototype.name "^1.1.2" + object.assign "^4.1.0" + object.fromentries "^2.0.2" + prop-types "^15.7.2" + semver "^5.7.1" + +enzyme-shallow-equal@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/enzyme-shallow-equal/-/enzyme-shallow-equal-1.0.1.tgz#7afe03db3801c9b76de8440694096412a8d9d49e" + integrity sha512-hGA3i1so8OrYOZSM9whlkNmVHOicJpsjgTzC+wn2JMJXhq1oO4kA4bJ5MsfzSIcC71aLDKzJ6gZpIxrqt3QTAQ== + dependencies: + has "^1.0.3" + object-is "^1.0.2" + +enzyme@^3.3.0: + version "3.11.0" + resolved "https://registry.yarnpkg.com/enzyme/-/enzyme-3.11.0.tgz#71d680c580fe9349f6f5ac6c775bc3e6b7a79c28" + integrity sha512-Dw8/Gs4vRjxY6/6i9wU0V+utmQO9kvh9XLnz3LIudviOnVYDEe2ec+0k+NQoMamn1VrjKgCUOWj5jG/5M5M0Qw== + dependencies: + array.prototype.flat "^1.2.3" + cheerio "^1.0.0-rc.3" + enzyme-shallow-equal "^1.0.1" + function.prototype.name "^1.1.2" + has "^1.0.3" + html-element-map "^1.2.0" + is-boolean-object "^1.0.1" + is-callable "^1.1.5" + is-number-object "^1.0.4" + is-regex "^1.0.5" + is-string "^1.0.5" + is-subset "^0.1.1" + lodash.escape "^4.0.1" + lodash.isequal "^4.5.0" + object-inspect "^1.7.0" + object-is "^1.0.2" + object.assign "^4.1.0" + object.entries "^1.1.1" + object.values "^1.1.1" + raf "^3.4.1" + rst-selector-parser "^2.2.3" + string.prototype.trim "^1.2.1" + +errno@~0.1.7: + version "0.1.7" + resolved "https://registry.yarnpkg.com/errno/-/errno-0.1.7.tgz#4684d71779ad39af177e3f007996f7c67c852618" + integrity sha512-MfrRBDWzIWifgq6tJj60gkAwtLNb6sQPlcFrSOflcP1aFmmruKQ2wRnze/8V6kgyz7H3FF8Npzv78mZ7XLLflg== + dependencies: + prr "~1.0.1" + +error-ex@^1.2.0: + version "1.3.2" + resolved "https://registry.yarnpkg.com/error-ex/-/error-ex-1.3.2.tgz#b4ac40648107fdcdcfae242f428bea8a14d4f1bf" + integrity sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g== + dependencies: + is-arrayish "^0.2.1" + +es-abstract@^1.17.0-next.1, es-abstract@^1.17.4: + version "1.17.4" + resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.17.4.tgz#e3aedf19706b20e7c2594c35fc0d57605a79e184" + integrity sha512-Ae3um/gb8F0mui/jPL+QiqmglkUsaQf7FwBEHYIFkztkneosu9imhqHpBzQ3h1vit8t5iQ74t6PEVvphBZiuiQ== + dependencies: + es-to-primitive "^1.2.1" + function-bind "^1.1.1" + has "^1.0.3" + has-symbols "^1.0.1" + is-callable "^1.1.5" + is-regex "^1.0.5" + object-inspect "^1.7.0" + object-keys "^1.1.1" + object.assign "^4.1.0" + string.prototype.trimleft "^2.1.1" + string.prototype.trimright "^2.1.1" + +es-get-iterator@^1.0.1: + version "1.1.0" + resolved "https://registry.yarnpkg.com/es-get-iterator/-/es-get-iterator-1.1.0.tgz#bb98ad9d6d63b31aacdc8f89d5d0ee57bcb5b4c8" + integrity sha512-UfrmHuWQlNMTs35e1ypnvikg6jCz3SK8v8ImvmDsh36fCVUR1MqoFDiyn0/k52C8NqO3YsO8Oe0azeesNuqSsQ== + dependencies: + es-abstract "^1.17.4" + has-symbols "^1.0.1" + is-arguments "^1.0.4" + is-map "^2.0.1" + is-set "^2.0.1" + is-string "^1.0.5" + isarray "^2.0.5" + +es-to-primitive@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/es-to-primitive/-/es-to-primitive-1.2.1.tgz#e55cd4c9cdc188bcefb03b366c736323fc5c898a" + integrity sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA== + dependencies: + is-callable "^1.1.4" + is-date-object "^1.0.1" + is-symbol "^1.0.2" + +escape-string-regexp@1.0.5, escape-string-regexp@^1.0.2, escape-string-regexp@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" + integrity sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ= + +escodegen@^1.6.1: + version "1.14.1" + resolved "https://registry.yarnpkg.com/escodegen/-/escodegen-1.14.1.tgz#ba01d0c8278b5e95a9a45350142026659027a457" + integrity sha512-Bmt7NcRySdIfNPfU2ZoXDrrXsG9ZjvDxcAlMfDUgRBjLOWTuIACXPBFJH7Z+cLb40JeQco5toikyc9t9P8E9SQ== + dependencies: + esprima "^4.0.1" + estraverse "^4.2.0" + esutils "^2.0.2" + optionator "^0.8.1" + optionalDependencies: + source-map "~0.6.1" + +esprima@^4.0.0, esprima@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71" + integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A== + +estraverse@^4.2.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-4.3.0.tgz#398ad3f3c5a24948be7725e83d11a7de28cdbd1d" + integrity sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw== + +esutils@^2.0.2: + version "2.0.3" + resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64" + integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g== + +exec-sh@^0.2.0: + version "0.2.2" + resolved "https://registry.yarnpkg.com/exec-sh/-/exec-sh-0.2.2.tgz#2a5e7ffcbd7d0ba2755bdecb16e5a427dfbdec36" + integrity sha512-FIUCJz1RbuS0FKTdaAafAByGS0CPvU3R0MeHxgtl+djzCc//F8HakL8GzmVNZanasTbTAY/3DRFA0KpVqj/eAw== + dependencies: + merge "^1.2.0" + +expand-brackets@^0.1.4: + version "0.1.5" + resolved "https://registry.yarnpkg.com/expand-brackets/-/expand-brackets-0.1.5.tgz#df07284e342a807cd733ac5af72411e581d1177b" + integrity sha1-3wcoTjQqgHzXM6xa9yQR5YHRF3s= + dependencies: + is-posix-bracket "^0.1.0" + +expand-range@^1.8.1: + version "1.8.2" + resolved "https://registry.yarnpkg.com/expand-range/-/expand-range-1.8.2.tgz#a299effd335fe2721ebae8e257ec79644fc85337" + integrity sha1-opnv/TNf4nIeuujiV+x5ZE/IUzc= + dependencies: + fill-range "^2.1.0" + +expect@^1.16.0: + version "1.20.2" + resolved "https://registry.yarnpkg.com/expect/-/expect-1.20.2.tgz#d458fe4c56004036bae3232416a3f6361f04f965" + integrity sha1-1Fj+TFYAQDa64yMkFqP2Nh8E+WU= + dependencies: + define-properties "~1.1.2" + has "^1.0.1" + is-equal "^1.5.1" + is-regex "^1.0.3" + object-inspect "^1.1.0" + object-keys "^1.0.9" + tmatch "^2.0.1" + +extend@~3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.2.tgz#f8b1136b4071fbd8eb140aff858b1019ec2915fa" + integrity sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g== + +extglob@^0.3.1: + version "0.3.2" + resolved "https://registry.yarnpkg.com/extglob/-/extglob-0.3.2.tgz#2e18ff3d2f49ab2765cec9023f011daa8d8349a1" + integrity sha1-Lhj/PS9JqydlzskCPwEdqo2DSaE= + dependencies: + is-extglob "^1.0.0" + +extsprintf@1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.3.0.tgz#96918440e3041a7a414f8c52e3c574eb3c3e1e05" + integrity sha1-lpGEQOMEGnpBT4xS48V06zw+HgU= + +extsprintf@^1.2.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.4.0.tgz#e2689f8f356fad62cca65a3a91c5df5f9551692f" + integrity sha1-4mifjzVvrWLMplo6kcXfX5VRaS8= + +fast-deep-equal@^3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.1.tgz#545145077c501491e33b15ec408c294376e94ae4" + integrity sha512-8UEa58QDLauDNfpbrX55Q9jrGHThw2ZMdOky5Gl1CDtVeJDPVrG4Jxx1N8jw2gkWaff5UUuX1KJd+9zGe2B+ZA== + +fast-json-stable-stringify@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633" + integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw== + +fast-levenshtein@~2.0.6: + version "2.0.6" + resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917" + integrity sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc= + +fb-watchman@^1.8.0: + version "1.9.2" + resolved "https://registry.yarnpkg.com/fb-watchman/-/fb-watchman-1.9.2.tgz#a24cf47827f82d38fb59a69ad70b76e3b6ae7383" + integrity sha1-okz0eCf4LTj7Waaa1wt247auc4M= + dependencies: + bser "1.0.2" + +fb-watchman@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/fb-watchman/-/fb-watchman-2.0.1.tgz#fc84fb39d2709cf3ff6d743706157bb5708a8a85" + integrity sha512-DkPJKQeY6kKwmuMretBhr7G6Vodr7bFwDYTXIkfG1gjvNpaxBTQV3PbXg6bR1c1UP4jPOX0jHUbbHANL9vRjVg== + dependencies: + bser "2.1.1" + +filename-regex@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/filename-regex/-/filename-regex-2.0.1.tgz#c1c4b9bee3e09725ddb106b75c1e301fe2f18b26" + integrity sha1-wcS5vuPglyXdsQa3XB4wH+LxiyY= + +fileset@^2.0.2: + version "2.0.3" + resolved "https://registry.yarnpkg.com/fileset/-/fileset-2.0.3.tgz#8e7548a96d3cc2327ee5e674168723a333bba2a0" + integrity sha1-jnVIqW08wjJ+5eZ0FocjozO7oqA= + dependencies: + glob "^7.0.3" + minimatch "^3.0.3" + +fill-range@^2.1.0: + version "2.2.4" + resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-2.2.4.tgz#eb1e773abb056dcd8df2bfdf6af59b8b3a936565" + integrity sha512-cnrcCbj01+j2gTG921VZPnHbjmdAf8oQV/iGeV2kZxGSyfYjjTyY79ErsK1WJWMpw6DaApEX72binqJE+/d+5Q== + dependencies: + is-number "^2.1.0" + isobject "^2.0.0" + randomatic "^3.0.0" + repeat-element "^1.1.2" + repeat-string "^1.5.2" + +find-cache-dir@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/find-cache-dir/-/find-cache-dir-2.1.0.tgz#8d0f94cd13fe43c6c7c261a0d86115ca918c05f7" + integrity sha512-Tq6PixE0w/VMFfCgbONnkiQIVol/JJL7nRMi20fqzA4NRs9AfeqMGeRdPi3wIhYkxjeBaWh2rxwapn5Tu3IqOQ== + dependencies: + commondir "^1.0.1" + make-dir "^2.0.0" + pkg-dir "^3.0.0" + +find-up@^1.0.0: + version "1.1.2" + resolved "https://registry.yarnpkg.com/find-up/-/find-up-1.1.2.tgz#6b2e9822b1a2ce0a60ab64d610eccad53cb24d0f" + integrity sha1-ay6YIrGizgpgq2TWEOzK1TyyTQ8= + dependencies: + path-exists "^2.0.0" + pinkie-promise "^2.0.0" + +find-up@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/find-up/-/find-up-2.1.0.tgz#45d1b7e506c717ddd482775a2b77920a3c0c57a7" + integrity sha1-RdG35QbHF93UgndaK3eSCjwMV6c= + dependencies: + locate-path "^2.0.0" + +find-up@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/find-up/-/find-up-3.0.0.tgz#49169f1d7993430646da61ecc5ae355c21c97b73" + integrity sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg== + dependencies: + locate-path "^3.0.0" + +for-in@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/for-in/-/for-in-1.0.2.tgz#81068d295a8142ec0ac726c6e2200c30fb6d5e80" + integrity sha1-gQaNKVqBQuwKxybG4iAMMPttXoA= + +for-own@^0.1.4: + version "0.1.5" + resolved "https://registry.yarnpkg.com/for-own/-/for-own-0.1.5.tgz#5265c681a4f294dabbf17c9509b6763aa84510ce" + integrity sha1-UmXGgaTylNq78XyVCbZ2OqhFEM4= + dependencies: + for-in "^1.0.1" + +forever-agent@~0.6.1: + version "0.6.1" + resolved "https://registry.yarnpkg.com/forever-agent/-/forever-agent-0.6.1.tgz#fbc71f0c41adeb37f96c577ad1ed42d8fdacca91" + integrity sha1-+8cfDEGt6zf5bFd60e1C2P2sypE= + +form-data@~2.3.2: + version "2.3.3" + resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.3.3.tgz#dcce52c05f644f298c6a7ab936bd724ceffbf3a6" + integrity sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ== + dependencies: + asynckit "^0.4.0" + combined-stream "^1.0.6" + mime-types "^2.1.12" + +formatio@1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/formatio/-/formatio-1.1.1.tgz#5ed3ccd636551097383465d996199100e86161e9" + integrity sha1-XtPM1jZVEJc4NGXZlhmRAOhhYek= + dependencies: + samsam "~1.1" + +fs.realpath@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" + integrity sha1-FQStJSMVjKpA20onh8sBQRmU6k8= + +function-bind@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d" + integrity sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A== + +function.prototype.name@^1.1.1, function.prototype.name@^1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/function.prototype.name/-/function.prototype.name-1.1.2.tgz#5cdf79d7c05db401591dfde83e3b70c5123e9a45" + integrity sha512-C8A+LlHBJjB2AdcRPorc5JvJ5VUoWlXdEHLOJdCI7kjHEtGTpHQUiqMvCIKUwIsGwZX2jZJy761AXsn356bJQg== + dependencies: + define-properties "^1.1.3" + es-abstract "^1.17.0-next.1" + functions-have-names "^1.2.0" + +functions-have-names@^1.2.0: + version "1.2.1" + resolved "https://registry.yarnpkg.com/functions-have-names/-/functions-have-names-1.2.1.tgz#a981ac397fa0c9964551402cdc5533d7a4d52f91" + integrity sha512-j48B/ZI7VKs3sgeI2cZp7WXWmZXu7Iq5pl5/vptV5N2mq+DGFuS/ulaDjtaoLpYzuD6u8UgrUKHfgo7fDTSiBA== + +gensync@^1.0.0-beta.1: + version "1.0.0-beta.1" + resolved "https://registry.yarnpkg.com/gensync/-/gensync-1.0.0-beta.1.tgz#58f4361ff987e5ff6e1e7a210827aa371eaac269" + integrity sha512-r8EC6NO1sngH/zdD9fiRDLdcgnbayXah+mLgManTaIZJqEC1MZstmnox8KpnI2/fxQwrp5OpCOYWLp4rBl4Jcg== + +get-caller-file@^1.0.1: + version "1.0.3" + resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-1.0.3.tgz#f978fa4c90d1dfe7ff2d6beda2a515e713bdcf4a" + integrity sha512-3t6rVToeoZfYSGd8YoLFR2DJkiQrIiUrGcjvFX2mDw3bn6k2OtwHN0TNCLbBO+w8qTvimhDkv+LSscbJY1vE6w== + +getpass@^0.1.1: + version "0.1.7" + resolved "https://registry.yarnpkg.com/getpass/-/getpass-0.1.7.tgz#5eff8e3e684d569ae4cb2b1282604e8ba62149fa" + integrity sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo= + dependencies: + assert-plus "^1.0.0" + +glob-base@^0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/glob-base/-/glob-base-0.3.0.tgz#dbb164f6221b1c0b1ccf82aea328b497df0ea3c4" + integrity sha1-27Fk9iIbHAscz4Kuoyi0l98Oo8Q= + dependencies: + glob-parent "^2.0.0" + is-glob "^2.0.0" + +glob-parent@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-2.0.0.tgz#81383d72db054fcccf5336daa902f182f6edbb28" + integrity sha1-gTg9ctsFT8zPUzbaqQLxgvbtuyg= + dependencies: + is-glob "^2.0.0" + +glob@7.1.2: + version "7.1.2" + resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.2.tgz#c19c9df9a028702d678612384a6552404c636d15" + integrity sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ== + dependencies: + fs.realpath "^1.0.0" + inflight "^1.0.4" + inherits "2" + minimatch "^3.0.4" + once "^1.3.0" + path-is-absolute "^1.0.0" + +glob@^7.0.3, glob@^7.1.1, glob@^7.1.3: + version "7.1.6" + resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.6.tgz#141f33b81a7c2492e125594307480c46679278a6" + integrity sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA== + dependencies: + fs.realpath "^1.0.0" + inflight "^1.0.4" + inherits "2" + minimatch "^3.0.4" + once "^1.3.0" + path-is-absolute "^1.0.0" + +global@^4.3.2: + version "4.4.0" + resolved "https://registry.yarnpkg.com/global/-/global-4.4.0.tgz#3e7b105179006a323ed71aafca3e9c57a5cc6406" + integrity sha512-wv/LAoHdRE3BeTGz53FAamhGlPLhlssK45usmGFThIi4XqnBmjKQ16u+RNbP7WvigRZDxUsM0J3gcQ5yicaL0w== + dependencies: + min-document "^2.19.0" + process "^0.11.10" + +globals@^11.1.0: + version "11.12.0" + resolved "https://registry.yarnpkg.com/globals/-/globals-11.12.0.tgz#ab8795338868a0babd8525758018c2a7eb95c42e" + integrity sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA== + +globals@^9.18.0: + version "9.18.0" + resolved "https://registry.yarnpkg.com/globals/-/globals-9.18.0.tgz#aa3896b3e69b487f17e31ed2143d69a8e30c2d8a" + integrity sha512-S0nG3CLEQiY/ILxqtztTWH/3iRRdyBLw6KMDxnKMchrtbj2OFmehVh0WUCfW3DUrIgx/qFrJPICrq4Z4sTR9UQ== + +graceful-fs@^4.1.11, graceful-fs@^4.1.2: + version "4.2.3" + resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.3.tgz#4a12ff1b60376ef09862c2093edd908328be8423" + integrity sha512-a30VEBm4PEdx1dRB7MFK7BejejvCvBronbLjht+sHuGYj8PHs7M/5Z+rt5lw551vZ7yfTCj4Vuyy3mSJytDWRQ== + +growl@1.10.5: + version "1.10.5" + resolved "https://registry.yarnpkg.com/growl/-/growl-1.10.5.tgz#f2735dc2283674fa67478b10181059355c369e5e" + integrity sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA== + +growly@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/growly/-/growly-1.3.0.tgz#f10748cbe76af964b7c96c93c6bcc28af120c081" + integrity sha1-8QdIy+dq+WS3yWyTxrzCivEgwIE= + +handlebars@^4.0.3: + version "4.7.3" + resolved "https://registry.yarnpkg.com/handlebars/-/handlebars-4.7.3.tgz#8ece2797826886cf8082d1726ff21d2a022550ee" + integrity sha512-SRGwSYuNfx8DwHD/6InAPzD6RgeruWLT+B8e8a7gGs8FWgHzlExpTFMEq2IA6QpAfOClpKHy6+8IqTjeBCu6Kg== + dependencies: + neo-async "^2.6.0" + optimist "^0.6.1" + source-map "^0.6.1" + optionalDependencies: + uglify-js "^3.1.4" + +har-schema@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/har-schema/-/har-schema-2.0.0.tgz#a94c2224ebcac04782a0d9035521f24735b7ec92" + integrity sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI= + +har-validator@~5.1.3: + version "5.1.3" + resolved "https://registry.yarnpkg.com/har-validator/-/har-validator-5.1.3.tgz#1ef89ebd3e4996557675eed9893110dc350fa080" + integrity sha512-sNvOCzEQNr/qrvJgc3UG/kD4QtlHycrzwS+6mfTrrSq97BvaYcPZZI1ZSqGSPR73Cxn4LKTD4PttRwfU7jWq5g== + dependencies: + ajv "^6.5.5" + har-schema "^2.0.0" + +has-ansi@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/has-ansi/-/has-ansi-2.0.0.tgz#34f5049ce1ecdf2b0649af3ef24e45ed35416d91" + integrity sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE= + dependencies: + ansi-regex "^2.0.0" + +has-flag@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-1.0.0.tgz#9d9e793165ce017a00f00418c43f942a7b1d11fa" + integrity sha1-nZ55MWXOAXoA8AQYxD+UKnsdEfo= + +has-flag@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd" + integrity sha1-tdRU3CGZriJWmfNGfloH87lVuv0= + +has-symbols@^1.0.0, has-symbols@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.1.tgz#9f5214758a44196c406d9bd76cebf81ec2dd31e8" + integrity sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg== + +has@^1.0.1, has@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/has/-/has-1.0.3.tgz#722d7cbfc1f6aa8241f16dd814e011e1f41e8796" + integrity sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw== + dependencies: + function-bind "^1.1.1" + +he@1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/he/-/he-1.1.1.tgz#93410fd21b009735151f8868c2f271f3427e23fd" + integrity sha1-k0EP0hsAlzUVH4howvJx80J+I/0= + +home-or-tmp@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/home-or-tmp/-/home-or-tmp-2.0.0.tgz#e36c3f2d2cae7d746a857e38d18d5f32a7882db8" + integrity sha1-42w/LSyufXRqhX440Y1fMqeILbg= + dependencies: + os-homedir "^1.0.0" + os-tmpdir "^1.0.1" + +hosted-git-info@^2.1.4: + version "2.8.8" + resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.8.8.tgz#7539bd4bc1e0e0a895815a2e0262420b12858488" + integrity sha512-f/wzC2QaWBs7t9IYqB4T3sR1xviIViXJRJTWBlx2Gf3g0Xi5vI7Yy4koXQ1c9OYDGHN9sBy1DQ2AB8fqZBWhUg== + +html-element-map@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/html-element-map/-/html-element-map-1.2.0.tgz#dfbb09efe882806af63d990cf6db37993f099f22" + integrity sha512-0uXq8HsuG1v2TmQ8QkIhzbrqeskE4kn52Q18QJ9iAA/SnHoEKXWiUxHQtclRsCFWEUD2So34X+0+pZZu862nnw== + dependencies: + array-filter "^1.0.0" + +html-encoding-sniffer@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/html-encoding-sniffer/-/html-encoding-sniffer-1.0.2.tgz#e70d84b94da53aa375e11fe3a351be6642ca46f8" + integrity sha512-71lZziiDnsuabfdYiUeWdCVyKuqwWi23L8YeIgV9jSSZHCtb6wB1BKWooH7L3tn4/FuZJMVWyNaIDr4RGmaSYw== + dependencies: + whatwg-encoding "^1.0.1" + +htmlparser2@^3.9.1: + version "3.10.1" + resolved "https://registry.yarnpkg.com/htmlparser2/-/htmlparser2-3.10.1.tgz#bd679dc3f59897b6a34bb10749c855bb53a9392f" + integrity sha512-IgieNijUMbkDovyoKObU1DUhm1iwNYE/fuifEoEHfd1oZKZDaONBSkal7Y01shxsM49R4XaMdGez3WnF9UfiCQ== + dependencies: + domelementtype "^1.3.1" + domhandler "^2.3.0" + domutils "^1.5.1" + entities "^1.1.1" + inherits "^2.0.1" + readable-stream "^3.1.1" + +http-signature@~1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/http-signature/-/http-signature-1.2.0.tgz#9aecd925114772f3d95b65a60abb8f7c18fbace1" + integrity sha1-muzZJRFHcvPZW2WmCruPfBj7rOE= + dependencies: + assert-plus "^1.0.0" + jsprim "^1.2.2" + sshpk "^1.7.0" + +iconv-lite@0.4.24: + version "0.4.24" + resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b" + integrity sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA== + dependencies: + safer-buffer ">= 2.1.2 < 3" + +inflight@^1.0.4: + version "1.0.6" + resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" + integrity sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk= + dependencies: + once "^1.3.0" + wrappy "1" + +inherits@2, inherits@^2.0.1, inherits@^2.0.3: + version "2.0.4" + resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" + integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== + +invariant@^2.2.2: + version "2.2.4" + resolved "https://registry.yarnpkg.com/invariant/-/invariant-2.2.4.tgz#610f3c92c9359ce1db616e538008d23ff35158e6" + integrity sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA== + dependencies: + loose-envify "^1.0.0" + +invert-kv@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/invert-kv/-/invert-kv-1.0.0.tgz#104a8e4aaca6d3d8cd157a8ef8bfab2d7a3ffdb6" + integrity sha1-EEqOSqym09jNFXqO+L+rLXo//bY= + +is-arguments@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/is-arguments/-/is-arguments-1.0.4.tgz#3faf966c7cba0ff437fb31f6250082fcf0448cf3" + integrity sha512-xPh0Rmt8NE65sNzvyUmWgI1tz3mKq74lGA0mL8LYZcoIzKOzDh6HmrYm3d18k60nHerC8A9Km8kYu87zfSFnLA== + +is-arrayish@^0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d" + integrity sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0= + +is-arrow-function@^2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/is-arrow-function/-/is-arrow-function-2.0.3.tgz#29be2c2d8d9450852b8bbafb635ba7b8d8e87ec2" + integrity sha1-Kb4sLY2UUIUri7r7Y1unuNjofsI= + dependencies: + is-callable "^1.0.4" + +is-bigint@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-bigint/-/is-bigint-1.0.0.tgz#73da8c33208d00f130e9b5e15d23eac9215601c4" + integrity sha512-t5mGUXC/xRheCK431ylNiSkGGpBp8bHENBcENTkDT6ppwPzEVxNGZRvgvmOEfbWkFhA7D2GEuE2mmQTr78sl2g== + +is-boolean-object@^1.0.0, is-boolean-object@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/is-boolean-object/-/is-boolean-object-1.0.1.tgz#10edc0900dd127697a92f6f9807c7617d68ac48e" + integrity sha512-TqZuVwa/sppcrhUCAYkGBk7w0yxfQQnxq28fjkO53tnK9FQXmdwz2JS5+GjsWQ6RByES1K40nI+yDic5c9/aAQ== + +is-buffer@^1.1.5: + version "1.1.6" + resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-1.1.6.tgz#efaa2ea9daa0d7ab2ea13a97b2b8ad51fefbe8be" + integrity sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w== + +is-builtin-module@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-builtin-module/-/is-builtin-module-1.0.0.tgz#540572d34f7ac3119f8f76c30cbc1b1e037affbe" + integrity sha1-VAVy0096wxGfj3bDDLwbHgN6/74= + dependencies: + builtin-modules "^1.0.0" + +is-callable@^1.0.4, is-callable@^1.1.4, is-callable@^1.1.5: + version "1.1.5" + resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.1.5.tgz#f7e46b596890456db74e7f6e976cb3273d06faab" + integrity sha512-ESKv5sMCJB2jnHTWZ3O5itG+O128Hsus4K4Qh1h2/cgn2vbgnLSVqfV46AeJA9D5EeeLa9w81KUXMtn34zhX+Q== + +is-ci@^1.0.10: + version "1.2.1" + resolved "https://registry.yarnpkg.com/is-ci/-/is-ci-1.2.1.tgz#e3779c8ee17fccf428488f6e281187f2e632841c" + integrity sha512-s6tfsaQaQi3JNciBH6shVqEDvhGut0SUXr31ag8Pd8BBbVVlcGfWhpPmEOoM6RJ5TFhbypvf5yyRw/VXW1IiWg== + dependencies: + ci-info "^1.5.0" + +is-date-object@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/is-date-object/-/is-date-object-1.0.2.tgz#bda736f2cd8fd06d32844e7743bfa7494c3bfd7e" + integrity sha512-USlDT524woQ08aoZFzh3/Z6ch9Y/EWXEHQ/AaRN0SkKq4t2Jw2R2339tSXmwuVoY7LLlBCbOIlx2myP/L5zk0g== + +is-dotfile@^1.0.0: + version "1.0.3" + resolved "https://registry.yarnpkg.com/is-dotfile/-/is-dotfile-1.0.3.tgz#a6a2f32ffd2dfb04f5ca25ecd0f6b83cf798a1e1" + integrity sha1-pqLzL/0t+wT1yiXs0Pa4PPeYoeE= + +is-equal-shallow@^0.1.3: + version "0.1.3" + resolved "https://registry.yarnpkg.com/is-equal-shallow/-/is-equal-shallow-0.1.3.tgz#2238098fc221de0bcfa5d9eac4c45d638aa1c534" + integrity sha1-IjgJj8Ih3gvPpdnqxMRdY4qhxTQ= + dependencies: + is-primitive "^2.0.0" + +is-equal@^1.5.1: + version "1.6.1" + resolved "https://registry.yarnpkg.com/is-equal/-/is-equal-1.6.1.tgz#74fafde5060fcaf187041c05f11f0b9f020bb9b3" + integrity sha512-3/79QTolnfNFrxQAvqH8M+O01uGWsVq54BUPG2mXQH7zi4BE/0TY+fmA444t8xSBvIwyNMvsTmCZ5ViVDlqPJg== + dependencies: + es-get-iterator "^1.0.1" + functions-have-names "^1.2.0" + has "^1.0.3" + is-arrow-function "^2.0.3" + is-bigint "^1.0.0" + is-boolean-object "^1.0.0" + is-callable "^1.1.4" + is-date-object "^1.0.1" + is-generator-function "^1.0.7" + is-number-object "^1.0.3" + is-regex "^1.0.4" + is-string "^1.0.4" + is-symbol "^1.0.3" + isarray "^2.0.5" + object-inspect "^1.7.0" + object.entries "^1.1.0" + which-boxed-primitive "^1.0.1" + which-collection "^1.0.0" + +is-extendable@^0.1.1: + version "0.1.1" + resolved "https://registry.yarnpkg.com/is-extendable/-/is-extendable-0.1.1.tgz#62b110e289a471418e3ec36a617d472e301dfc89" + integrity sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik= + +is-extglob@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-1.0.0.tgz#ac468177c4943405a092fc8f29760c6ffc6206c0" + integrity sha1-rEaBd8SUNAWgkvyPKXYMb/xiBsA= + +is-finite@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/is-finite/-/is-finite-1.1.0.tgz#904135c77fb42c0641d6aa1bcdbc4daa8da082f3" + integrity sha512-cdyMtqX/BOqqNBBiKlIVkytNHm49MtMlYyn1zxzvJKWmFMlGzm+ry5BBfYyeY9YmNKbRSo/o7OX9w9ale0wg3w== + +is-fullwidth-code-point@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz#ef9e31386f031a7f0d643af82fde50c457ef00cb" + integrity sha1-754xOG8DGn8NZDr4L95QxFfvAMs= + dependencies: + number-is-nan "^1.0.0" + +is-generator-function@^1.0.7: + version "1.0.7" + resolved "https://registry.yarnpkg.com/is-generator-function/-/is-generator-function-1.0.7.tgz#d2132e529bb0000a7f80794d4bdf5cd5e5813522" + integrity sha512-YZc5EwyO4f2kWCax7oegfuSr9mFz1ZvieNYBEjmukLxgXfBUbxAWGVF7GZf0zidYtoBl3WvC07YK0wT76a+Rtw== + +is-glob@^2.0.0, is-glob@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-2.0.1.tgz#d096f926a3ded5600f3fdfd91198cb0888c2d863" + integrity sha1-0Jb5JqPe1WAPP9/ZEZjLCIjC2GM= + dependencies: + is-extglob "^1.0.0" + +is-map@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/is-map/-/is-map-2.0.1.tgz#520dafc4307bb8ebc33b813de5ce7c9400d644a1" + integrity sha512-T/S49scO8plUiAOA2DBTBG3JHpn1yiw0kRp6dgiZ0v2/6twi5eiB0rHtHFH9ZIrvlWc6+4O+m4zg5+Z833aXgw== + +is-number-object@^1.0.3, is-number-object@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/is-number-object/-/is-number-object-1.0.4.tgz#36ac95e741cf18b283fc1ddf5e83da798e3ec197" + integrity sha512-zohwelOAur+5uXtk8O3GPQ1eAcu4ZX3UwxQhUlfFFMNpUd83gXgjbhJh6HmB6LUNV/ieOLQuDwJO3dWJosUeMw== + +is-number@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/is-number/-/is-number-2.1.0.tgz#01fcbbb393463a548f2f466cce16dece49db908f" + integrity sha1-Afy7s5NGOlSPL0ZszhbezknbkI8= + dependencies: + kind-of "^3.0.2" + +is-number@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/is-number/-/is-number-4.0.0.tgz#0026e37f5454d73e356dfe6564699867c6a7f0ff" + integrity sha512-rSklcAIlf1OmFdyAqbnWTLVelsQ58uvZ66S/ZyawjWqIviTWCjg2PzVGw8WUA+nNuPTqb4wgA+NszrJ+08LlgQ== + +is-posix-bracket@^0.1.0: + version "0.1.1" + resolved "https://registry.yarnpkg.com/is-posix-bracket/-/is-posix-bracket-0.1.1.tgz#3334dc79774368e92f016e6fbc0a88f5cd6e6bc4" + integrity sha1-MzTceXdDaOkvAW5vvAqI9c1ua8Q= + +is-primitive@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/is-primitive/-/is-primitive-2.0.0.tgz#207bab91638499c07b2adf240a41a87210034575" + integrity sha1-IHurkWOEmcB7Kt8kCkGochADRXU= + +is-regex@^1.0.3, is-regex@^1.0.4, is-regex@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.0.5.tgz#39d589a358bf18967f726967120b8fc1aed74eae" + integrity sha512-vlKW17SNq44owv5AQR3Cq0bQPEb8+kF3UKZ2fiZNOWtztYE5i0CzCZxFDwO58qAOWtxdBRVO/V5Qin1wjCqFYQ== + dependencies: + has "^1.0.3" + +is-set@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/is-set/-/is-set-2.0.1.tgz#d1604afdab1724986d30091575f54945da7e5f43" + integrity sha512-eJEzOtVyenDs1TMzSQ3kU3K+E0GUS9sno+F0OBT97xsgcJsF9nXMBtkT9/kut5JEpM7oL7X/0qxR17K3mcwIAA== + +is-string@^1.0.4, is-string@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/is-string/-/is-string-1.0.5.tgz#40493ed198ef3ff477b8c7f92f644ec82a5cd3a6" + integrity sha512-buY6VNRjhQMiF1qWDouloZlQbRhDPCebwxSjxMjxgemYT46YMd2NR0/H+fBhEfWX4A/w9TBJ+ol+okqJKFE6vQ== + +is-subset@^0.1.1: + version "0.1.1" + resolved "https://registry.yarnpkg.com/is-subset/-/is-subset-0.1.1.tgz#8a59117d932de1de00f245fcdd39ce43f1e939a6" + integrity sha1-ilkRfZMt4d4A8kX83TnOQ/HpOaY= + +is-symbol@^1.0.2, is-symbol@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/is-symbol/-/is-symbol-1.0.3.tgz#38e1014b9e6329be0de9d24a414fd7441ec61937" + integrity sha512-OwijhaRSgqvhm/0ZdAcXNZt9lYdKFpcRDT5ULUuYXPoT794UNOdU+gpT6Rzo7b4V2HUl/op6GqY894AZwv9faQ== + dependencies: + has-symbols "^1.0.1" + +is-typedarray@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a" + integrity sha1-5HnICFjfDBsR3dppQPlgEfzaSpo= + +is-utf8@^0.2.0: + version "0.2.1" + resolved "https://registry.yarnpkg.com/is-utf8/-/is-utf8-0.2.1.tgz#4b0da1442104d1b336340e80797e865cf39f7d72" + integrity sha1-Sw2hRCEE0bM2NA6AeX6GXPOffXI= + +is-weakmap@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/is-weakmap/-/is-weakmap-2.0.1.tgz#5008b59bdc43b698201d18f62b37b2ca243e8cf2" + integrity sha512-NSBR4kH5oVj1Uwvv970ruUkCV7O1mzgVFO4/rev2cLRda9Tm9HrL70ZPut4rOHgY0FNrUu9BCbXA2sdQ+x0chA== + +is-weakset@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/is-weakset/-/is-weakset-2.0.1.tgz#e9a0af88dbd751589f5e50d80f4c98b780884f83" + integrity sha512-pi4vhbhVHGLxohUw7PhGsueT4vRGFoXhP7+RGN0jKIv9+8PWYCQTqtADngrxOm2g46hoH0+g8uZZBzMrvVGDmw== + +is-wsl@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/is-wsl/-/is-wsl-1.1.0.tgz#1f16e4aa22b04d1336b66188a66af3c600c3a66d" + integrity sha1-HxbkqiKwTRM2tmGIpmrzxgDDpm0= + +isarray@1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" + integrity sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE= + +isarray@^2.0.5: + version "2.0.5" + resolved "https://registry.yarnpkg.com/isarray/-/isarray-2.0.5.tgz#8af1e4c1221244cc62459faf38940d4e644a5723" + integrity sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw== + +isexe@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" + integrity sha1-6PvzdNxVb/iUehDcsFctYz8s+hA= + +isobject@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/isobject/-/isobject-2.1.0.tgz#f065561096a3f1da2ef46272f815c840d87e0c89" + integrity sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk= + dependencies: + isarray "1.0.0" + +isstream@~0.1.2: + version "0.1.2" + resolved "https://registry.yarnpkg.com/isstream/-/isstream-0.1.2.tgz#47e63f7af55afa6f92e1500e690eb8b8529c099a" + integrity sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo= + +istanbul-api@^1.1.1: + version "1.3.7" + resolved "https://registry.yarnpkg.com/istanbul-api/-/istanbul-api-1.3.7.tgz#a86c770d2b03e11e3f778cd7aedd82d2722092aa" + integrity sha512-4/ApBnMVeEPG3EkSzcw25wDe4N66wxwn+KKn6b47vyek8Xb3NBAcg4xfuQbS7BqcZuTX4wxfD5lVagdggR3gyA== + dependencies: + async "^2.1.4" + fileset "^2.0.2" + istanbul-lib-coverage "^1.2.1" + istanbul-lib-hook "^1.2.2" + istanbul-lib-instrument "^1.10.2" + istanbul-lib-report "^1.1.5" + istanbul-lib-source-maps "^1.2.6" + istanbul-reports "^1.5.1" + js-yaml "^3.7.0" + mkdirp "^0.5.1" + once "^1.4.0" + +istanbul-lib-coverage@^1.0.1, istanbul-lib-coverage@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/istanbul-lib-coverage/-/istanbul-lib-coverage-1.2.1.tgz#ccf7edcd0a0bb9b8f729feeb0930470f9af664f0" + integrity sha512-PzITeunAgyGbtY1ibVIUiV679EFChHjoMNRibEIobvmrCRaIgwLxNucOSimtNWUhEib/oO7QY2imD75JVgCJWQ== + +istanbul-lib-hook@^1.2.2: + version "1.2.2" + resolved "https://registry.yarnpkg.com/istanbul-lib-hook/-/istanbul-lib-hook-1.2.2.tgz#bc6bf07f12a641fbf1c85391d0daa8f0aea6bf86" + integrity sha512-/Jmq7Y1VeHnZEQ3TL10VHyb564mn6VrQXHchON9Jf/AEcmQ3ZIiyD1BVzNOKTZf/G3gE+kiGK6SmpF9y3qGPLw== + dependencies: + append-transform "^0.4.0" + +istanbul-lib-instrument@^1.10.1, istanbul-lib-instrument@^1.10.2, istanbul-lib-instrument@^1.4.2: + version "1.10.2" + resolved "https://registry.yarnpkg.com/istanbul-lib-instrument/-/istanbul-lib-instrument-1.10.2.tgz#1f55ed10ac3c47f2bdddd5307935126754d0a9ca" + integrity sha512-aWHxfxDqvh/ZlxR8BBaEPVSWDPUkGD63VjGQn3jcw8jCp7sHEMKcrj4xfJn/ABzdMEHiQNyvDQhqm5o8+SQg7A== + dependencies: + babel-generator "^6.18.0" + babel-template "^6.16.0" + babel-traverse "^6.18.0" + babel-types "^6.18.0" + babylon "^6.18.0" + istanbul-lib-coverage "^1.2.1" + semver "^5.3.0" + +istanbul-lib-report@^1.1.5: + version "1.1.5" + resolved "https://registry.yarnpkg.com/istanbul-lib-report/-/istanbul-lib-report-1.1.5.tgz#f2a657fc6282f96170aaf281eb30a458f7f4170c" + integrity sha512-UsYfRMoi6QO/doUshYNqcKJqVmFe9w51GZz8BS3WB0lYxAllQYklka2wP9+dGZeHYaWIdcXUx8JGdbqaoXRXzw== + dependencies: + istanbul-lib-coverage "^1.2.1" + mkdirp "^0.5.1" + path-parse "^1.0.5" + supports-color "^3.1.2" + +istanbul-lib-source-maps@^1.1.0, istanbul-lib-source-maps@^1.2.6: + version "1.2.6" + resolved "https://registry.yarnpkg.com/istanbul-lib-source-maps/-/istanbul-lib-source-maps-1.2.6.tgz#37b9ff661580f8fca11232752ee42e08c6675d8f" + integrity sha512-TtbsY5GIHgbMsMiRw35YBHGpZ1DVFEO19vxxeiDMYaeOFOCzfnYVxvl6pOUIZR4dtPhAGpSMup8OyF8ubsaqEg== + dependencies: + debug "^3.1.0" + istanbul-lib-coverage "^1.2.1" + mkdirp "^0.5.1" + rimraf "^2.6.1" + source-map "^0.5.3" + +istanbul-reports@^1.5.1: + version "1.5.1" + resolved "https://registry.yarnpkg.com/istanbul-reports/-/istanbul-reports-1.5.1.tgz#97e4dbf3b515e8c484caea15d6524eebd3ff4e1a" + integrity sha512-+cfoZ0UXzWjhAdzosCPP3AN8vvef8XDkWtTfgaN+7L3YTpNYITnCaEkceo5SEYy644VkHka/P1FvkWvrG/rrJw== + dependencies: + handlebars "^4.0.3" + +jest-changed-files@^20.0.3: + version "20.0.3" + resolved "https://registry.yarnpkg.com/jest-changed-files/-/jest-changed-files-20.0.3.tgz#9394d5cc65c438406149bef1bf4d52b68e03e3f8" + integrity sha1-k5TVzGXEOEBhSb7xv01Sto4D4/g= + +jest-cli@^20.0.4: + version "20.0.4" + resolved "https://registry.yarnpkg.com/jest-cli/-/jest-cli-20.0.4.tgz#e532b19d88ae5bc6c417e8b0593a6fe954b1dc93" + integrity sha1-5TKxnYiuW8bEF+iwWTpv6VSx3JM= + dependencies: + ansi-escapes "^1.4.0" + callsites "^2.0.0" + chalk "^1.1.3" + graceful-fs "^4.1.11" + is-ci "^1.0.10" + istanbul-api "^1.1.1" + istanbul-lib-coverage "^1.0.1" + istanbul-lib-instrument "^1.4.2" + istanbul-lib-source-maps "^1.1.0" + jest-changed-files "^20.0.3" + jest-config "^20.0.4" + jest-docblock "^20.0.3" + jest-environment-jsdom "^20.0.3" + jest-haste-map "^20.0.4" + jest-jasmine2 "^20.0.4" + jest-message-util "^20.0.3" + jest-regex-util "^20.0.3" + jest-resolve-dependencies "^20.0.3" + jest-runtime "^20.0.4" + jest-snapshot "^20.0.3" + jest-util "^20.0.3" + micromatch "^2.3.11" + node-notifier "^5.0.2" + pify "^2.3.0" + slash "^1.0.0" + string-length "^1.0.1" + throat "^3.0.0" + which "^1.2.12" + worker-farm "^1.3.1" + yargs "^7.0.2" + +jest-config@^20.0.4: + version "20.0.4" + resolved "https://registry.yarnpkg.com/jest-config/-/jest-config-20.0.4.tgz#e37930ab2217c913605eff13e7bd763ec48faeea" + integrity sha1-43kwqyIXyRNgXv8T5712PsSPruo= + dependencies: + chalk "^1.1.3" + glob "^7.1.1" + jest-environment-jsdom "^20.0.3" + jest-environment-node "^20.0.3" + jest-jasmine2 "^20.0.4" + jest-matcher-utils "^20.0.3" + jest-regex-util "^20.0.3" + jest-resolve "^20.0.4" + jest-validate "^20.0.3" + pretty-format "^20.0.3" + +jest-diff@^20.0.3: + version "20.0.3" + resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-20.0.3.tgz#81f288fd9e675f0fb23c75f1c2b19445fe586617" + integrity sha1-gfKI/Z5nXw+yPHXxwrGURf5YZhc= + dependencies: + chalk "^1.1.3" + diff "^3.2.0" + jest-matcher-utils "^20.0.3" + pretty-format "^20.0.3" + +jest-docblock@^20.0.3: + version "20.0.3" + resolved "https://registry.yarnpkg.com/jest-docblock/-/jest-docblock-20.0.3.tgz#17bea984342cc33d83c50fbe1545ea0efaa44712" + integrity sha1-F76phDQswz2DxQ++FUXqDvqkRxI= + +jest-environment-jsdom@^20.0.3: + version "20.0.3" + resolved "https://registry.yarnpkg.com/jest-environment-jsdom/-/jest-environment-jsdom-20.0.3.tgz#048a8ac12ee225f7190417713834bb999787de99" + integrity sha1-BIqKwS7iJfcZBBdxODS7mZeH3pk= + dependencies: + jest-mock "^20.0.3" + jest-util "^20.0.3" + jsdom "^9.12.0" + +jest-environment-node@^20.0.3: + version "20.0.3" + resolved "https://registry.yarnpkg.com/jest-environment-node/-/jest-environment-node-20.0.3.tgz#d488bc4612af2c246e986e8ae7671a099163d403" + integrity sha1-1Ii8RhKvLCRumG6K52caCZFj1AM= + dependencies: + jest-mock "^20.0.3" + jest-util "^20.0.3" + +jest-haste-map@^20.0.4: + version "20.0.5" + resolved "https://registry.yarnpkg.com/jest-haste-map/-/jest-haste-map-20.0.5.tgz#abad74efb1a005974a7b6517e11010709cab9112" + integrity sha512-0IKAQjUvuZjMCNi/0VNQQF74/H9KB67hsHJqGiwTWQC6XO5Azs7kLWm+6Q/dwuhvDUvABDOBMFK2/FwZ3sZ07Q== + dependencies: + fb-watchman "^2.0.0" + graceful-fs "^4.1.11" + jest-docblock "^20.0.3" + micromatch "^2.3.11" + sane "~1.6.0" + worker-farm "^1.3.1" + +jest-jasmine2@^20.0.4: + version "20.0.4" + resolved "https://registry.yarnpkg.com/jest-jasmine2/-/jest-jasmine2-20.0.4.tgz#fcc5b1411780d911d042902ef1859e852e60d5e1" + integrity sha1-/MWxQReA2RHQQpAu8YWehS5g1eE= + dependencies: + chalk "^1.1.3" + graceful-fs "^4.1.11" + jest-diff "^20.0.3" + jest-matcher-utils "^20.0.3" + jest-matchers "^20.0.3" + jest-message-util "^20.0.3" + jest-snapshot "^20.0.3" + once "^1.4.0" + p-map "^1.1.1" + +jest-matcher-utils@^20.0.3: + version "20.0.3" + resolved "https://registry.yarnpkg.com/jest-matcher-utils/-/jest-matcher-utils-20.0.3.tgz#b3a6b8e37ca577803b0832a98b164f44b7815612" + integrity sha1-s6a443yld4A7CDKpixZPRLeBVhI= + dependencies: + chalk "^1.1.3" + pretty-format "^20.0.3" + +jest-matchers@^20.0.3: + version "20.0.3" + resolved "https://registry.yarnpkg.com/jest-matchers/-/jest-matchers-20.0.3.tgz#ca69db1c32db5a6f707fa5e0401abb55700dfd60" + integrity sha1-ymnbHDLbWm9wf6XgQBq7VXAN/WA= + dependencies: + jest-diff "^20.0.3" + jest-matcher-utils "^20.0.3" + jest-message-util "^20.0.3" + jest-regex-util "^20.0.3" + +jest-message-util@^20.0.3: + version "20.0.3" + resolved "https://registry.yarnpkg.com/jest-message-util/-/jest-message-util-20.0.3.tgz#6aec2844306fcb0e6e74d5796c1006d96fdd831c" + integrity sha1-auwoRDBvyw5udNV5bBAG2W/dgxw= + dependencies: + chalk "^1.1.3" + micromatch "^2.3.11" + slash "^1.0.0" + +jest-mock@^20.0.3: + version "20.0.3" + resolved "https://registry.yarnpkg.com/jest-mock/-/jest-mock-20.0.3.tgz#8bc070e90414aa155c11a8d64c869a0d5c71da59" + integrity sha1-i8Bw6QQUqhVcEajWTIaaDVxx2lk= + +jest-regex-util@^20.0.3: + version "20.0.3" + resolved "https://registry.yarnpkg.com/jest-regex-util/-/jest-regex-util-20.0.3.tgz#85bbab5d133e44625b19faf8c6aa5122d085d762" + integrity sha1-hburXRM+RGJbGfr4xqpRItCF12I= + +jest-resolve-dependencies@^20.0.3: + version "20.0.3" + resolved "https://registry.yarnpkg.com/jest-resolve-dependencies/-/jest-resolve-dependencies-20.0.3.tgz#6e14a7b717af0f2cb3667c549de40af017b1723a" + integrity sha1-bhSntxevDyyzZnxUneQK8Bexcjo= + dependencies: + jest-regex-util "^20.0.3" + +jest-resolve@^20.0.4: + version "20.0.4" + resolved "https://registry.yarnpkg.com/jest-resolve/-/jest-resolve-20.0.4.tgz#9448b3e8b6bafc15479444c6499045b7ffe597a5" + integrity sha1-lEiz6La6/BVHlETGSZBFt//ll6U= + dependencies: + browser-resolve "^1.11.2" + is-builtin-module "^1.0.0" + resolve "^1.3.2" + +jest-runtime@^20.0.4: + version "20.0.4" + resolved "https://registry.yarnpkg.com/jest-runtime/-/jest-runtime-20.0.4.tgz#a2c802219c4203f754df1404e490186169d124d8" + integrity sha1-osgCIZxCA/dU3xQE5JAYYWnRJNg= + dependencies: + babel-core "^6.0.0" + babel-jest "^20.0.3" + babel-plugin-istanbul "^4.0.0" + chalk "^1.1.3" + convert-source-map "^1.4.0" + graceful-fs "^4.1.11" + jest-config "^20.0.4" + jest-haste-map "^20.0.4" + jest-regex-util "^20.0.3" + jest-resolve "^20.0.4" + jest-util "^20.0.3" + json-stable-stringify "^1.0.1" + micromatch "^2.3.11" + strip-bom "3.0.0" + yargs "^7.0.2" + +jest-snapshot@^20.0.3: + version "20.0.3" + resolved "https://registry.yarnpkg.com/jest-snapshot/-/jest-snapshot-20.0.3.tgz#5b847e1adb1a4d90852a7f9f125086e187c76566" + integrity sha1-W4R+GtsaTZCFKn+fElCG4YfHZWY= + dependencies: + chalk "^1.1.3" + jest-diff "^20.0.3" + jest-matcher-utils "^20.0.3" + jest-util "^20.0.3" + natural-compare "^1.4.0" + pretty-format "^20.0.3" + +jest-util@^20.0.3: + version "20.0.3" + resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-20.0.3.tgz#0c07f7d80d82f4e5a67c6f8b9c3fe7f65cfd32ad" + integrity sha1-DAf32A2C9OWmfG+LnD/n9lz9Mq0= + dependencies: + chalk "^1.1.3" + graceful-fs "^4.1.11" + jest-message-util "^20.0.3" + jest-mock "^20.0.3" + jest-validate "^20.0.3" + leven "^2.1.0" + mkdirp "^0.5.1" + +jest-validate@^20.0.3: + version "20.0.3" + resolved "https://registry.yarnpkg.com/jest-validate/-/jest-validate-20.0.3.tgz#d0cfd1de4f579f298484925c280f8f1d94ec3cab" + integrity sha1-0M/R3k9XnymEhJJcKA+PHZTsPKs= + dependencies: + chalk "^1.1.3" + jest-matcher-utils "^20.0.3" + leven "^2.1.0" + pretty-format "^20.0.3" + +jest@^20.0.4: + version "20.0.4" + resolved "https://registry.yarnpkg.com/jest/-/jest-20.0.4.tgz#3dd260c2989d6dad678b1e9cc4d91944f6d602ac" + integrity sha1-PdJgwpidba1nix6cxNkZRPbWAqw= + dependencies: + jest-cli "^20.0.4" + +"js-tokens@^3.0.0 || ^4.0.0", js-tokens@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" + integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== + +js-tokens@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-3.0.2.tgz#9866df395102130e38f7f996bceb65443209c25b" + integrity sha1-mGbfOVECEw449/mWvOtlRDIJwls= + +js-yaml@^3.7.0: + version "3.13.1" + resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.13.1.tgz#aff151b30bfdfa8e49e05da22e7415e9dfa37847" + integrity sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw== + dependencies: + argparse "^1.0.7" + esprima "^4.0.0" + +jsbn@~0.1.0: + version "0.1.1" + resolved "https://registry.yarnpkg.com/jsbn/-/jsbn-0.1.1.tgz#a5e654c2e5a2deb5f201d96cefbca80c0ef2f513" + integrity sha1-peZUwuWi3rXyAdls77yoDA7y9RM= + +jsdom-global@^2.0.0: + version "2.1.1" + resolved "https://registry.yarnpkg.com/jsdom-global/-/jsdom-global-2.1.1.tgz#47d46fe77f6167baf5d34431d3bb59fc41b0915a" + integrity sha1-R9Rv539hZ7r100Qx07tZ/EGwkVo= + +jsdom@^9.12.0, jsdom@^9.4.1: + version "9.12.0" + resolved "https://registry.yarnpkg.com/jsdom/-/jsdom-9.12.0.tgz#e8c546fffcb06c00d4833ca84410fed7f8a097d4" + integrity sha1-6MVG//ywbADUgzyoRBD+1/igl9Q= + dependencies: + abab "^1.0.3" + acorn "^4.0.4" + acorn-globals "^3.1.0" + array-equal "^1.0.0" + content-type-parser "^1.0.1" + cssom ">= 0.3.2 < 0.4.0" + cssstyle ">= 0.2.37 < 0.3.0" + escodegen "^1.6.1" + html-encoding-sniffer "^1.0.1" + nwmatcher ">= 1.3.9 < 2.0.0" + parse5 "^1.5.1" + request "^2.79.0" + sax "^1.2.1" + symbol-tree "^3.2.1" + tough-cookie "^2.3.2" + webidl-conversions "^4.0.0" + whatwg-encoding "^1.0.1" + whatwg-url "^4.3.0" + xml-name-validator "^2.0.1" + +jsesc@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-1.3.0.tgz#46c3fec8c1892b12b0833db9bc7622176dbab34b" + integrity sha1-RsP+yMGJKxKwgz25vHYiF226s0s= + +jsesc@^2.5.1: + version "2.5.2" + resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-2.5.2.tgz#80564d2e483dacf6e8ef209650a67df3f0c283a4" + integrity sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA== + +json-schema-traverse@^0.4.1: + version "0.4.1" + resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660" + integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg== + +json-schema@0.2.3: + version "0.2.3" + resolved "https://registry.yarnpkg.com/json-schema/-/json-schema-0.2.3.tgz#b480c892e59a2f05954ce727bd3f2a4e882f9e13" + integrity sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM= + +json-stable-stringify@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/json-stable-stringify/-/json-stable-stringify-1.0.1.tgz#9a759d39c5f2ff503fd5300646ed445f88c4f9af" + integrity sha1-mnWdOcXy/1A/1TAGRu1EX4jE+a8= + dependencies: + jsonify "~0.0.0" + +json-stringify-safe@~5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz#1296a2d58fd45f19a0f6ce01d65701e2c735b6eb" + integrity sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus= + +json5@^0.5.1: + version "0.5.1" + resolved "https://registry.yarnpkg.com/json5/-/json5-0.5.1.tgz#1eade7acc012034ad84e2396767ead9fa5495821" + integrity sha1-Hq3nrMASA0rYTiOWdn6tn6VJWCE= + +json5@^2.1.0: + version "2.1.2" + resolved "https://registry.yarnpkg.com/json5/-/json5-2.1.2.tgz#43ef1f0af9835dd624751a6b7fa48874fb2d608e" + integrity sha512-MoUOQ4WdiN3yxhm7NEVJSJrieAo5hNSLQ5sj05OTRHPL9HOBy8u4Bu88jsC1jvqAdN+E1bJmsUcZH+1HQxliqQ== + dependencies: + minimist "^1.2.5" + +jsonify@~0.0.0: + version "0.0.0" + resolved "https://registry.yarnpkg.com/jsonify/-/jsonify-0.0.0.tgz#2c74b6ee41d93ca51b7b5aaee8f503631d252a73" + integrity sha1-LHS27kHZPKUbe1qu6PUDYx0lKnM= + +jsprim@^1.2.2: + version "1.4.1" + resolved "https://registry.yarnpkg.com/jsprim/-/jsprim-1.4.1.tgz#313e66bc1e5cc06e438bc1b7499c2e5c56acb6a2" + integrity sha1-MT5mvB5cwG5Di8G3SZwuXFastqI= + dependencies: + assert-plus "1.0.0" + extsprintf "1.3.0" + json-schema "0.2.3" + verror "1.10.0" + +kind-of@^3.0.2: + version "3.2.2" + resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-3.2.2.tgz#31ea21a734bab9bbb0f32466d893aea51e4a3c64" + integrity sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ= + dependencies: + is-buffer "^1.1.5" + +kind-of@^6.0.0: + version "6.0.3" + resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-6.0.3.tgz#07c05034a6c349fa06e24fa35aa76db4580ce4dd" + integrity sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw== + +lcid@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/lcid/-/lcid-1.0.0.tgz#308accafa0bc483a3867b4b6f2b9506251d1b835" + integrity sha1-MIrMr6C8SDo4Z7S28rlQYlHRuDU= + dependencies: + invert-kv "^1.0.0" + +leven@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/leven/-/leven-2.1.0.tgz#c2e7a9f772094dee9d34202ae8acce4687875580" + integrity sha1-wuep93IJTe6dNCAq6KzORoeHVYA= + +levn@~0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/levn/-/levn-0.3.0.tgz#3b09924edf9f083c0490fdd4c0bc4421e04764ee" + integrity sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4= + dependencies: + prelude-ls "~1.1.2" + type-check "~0.3.2" + +load-json-file@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/load-json-file/-/load-json-file-1.1.0.tgz#956905708d58b4bab4c2261b04f59f31c99374c0" + integrity sha1-lWkFcI1YtLq0wiYbBPWfMcmTdMA= + dependencies: + graceful-fs "^4.1.2" + parse-json "^2.2.0" + pify "^2.0.0" + pinkie-promise "^2.0.0" + strip-bom "^2.0.0" + +locate-path@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-2.0.0.tgz#2b568b265eec944c6d9c0de9c3dbbbca0354cd8e" + integrity sha1-K1aLJl7slExtnA3pw9u7ygNUzY4= + dependencies: + p-locate "^2.0.0" + path-exists "^3.0.0" + +locate-path@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-3.0.0.tgz#dbec3b3ab759758071b58fe59fc41871af21400e" + integrity sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A== + dependencies: + p-locate "^3.0.0" + path-exists "^3.0.0" + +lodash.escape@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/lodash.escape/-/lodash.escape-4.0.1.tgz#c9044690c21e04294beaa517712fded1fa88de98" + integrity sha1-yQRGkMIeBClL6qUXcS/e0fqI3pg= + +lodash.flattendeep@^4.4.0: + version "4.4.0" + resolved "https://registry.yarnpkg.com/lodash.flattendeep/-/lodash.flattendeep-4.4.0.tgz#fb030917f86a3134e5bc9bec0d69e0013ddfedb2" + integrity sha1-+wMJF/hqMTTlvJvsDWngAT3f7bI= + +lodash.isequal@^4.5.0: + version "4.5.0" + resolved "https://registry.yarnpkg.com/lodash.isequal/-/lodash.isequal-4.5.0.tgz#415c4478f2bcc30120c22ce10ed3226f7d3e18e0" + integrity sha1-QVxEePK8wwEgwizhDtMib30+GOA= + +lodash@^4.15.0, lodash@^4.17.13, lodash@^4.17.14, lodash@^4.17.4: + version "4.17.15" + resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.15.tgz#b447f6670a0455bbfeedd11392eff330ea097548" + integrity sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A== + +lolex@1.3.2: + version "1.3.2" + resolved "https://registry.yarnpkg.com/lolex/-/lolex-1.3.2.tgz#7c3da62ffcb30f0f5a80a2566ca24e45d8a01f31" + integrity sha1-fD2mL/yzDw9agKJWbKJORdigHzE= + +loose-envify@^1.0.0, loose-envify@^1.1.0, loose-envify@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf" + integrity sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q== + dependencies: + js-tokens "^3.0.0 || ^4.0.0" + +make-dir@^2.0.0, make-dir@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-2.1.0.tgz#5f0310e18b8be898cc07009295a30ae41e91e6f5" + integrity sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA== + dependencies: + pify "^4.0.1" + semver "^5.6.0" + +makeerror@1.0.x: + version "1.0.11" + resolved "https://registry.yarnpkg.com/makeerror/-/makeerror-1.0.11.tgz#e01a5c9109f2af79660e4e8b9587790184f5a96c" + integrity sha1-4BpckQnyr3lmDk6LlYd5AYT1qWw= + dependencies: + tmpl "1.0.x" + +math-random@^1.0.1: + version "1.0.4" + resolved "https://registry.yarnpkg.com/math-random/-/math-random-1.0.4.tgz#5dd6943c938548267016d4e34f057583080c514c" + integrity sha512-rUxjysqif/BZQH2yhd5Aaq7vXMSx9NdEsQcyA07uEzIvxgI7zIr33gGsh+RU0/XjmQpCW7RsVof1vlkvQVCK5A== + +merge@^1.2.0: + version "1.2.1" + resolved "https://registry.yarnpkg.com/merge/-/merge-1.2.1.tgz#38bebf80c3220a8a487b6fcfb3941bb11720c145" + integrity sha512-VjFo4P5Whtj4vsLzsYBu5ayHhoHJ0UqNm7ibvShmbmoz7tGi0vXaoJbGdB+GmDMLUdg8DpQXEIeVDAe8MaABvQ== + +micromatch@^2.1.5, micromatch@^2.3.11: + version "2.3.11" + resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-2.3.11.tgz#86677c97d1720b363431d04d0d15293bd38c1565" + integrity sha1-hmd8l9FyCzY0MdBNDRUpO9OMFWU= + dependencies: + arr-diff "^2.0.0" + array-unique "^0.2.1" + braces "^1.8.2" + expand-brackets "^0.1.4" + extglob "^0.3.1" + filename-regex "^2.0.0" + is-extglob "^1.0.0" + is-glob "^2.0.1" + kind-of "^3.0.2" + normalize-path "^2.0.1" + object.omit "^2.0.0" + parse-glob "^3.0.4" + regex-cache "^0.4.2" + +mime-db@1.43.0: + version "1.43.0" + resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.43.0.tgz#0a12e0502650e473d735535050e7c8f4eb4fae58" + integrity sha512-+5dsGEEovYbT8UY9yD7eE4XTc4UwJ1jBYlgaQQF38ENsKR3wj/8q8RFZrF9WIZpB2V1ArTVFUva8sAul1NzRzQ== + +mime-types@^2.1.12, mime-types@~2.1.19: + version "2.1.26" + resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.26.tgz#9c921fc09b7e149a65dfdc0da4d20997200b0a06" + integrity sha512-01paPWYgLrkqAyrlDorC1uDwl2p3qZT7yl806vW7DvDoxwXi46jsjFbg+WdwotBIk6/MbEhO/dh5aZ5sNj/dWQ== + dependencies: + mime-db "1.43.0" + +min-document@^2.19.0: + version "2.19.0" + resolved "https://registry.yarnpkg.com/min-document/-/min-document-2.19.0.tgz#7bd282e3f5842ed295bb748cdd9f1ffa2c824685" + integrity sha1-e9KC4/WELtKVu3SM3Z8f+iyCRoU= + dependencies: + dom-walk "^0.1.0" + +minimatch@3.0.4, minimatch@^3.0.2, minimatch@^3.0.3, minimatch@^3.0.4: + version "3.0.4" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083" + integrity sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA== + dependencies: + brace-expansion "^1.1.7" + +minimist@0.0.8: + version "0.0.8" + resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.8.tgz#857fcabfc3397d2625b8228262e86aa7a011b05d" + integrity sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0= + +minimist@^1.1.1, minimist@^1.2.5: + version "1.2.5" + resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.5.tgz#67d66014b66a6a8aaa0c083c5fd58df4e4e97602" + integrity sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw== + +minimist@~0.0.1: + version "0.0.10" + resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.10.tgz#de3f98543dbf96082be48ad1a0c7cda836301dcf" + integrity sha1-3j+YVD2/lggr5IrRoMfNqDYwHc8= + +mkdirp@0.5.1: + version "0.5.1" + resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.1.tgz#30057438eac6cf7f8c4767f38648d6697d75c903" + integrity sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM= + dependencies: + minimist "0.0.8" + +mkdirp@^0.5.1: + version "0.5.3" + resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.3.tgz#5a514b7179259287952881e94410ec5465659f8c" + integrity sha512-P+2gwrFqx8lhew375MQHHeTlY8AuOJSrGf0R5ddkEndUkmwpgUob/vQuBD1V22/Cw1/lJr4x+EjllSezBThzBg== + dependencies: + minimist "^1.2.5" + +mocha@^5.0.1: + version "5.2.0" + resolved "https://registry.yarnpkg.com/mocha/-/mocha-5.2.0.tgz#6d8ae508f59167f940f2b5b3c4a612ae50c90ae6" + integrity sha512-2IUgKDhc3J7Uug+FxMXuqIyYzH7gJjXECKe/w43IGgQHTSj3InJi+yAA7T24L9bQMRKiUEHxEX37G5JpVUGLcQ== + dependencies: + browser-stdout "1.3.1" + commander "2.15.1" + debug "3.1.0" + diff "3.5.0" + escape-string-regexp "1.0.5" + glob "7.1.2" + growl "1.10.5" + he "1.1.1" + minimatch "3.0.4" + mkdirp "0.5.1" + supports-color "5.4.0" + +mock-local-storage@^1.0.5: + version "1.1.11" + resolved "https://registry.yarnpkg.com/mock-local-storage/-/mock-local-storage-1.1.11.tgz#2a36faeb30f76ef3c5005460b6bbf12f19555811" + integrity sha512-71sytP93tB0CkPbacafcP1iTVj9ssXU+ztRmx1MrS488JpO0az5d2sx+SNuvhGVSW56XO5M0f82uFhxteZEp9w== + dependencies: + core-js "^0.8.3" + global "^4.3.2" + +moo@^0.5.0: + version "0.5.1" + resolved "https://registry.yarnpkg.com/moo/-/moo-0.5.1.tgz#7aae7f384b9b09f620b6abf6f74ebbcd1b65dbc4" + integrity sha512-I1mnb5xn4fO80BH9BLcF0yLypy2UKl+Cb01Fu0hJRkJjlCRtxZMWkTdAtDd5ZqCOxtCkhmRwyI57vWT+1iZ67w== + +ms@2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" + integrity sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g= + +ms@^2.1.1: + version "2.1.2" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" + integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== + +natural-compare@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7" + integrity sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc= + +nearley@^2.7.10: + version "2.19.1" + resolved "https://registry.yarnpkg.com/nearley/-/nearley-2.19.1.tgz#4af4006e16645ff800e9f993c3af039857d9dbdc" + integrity sha512-xq47GIUGXxU9vQg7g/y1o1xuKnkO7ev4nRWqftmQrLkfnE/FjRqDaGOUakM8XHPn/6pW3bGjU2wgoJyId90rqg== + dependencies: + commander "^2.19.0" + moo "^0.5.0" + railroad-diagrams "^1.0.0" + randexp "0.4.6" + semver "^5.4.1" + +neo-async@^2.6.0: + version "2.6.1" + resolved "https://registry.yarnpkg.com/neo-async/-/neo-async-2.6.1.tgz#ac27ada66167fa8849a6addd837f6b189ad2081c" + integrity sha512-iyam8fBuCUpWeKPGpaNMetEocMt364qkCsfL9JuhjXX6dRnguRVOfk2GZaDpPjcOKiiXCPINZC1GczQ7iTq3Zw== + +node-int64@^0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/node-int64/-/node-int64-0.4.0.tgz#87a9065cdb355d3182d8f94ce11188b825c68a3b" + integrity sha1-h6kGXNs1XTGC2PlM4RGIuCXGijs= + +node-modules-regexp@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/node-modules-regexp/-/node-modules-regexp-1.0.0.tgz#8d9dbe28964a4ac5712e9131642107c71e90ec40" + integrity sha1-jZ2+KJZKSsVxLpExZCEHxx6Q7EA= + +node-notifier@^5.0.2: + version "5.4.3" + resolved "https://registry.yarnpkg.com/node-notifier/-/node-notifier-5.4.3.tgz#cb72daf94c93904098e28b9c590fd866e464bd50" + integrity sha512-M4UBGcs4jeOK9CjTsYwkvH6/MzuUmGCyTW+kCY7uO+1ZVr0+FHGdPdIf5CCLqAaxnRrWidyoQlNkMIIVwbKB8Q== + dependencies: + growly "^1.3.0" + is-wsl "^1.1.0" + semver "^5.5.0" + shellwords "^0.1.1" + which "^1.3.0" + +normalize-package-data@^2.3.2: + version "2.5.0" + resolved "https://registry.yarnpkg.com/normalize-package-data/-/normalize-package-data-2.5.0.tgz#e66db1838b200c1dfc233225d12cb36520e234a8" + integrity sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA== + dependencies: + hosted-git-info "^2.1.4" + resolve "^1.10.0" + semver "2 || 3 || 4 || 5" + validate-npm-package-license "^3.0.1" + +normalize-path@^2.0.0, normalize-path@^2.0.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-2.1.1.tgz#1ab28b556e198363a8c1a6f7e6fa20137fe6aed9" + integrity sha1-GrKLVW4Zg2Oowab35vogE3/mrtk= + dependencies: + remove-trailing-separator "^1.0.1" + +nth-check@~1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/nth-check/-/nth-check-1.0.2.tgz#b2bd295c37e3dd58a3bf0700376663ba4d9cf05c" + integrity sha512-WeBOdju8SnzPN5vTUJYxYUxLeXpCaVP5i5e0LF8fg7WORF2Wd7wFX/pk0tYZk7s8T+J7VLy0Da6J1+wCT0AtHg== + dependencies: + boolbase "~1.0.0" + +number-is-nan@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/number-is-nan/-/number-is-nan-1.0.1.tgz#097b602b53422a522c1afb8790318336941a011d" + integrity sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0= + +"nwmatcher@>= 1.3.9 < 2.0.0": + version "1.4.4" + resolved "https://registry.yarnpkg.com/nwmatcher/-/nwmatcher-1.4.4.tgz#2285631f34a95f0d0395cd900c96ed39b58f346e" + integrity sha512-3iuY4N5dhgMpCUrOVnuAdGrgxVqV2cJpM+XNccjR2DKOB1RUP0aA+wGXEiNziG/UKboFyGBIoKOaNlJxx8bciQ== + +oauth-sign@~0.9.0: + version "0.9.0" + resolved "https://registry.yarnpkg.com/oauth-sign/-/oauth-sign-0.9.0.tgz#47a7b016baa68b5fa0ecf3dee08a85c679ac6455" + integrity sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ== + +object-assign@^4.1.0, object-assign@^4.1.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" + integrity sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM= + +object-inspect@^1.1.0, object-inspect@^1.7.0: + version "1.7.0" + resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.7.0.tgz#f4f6bd181ad77f006b5ece60bd0b6f398ff74a67" + integrity sha512-a7pEHdh1xKIAgTySUGgLMx/xwDZskN1Ud6egYYN3EdRW4ZMPNEDUTF+hwy2LUC+Bl+SyLXANnwz/jyh/qutKUw== + +object-is@^1.0.1, object-is@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/object-is/-/object-is-1.0.2.tgz#6b80eb84fe451498f65007982f035a5b445edec4" + integrity sha512-Epah+btZd5wrrfjkJZq1AOB9O6OxUQto45hzFd7lXGrpHPGE0W1k+426yrZV+k6NJOzLNNW/nVsmZdIWsAqoOQ== + +object-keys@^1.0.11, object-keys@^1.0.12, object-keys@^1.0.9, object-keys@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.1.1.tgz#1c47f272df277f3b1daf061677d9c82e2322c60e" + integrity sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA== + +object.assign@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/object.assign/-/object.assign-4.1.0.tgz#968bf1100d7956bb3ca086f006f846b3bc4008da" + integrity sha512-exHJeq6kBKj58mqGyTQ9DFvrZC/eR6OwxzoM9YRoGBqrXYonaFyGiFMuc9VZrXf7DarreEwMpurG3dd+CNyW5w== + dependencies: + define-properties "^1.1.2" + function-bind "^1.1.1" + has-symbols "^1.0.0" + object-keys "^1.0.11" + +object.entries@^1.1.0, object.entries@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/object.entries/-/object.entries-1.1.1.tgz#ee1cf04153de02bb093fec33683900f57ce5399b" + integrity sha512-ilqR7BgdyZetJutmDPfXCDffGa0/Yzl2ivVNpbx/g4UeWrCdRnFDUBrKJGLhGieRHDATnyZXWBeCb29k9CJysQ== + dependencies: + define-properties "^1.1.3" + es-abstract "^1.17.0-next.1" + function-bind "^1.1.1" + has "^1.0.3" + +object.fromentries@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/object.fromentries/-/object.fromentries-2.0.2.tgz#4a09c9b9bb3843dd0f89acdb517a794d4f355ac9" + integrity sha512-r3ZiBH7MQppDJVLx6fhD618GKNG40CZYH9wgwdhKxBDDbQgjeWGGd4AtkZad84d291YxvWe7bJGuE65Anh0dxQ== + dependencies: + define-properties "^1.1.3" + es-abstract "^1.17.0-next.1" + function-bind "^1.1.1" + has "^1.0.3" + +object.omit@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/object.omit/-/object.omit-2.0.1.tgz#1a9c744829f39dbb858c76ca3579ae2a54ebd1fa" + integrity sha1-Gpx0SCnznbuFjHbKNXmuKlTr0fo= + dependencies: + for-own "^0.1.4" + is-extendable "^0.1.1" + +object.values@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/object.values/-/object.values-1.1.1.tgz#68a99ecde356b7e9295a3c5e0ce31dc8c953de5e" + integrity sha512-WTa54g2K8iu0kmS/us18jEmdv1a4Wi//BZ/DTVYEcH0XhLM5NYdpDHja3gt57VrZLcNAO2WGA+KpWsDBaHt6eA== + dependencies: + define-properties "^1.1.3" + es-abstract "^1.17.0-next.1" + function-bind "^1.1.1" + has "^1.0.3" + +once@^1.3.0, once@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" + integrity sha1-WDsap3WWHUsROsF9nFC6753Xa9E= + dependencies: + wrappy "1" + +optimist@^0.6.1: + version "0.6.1" + resolved "https://registry.yarnpkg.com/optimist/-/optimist-0.6.1.tgz#da3ea74686fa21a19a111c326e90eb15a0196686" + integrity sha1-2j6nRob6IaGaERwybpDrFaAZZoY= + dependencies: + minimist "~0.0.1" + wordwrap "~0.0.2" + +optionator@^0.8.1: + version "0.8.3" + resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.8.3.tgz#84fa1d036fe9d3c7e21d99884b601167ec8fb495" + integrity sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA== + dependencies: + deep-is "~0.1.3" + fast-levenshtein "~2.0.6" + levn "~0.3.0" + prelude-ls "~1.1.2" + type-check "~0.3.2" + word-wrap "~1.2.3" + +os-homedir@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/os-homedir/-/os-homedir-1.0.2.tgz#ffbc4988336e0e833de0c168c7ef152121aa7fb3" + integrity sha1-/7xJiDNuDoM94MFox+8VISGqf7M= + +os-locale@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/os-locale/-/os-locale-1.4.0.tgz#20f9f17ae29ed345e8bde583b13d2009803c14d9" + integrity sha1-IPnxeuKe00XoveWDsT0gCYA8FNk= + dependencies: + lcid "^1.0.0" + +os-tmpdir@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz#bbe67406c79aa85c5cfec766fe5734555dfa1274" + integrity sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ= + +p-limit@^1.1.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-1.3.0.tgz#b86bd5f0c25690911c7590fcbfc2010d54b3ccb8" + integrity sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q== + dependencies: + p-try "^1.0.0" + +p-limit@^2.0.0: + version "2.2.2" + resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-2.2.2.tgz#61279b67721f5287aa1c13a9a7fbbc48c9291b1e" + integrity sha512-WGR+xHecKTr7EbUEhyLSh5Dube9JtdiG78ufaeLxTgpudf/20KqyMioIUZJAezlTIi6evxuoUs9YXc11cU+yzQ== + dependencies: + p-try "^2.0.0" + +p-locate@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-2.0.0.tgz#20a0103b222a70c8fd39cc2e580680f3dde5ec43" + integrity sha1-IKAQOyIqcMj9OcwuWAaA893l7EM= + dependencies: + p-limit "^1.1.0" + +p-locate@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-3.0.0.tgz#322d69a05c0264b25997d9f40cd8a891ab0064a4" + integrity sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ== + dependencies: + p-limit "^2.0.0" + +p-map@^1.1.1: + version "1.2.0" + resolved "https://registry.yarnpkg.com/p-map/-/p-map-1.2.0.tgz#e4e94f311eabbc8633a1e79908165fca26241b6b" + integrity sha512-r6zKACMNhjPJMTl8KcFH4li//gkrXWfbD6feV8l6doRHlzljFWGJ2AP6iKaCJXyZmAUMOPtvbW7EXkbWO/pLEA== + +p-try@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/p-try/-/p-try-1.0.0.tgz#cbc79cdbaf8fd4228e13f621f2b1a237c1b207b3" + integrity sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M= + +p-try@^2.0.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.2.0.tgz#cb2868540e313d61de58fafbe35ce9004d5540e6" + integrity sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ== + +parse-glob@^3.0.4: + version "3.0.4" + resolved "https://registry.yarnpkg.com/parse-glob/-/parse-glob-3.0.4.tgz#b2c376cfb11f35513badd173ef0bb6e3a388391c" + integrity sha1-ssN2z7EfNVE7rdFz7wu246OIORw= + dependencies: + glob-base "^0.3.0" + is-dotfile "^1.0.0" + is-extglob "^1.0.0" + is-glob "^2.0.0" + +parse-json@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-2.2.0.tgz#f480f40434ef80741f8469099f8dea18f55a4dc9" + integrity sha1-9ID0BDTvgHQfhGkJn43qGPVaTck= + dependencies: + error-ex "^1.2.0" + +parse5@^1.5.1: + version "1.5.1" + resolved "https://registry.yarnpkg.com/parse5/-/parse5-1.5.1.tgz#9b7f3b0de32be78dc2401b17573ccaf0f6f59d94" + integrity sha1-m387DeMr543CQBsXVzzK8Pb1nZQ= + +parse5@^3.0.1: + version "3.0.3" + resolved "https://registry.yarnpkg.com/parse5/-/parse5-3.0.3.tgz#042f792ffdd36851551cf4e9e066b3874ab45b5c" + integrity sha512-rgO9Zg5LLLkfJF9E6CCmXlSE4UVceloys8JrFqCcHloC3usd/kJCyPDwH2SOlzix2j3xaP9sUX3e8+kvkuleAA== + dependencies: + "@types/node" "*" + +path-exists@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-2.1.0.tgz#0feb6c64f0fc518d9a754dd5efb62c7022761f4b" + integrity sha1-D+tsZPD8UY2adU3V77YscCJ2H0s= + dependencies: + pinkie-promise "^2.0.0" + +path-exists@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-3.0.0.tgz#ce0ebeaa5f78cb18925ea7d810d7b59b010fd515" + integrity sha1-zg6+ql94yxiSXqfYENe1mwEP1RU= + +path-is-absolute@^1.0.0, path-is-absolute@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" + integrity sha1-F0uSaHNVNP+8es5r9TpanhtcX18= + +path-parse@^1.0.5, path-parse@^1.0.6: + version "1.0.6" + resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.6.tgz#d62dbb5679405d72c4737ec58600e9ddcf06d24c" + integrity sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw== + +path-type@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/path-type/-/path-type-1.1.0.tgz#59c44f7ee491da704da415da5a4070ba4f8fe441" + integrity sha1-WcRPfuSR2nBNpBXaWkBwuk+P5EE= + dependencies: + graceful-fs "^4.1.2" + pify "^2.0.0" + pinkie-promise "^2.0.0" + +performance-now@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-2.1.0.tgz#6309f4e0e5fa913ec1c69307ae364b4b377c9e7b" + integrity sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns= + +pify@^2.0.0, pify@^2.3.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/pify/-/pify-2.3.0.tgz#ed141a6ac043a849ea588498e7dca8b15330e90c" + integrity sha1-7RQaasBDqEnqWISY59yosVMw6Qw= + +pify@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/pify/-/pify-4.0.1.tgz#4b2cd25c50d598735c50292224fd8c6df41e3231" + integrity sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g== + +pinkie-promise@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/pinkie-promise/-/pinkie-promise-2.0.1.tgz#2135d6dfa7a358c069ac9b178776288228450ffa" + integrity sha1-ITXW36ejWMBprJsXh3YogihFD/o= + dependencies: + pinkie "^2.0.0" + +pinkie@^2.0.0: + version "2.0.4" + resolved "https://registry.yarnpkg.com/pinkie/-/pinkie-2.0.4.tgz#72556b80cfa0d48a974e80e77248e80ed4f7f870" + integrity sha1-clVrgM+g1IqXToDnckjoDtT3+HA= + +pirates@^4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/pirates/-/pirates-4.0.1.tgz#643a92caf894566f91b2b986d2c66950a8e2fb87" + integrity sha512-WuNqLTbMI3tmfef2TKxlQmAiLHKtFhlsCZnPIpuv2Ow0RDVO8lfy1Opf4NUzlMXLjPl+Men7AuVdX6TA+s+uGA== + dependencies: + node-modules-regexp "^1.0.0" + +pkg-dir@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-3.0.0.tgz#2749020f239ed990881b1f71210d51eb6523bea3" + integrity sha512-/E57AYkoeQ25qkxMj5PBOVgF8Kiu/h7cYS30Z5+R7WaiCCBfLq58ZI/dSeaEKb9WVJV5n/03QwrN3IeWIFllvw== + dependencies: + find-up "^3.0.0" + +prelude-ls@~1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.1.2.tgz#21932a549f5e52ffd9a827f570e04be62a97da54" + integrity sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ= + +preserve@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/preserve/-/preserve-0.2.0.tgz#815ed1f6ebc65926f865b310c0713bcb3315ce4b" + integrity sha1-gV7R9uvGWSb4ZbMQwHE7yzMVzks= + +pretty-format@^20.0.3: + version "20.0.3" + resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-20.0.3.tgz#020e350a560a1fe1a98dc3beb6ccffb386de8b14" + integrity sha1-Ag41ClYKH+GpjcO+tsz/s4beixQ= + dependencies: + ansi-regex "^2.1.1" + ansi-styles "^3.0.0" + +private@^0.1.8: + version "0.1.8" + resolved "https://registry.yarnpkg.com/private/-/private-0.1.8.tgz#2381edb3689f7a53d653190060fcf822d2f368ff" + integrity sha512-VvivMrbvd2nKkiG38qjULzlc+4Vx4wm/whI9pQD35YrARNnhxeiRktSOhSukRLFNlzg6Br/cJPet5J/u19r/mg== + +process@^0.11.10: + version "0.11.10" + resolved "https://registry.yarnpkg.com/process/-/process-0.11.10.tgz#7332300e840161bda3e69a1d1d91a7d4bc16f182" + integrity sha1-czIwDoQBYb2j5podHZGn1LwW8YI= + +prop-types-exact@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/prop-types-exact/-/prop-types-exact-1.2.0.tgz#825d6be46094663848237e3925a98c6e944e9869" + integrity sha512-K+Tk3Kd9V0odiXFP9fwDHUYRyvK3Nun3GVyPapSIs5OBkITAm15W0CPFD/YKTkMUAbc0b9CUwRQp2ybiBIq+eA== + dependencies: + has "^1.0.3" + object.assign "^4.1.0" + reflect.ownkeys "^0.2.0" + +prop-types@^15.6.2, prop-types@^15.7.2: + version "15.7.2" + resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.7.2.tgz#52c41e75b8c87e72b9d9360e0206b99dcbffa6c5" + integrity sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ== + dependencies: + loose-envify "^1.4.0" + object-assign "^4.1.1" + react-is "^16.8.1" + +prr@~1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/prr/-/prr-1.0.1.tgz#d3fc114ba06995a45ec6893f484ceb1d78f5f476" + integrity sha1-0/wRS6BplaRexok/SEzrHXj19HY= + +psl@^1.1.28: + version "1.7.0" + resolved "https://registry.yarnpkg.com/psl/-/psl-1.7.0.tgz#f1c4c47a8ef97167dea5d6bbf4816d736e884a3c" + integrity sha512-5NsSEDv8zY70ScRnOTn7bK7eanl2MvFrOrS/R6x+dBt5g1ghnj9Zv90kO8GwT8gxcu2ANyFprnFYB85IogIJOQ== + +punycode@^2.1.0, punycode@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec" + integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A== + +qs@~6.5.2: + version "6.5.2" + resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.2.tgz#cb3ae806e8740444584ef154ce8ee98d403f3e36" + integrity sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA== + +raf@^3.4.1: + version "3.4.1" + resolved "https://registry.yarnpkg.com/raf/-/raf-3.4.1.tgz#0742e99a4a6552f445d73e3ee0328af0ff1ede39" + integrity sha512-Sq4CW4QhwOHE8ucn6J34MqtZCeWFP2aQSmrlroYgqAV1PjStIhJXxYuTgUIfkEk7zTLjmIjLmU5q+fbD1NnOJA== + dependencies: + performance-now "^2.1.0" + +railroad-diagrams@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/railroad-diagrams/-/railroad-diagrams-1.0.0.tgz#eb7e6267548ddedfb899c1b90e57374559cddb7e" + integrity sha1-635iZ1SN3t+4mcG5Dlc3RVnN234= + +randexp@0.4.6: + version "0.4.6" + resolved "https://registry.yarnpkg.com/randexp/-/randexp-0.4.6.tgz#e986ad5e5e31dae13ddd6f7b3019aa7c87f60ca3" + integrity sha512-80WNmd9DA0tmZrw9qQa62GPPWfuXJknrmVmLcxvq4uZBdYqb1wYoKTmnlGUchvVWe0XiLupYkBoXVOxz3C8DYQ== + dependencies: + discontinuous-range "1.0.0" + ret "~0.1.10" + +randomatic@^3.0.0: + version "3.1.1" + resolved "https://registry.yarnpkg.com/randomatic/-/randomatic-3.1.1.tgz#b776efc59375984e36c537b2f51a1f0aff0da1ed" + integrity sha512-TuDE5KxZ0J461RVjrJZCJc+J+zCkTb1MbH9AQUq68sMhOMcy9jLcb3BrZKgp9q9Ncltdg4QVqWrH02W2EFFVYw== + dependencies: + is-number "^4.0.0" + kind-of "^6.0.0" + math-random "^1.0.1" + +react-is@^16.12.0, react-is@^16.8.1, react-is@^16.8.6, react-is@^16.9.0: + version "16.13.0" + resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.0.tgz#0f37c3613c34fe6b37cd7f763a0d6293ab15c527" + integrity sha512-GFMtL0vHkiBv9HluwNZTggSn/sCyEt9n02aM0dSAjGGyqyNlAyftYm4phPxdvCigG15JreC5biwxCgTAJZ7yAA== + +react-test-renderer@^16.0.0-0: + version "16.13.0" + resolved "https://registry.yarnpkg.com/react-test-renderer/-/react-test-renderer-16.13.0.tgz#39ba3bf72cedc8210c3f81983f0bb061b14a3014" + integrity sha512-NQ2S9gdMUa7rgPGpKGyMcwl1d6D9MCF0lftdI3kts6kkiX+qvpC955jNjAZXlIDTjnN9jwFI8A8XhRh/9v0spA== + dependencies: + object-assign "^4.1.1" + prop-types "^15.6.2" + react-is "^16.8.6" + scheduler "^0.19.0" + +read-pkg-up@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/read-pkg-up/-/read-pkg-up-1.0.1.tgz#9d63c13276c065918d57f002a57f40a1b643fb02" + integrity sha1-nWPBMnbAZZGNV/ACpX9AobZD+wI= + dependencies: + find-up "^1.0.0" + read-pkg "^1.0.0" + +read-pkg@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/read-pkg/-/read-pkg-1.1.0.tgz#f5ffaa5ecd29cb31c0474bca7d756b6bb29e3f28" + integrity sha1-9f+qXs0pyzHAR0vKfXVra7KePyg= + dependencies: + load-json-file "^1.0.0" + normalize-package-data "^2.3.2" + path-type "^1.0.0" + +readable-stream@^3.1.1: + version "3.6.0" + resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.0.tgz#337bbda3adc0706bd3e024426a286d4b4b2c9198" + integrity sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA== + dependencies: + inherits "^2.0.3" + string_decoder "^1.1.1" + util-deprecate "^1.0.1" + +reflect.ownkeys@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/reflect.ownkeys/-/reflect.ownkeys-0.2.0.tgz#749aceec7f3fdf8b63f927a04809e90c5c0b3460" + integrity sha1-dJrO7H8/34tj+SegSAnpDFwLNGA= + +regenerator-runtime@^0.11.0: + version "0.11.1" + resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz#be05ad7f9bf7d22e056f9726cee5017fbf19e2e9" + integrity sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg== + +regex-cache@^0.4.2: + version "0.4.4" + resolved "https://registry.yarnpkg.com/regex-cache/-/regex-cache-0.4.4.tgz#75bdc58a2a1496cec48a12835bc54c8d562336dd" + integrity sha512-nVIZwtCjkC9YgvWkpM55B5rBhBYRZhAaJbgcFYXXsHnbZ9UZI9nnVWYZpBlCqv9ho2eZryPnWrZGsOdPwVWXWQ== + dependencies: + is-equal-shallow "^0.1.3" + +remove-trailing-separator@^1.0.1: + version "1.1.0" + resolved "https://registry.yarnpkg.com/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz#c24bce2a283adad5bc3f58e0d48249b92379d8ef" + integrity sha1-wkvOKig62tW8P1jg1IJJuSN52O8= + +repeat-element@^1.1.2: + version "1.1.3" + resolved "https://registry.yarnpkg.com/repeat-element/-/repeat-element-1.1.3.tgz#782e0d825c0c5a3bb39731f84efee6b742e6b1ce" + integrity sha512-ahGq0ZnV5m5XtZLMb+vP76kcAM5nkLqk0lpqAuojSKGgQtn4eRi4ZZGm2olo2zKFH+sMsWaqOCW1dqAnOru72g== + +repeat-string@^1.5.2: + version "1.6.1" + resolved "https://registry.yarnpkg.com/repeat-string/-/repeat-string-1.6.1.tgz#8dcae470e1c88abc2d600fff4a776286da75e637" + integrity sha1-jcrkcOHIirwtYA//Sndihtp15jc= + +repeating@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/repeating/-/repeating-2.0.1.tgz#5214c53a926d3552707527fbab415dbc08d06dda" + integrity sha1-UhTFOpJtNVJwdSf7q0FdvAjQbdo= + dependencies: + is-finite "^1.0.0" + +request@^2.79.0: + version "2.88.2" + resolved "https://registry.yarnpkg.com/request/-/request-2.88.2.tgz#d73c918731cb5a87da047e207234146f664d12b3" + integrity sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw== + dependencies: + aws-sign2 "~0.7.0" + aws4 "^1.8.0" + caseless "~0.12.0" + combined-stream "~1.0.6" + extend "~3.0.2" + forever-agent "~0.6.1" + form-data "~2.3.2" + har-validator "~5.1.3" + http-signature "~1.2.0" + is-typedarray "~1.0.0" + isstream "~0.1.2" + json-stringify-safe "~5.0.1" + mime-types "~2.1.19" + oauth-sign "~0.9.0" + performance-now "^2.1.0" + qs "~6.5.2" + safe-buffer "^5.1.2" + tough-cookie "~2.5.0" + tunnel-agent "^0.6.0" + uuid "^3.3.2" + +require-directory@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42" + integrity sha1-jGStX9MNqxyXbiNE/+f3kqam30I= + +require-hacker@^2.1.4: + version "2.1.4" + resolved "https://registry.yarnpkg.com/require-hacker/-/require-hacker-2.1.4.tgz#1683da866119495e0ffcda8ebed9bbcf556849f2" + integrity sha1-FoPahmEZSV4P/NqOvtm7z1VoSfI= + dependencies: + babel-runtime "^6.6.1" + colors "^1.1.2" + +require-main-filename@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/require-main-filename/-/require-main-filename-1.0.1.tgz#97f717b69d48784f5f526a6c5aa8ffdda055a4d1" + integrity sha1-l/cXtp1IeE9fUmpsWqj/3aBVpNE= + +resolve@1.1.7: + version "1.1.7" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.1.7.tgz#203114d82ad2c5ed9e8e0411b3932875e889e97b" + integrity sha1-IDEU2CrSxe2ejgQRs5ModeiJ6Xs= + +resolve@^1.10.0, resolve@^1.3.2: + version "1.15.1" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.15.1.tgz#27bdcdeffeaf2d6244b95bb0f9f4b4653451f3e8" + integrity sha512-84oo6ZTtoTUpjgNEr5SJyzQhzL72gaRodsSfyxC/AXRvwu0Yse9H8eF9IpGo7b8YetZhlI6v7ZQ6bKBFV/6S7w== + dependencies: + path-parse "^1.0.6" + +ret@~0.1.10: + version "0.1.15" + resolved "https://registry.yarnpkg.com/ret/-/ret-0.1.15.tgz#b8a4825d5bdb1fc3f6f53c2bc33f81388681c7bc" + integrity sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg== + +rimraf@^2.6.1: + version "2.7.1" + resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.7.1.tgz#35797f13a7fdadc566142c29d4f07ccad483e3ec" + integrity sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w== + dependencies: + glob "^7.1.3" + +rst-selector-parser@^2.2.3: + version "2.2.3" + resolved "https://registry.yarnpkg.com/rst-selector-parser/-/rst-selector-parser-2.2.3.tgz#81b230ea2fcc6066c89e3472de794285d9b03d91" + integrity sha1-gbIw6i/MYGbInjRy3nlChdmwPZE= + dependencies: + lodash.flattendeep "^4.4.0" + nearley "^2.7.10" + +safe-buffer@^5.0.1, safe-buffer@^5.1.2, safe-buffer@~5.2.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.0.tgz#b74daec49b1148f88c64b68d49b1e815c1f2f519" + integrity sha512-fZEwUGbVl7kouZs1jCdMLdt95hdIv0ZeHg6L7qPeciMZhZ+/gdesW4wgTARkrFWEpspjEATAzUGPG8N2jJiwbg== + +safe-buffer@~5.1.1: + version "5.1.2" + resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" + integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== + +"safer-buffer@>= 2.1.2 < 3", safer-buffer@^2.0.2, safer-buffer@^2.1.0, safer-buffer@~2.1.0: + version "2.1.2" + resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" + integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== + +samsam@1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/samsam/-/samsam-1.1.2.tgz#bec11fdc83a9fda063401210e40176c3024d1567" + integrity sha1-vsEf3IOp/aBjQBIQ5AF2wwJNFWc= + +samsam@~1.1: + version "1.1.3" + resolved "https://registry.yarnpkg.com/samsam/-/samsam-1.1.3.tgz#9f5087419b4d091f232571e7fa52e90b0f552621" + integrity sha1-n1CHQZtNCR8jJXHn+lLpCw9VJiE= + +sane@~1.6.0: + version "1.6.0" + resolved "https://registry.yarnpkg.com/sane/-/sane-1.6.0.tgz#9610c452307a135d29c1fdfe2547034180c46775" + integrity sha1-lhDEUjB6E10pwf3+JUcDQYDEZ3U= + dependencies: + anymatch "^1.3.0" + exec-sh "^0.2.0" + fb-watchman "^1.8.0" + minimatch "^3.0.2" + minimist "^1.1.1" + walker "~1.0.5" + watch "~0.10.0" + +sax@^1.2.1: + version "1.2.4" + resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9" + integrity sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw== + +scheduler@^0.19.0: + version "0.19.0" + resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.19.0.tgz#a715d56302de403df742f4a9be11975b32f5698d" + integrity sha512-xowbVaTPe9r7y7RUejcK73/j8tt2jfiyTednOvHbA8JoClvMYCp+r8QegLwK/n8zWQAtZb1fFnER4XLBZXrCxA== + dependencies: + loose-envify "^1.1.0" + object-assign "^4.1.1" + +"semver@2 || 3 || 4 || 5", semver@^5.3.0, semver@^5.4.1, semver@^5.5.0, semver@^5.6.0, semver@^5.7.0, semver@^5.7.1: + version "5.7.1" + resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7" + integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ== + +set-blocking@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7" + integrity sha1-BF+XgtARrppoA93TgrJDkrPYkPc= + +shellwords@^0.1.1: + version "0.1.1" + resolved "https://registry.yarnpkg.com/shellwords/-/shellwords-0.1.1.tgz#d6b9181c1a48d397324c84871efbcfc73fc0654b" + integrity sha512-vFwSUfQvqybiICwZY5+DAWIPLKsWO31Q91JSKl3UYv+K5c2QRPzn0qzec6QPu1Qc9eHYItiP3NdJqNVqetYAww== + +sinon@^1.17.5: + version "1.17.7" + resolved "https://registry.yarnpkg.com/sinon/-/sinon-1.17.7.tgz#4542a4f49ba0c45c05eb2e9dd9d203e2b8efe0bf" + integrity sha1-RUKk9JugxFwF6y6d2dID4rjv4L8= + dependencies: + formatio "1.1.1" + lolex "1.3.2" + samsam "1.1.2" + util ">=0.10.3 <1" + +slash@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/slash/-/slash-1.0.0.tgz#c41f2f6c39fc16d1cd17ad4b5d896114ae470d55" + integrity sha1-xB8vbDn8FtHNF61LXYlhFK5HDVU= + +source-map-support@^0.4.15: + version "0.4.18" + resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.4.18.tgz#0286a6de8be42641338594e97ccea75f0a2c585f" + integrity sha512-try0/JqxPLF9nOjvSta7tVondkP5dwgyLDjVoyMDlmjugT2lRZ1OfsrYTkCd2hkDnJTKRbO/Rl3orm8vlsUzbA== + dependencies: + source-map "^0.5.6" + +source-map-support@^0.5.16: + version "0.5.16" + resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.16.tgz#0ae069e7fe3ba7538c64c98515e35339eac5a042" + integrity sha512-efyLRJDr68D9hBBNIPWFjhpFzURh+KJykQwvMyW5UiZzYwoF6l4YMMDIJJEyFWxWCqfyxLzz6tSfUFR+kXXsVQ== + dependencies: + buffer-from "^1.0.0" + source-map "^0.6.0" + +source-map@^0.5.0, source-map@^0.5.3, source-map@^0.5.6, source-map@^0.5.7: + version "0.5.7" + resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc" + integrity sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w= + +source-map@^0.6.0, source-map@^0.6.1, source-map@~0.6.1: + version "0.6.1" + resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" + integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== + +spdx-correct@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/spdx-correct/-/spdx-correct-3.1.0.tgz#fb83e504445268f154b074e218c87c003cd31df4" + integrity sha512-lr2EZCctC2BNR7j7WzJ2FpDznxky1sjfxvvYEyzxNyb6lZXHODmEoJeFu4JupYlkfha1KZpJyoqiJ7pgA1qq8Q== + dependencies: + spdx-expression-parse "^3.0.0" + spdx-license-ids "^3.0.0" + +spdx-exceptions@^2.1.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/spdx-exceptions/-/spdx-exceptions-2.2.0.tgz#2ea450aee74f2a89bfb94519c07fcd6f41322977" + integrity sha512-2XQACfElKi9SlVb1CYadKDXvoajPgBVPn/gOQLrTvHdElaVhr7ZEbqJaRnJLVNeaI4cMEAgVCeBMKF6MWRDCRA== + +spdx-expression-parse@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/spdx-expression-parse/-/spdx-expression-parse-3.0.0.tgz#99e119b7a5da00e05491c9fa338b7904823b41d0" + integrity sha512-Yg6D3XpRD4kkOmTpdgbUiEJFKghJH03fiC1OPll5h/0sO6neh2jqRDVHOQ4o/LMea0tgCkbMgea5ip/e+MkWyg== + dependencies: + spdx-exceptions "^2.1.0" + spdx-license-ids "^3.0.0" + +spdx-license-ids@^3.0.0: + version "3.0.5" + resolved "https://registry.yarnpkg.com/spdx-license-ids/-/spdx-license-ids-3.0.5.tgz#3694b5804567a458d3c8045842a6358632f62654" + integrity sha512-J+FWzZoynJEXGphVIS+XEh3kFSjZX/1i9gFBaWQcB+/tmpe2qUsSBABpcxqxnAxFdiUFEgAX1bjYGQvIZmoz9Q== + +sprintf-js@~1.0.2: + version "1.0.3" + resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" + integrity sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw= + +sshpk@^1.7.0: + version "1.16.1" + resolved "https://registry.yarnpkg.com/sshpk/-/sshpk-1.16.1.tgz#fb661c0bef29b39db40769ee39fa70093d6f6877" + integrity sha512-HXXqVUq7+pcKeLqqZj6mHFUMvXtOJt1uoUx09pFW6011inTMxqI8BA8PM95myrIyyKwdnzjdFjLiE6KBPVtJIg== + dependencies: + asn1 "~0.2.3" + assert-plus "^1.0.0" + bcrypt-pbkdf "^1.0.0" + dashdash "^1.12.0" + ecc-jsbn "~0.1.1" + getpass "^0.1.1" + jsbn "~0.1.0" + safer-buffer "^2.0.2" + tweetnacl "~0.14.0" + +string-length@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/string-length/-/string-length-1.0.1.tgz#56970fb1c38558e9e70b728bf3de269ac45adfac" + integrity sha1-VpcPscOFWOnnC3KL894mmsRa36w= + dependencies: + strip-ansi "^3.0.0" + +string-width@^1.0.1, string-width@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-1.0.2.tgz#118bdf5b8cdc51a2a7e70d211e07e2b0b9b107d3" + integrity sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M= + dependencies: + code-point-at "^1.0.0" + is-fullwidth-code-point "^1.0.0" + strip-ansi "^3.0.0" + +string.prototype.trim@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/string.prototype.trim/-/string.prototype.trim-1.2.1.tgz#141233dff32c82bfad80684d7e5f0869ee0fb782" + integrity sha512-MjGFEeqixw47dAMFMtgUro/I0+wNqZB5GKXGt1fFr24u3TzDXCPu7J9Buppzoe3r/LqkSDLDDJzE15RGWDGAVw== + dependencies: + define-properties "^1.1.3" + es-abstract "^1.17.0-next.1" + function-bind "^1.1.1" + +string.prototype.trimleft@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/string.prototype.trimleft/-/string.prototype.trimleft-2.1.1.tgz#9bdb8ac6abd6d602b17a4ed321870d2f8dcefc74" + integrity sha512-iu2AGd3PuP5Rp7x2kEZCrB2Nf41ehzh+goo8TV7z8/XDBbsvc6HQIlUl9RjkZ4oyrW1XM5UwlGl1oVEaDjg6Ag== + dependencies: + define-properties "^1.1.3" + function-bind "^1.1.1" + +string.prototype.trimright@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/string.prototype.trimright/-/string.prototype.trimright-2.1.1.tgz#440314b15996c866ce8a0341894d45186200c5d9" + integrity sha512-qFvWL3/+QIgZXVmJBfpHmxLB7xsUXz6HsUmP8+5dRaC3Q7oKUv9Vo6aMCRZC1smrtyECFsIT30PqBJ1gTjAs+g== + dependencies: + define-properties "^1.1.3" + function-bind "^1.1.1" + +string_decoder@^1.1.1: + version "1.3.0" + resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.3.0.tgz#42f114594a46cf1a8e30b0a84f56c78c3edac21e" + integrity sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA== + dependencies: + safe-buffer "~5.2.0" + +strip-ansi@^3.0.0, strip-ansi@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-3.0.1.tgz#6a385fb8853d952d5ff05d0e8aaf94278dc63dcf" + integrity sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8= + dependencies: + ansi-regex "^2.0.0" + +strip-bom@3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-3.0.0.tgz#2334c18e9c759f7bdd56fdef7e9ae3d588e68ed3" + integrity sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM= + +strip-bom@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-2.0.0.tgz#6219a85616520491f35788bdbf1447a99c7e6b0e" + integrity sha1-YhmoVhZSBJHzV4i9vxRHqZx+aw4= + dependencies: + is-utf8 "^0.2.0" + +supports-color@5.4.0: + version "5.4.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.4.0.tgz#1c6b337402c2137605efe19f10fec390f6faab54" + integrity sha512-zjaXglF5nnWpsq470jSv6P9DwPvgLkuapYmfDm3JWOm0vkNTVF2tI4UrN2r6jH1qM/uc/WtxYY1hYoA2dOKj5w== + dependencies: + has-flag "^3.0.0" + +supports-color@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-2.0.0.tgz#535d045ce6b6363fa40117084629995e9df324c7" + integrity sha1-U10EXOa2Nj+kARcIRimZXp3zJMc= + +supports-color@^3.1.2: + version "3.2.3" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-3.2.3.tgz#65ac0504b3954171d8a64946b2ae3cbb8a5f54f6" + integrity sha1-ZawFBLOVQXHYpklGsq48u4pfVPY= + dependencies: + has-flag "^1.0.0" + +supports-color@^5.3.0: + version "5.5.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f" + integrity sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow== + dependencies: + has-flag "^3.0.0" + +symbol-tree@^3.2.1: + version "3.2.4" + resolved "https://registry.yarnpkg.com/symbol-tree/-/symbol-tree-3.2.4.tgz#430637d248ba77e078883951fb9aa0eed7c63fa2" + integrity sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw== + +test-exclude@^4.2.1: + version "4.2.3" + resolved "https://registry.yarnpkg.com/test-exclude/-/test-exclude-4.2.3.tgz#a9a5e64474e4398339245a0a769ad7c2f4a97c20" + integrity sha512-SYbXgY64PT+4GAL2ocI3HwPa4Q4TBKm0cwAVeKOt/Aoc0gSpNRjJX8w0pA1LMKZ3LBmd8pYBqApFNQLII9kavA== + dependencies: + arrify "^1.0.1" + micromatch "^2.3.11" + object-assign "^4.1.0" + read-pkg-up "^1.0.1" + require-main-filename "^1.0.1" + +throat@^3.0.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/throat/-/throat-3.2.0.tgz#50cb0670edbc40237b9e347d7e1f88e4620af836" + integrity sha512-/EY8VpvlqJ+sFtLPeOgc8Pl7kQVOWv0woD87KTXVHPIAE842FGT+rokxIhe8xIUP1cfgrkt0as0vDLjDiMtr8w== + +tmatch@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/tmatch/-/tmatch-2.0.1.tgz#0c56246f33f30da1b8d3d72895abaf16660f38cf" + integrity sha1-DFYkbzPzDaG409colauvFmYPOM8= + +tmpl@1.0.x: + version "1.0.4" + resolved "https://registry.yarnpkg.com/tmpl/-/tmpl-1.0.4.tgz#23640dd7b42d00433911140820e5cf440e521dd1" + integrity sha1-I2QN17QtAEM5ERQIIOXPRA5SHdE= + +to-fast-properties@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/to-fast-properties/-/to-fast-properties-1.0.3.tgz#b83571fa4d8c25b82e231b06e3a3055de4ca1a47" + integrity sha1-uDVx+k2MJbguIxsG46MFXeTKGkc= + +to-fast-properties@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/to-fast-properties/-/to-fast-properties-2.0.0.tgz#dc5e698cbd079265bc73e0377681a4e4e83f616e" + integrity sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4= + +tough-cookie@^2.3.2, tough-cookie@~2.5.0: + version "2.5.0" + resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.5.0.tgz#cd9fb2a0aa1d5a12b473bd9fb96fa3dcff65ade2" + integrity sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g== + dependencies: + psl "^1.1.28" + punycode "^2.1.1" + +tr46@~0.0.3: + version "0.0.3" + resolved "https://registry.yarnpkg.com/tr46/-/tr46-0.0.3.tgz#8184fd347dac9cdc185992f3a6622e14b9d9ab6a" + integrity sha1-gYT9NH2snNwYWZLzpmIuFLnZq2o= + +trim-right@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/trim-right/-/trim-right-1.0.1.tgz#cb2e1203067e0c8de1f614094b9fe45704ea6003" + integrity sha1-yy4SAwZ+DI3h9hQJS5/kVwTqYAM= + +tunnel-agent@^0.6.0: + version "0.6.0" + resolved "https://registry.yarnpkg.com/tunnel-agent/-/tunnel-agent-0.6.0.tgz#27a5dea06b36b04a0a9966774b290868f0fc40fd" + integrity sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0= + dependencies: + safe-buffer "^5.0.1" + +tweetnacl@^0.14.3, tweetnacl@~0.14.0: + version "0.14.5" + resolved "https://registry.yarnpkg.com/tweetnacl/-/tweetnacl-0.14.5.tgz#5ae68177f192d4456269d108afa93ff8743f4f64" + integrity sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q= + +type-check@~0.3.2: + version "0.3.2" + resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.3.2.tgz#5884cab512cf1d355e3fb784f30804b2b520db72" + integrity sha1-WITKtRLPHTVeP7eE8wgEsrUg23I= + dependencies: + prelude-ls "~1.1.2" + +uglify-js@^3.1.4: + version "3.8.0" + resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-3.8.0.tgz#f3541ae97b2f048d7e7e3aa4f39fd8a1f5d7a805" + integrity sha512-ugNSTT8ierCsDHso2jkBHXYrU8Y5/fY2ZUprfrJUiD7YpuFvV4jODLFmb3h4btQjqr5Nh4TX4XtgDfCU1WdioQ== + dependencies: + commander "~2.20.3" + source-map "~0.6.1" + +uri-js@^4.2.2: + version "4.2.2" + resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.2.2.tgz#94c540e1ff772956e2299507c010aea6c8838eb0" + integrity sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ== + dependencies: + punycode "^2.1.0" + +util-deprecate@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" + integrity sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8= + +"util@>=0.10.3 <1": + version "0.12.2" + resolved "https://registry.yarnpkg.com/util/-/util-0.12.2.tgz#54adb634c9e7c748707af2bf5a8c7ab640cbba2b" + integrity sha512-XE+MkWQvglYa+IOfBt5UFG93EmncEMP23UqpgDvVZVFBPxwmkK10QRp6pgU4xICPnWRf/t0zPv4noYSUq9gqUQ== + dependencies: + inherits "^2.0.3" + is-arguments "^1.0.4" + is-generator-function "^1.0.7" + safe-buffer "^5.1.2" + +uuid@^3.3.2: + version "3.4.0" + resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.4.0.tgz#b23e4358afa8a202fe7a100af1f5f883f02007ee" + integrity sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A== + +validate-npm-package-license@^3.0.1: + version "3.0.4" + resolved "https://registry.yarnpkg.com/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz#fc91f6b9c7ba15c857f4cb2c5defeec39d4f410a" + integrity sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew== + dependencies: + spdx-correct "^3.0.0" + spdx-expression-parse "^3.0.0" + +verror@1.10.0: + version "1.10.0" + resolved "https://registry.yarnpkg.com/verror/-/verror-1.10.0.tgz#3a105ca17053af55d6e270c1f8288682e18da400" + integrity sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA= + dependencies: + assert-plus "^1.0.0" + core-util-is "1.0.2" + extsprintf "^1.2.0" + +walker@~1.0.5: + version "1.0.7" + resolved "https://registry.yarnpkg.com/walker/-/walker-1.0.7.tgz#2f7f9b8fd10d677262b18a884e28d19618e028fb" + integrity sha1-L3+bj9ENZ3JisYqITijRlhjgKPs= + dependencies: + makeerror "1.0.x" + +watch@~0.10.0: + version "0.10.0" + resolved "https://registry.yarnpkg.com/watch/-/watch-0.10.0.tgz#77798b2da0f9910d595f1ace5b0c2258521f21dc" + integrity sha1-d3mLLaD5kQ1ZXxrOWwwiWFIfIdw= + +webidl-conversions@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-3.0.1.tgz#24534275e2a7bc6be7bc86611cc16ae0a5654871" + integrity sha1-JFNCdeKnvGvnvIZhHMFq4KVlSHE= + +webidl-conversions@^4.0.0: + version "4.0.2" + resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-4.0.2.tgz#a855980b1f0b6b359ba1d5d9fb39ae941faa63ad" + integrity sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg== + +whatwg-encoding@^1.0.1: + version "1.0.5" + resolved "https://registry.yarnpkg.com/whatwg-encoding/-/whatwg-encoding-1.0.5.tgz#5abacf777c32166a51d085d6b4f3e7d27113ddb0" + integrity sha512-b5lim54JOPN9HtzvK9HFXvBma/rnfFeqsic0hSpjtDbVxR3dJKLc+KB4V6GgiGOvl7CY/KNh8rxSo9DKQrnUEw== + dependencies: + iconv-lite "0.4.24" + +whatwg-url@^4.3.0: + version "4.8.0" + resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-4.8.0.tgz#d2981aa9148c1e00a41c5a6131166ab4683bbcc0" + integrity sha1-0pgaqRSMHgCkHFphMRZqtGg7vMA= + dependencies: + tr46 "~0.0.3" + webidl-conversions "^3.0.0" + +which-boxed-primitive@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/which-boxed-primitive/-/which-boxed-primitive-1.0.1.tgz#cbe8f838ebe91ba2471bb69e9edbda67ab5a5ec1" + integrity sha512-7BT4TwISdDGBgaemWU0N0OU7FeAEJ9Oo2P1PHRm/FCWoEi2VLWC9b6xvxAA3C/NMpxg3HXVgi0sMmGbNUbNepQ== + dependencies: + is-bigint "^1.0.0" + is-boolean-object "^1.0.0" + is-number-object "^1.0.3" + is-string "^1.0.4" + is-symbol "^1.0.2" + +which-collection@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/which-collection/-/which-collection-1.0.1.tgz#70eab71ebbbd2aefaf32f917082fc62cdcb70906" + integrity sha512-W8xeTUwaln8i3K/cY1nGXzdnVZlidBcagyNFtBdD5kxnb4TvGKR7FfSIS3mYpwWS1QUCutfKz8IY8RjftB0+1A== + dependencies: + is-map "^2.0.1" + is-set "^2.0.1" + is-weakmap "^2.0.1" + is-weakset "^2.0.1" + +which-module@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/which-module/-/which-module-1.0.0.tgz#bba63ca861948994ff307736089e3b96026c2a4f" + integrity sha1-u6Y8qGGUiZT/MHc2CJ47lgJsKk8= + +which@^1.2.12, which@^1.3.0: + version "1.3.1" + resolved "https://registry.yarnpkg.com/which/-/which-1.3.1.tgz#a45043d54f5805316da8d62f9f50918d3da70b0a" + integrity sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ== + dependencies: + isexe "^2.0.0" + +word-wrap@~1.2.3: + version "1.2.3" + resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.3.tgz#610636f6b1f703891bd34771ccb17fb93b47079c" + integrity sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ== + +wordwrap@~0.0.2: + version "0.0.3" + resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-0.0.3.tgz#a3d5da6cd5c0bc0008d37234bbaf1bed63059107" + integrity sha1-o9XabNXAvAAI03I0u68b7WMFkQc= + +worker-farm@^1.3.1: + version "1.7.0" + resolved "https://registry.yarnpkg.com/worker-farm/-/worker-farm-1.7.0.tgz#26a94c5391bbca926152002f69b84a4bf772e5a8" + integrity sha512-rvw3QTZc8lAxyVrqcSGVm5yP/IJ2UcB3U0graE3LCFoZ0Yn2x4EoVSqJKdB/T5M+FLcRPjz4TDacRf3OCfNUzw== + dependencies: + errno "~0.1.7" + +wrap-ansi@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-2.1.0.tgz#d8fc3d284dd05794fe84973caecdd1cf824fdd85" + integrity sha1-2Pw9KE3QV5T+hJc8rs3Rz4JP3YU= + dependencies: + string-width "^1.0.1" + strip-ansi "^3.0.1" + +wrappy@1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" + integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8= + +xml-name-validator@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/xml-name-validator/-/xml-name-validator-2.0.1.tgz#4d8b8f1eccd3419aa362061becef515e1e559635" + integrity sha1-TYuPHszTQZqjYgYb7O9RXh5VljU= + +y18n@^3.2.1: + version "3.2.1" + resolved "https://registry.yarnpkg.com/y18n/-/y18n-3.2.1.tgz#6d15fba884c08679c0d77e88e7759e811e07fa41" + integrity sha1-bRX7qITAhnnA136I53WegR4H+kE= + +yargs-parser@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-5.0.0.tgz#275ecf0d7ffe05c77e64e7c86e4cd94bf0e1228a" + integrity sha1-J17PDX/+Bcd+ZOfIbkzZS/DhIoo= + dependencies: + camelcase "^3.0.0" + +yargs@^7.0.2: + version "7.1.0" + resolved "https://registry.yarnpkg.com/yargs/-/yargs-7.1.0.tgz#6ba318eb16961727f5d284f8ea003e8d6154d0c8" + integrity sha1-a6MY6xaWFyf10oT46gA+jWFU0Mg= + dependencies: + camelcase "^3.0.0" + cliui "^3.2.0" + decamelize "^1.1.1" + get-caller-file "^1.0.1" + os-locale "^1.4.0" + read-pkg-up "^1.0.1" + require-directory "^2.1.1" + require-main-filename "^1.0.1" + set-blocking "^2.0.0" + string-width "^1.0.2" + which-module "^1.0.0" + y18n "^3.2.1" + yargs-parser "^5.0.0" diff --git a/devtools/client/webconsole/utils/messages.js b/devtools/client/webconsole/utils/messages.js index 1d568d3559..e75d062586 100644 --- a/devtools/client/webconsole/utils/messages.js +++ b/devtools/client/webconsole/utils/messages.js @@ -692,9 +692,29 @@ function getArrayTypeNames() { ]; } +function getDescriptorValue(descriptor) { + if (!descriptor) { + return descriptor; + } + + if (Object.prototype.hasOwnProperty.call(descriptor, "safeGetterValues")) { + return descriptor.safeGetterValues; + } + + if (Object.prototype.hasOwnProperty.call(descriptor, "getterValue")) { + return descriptor.getterValue; + } + + if (Object.prototype.hasOwnProperty.call(descriptor, "value")) { + return descriptor.value; + } + return descriptor; +} + module.exports = { createWarningGroupMessage, getArrayTypeNames, + getDescriptorValue, getInitialMessageCountForViewport, getParentWarningGroupMessageId, getWarningGroupType, diff --git a/devtools/client/webconsole/utils/object-inspector.js b/devtools/client/webconsole/utils/object-inspector.js index a4b22eef2b..1299c2eedd 100644 --- a/devtools/client/webconsole/utils/object-inspector.js +++ b/devtools/client/webconsole/utils/object-inspector.js @@ -48,7 +48,9 @@ function getObjectInspector(grip, serviceContainer, override = {}) { onDOMNodeMouseOver = serviceContainer.highlightDomElement ? object => serviceContainer.highlightDomElement(object) : null; - onDOMNodeMouseOut = serviceContainer.unHighlightDomElement; + onDOMNodeMouseOut = serviceContainer.unHighlightDomElement + ? object => serviceContainer.unHighlightDomElement(object) + : null; onInspectIconClick = serviceContainer.openNodeInInspector ? (object, e) => { // Stop the event propagation so we don't trigger ObjectInspector expand/collapse. @@ -78,10 +80,6 @@ function getObjectInspector(grip, serviceContainer, override = {}) { ? serviceContainer.onViewSourceInDebugger || serviceContainer.onViewSource : null, - onViewSourceInScratchpad: serviceContainer - ? serviceContainer.onViewSourceInScratchpad || - serviceContainer.onViewSource - : null, onViewSource: serviceContainer.onViewSource, onReady: override.maybeScrollToBottom, sourceMapService: serviceContainer diff --git a/devtools/client/webconsole/webconsole-connection-proxy.js b/devtools/client/webconsole/webconsole-connection-proxy.js index e312d20df1..6603e7fa5b 100644 --- a/devtools/client/webconsole/webconsole-connection-proxy.js +++ b/devtools/client/webconsole/webconsole-connection-proxy.js @@ -4,7 +4,6 @@ "use strict"; -const defer = require("devtools/shared/defer"); const Services = require("Services"); const l10n = require("devtools/client/webconsole/utils/l10n"); @@ -15,86 +14,42 @@ const PREF_CONNECTION_TIMEOUT = "devtools.debugger.remote-timeout"; /** * The WebConsoleConnectionProxy handles the connection between the Web Console * and the application we connect to through the remote debug protocol. - * - * @constructor - * @param {WebConsoleUI} webConsoleUI - * A WebConsoleUI instance that owns this connection proxy. - * @param RemoteTarget target - * The target that the console will connect to. */ -function WebConsoleConnectionProxy(webConsoleUI, target) { - this.webConsoleUI = webConsoleUI; - this.target = target; - this.webConsoleClient = target.activeConsole; - - this._onPageError = this._onPageError.bind(this); - this._onLogMessage = this._onLogMessage.bind(this); - this._onConsoleAPICall = this._onConsoleAPICall.bind(this); - this._onNetworkEvent = this._onNetworkEvent.bind(this); - this._onNetworkEventUpdate = this._onNetworkEventUpdate.bind(this); - this._onTabNavigated = this._onTabNavigated.bind(this); - this._onTabWillNavigate = this._onTabWillNavigate.bind(this); - this._onAttachConsole = this._onAttachConsole.bind(this); - this._onCachedMessages = this._onCachedMessages.bind(this); - this._connectionTimeout = this._connectionTimeout.bind(this); - this._onLastPrivateContextExited = this._onLastPrivateContextExited.bind( - this - ); - this._clearLogpointMessages = this._clearLogpointMessages.bind(this); -} - -WebConsoleConnectionProxy.prototype = { +class WebConsoleConnectionProxy { /** - * The owning WebConsoleUI instance. - * - * @see WebConsoleUI - * @type object + * @constructor + * @param {WebConsoleUI} webConsoleUI + * A WebConsoleUI instance that owns this connection proxy. + * @param RemoteTarget target + * The target that the console will connect to. */ - webConsoleUI: null, + constructor(webConsoleUI, target) { + this.webConsoleUI = webConsoleUI; + this.target = target; + this.webConsoleClient = target.activeConsole; - /** - * The target that the console connects to. - * @type RemoteTarget - */ - target: null, + /** + * The DebuggerClient object. + * + * @see DebuggerClient + * @type object + */ + this.client = target.client; - /** - * The DebuggerClient object. - * - * @see DebuggerClient - * @type object - */ - client: null, + this._connecter = null; - /** - * The WebConsoleClient object. - * - * @see WebConsoleClient - * @type object - */ - webConsoleClient: null, - - /** - * Tells if the connection is established. - * @type boolean - */ - connected: false, - - /** - * Tells if the console is attached. - * @type boolean - */ - isAttached: null, - - /** - * Timer used for the connection. - * @private - * @type object - */ - _connectTimer: null, - - _connectDefer: null, - _disconnecter: null, + this._onPageError = this._onPageError.bind(this); + this._onLogMessage = this._onLogMessage.bind(this); + this._onConsoleAPICall = this._onConsoleAPICall.bind(this); + this._onNetworkEvent = this._onNetworkEvent.bind(this); + this._onNetworkEventUpdate = this._onNetworkEventUpdate.bind(this); + this._onTabNavigated = this._onTabNavigated.bind(this); + this._onTabWillNavigate = this._onTabWillNavigate.bind(this); + this._onLastPrivateContextExited = this._onLastPrivateContextExited.bind( + this + ); + this._clearLogpointMessages = this._clearLogpointMessages.bind(this); + } /** * Initialize a debugger client and connect it to the debugger server. @@ -103,93 +58,80 @@ WebConsoleConnectionProxy.prototype = { * A promise object that is resolved/rejected based on the success of * the connection initialization. */ - connect: function() { - if (this._connectDefer) { - return this._connectDefer.promise; + connect() { + if (this._connecter) { + return this._connecter; } - this._connectDefer = defer(); - - const timeout = Services.prefs.getIntPref(PREF_CONNECTION_TIMEOUT); - this._connectTimer = setTimeout(this._connectionTimeout, timeout); - - const connPromise = this._connectDefer.promise; - connPromise.then( - () => { - clearTimeout(this._connectTimer); - this._connectTimer = null; - }, - () => { - clearTimeout(this._connectTimer); - this._connectTimer = null; - } - ); - this.client = this.target.client; - this.target.on("will-navigate", this._onTabWillNavigate); this.target.on("navigate", this._onTabNavigated); - this.isAttached = this._attachConsole(); + const connection = (async () => { + this._addWebConsoleClientEventListeners(); + await this._attachConsole(); - return connPromise; - }, + // There is no way to view response bodies from the Browser Console, so do + // not waste the memory. + const saveBodies = + !this.webConsoleUI.isBrowserConsole && + Services.prefs.getBoolPref( + "devtools.netmonitor.saveRequestAndResponseBodies" + ); + await this.webConsoleUI.setSaveRequestAndResponseBodies(saveBodies); - /** - * Connection timeout handler. - * @private - */ - _connectionTimeout: function() { - const error = { - error: "timeout", - message: l10n.getStr("connectionTimeout"), - }; + const cachedMessages = await this._getCachedMessages(); + const networkMessages = this._getNetworkMessages(); + const messages = cachedMessages.concat(networkMessages); + messages.sort((a, b) => a.timeStamp - b.timeStamp); + this.dispatchMessagesAdd(messages); - this._connectDefer.reject(error); - }, + if (!this.webConsoleClient.hasNativeConsoleAPI) { + await this.webConsoleUI.logWarningAboutReplacedAPI(); + } + })(); + + let timeoutId; + const connectionTimeout = new Promise((_, reject) => { + timeoutId = setTimeout(() => { + reject({ + error: "timeout", + message: l10n.getStr("connectionTimeout"), + }); + }, Services.prefs.getIntPref(PREF_CONNECTION_TIMEOUT)); + }); + + // If the connectionTimeout rejects before the connection promise settles, then + // this._connecter will reject with the same arguments connectionTimeout was + // rejected with. + this._connecter = Promise.race([connection, connectionTimeout]); + + // In case the connection was successful, cancel the setTimeout. + connection.then(() => clearTimeout(timeoutId)); + + return this._connecter; + } /** * Attach to the Web Console actor. * @private * @returns Promise */ - _attachConsole: function() { + _attachConsole() { const listeners = ["PageError", "ConsoleAPI", "NetworkActivity"]; // Enable the forwarding of console messages to the parent process // when we open the Browser Console or Toolbox. if (this.target.chrome && !this.target.isAddon) { listeners.push("ContentProcessMessages"); } - return this.webConsoleClient - .startListeners(listeners) - .then(this._onAttachConsole, error => { - console.error("attachConsole failed: " + error); - this._connectDefer.reject(error); - }); - }, + return this.webConsoleClient.startListeners(listeners); + } /** - * The "attachConsole" response handler. + * Sets all the relevant event listeners on the webconsole client. * * @private - * @param object response - * The JSON response object received from the server. - * @param object webConsoleClient - * The WebConsoleClient instance for the attached console, for the - * specific tab we work with. */ - _onAttachConsole: async function(response) { - let saveBodies = Services.prefs.getBoolPref( - "devtools.netmonitor.saveRequestAndResponseBodies" - ); - - // There is no way to view response bodies from the Browser Console, so do - // not waste the memory. - if (this.webConsoleUI.isBrowserConsole) { - saveBodies = false; - } - - this.webConsoleUI.setSaveRequestAndResponseBodies(saveBodies); - + _addWebConsoleClientEventListeners() { this.webConsoleClient.on("networkEvent", this._onNetworkEvent); this.webConsoleClient.on("networkEventUpdate", this._onNetworkEventUpdate); this.webConsoleClient.on("logMessage", this._onLogMessage); @@ -203,84 +145,62 @@ WebConsoleConnectionProxy.prototype = { "clearLogpointMessages", this._clearLogpointMessages ); - - const msgs = ["PageError", "ConsoleAPI"]; - const cachedMessages = await this.webConsoleClient.getCachedMessages(msgs); - this._onCachedMessages(cachedMessages); - }, + } /** - * Dispatch a message add on the new frontend and emit an event for tests. - */ - dispatchMessageAdd: function(packet) { - this.webConsoleUI.wrapper.dispatchMessageAdd(packet); - }, - - /** - * Batched dispatch of messages. - */ - dispatchMessagesAdd: function(packets) { - this.webConsoleUI.wrapper.dispatchMessagesAdd(packets); - }, - - /** - * Dispatch a message event on the new frontend and emit an event for tests. - */ - dispatchMessageUpdate: function(networkInfo, response) { - // Some message might try to update while we are closing the toolbox. - if (!this.webConsoleUI) { - return; - } - this.webConsoleUI.wrapper.dispatchMessageUpdate(networkInfo, response); - }, - - dispatchRequestUpdate: function(id, data) { - // Some request might try to update while we are closing the toolbox. - if (!this.webConsoleUI) { - return; - } - - this.webConsoleUI.wrapper.dispatchRequestUpdate(id, data); - }, - - /** - * The "cachedMessages" response handler. + * Remove all set event listeners on the webconsole client. * * @private - * @param object response - * The JSON response object received from the server. */ - _onCachedMessages: async function(response) { - if (response.error) { - console.error( - "Web Console getCachedMessages error: " + - response.error + - " " + - response.message - ); - this._connectDefer.reject(response); - return; - } - - if (!this._connectTimer) { - // This happens if the promise is rejected (eg. a timeout), but the - // connection attempt is successful, nonetheless. - console.error("Web Console getCachedMessages error: invalid state."); - } - - const messages = response.messages.concat( - ...this.webConsoleClient.getNetworkEvents() + _removeWebConsoleClientEventListeners() { + this.webConsoleClient.off("networkEvent", this._onNetworkEvent); + this.webConsoleClient.off("networkEventUpdate", this._onNetworkEventUpdate); + this.webConsoleClient.off("logMessage", this._onLogMessage); + this.webConsoleClient.off("pageError", this._onPageError); + this.webConsoleClient.off("consoleAPICall", this._onConsoleAPICall); + this.webConsoleClient.off( + "lastPrivateContextExited", + this._onLastPrivateContextExited ); - messages.sort((a, b) => a.timeStamp - b.timeStamp); + this.webConsoleClient.off( + "clearLogpointMessages", + this._clearLogpointMessages + ); + } - this.dispatchMessagesAdd(messages); - if (!this.webConsoleClient.hasNativeConsoleAPI) { - await this.webConsoleUI.logWarningAboutReplacedAPI(); + /** + * Get cached messages from the server. + * + * @private + * @returns A Promise that resolves with the cached messages, or reject if something + * went wront. + */ + async _getCachedMessages() { + const response = await this.webConsoleClient.getCachedMessages([ + "PageError", + "ConsoleAPI", + ]); + + if (response.error) { + throw new Error( + `Web Console getCachedMessages error: ${response.error} ${ + response.message + }` + ); } - this.connected = true; - this._connectDefer.resolve(this); - }, + return response.messages; + } + + /** + * Get network messages from the server. + * + * @private + * @returns An array of network messages. + */ + _getNetworkMessages() { + return Array.from(this.webConsoleClient.getNetworkEvents()); + } /** * The "pageError" message type handler. We redirect any page errors to the UI @@ -290,13 +210,14 @@ WebConsoleConnectionProxy.prototype = { * @param object packet * The message received from the server. */ - _onPageError: function(packet) { + _onPageError(packet) { if (!this.webConsoleUI) { return; } packet.type = "pageError"; this.dispatchMessageAdd(packet); - }, + } + /** * The "logMessage" message type handler. We redirect any message to the UI * for displaying. @@ -305,13 +226,14 @@ WebConsoleConnectionProxy.prototype = { * @param object packet * The message received from the server. */ - _onLogMessage: function(packet) { + _onLogMessage(packet) { if (!this.webConsoleUI) { return; } packet.type = "logMessage"; this.dispatchMessageAdd(packet); - }, + } + /** * The "consoleAPICall" message type handler. We redirect any message to * the UI for displaying. @@ -322,19 +244,19 @@ WebConsoleConnectionProxy.prototype = { * @param object packet * The message received from the server. */ - _onConsoleAPICall: function(packet) { + _onConsoleAPICall(packet) { if (!this.webConsoleUI) { return; } packet.type = "consoleAPICall"; this.dispatchMessageAdd(packet); - }, + } _clearLogpointMessages(logpointId) { if (this.webConsoleUI) { this.webConsoleUI.wrapper.dispatchClearLogpointMessages(logpointId); } - }, + } /** * The "networkEvent" message type handler. We redirect any message to @@ -344,12 +266,13 @@ WebConsoleConnectionProxy.prototype = { * @param object networkInfo * The network request information. */ - _onNetworkEvent: function(networkInfo) { + _onNetworkEvent(networkInfo) { if (!this.webConsoleUI) { return; } this.dispatchMessageAdd(networkInfo); - }, + } + /** * The "networkEventUpdate" message type handler. We redirect any message to * the UI for displaying. @@ -358,12 +281,13 @@ WebConsoleConnectionProxy.prototype = { * @param object response * The update response received from the server. */ - _onNetworkEventUpdate: function(response) { + _onNetworkEventUpdate(response) { if (!this.webConsoleUI) { return; } this.dispatchMessageUpdate(response.networkInfo, response); - }, + } + /** * The "lastPrivateContextExited" message type handler. When this message is * received the Web Console UI is cleared. @@ -374,11 +298,11 @@ WebConsoleConnectionProxy.prototype = { * @param object packet * The message received from the server. */ - _onLastPrivateContextExited: function(packet) { + _onLastPrivateContextExited(packet) { if (this.webConsoleUI) { this.webConsoleUI.clearPrivateMessages(); } - }, + } /** * The "navigate" event handlers. We redirect any message to the UI for displaying. @@ -387,9 +311,9 @@ WebConsoleConnectionProxy.prototype = { * @param object packet * The message received from the server. */ - _onTabNavigated: function(packet) { + _onTabNavigated(packet) { this.webConsoleUI.handleTabNavigated(packet); - }, + } /** * The "will-navigate" event handlers. We redirect any message to the UI for displaying. @@ -398,9 +322,43 @@ WebConsoleConnectionProxy.prototype = { * @param object packet * The message received from the server. */ - _onTabWillNavigate: function(packet) { + _onTabWillNavigate(packet) { this.webConsoleUI.handleTabWillNavigate(packet); - }, + } + + /** + * Dispatch a message add on the new frontend and emit an event for tests. + */ + dispatchMessageAdd(packet) { + this.webConsoleUI.wrapper.dispatchMessageAdd(packet); + } + + /** + * Batched dispatch of messages. + */ + dispatchMessagesAdd(packets) { + this.webConsoleUI.wrapper.dispatchMessagesAdd(packets); + } + + /** + * Dispatch a message event on the new frontend and emit an event for tests. + */ + dispatchMessageUpdate(networkInfo, response) { + // Some message might try to update while we are closing the toolbox. + if (!this.webConsoleUI) { + return; + } + this.webConsoleUI.wrapper.dispatchMessageUpdate(networkInfo, response); + } + + dispatchRequestUpdate(id, data) { + // Some request might try to update while we are closing the toolbox. + if (!this.webConsoleUI) { + return Promise.resolve(); + } + + return this.webConsoleUI.wrapper.dispatchRequestUpdate(id, data); + } /** * Release an object actor. @@ -408,11 +366,11 @@ WebConsoleConnectionProxy.prototype = { * @param string actor * The actor ID to send the request to. */ - releaseActor: function(actor) { + releaseActor(actor) { if (this.client) { this.client.release(actor).catch(() => {}); } - }, + } /** * Disconnect the Web Console from the remote server. @@ -420,47 +378,20 @@ WebConsoleConnectionProxy.prototype = { * @return object * A promise object that is resolved when disconnect completes. */ - disconnect: async function() { - if (this._disconnecter) { - return this._disconnecter.promise; - } - - this._disconnecter = defer(); - - // allow the console to finish attaching if it started. - if (this.isAttached) { - await this.isAttached; - } + disconnect() { if (!this.client) { - this._disconnecter.resolve(null); - return this._disconnecter.promise; + return; } - this.webConsoleClient.off("logMessage", this._onLogMessage); - this.webConsoleClient.off("pageError", this._onPageError); - this.webConsoleClient.off("consoleAPICall", this._onConsoleAPICall); - this.webConsoleClient.off( - "lastPrivateContextExited", - this._onLastPrivateContextExited - ); - this.webConsoleClient.off("networkEvent", this._onNetworkEvent); - this.webConsoleClient.off("networkEventUpdate", this._onNetworkEventUpdate); - this.webConsoleClient.off( - "clearLogpointMessages", - this._clearLogpointMessages - ); + this._removeWebConsoleClientEventListeners(); this.target.off("will-navigate", this._onTabWillNavigate); this.target.off("navigate", this._onTabNavigated); this.client = null; this.webConsoleClient = null; this.target = null; - this.connected = false; this.webConsoleUI = null; - this._disconnecter.resolve(null); - - return this._disconnecter.promise; - }, -}; + } +} exports.WebConsoleConnectionProxy = WebConsoleConnectionProxy; diff --git a/devtools/client/webconsole/webconsole-ui.js b/devtools/client/webconsole/webconsole-ui.js index b406de4c04..911556ee02 100644 --- a/devtools/client/webconsole/webconsole-ui.js +++ b/devtools/client/webconsole/webconsole-ui.js @@ -86,10 +86,10 @@ class WebConsoleUI { } destroy() { - if (this._destroyer) { - return this._destroyer.promise; + if (!this.hud) { + return; } - this._destroyer = defer(); + this.React = this.ReactDOM = this.FrameView = null; if (this.outputNode) { @@ -116,17 +116,10 @@ class WebConsoleUI { this.window = this.hud = this.wrapper = null; - const onDestroy = () => { - this._destroyer.resolve(null); - }; if (this.proxy) { - this.proxy.disconnect().then(onDestroy); + this.proxy.disconnect(); this.proxy = null; - } else { - onDestroy(); } - - return this._destroyer.promise; } /** diff --git a/devtools/client/webconsole/webconsole-wrapper.js b/devtools/client/webconsole/webconsole-wrapper.js index c702bd68f0..12b0cb49f7 100644 --- a/devtools/client/webconsole/webconsole-wrapper.js +++ b/devtools/client/webconsole/webconsole-wrapper.js @@ -94,7 +94,6 @@ class WebConsoleWrapper { const { prefs } = store.getState(); const autocomplete = prefs.autocomplete; - const editorFeatureEnabled = prefs.editor; this.prefsObservers = new Map(); this.prefsObservers.set(PREFS.UI.MESSAGE_TIMESTAMP, () => { @@ -119,7 +118,6 @@ class WebConsoleWrapper { onFirstMeaningfulPaint: resolve, closeSplitConsole: this.closeSplitConsole.bind(this), autocomplete, - editorFeatureEnabled, hidePersistLogsCheckbox: webConsoleUI.isBrowserConsole || webConsoleUI.isBrowserToolboxConsole, hideShowContentMessagesCheckbox: @@ -272,7 +270,7 @@ class WebConsoleWrapper { } dispatchRequestUpdate(id, data) { - this.batchedRequestUpdates({ id, data }); + return this.batchedRequestUpdates({ id, data }); } dispatchSidebarClose() { @@ -315,7 +313,7 @@ class WebConsoleWrapper { batchedRequestUpdates(message) { this.queuedRequestUpdates.push(message); - this.setTimeoutIfNeeded(); + return this.setTimeoutIfNeeded(); } batchedMessageAdd(message) { @@ -356,11 +354,10 @@ class WebConsoleWrapper { setTimeoutIfNeeded() { if (this.throttledDispatchPromise) { - return; + return this.throttledDispatchPromise; } - this.throttledDispatchPromise = new Promise(done => { - setTimeout(() => { + setTimeout(async () => { this.throttledDispatchPromise = null; if (!store) { @@ -390,16 +387,18 @@ class WebConsoleWrapper { this.queuedMessageAdds = []; if (this.queuedMessageUpdates.length > 0) { - this.queuedMessageUpdates.forEach(({ message, res }) => { - store.dispatch(actions.networkMessageUpdate(message, null, res)); + for (const { message, res } of this.queuedMessageUpdates) { + await store.dispatch( + actions.networkMessageUpdate(message, null, res) + ); this.webConsoleUI.emit("network-message-updated", res); - }); + } this.queuedMessageUpdates = []; } if (this.queuedRequestUpdates.length > 0) { - this.queuedRequestUpdates.forEach(({ id, data }) => { - store.dispatch(actions.networkUpdateRequest(id, data)); - }); + for (const { id, data } of this.queuedRequestUpdates) { + await store.dispatch(actions.networkUpdateRequest(id, data)); + } this.queuedRequestUpdates = []; // Fire an event indicating that all data fetched from @@ -414,6 +413,7 @@ class WebConsoleWrapper { done(); }, 50); }); + return this.throttledDispatchPromise; } getStore() { diff --git a/devtools/client/webconsole/webconsole.js b/devtools/client/webconsole/webconsole.js index 3116c6ef31..2cfb6f26e0 100644 --- a/devtools/client/webconsole/webconsole.js +++ b/devtools/client/webconsole/webconsole.js @@ -23,11 +23,6 @@ loader.lazyRequireGetter( "devtools/client/framework/devtools", true ); -loader.lazyRequireGetter( - this, - "viewSource", - "devtools/client/shared/view-source" -); loader.lazyRequireGetter( this, "openDocLink", @@ -223,17 +218,6 @@ class WebConsole { }); } - /** - * Tries to open a JavaScript file related to the web page for the web console - * instance in the corresponding Scratchpad. - * - * @param string sourceURL - * The URL of the file which corresponds to a Scratchpad id. - */ - viewSourceInScratchpad(sourceURL, sourceLine) { - viewSource.viewSourceInScratchpad(sourceURL, sourceLine); - } - /** * Retrieve information about the JavaScript debugger's stackframes list. This * is used to allow the Web Console to evaluate code in the selected @@ -350,29 +334,25 @@ class WebConsole { * @return object * A promise object that is resolved once the Web Console is closed. */ - async destroy() { - if (this._destroyer) { - return this._destroyer; + destroy() { + if (!this.hudId) { + return; } - this._destroyer = (async () => { + if (this.ui) { + this.ui.destroy(); + } - if (this.ui) { - await this.ui.destroy(); - } + if (this._parserService) { + this._parserService.stop(); + this._parserService = null; + } - if (this._parserService) { - this._parserService.stop(); - this._parserService = null; - } + const id = Utils.supportsString(this.hudId); + Services.obs.notifyObservers(id, "web-console-destroyed"); + this.hudId = null; - const id = Utils.supportsString(this.hudId); - Services.obs.notifyObservers(id, "web-console-destroyed"); - - this.emit("destroyed"); - })(); - - return this._destroyer; + this.emit("destroyed"); } } diff --git a/devtools/docs/backend/actor-registration.md b/devtools/docs/backend/actor-registration.md index 6d677c3534..cb7c16ecd6 100644 --- a/devtools/docs/backend/actor-registration.md +++ b/devtools/docs/backend/actor-registration.md @@ -23,9 +23,9 @@ ActorRegistry.registerModule("devtools/server/actors/webconsole", { To register a global actor: ``` -ActorRegistry.registerModule("devtools/server/actors/addon/addons", { - prefix: "addons", - constructor: "AddonsActor", +ActorRegistry.registerModule("devtools/server/actors/preference", { + prefix: "preference", + constructor: "PreferenceActor", type: { global: true } }); ``` diff --git a/devtools/docs/backend/client-api.md b/devtools/docs/backend/client-api.md index 9554783a01..bcefac0c54 100644 --- a/devtools/docs/backend/client-api.md +++ b/devtools/docs/backend/client-api.md @@ -137,8 +137,6 @@ Here is the source code for a complete debugger application: ```javascript /* * Debugger API demo. - * Try it in Scratchpad with Environment -> Browser, using - * http://htmlpad.org/debugger/ as the current page. */ Components.utils.import("resource://gre/modules/devtools/dbg-server.jsm"); Components.utils.import("resource://gre/modules/devtools/dbg-client.jsm"); diff --git a/devtools/docs/contributing/css.md b/devtools/docs/contributing/css.md index 5971b09fbf..e046c04d52 100644 --- a/devtools/docs/contributing/css.md +++ b/devtools/docs/contributing/css.md @@ -83,7 +83,7 @@ It's recommended to use SVG since it keeps the CSS clean when supporting multipl * Read [Writing Efficient CSS](https://developer.mozilla.org/en-US/docs/Web/Guide/CSS/Writing_efficient_CSS). * Use an iframe where possible so your rules are scoped to the smallest possible set of nodes. -* If your CSS is used in `browser.xul`, you need to take special care with performance: +* If your CSS is used in `browser.xhtml`, you need to take special care with performance: * Descendent selectors should be avoided. * If possible, find ways to use **only** id selectors, class selectors and selector groups. diff --git a/devtools/docs/files/adding-files.md b/devtools/docs/files/adding-files.md index 5fc53ecb37..4795e8f8ba 100644 --- a/devtools/docs/files/adding-files.md +++ b/devtools/docs/files/adding-files.md @@ -56,10 +56,10 @@ Example: Example: -* File: `/devtools/client/framework/gDevTools.jsm` +* File: `/toolkit/mozapps/extensions/AddonManager.jsm` * Usage (prefer lazy in most cases): - * `loader.lazyImporter(this, "gDevTools", "resource://devtools/client/framework/gDevTools.jsm")` - * `const { gDevTools } = ChromeUtils.import("resource://devtools/client/framework/gDevTools.jsm")` + * `loader.lazyImporter(this, "AddonManager", "resource://gre/modules/AddonManager.jsm")` + * `const { AddonManager } = ChromeUtils.import("resource://gre/modules/AddonManager.jsm")` ## Chrome Content diff --git a/devtools/docs/frontend/csp.md b/devtools/docs/frontend/csp.md index 859da57c19..b1265d6c05 100644 --- a/devtools/docs/frontend/csp.md +++ b/devtools/docs/frontend/csp.md @@ -5,7 +5,7 @@ The DevTools toolbox is loaded in an iframe pointing to about:devtools-toolbox. The current policy for about:devtools-toolbox is: ``` -default-src chrome: resource:; img-src chrome: resource: data:; +default-src chrome: resource:; img-src chrome: resource: data:; object-src 'none' ``` This means: diff --git a/devtools/docs/getting-started/development-profiles.md b/devtools/docs/getting-started/development-profiles.md index 74a9d53d56..41990eda94 100644 --- a/devtools/docs/getting-started/development-profiles.md +++ b/devtools/docs/getting-started/development-profiles.md @@ -52,7 +52,7 @@ Restart the browser to apply configuration changes. -These settings allow you to use the [browser toolbox](https://developer.mozilla.org/docs/Tools/Browser_Toolbox) to inspect the DevTools themselves, set breakpoints inside of DevTools code, and run the [Scratchpad](https://developer.mozilla.org/en-US/docs/Tools/Scratchpad) in the *Browser* environment. +These settings allow you to use the [browser toolbox](https://developer.mozilla.org/docs/Tools/Browser_Toolbox) to inspect the DevTools themselves, set breakpoints inside of DevTools code in the *Browser* environment. Open DevTools, and click the "Toolbox Options" gear icon in the top right (the image underneath is outdated). diff --git a/devtools/server/actors/addon/addons.js b/devtools/server/actors/addon/addons.js index 4b90a6bddf..91e9b5c4ef 100644 --- a/devtools/server/actors/addon/addons.js +++ b/devtools/server/actors/addon/addons.js @@ -9,6 +9,8 @@ const protocol = require("devtools/shared/protocol"); const { FileUtils } = require("resource://gre/modules/FileUtils.jsm"); const { addonsSpec } = require("devtools/shared/specs/addon/addons"); +// This actor is not used by DevTools, but is relied on externally by +// webext-run and the Firefox VS-Code plugin. see bug #1578108 const AddonsActor = protocol.ActorClassWithSpec(addonsSpec, { initialize: function(conn) { protocol.Actor.prototype.initialize.call(this, conn); diff --git a/devtools/server/actors/addon/moz.build b/devtools/server/actors/addon/moz.build index 40b296a0b1..8cd7e08145 100644 --- a/devtools/server/actors/addon/moz.build +++ b/devtools/server/actors/addon/moz.build @@ -5,5 +5,4 @@ DevToolsModules( 'addons.js', 'webextension-inspected-window.js', - 'webextension.js', ) diff --git a/devtools/server/actors/addon/webextension-inspected-window.js b/devtools/server/actors/addon/webextension-inspected-window.js index a5f6728027..e348206713 100644 --- a/devtools/server/actors/addon/webextension-inspected-window.js +++ b/devtools/server/actors/addon/webextension-inspected-window.js @@ -188,7 +188,7 @@ CustomizedReload.prototype = { } const document = subject; - const window = document && document.defaultView; + const window = document?.defaultView; // Filter out non interesting documents. if (!document || !document.location || !window) { @@ -623,14 +623,12 @@ var WebExtensionInspectedWindowActor = protocol.ActorClassWithSpec( throwErr && typeof throwErr === "object" && throwErr.unsafeDereference(); - const message = - unsafeDereference && unsafeDereference.toString - ? unsafeDereference.toString() - : String(throwErr); - const stack = - unsafeDereference && unsafeDereference.stack - ? unsafeDereference.stack - : null; + const message = unsafeDereference?.toString + ? unsafeDereference.toString() + : String(throwErr); + const stack = unsafeDereference?.stack + ? unsafeDereference.stack + : null; return { exceptionInfo: { diff --git a/devtools/server/actors/addon/webextension.js b/devtools/server/actors/addon/webextension.js deleted file mode 100644 index 2699102422..0000000000 --- a/devtools/server/actors/addon/webextension.js +++ /dev/null @@ -1,195 +0,0 @@ -/* 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"; - -/* - * Represents a WebExtension add-on in the parent process. This gives some metadata about - * the add-on and watches for uninstall events. This uses a proxy to access the - * WebExtension in the WebExtension process via the message manager. - * - * See devtools/docs/backend/actor-hierarchy.md for more details. - */ - -const protocol = require("devtools/shared/protocol"); -const { - webExtensionSpec, -} = require("devtools/shared/specs/addon/webextension"); -const { - WebExtensionTargetActorProxy, -} = require("devtools/server/actors/targets/webextension-proxy"); - -loader.lazyImporter( - this, - "AddonManager", - "resource://gre/modules/AddonManager.jsm" -); -loader.lazyImporter( - this, - "ExtensionParent", - "resource://gre/modules/ExtensionParent.jsm" -); - -/** - * Creates the actor that represents the addon in the parent process, which connects - * itself to a WebExtensionTargetActor counterpart which is created in the extension - * process (or in the main process if the WebExtensions OOP mode is disabled). - * - * The WebExtensionActor subscribes itself as an AddonListener on the AddonManager - * and forwards this events to child actor (e.g. on addon reload or when the addon is - * uninstalled completely) and connects to the child extension process using a `browser` - * element provided by the extension internals (it is not related to any single extension, - * but it will be created automatically to the currently selected "WebExtensions OOP mode" - * and it persist across the extension reloads (it is destroyed once the actor exits). - * WebExtensionActor is a child of RootActor, it can be retrieved via - * RootActor.listAddons request. - * - * @param {DebuggerServerConnection} conn - * The connection to the client. - * @param {AddonWrapper} addon - * The target addon. - */ -const WebExtensionActor = protocol.ActorClassWithSpec(webExtensionSpec, { - initialize(conn, addon) { - this.conn = conn; - this.addon = addon; - this.addonId = addon.id; - this._childFormPromise = null; - - AddonManager.addAddonListener(this); - }, - - destroy() { - AddonManager.removeAddonListener(this); - - this.addon = null; - this._childFormPromise = null; - - if (this._destroyProxy) { - this._destroyProxy(); - delete this._destroyProxy; - } - }, - - reload() { - return this.addon.reload().then(() => { - return {}; - }); - }, - - form() { - const policy = ExtensionParent.WebExtensionPolicy.getByID(this.addonId); - return { - actor: this.actorID, - debuggable: this.addon.isDebuggable, - hidden: this.addon.hidden, - // iconDataURL is available after calling loadIconDataURL - iconDataURL: this._iconDataURL, - iconURL: this.addon.iconURL, - id: this.addonId, - isAPIExtension: this.addon.isAPIExtension, - isSystem: this.addon.isSystem, - isWebExtension: this.addon.isWebExtension, - manifestURL: policy && policy.getURL("manifest.json"), - name: this.addon.name, - temporarilyInstalled: this.addon.temporarilyInstalled, - type: this.addon.type, - url: this.addon.sourceURI ? this.addon.sourceURI.spec : undefined, - warnings: ExtensionParent.DebugUtils.getExtensionManifestWarnings( - this.addonId - ), - }; - }, - - connect() { - if (this._childFormPormise) { - return this._childFormPromise; - } - - const proxy = new WebExtensionTargetActorProxy(this.conn, this); - this._childFormPromise = proxy.connect().then(form => { - // Merge into the child actor form, some addon metadata - // (e.g. the addon name shown in the addon debugger window title). - return Object.assign(form, { - iconURL: this.addon.iconURL, - id: this.addon.id, - // Set the isOOP attribute on the connected child actor form. - isOOP: proxy.isOOP, - name: this.addon.name, - }); - }); - this._destroyProxy = () => proxy.destroy(); - - return this._childFormPromise; - }, - - // This function will be called from RootActor in case that the debugger client - // retrieves list of addons with `iconDataURL` option. - async loadIconDataURL() { - this._iconDataURL = await this.getIconDataURL(); - }, - - async getIconDataURL() { - if (!this.addon.iconURL) { - return null; - } - - const xhr = new XMLHttpRequest(); - xhr.responseType = "blob"; - xhr.open("GET", this.addon.iconURL, true); - - if (this.addon.iconURL.toLowerCase().endsWith(".svg")) { - // Maybe SVG, thus force to change mime type. - xhr.overrideMimeType("image/svg+xml"); - } - - try { - const blob = await new Promise((resolve, reject) => { - xhr.onload = () => resolve(xhr.response); - xhr.onerror = reject; - xhr.send(); - }); - - const reader = new FileReader(); - return await new Promise((resolve, reject) => { - reader.onloadend = () => resolve(reader.result); - reader.onerror = reject; - reader.readAsDataURL(blob); - }); - } catch (_) { - console.warn(`Failed to create data url from [${this.addon.iconURL}]`); - return null; - } - }, - - // WebExtensionTargetActorProxy callbacks. - - onProxyDestroy() { - // Invalidate the cached child actor and form Promise - // if the child actor exits. - this._childFormPromise = null; - delete this._destroyProxy; - }, - - // AddonManagerListener callbacks. - - onInstalled(addon) { - if (addon.id != this.addonId) { - return; - } - - // Update the AddonManager's addon object on reload/update. - this.addon = addon; - }, - - onUninstalled(addon) { - if (addon != this.addon) { - return; - } - - this.destroy(); - }, -}); - -exports.WebExtensionActor = WebExtensionActor; diff --git a/devtools/server/actors/animation-type-longhand.js b/devtools/server/actors/animation-type-longhand.js index 109f23f92c..84af3cd68d 100644 --- a/devtools/server/actors/animation-type-longhand.js +++ b/devtools/server/actors/animation-type-longhand.js @@ -140,7 +140,6 @@ exports.ANIMATION_TYPE_FOR_LONGHANDS = [ "scroll-snap-align", "scroll-snap-type", "shape-rendering", - "-moz-stack-sizing", "scrollbar-width", "stroke-linecap", "stroke-linejoin", @@ -190,7 +189,6 @@ exports.ANIMATION_TYPE_FOR_LONGHANDS = [ "animation-name", "animation-play-state", "animation-timing-function", - "-moz-binding", "block-size", "border-block-end-color", "border-block-end-style", diff --git a/devtools/server/actors/common.js b/devtools/server/actors/common.js index dad12384bf..2f8c17772e 100644 --- a/devtools/server/actors/common.js +++ b/devtools/server/actors/common.js @@ -100,6 +100,20 @@ ActorPool.prototype = { callback(this._actors[name]); } }, + + // Generator that yields each non-self child of the pool. + *poolChildren() { + if (!this._actors) { + return; + } + for (const actor of Object.values(this._actors)) { + // Self-owned actors are ok, but don't need visiting twice. + if (actor === this) { + continue; + } + yield actor; + } + }, }; exports.ActorPool = ActorPool; diff --git a/devtools/server/actors/descriptors/moz.build b/devtools/server/actors/descriptors/moz.build index 6f32607941..6f17d1523d 100644 --- a/devtools/server/actors/descriptors/moz.build +++ b/devtools/server/actors/descriptors/moz.build @@ -4,4 +4,6 @@ DevToolsModules( 'process.js', + 'webextension.js', ) + diff --git a/devtools/server/actors/descriptors/webextension.js b/devtools/server/actors/descriptors/webextension.js new file mode 100644 index 0000000000..0e94e9ade5 --- /dev/null +++ b/devtools/server/actors/descriptors/webextension.js @@ -0,0 +1,262 @@ +/* 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"; + +/* + * Represents a WebExtension add-on in the parent process. This gives some metadata about + * the add-on and watches for uninstall events. This uses a proxy to access the + * WebExtension in the WebExtension process via the message manager. + * + * See devtools/docs/backend/actor-hierarchy.md for more details. + */ + +const protocol = require("devtools/shared/protocol"); +const { + webExtensionDescriptorSpec, +} = require("devtools/shared/specs/descriptors/webextension"); +const { DebuggerServer } = require("devtools/server/debugger-server"); + +loader.lazyImporter( + this, + "AddonManager", + "resource://gre/modules/AddonManager.jsm" +); +loader.lazyImporter( + this, + "ExtensionParent", + "resource://gre/modules/ExtensionParent.jsm" +); + +/** + * Creates the actor that represents the addon in the parent process, which connects + * itself to a WebExtensionTargetActor counterpart which is created in the extension + * process (or in the main process if the WebExtensions OOP mode is disabled). + * + * The WebExtensionDescriptorActor subscribes itself as an AddonListener on the AddonManager + * and forwards this events to child actor (e.g. on addon reload or when the addon is + * uninstalled completely) and connects to the child extension process using a `browser` + * element provided by the extension internals (it is not related to any single extension, + * but it will be created automatically to the currently selected "WebExtensions OOP mode" + * and it persist across the extension reloads (it is destroyed once the actor exits). + * WebExtensionDescriptorActor is a child of RootActor, it can be retrieved via + * RootActor.listAddons request. + * + * @param {DebuggerServerConnection} conn + * The connection to the client. + * @param {AddonWrapper} addon + * The target addon. + */ +const WebExtensionDescriptorActor = protocol.ActorClassWithSpec( + webExtensionDescriptorSpec, + { + initialize(conn, addon) { + protocol.Actor.prototype.initialize.call(this, conn); + this.addon = addon; + this.addonId = addon.id; + this._childFormPromise = null; + + // Called when the debug browser element has been destroyed + this._extensionFrameDisconnect = this._extensionFrameDisconnect.bind( + this + ); + this._onChildExit = this._onChildExit.bind(this); + AddonManager.addAddonListener(this); + }, + + form() { + const policy = ExtensionParent.WebExtensionPolicy.getByID(this.addonId); + return { + actor: this.actorID, + debuggable: this.addon.isDebuggable, + hidden: this.addon.hidden, + // iconDataURL is available after calling loadIconDataURL + iconDataURL: this._iconDataURL, + iconURL: this.addon.iconURL, + id: this.addonId, + isAPIExtension: this.addon.isAPIExtension, + isSystem: this.addon.isSystem, + isWebExtension: this.addon.isWebExtension, + manifestURL: policy && policy.getURL("manifest.json"), + name: this.addon.name, + temporarilyInstalled: this.addon.temporarilyInstalled, + type: this.addon.type, + url: this.addon.sourceURI ? this.addon.sourceURI.spec : undefined, + warnings: ExtensionParent.DebugUtils.getExtensionManifestWarnings( + this.addonId + ), + }; + }, + + connect() { + return this.getTarget(); + }, + + getTarget() { + if (this._childTargetPromise) { + return this._childTargetPromise; + } + + this._childTargetPromise = (async () => { + const form = await this._extensionFrameConnect(); + // Merge into the child actor form, some addon metadata + // (e.g. the addon name shown in the addon debugger window title). + return Object.assign(form, { + iconURL: this.addon.iconURL, + id: this.addon.id, + // Set the isOOP attribute on the connected child actor form. + isOOP: this.isOOP, + name: this.addon.name, + }); + })(); + + return this._childTargetPromise; + }, + + async _extensionFrameConnect() { + if (this._browser) { + throw new Error( + "This actor is already connected to the extension process" + ); + } + + this._browser = await ExtensionParent.DebugUtils.getExtensionProcessBrowser( + this + ); + + this._form = await DebuggerServer.connectToFrame( + this.conn, + this._browser, + this._extensionFrameDisconnect, + { addonId: this.addonId } + ); + + this._childActorID = this._form.actor; + + // Exit the proxy child actor if the child actor has been destroyed. + this._mm.addMessageListener("debug:webext_child_exit", this._onChildExit); + + return this._form; + }, + + /** WebExtension Actor Methods **/ + async reload() { + await this.addon.reload(); + return {}; + }, + + // This function will be called from RootActor in case that the debugger client + // retrieves list of addons with `iconDataURL` option. + async loadIconDataURL() { + this._iconDataURL = await this.getIconDataURL(); + }, + + async getIconDataURL() { + if (!this.addon.iconURL) { + return null; + } + + const xhr = new XMLHttpRequest(); + xhr.responseType = "blob"; + xhr.open("GET", this.addon.iconURL, true); + + if (this.addon.iconURL.toLowerCase().endsWith(".svg")) { + // Maybe SVG, thus force to change mime type. + xhr.overrideMimeType("image/svg+xml"); + } + + try { + const blob = await new Promise((resolve, reject) => { + xhr.onload = () => resolve(xhr.response); + xhr.onerror = reject; + xhr.send(); + }); + + const reader = new FileReader(); + return await new Promise((resolve, reject) => { + reader.onloadend = () => resolve(reader.result); + reader.onerror = reject; + reader.readAsDataURL(blob); + }); + } catch (_) { + console.warn(`Failed to create data url from [${this.addon.iconURL}]`); + return null; + } + }, + + // TODO: check if we need this, as it is only used in a test + get isOOP() { + return this._browser ? this._browser.isRemoteBrowser : undefined; + }, + + // Private Methods + get _mm() { + return ( + this._browser && + (this._browser.messageManager || + this._browser.frameLoader.messageManager) + ); + }, + + _extensionFrameDisconnect() { + this._childTargetPromise = null; + AddonManager.removeAddonListener(this); + + this.addon = null; + this._childTargetPromise = null; + + if (this._mm) { + this._mm.removeMessageListener( + "debug:webext_child_exit", + this._onChildExit + ); + + this._mm.sendAsyncMessage("debug:webext_parent_exit", { + actor: this._childActorID, + }); + + ExtensionParent.DebugUtils.releaseExtensionProcessBrowser(this); + } + + this._browser = null; + this._childActorID = null; + }, + + /** + * Handle the child actor exit. + */ + _onChildExit(msg) { + if (msg.json.actor !== this._childActorID) { + return; + } + + this._extensionFrameDisconnect(); + }, + + // AddonManagerListener callbacks. + onInstalled(addon) { + if (addon.id != this.addonId) { + return; + } + + // Update the AddonManager's addon object on reload/update. + this.addon = addon; + }, + + onUninstalled(addon) { + if (addon != this.addon) { + return; + } + + this._extensionFrameDisconnect(); + }, + + destroy() { + this._extensionFrameDisconnect(); + protocol.Actor.prototype.destroy.call(this); + }, + } +); + +exports.WebExtensionDescriptorActor = WebExtensionDescriptorActor; diff --git a/devtools/server/actors/environment.js b/devtools/server/actors/environment.js index 876c99784a..ed5ba846c9 100644 --- a/devtools/server/actors/environment.js +++ b/devtools/server/actors/environment.js @@ -147,7 +147,7 @@ const EnvironmentActor = ActorClassWithSpec(environmentSpec, { const desc = { value: value, configurable: false, - writable: !(value && value.optimizedOut), + writable: !value?.optimizedOut, enumerable: true, }; diff --git a/devtools/server/actors/highlighters.js b/devtools/server/actors/highlighters.js index d0b5e59804..9657dcf32a 100644 --- a/devtools/server/actors/highlighters.js +++ b/devtools/server/actors/highlighters.js @@ -113,8 +113,6 @@ exports.HighlighterActor = protocol.ActorClassWithSpec(highlighterSpec, { this._highlighterEnv = new HighlighterEnvironment(); this._highlighterEnv.initFromTargetActor(this._targetActor); - this._highlighterReady = this._highlighterReady.bind(this); - this._highlighterHidden = this._highlighterHidden.bind(this); this._onNavigate = this._onNavigate.bind(this); const doc = this._targetActor.window.document; @@ -147,8 +145,6 @@ exports.HighlighterActor = protocol.ActorClassWithSpec(highlighterSpec, { this._highlighterEnv, this._inspector ); - this._highlighter.on("ready", this._highlighterReady); - this._highlighter.on("hide", this._highlighterHidden); } else { this._highlighter = new SimpleOutlineHighlighter(this._highlighterEnv); } @@ -156,10 +152,6 @@ exports.HighlighterActor = protocol.ActorClassWithSpec(highlighterSpec, { _destroyHighlighter: function() { if (this._highlighter) { - if (!this._isPreviousWindowXUL) { - this._highlighter.off("ready", this._highlighterReady); - this._highlighter.off("hide", this._highlighterHidden); - } this._highlighter.destroy(); this._highlighter = null; } @@ -375,9 +367,11 @@ exports.HighlighterActor = protocol.ActorClassWithSpec(highlighterSpec, { this._walker.emit("picker-node-canceled"); return; case event.DOM_VK_C: + const { altKey, ctrlKey, metaKey, shiftKey } = event; + if ( - (IS_OSX && event.metaKey && event.altKey) || - (!IS_OSX && event.ctrlKey && event.shiftKey) + (IS_OSX && metaKey && altKey | shiftKey) || + (!IS_OSX && ctrlKey && shiftKey) ) { this.cancelPick(); this._walker.emit("picker-node-canceled"); @@ -476,14 +470,6 @@ exports.HighlighterActor = protocol.ActorClassWithSpec(highlighterSpec, { this._setSuppressedEventListener(null); }, - _highlighterReady: function() { - this._inspector.walker.emit("highlighter-ready"); - }, - - _highlighterHidden: function() { - this._inspector.walker.emit("highlighter-hide"); - }, - cancelPick: function() { if (this._targetActor.threadActor) { this._targetActor.threadActor.showOverlay(); @@ -587,7 +573,7 @@ exports.CustomHighlighterActor = protocol.ActorClassWithSpec( return null; } - const rawNode = node && node.rawNode; + const rawNode = node?.rawNode; return this._highlighter.show(rawNode, options); }, diff --git a/devtools/server/actors/highlighters/box-model.js b/devtools/server/actors/highlighters/box-model.js index b4b28b52ea..06d0486cdd 100644 --- a/devtools/server/actors/highlighters/box-model.js +++ b/devtools/server/actors/highlighters/box-model.js @@ -331,7 +331,6 @@ class BoxModelHighlighter extends AutoRefreshHighlighter { const shown = this._update(); this._trackMutations(); - this.emit("ready"); return shown; } diff --git a/devtools/server/actors/highlighters/paused-debugger.js b/devtools/server/actors/highlighters/paused-debugger.js index a12953ae94..77d2f42c3e 100644 --- a/devtools/server/actors/highlighters/paused-debugger.js +++ b/devtools/server/actors/highlighters/paused-debugger.js @@ -24,7 +24,6 @@ loader.lazyGetter(this, "L10N", () => { */ function PausedDebuggerOverlay(highlighterEnv, options = {}) { this.env = highlighterEnv; - this.showOverlayStepButtons = options.showOverlayStepButtons; this.resume = options.resume; this.stepOver = options.stepOver; @@ -80,54 +79,52 @@ PausedDebuggerOverlay.prototype = { prefix, }); - if (this.showOverlayStepButtons) { - createNode(window, { - parent: toolbar, - attributes: { - id: "divider", - class: "divider", - }, - prefix, - }); + createNode(window, { + parent: toolbar, + attributes: { + id: "divider", + class: "divider", + }, + prefix, + }); - const stepWrapper = createNode(window, { - parent: toolbar, - attributes: { - id: "step-button-wrapper", - class: "step-button-wrapper", - }, - prefix, - }); + const stepWrapper = createNode(window, { + parent: toolbar, + attributes: { + id: "step-button-wrapper", + class: "step-button-wrapper", + }, + prefix, + }); - createNode(window, { - nodeType: "button", - parent: stepWrapper, - attributes: { - id: "step-button", - class: "step-button", - }, - prefix, - }); + createNode(window, { + nodeType: "button", + parent: stepWrapper, + attributes: { + id: "step-button", + class: "step-button", + }, + prefix, + }); - const resumeWrapper = createNode(window, { - parent: toolbar, - attributes: { - id: "resume-button-wrapper", - class: "resume-button-wrapper", - }, - prefix, - }); + const resumeWrapper = createNode(window, { + parent: toolbar, + attributes: { + id: "resume-button-wrapper", + class: "resume-button-wrapper", + }, + prefix, + }); - createNode(window, { - nodeType: "button", - parent: resumeWrapper, - attributes: { - id: "resume-button", - class: "resume-button", - }, - prefix, - }); - } + createNode(window, { + nodeType: "button", + parent: resumeWrapper, + attributes: { + id: "resume-button", + class: "resume-button", + }, + prefix, + }); return container; }, diff --git a/devtools/server/actors/highlighters/utils/markup.js b/devtools/server/actors/highlighters/utils/markup.js index a4830e710b..8d50aece8a 100644 --- a/devtools/server/actors/highlighters/utils/markup.js +++ b/devtools/server/actors/highlighters/utils/markup.js @@ -9,7 +9,6 @@ const { getCurrentZoom, getWindowDimensions, getViewportDimensions, - getRootBindingParent, loadSheet, } = require("devtools/shared/layout/utils"); const EventEmitter = require("devtools/shared/event-emitter"); @@ -102,7 +101,14 @@ ClassList.prototype = { * @return {Boolean} */ function isXUL(window) { - return window.document.documentElement.namespaceURI === XUL_NS; + // XXX: We temporarily return true for HTML documents if the document disables + // scroll frames since the regular highlighter is broken in this case. This + // should be removed when bug 1594587 is fixed. + return ( + window.document.documentElement.namespaceURI === XUL_NS || + (window.isChromeWindow && + window.document.documentElement.getAttribute("scrolling") === "false") + ); } exports.isXUL = isXUL; @@ -130,10 +136,8 @@ function isNodeValid(node, nodeType = Node.ELEMENT_NODE) { return false; } - // Is the node connected to the document? Using getBindingParent adds - // support for anonymous elements generated by a node in the document. - const bindingParent = getRootBindingParent(node); - if (!doc.documentElement.contains(bindingParent)) { + // Is the node connected to the document? + if (!node.isConnected) { return false; } diff --git a/devtools/server/actors/inspector/css-logic.js b/devtools/server/actors/inspector/css-logic.js index 9fe346888c..a0a00d9b88 100644 --- a/devtools/server/actors/inspector/css-logic.js +++ b/devtools/server/actors/inspector/css-logic.js @@ -33,6 +33,7 @@ const { getBindingElementAndPseudo, getCSSStyleRules, l10n, + hasVisitedState, isAgentStylesheet, isAuthorStylesheet, isUserStylesheet, @@ -319,7 +320,7 @@ CssLogic.prototype = { if (domSheet.href) { cacheId = domSheet.href; - } else if (domSheet.ownerNode && domSheet.ownerNode.ownerDocument) { + } else if (domSheet.ownerNode?.ownerDocument) { cacheId = domSheet.ownerNode.ownerDocument.location; } @@ -653,6 +654,12 @@ CssLogic.getShortName = function(element) { * An array of string selectors. */ CssLogic.getSelectors = function(domRule) { + if (domRule.type === CSSRule.MEDIA_RULE) { + // Return empty array since InspectorUtils.getSelectorCount() does not assume + // MEDIA_RULE type. + return []; + } + const selectors = []; const len = InspectorUtils.getSelectorCount(domRule); @@ -724,6 +731,11 @@ CssLogic.href = function(sheet) { return href; }; +/** + * Returns true if the given node has visited state. + */ +CssLogic.hasVisitedState = hasVisitedState; + /** * A safe way to access cached bits of information about a stylesheet. * diff --git a/devtools/server/actors/inspector/custom-element-watcher.js b/devtools/server/actors/inspector/custom-element-watcher.js index 50defb417f..0732585e04 100644 --- a/devtools/server/actors/inspector/custom-element-watcher.js +++ b/devtools/server/actors/inspector/custom-element-watcher.js @@ -138,8 +138,7 @@ class CustomElementWatcher extends EventEmitter { return ( !Cu.isDeadWrapper(node) && node.ownerGlobal && - node.ownerDocument && - node.ownerDocument.documentElement + node.ownerDocument?.documentElement ); } } diff --git a/devtools/server/actors/inspector/document-walker.js b/devtools/server/actors/inspector/document-walker.js index 892ff87f0b..d44bae42d1 100644 --- a/devtools/server/actors/inspector/document-walker.js +++ b/devtools/server/actors/inspector/document-walker.js @@ -193,8 +193,8 @@ DocumentWalker.prototype = { let previous = node; let next = node; while (previous || next) { - previous = previous && previous.previousSibling; - next = next && next.nextSibling; + previous = previous?.previousSibling; + next = next?.nextSibling; if ( previous && diff --git a/devtools/server/actors/inspector/event-collector.js b/devtools/server/actors/inspector/event-collector.js index 1ec206b497..8994a900d3 100644 --- a/devtools/server/actors/inspector/event-collector.js +++ b/devtools/server/actors/inspector/event-collector.js @@ -289,8 +289,7 @@ class MainEventCollector { return null; } - const hasJQuery = - global.jQuery && global.jQuery.fn && global.jQuery.fn.jquery; + const hasJQuery = global.jQuery?.fn?.jquery; if (hasJQuery) { return global.jQuery; @@ -577,7 +576,7 @@ class JQueryLiveEventCollector extends MainEventCollector { }, }; - if (!eventInfo.type && event.data && event.data.live) { + if (!eventInfo.type && event.data?.live) { eventInfo.type = event.data.live; } @@ -666,7 +665,7 @@ class ReactEventCollector extends MainEventCollector { if (props) { for (const [name, prop] of Object.entries(props)) { if (REACT_EVENT_NAMES.includes(name)) { - const listener = (prop && prop.__reactBoundMethod) || prop; + const listener = prop?.__reactBoundMethod || prop; if (typeof listener !== "function") { continue; @@ -713,7 +712,7 @@ class ReactEventCollector extends MainEventCollector { if (value.memoizedProps) { return value.memoizedProps; // React 16 } - return value && value._currentElement && value._currentElement.props; // React 15 + return value?._currentElement?.props; // React 15 } } return null; @@ -939,7 +938,7 @@ class EventCollector { listenerDO = listenerDO.proto; } - if (desc && desc.value) { + if (desc?.value) { listenerDO = desc.value; } } diff --git a/devtools/server/actors/inspector/node.js b/devtools/server/actors/inspector/node.js index 5af7c2b70d..9d28e01729 100644 --- a/devtools/server/actors/inspector/node.js +++ b/devtools/server/actors/inspector/node.js @@ -29,6 +29,12 @@ loader.lazyRequireGetter( "devtools/shared/inspector/css-logic", true ); +loader.lazyRequireGetter( + this, + "findAllCssSelectors", + "devtools/shared/inspector/css-logic", + true +); loader.lazyRequireGetter( this, @@ -66,12 +72,6 @@ loader.lazyRequireGetter( "devtools/shared/layout/utils", true ); -loader.lazyRequireGetter( - this, - "isShadowAnonymous", - "devtools/shared/layout/utils", - true -); loader.lazyRequireGetter( this, "isShadowHost", @@ -90,13 +90,6 @@ loader.lazyRequireGetter( "devtools/shared/layout/utils", true ); -loader.lazyRequireGetter( - this, - "isXBLAnonymous", - "devtools/shared/layout/utils", - true -); - loader.lazyRequireGetter( this, "InspectorActorUtils", @@ -138,6 +131,12 @@ loader.lazyRequireGetter( "devtools/server/actors/inspector/utils", true ); +loader.lazyRequireGetter( + this, + "DOMHelpers", + "resource://devtools/client/shared/DOMHelpers.jsm", + true +); const SUBGRID_ENABLED = Services.prefs.getBoolPref( "layout.css.grid-template-subgrid-value.enabled" @@ -243,8 +242,6 @@ const NodeActor = protocol.ActorClassWithSpec(nodeSpec, { isAfterPseudoElement: isAfterPseudoElement(this.rawNode), isAnonymous: isAnonymous(this.rawNode), isNativeAnonymous: isNativeAnonymous(this.rawNode), - isXBLAnonymous: isXBLAnonymous(this.rawNode), - isShadowAnonymous: isShadowAnonymous(this.rawNode), isShadowRoot: shadowRoot, shadowRootMode: getShadowRootMode(this.rawNode), isShadowHost: isShadowHost(this.rawNode), @@ -257,6 +254,10 @@ const NodeActor = protocol.ActorClassWithSpec(nodeSpec, { this.rawNode.ownerDocument && this.rawNode.ownerDocument.contentType === "text/html", hasEventListeners: this._hasEventListeners, + traits: { + supportsGetAllSelectors: true, + supportsWaitForFrameLoad: true, + }, }; if (this.isDocumentElement()) { @@ -310,10 +311,6 @@ const NodeActor = protocol.ActorClassWithSpec(nodeSpec, { const rawNode = this.rawNode; let numChildren = rawNode.childNodes.length; - const hasAnonChildren = - rawNode.nodeType === Node.ELEMENT_NODE && - rawNode.ownerDocument.getAnonymousNodes(rawNode); - const hasContentDocument = rawNode.contentDocument; const hasSVGDocument = rawNode.getSVGDocument && rawNode.getSVGDocument(); if (numChildren === 0 && (hasContentDocument || hasSVGDocument)) { @@ -323,11 +320,13 @@ const NodeActor = protocol.ActorClassWithSpec(nodeSpec, { // Normal counting misses ::before/::after. Also, some anonymous children // may ultimately be skipped, so we have to consult with the walker. + // + // FIXME: We should be able to just check rather than + // containingShadowRoot. if ( numChildren === 0 || - hasAnonChildren || isShadowHost(this.rawNode) || - isShadowAnonymous(this.rawNode) + this.rawNode.containingShadowRoot ) { numChildren = this.walker.countChildren(this); } @@ -526,11 +525,22 @@ const NodeActor = protocol.ActorClassWithSpec(nodeSpec, { */ getUniqueSelector: function() { if (Cu.isDeadWrapper(this.rawNode)) { - return ""; + return []; } return findCssSelector(this.rawNode); }, + /** + * Get the full array of selectors from the topmost document, going through + * iframes. + */ + getAllSelectors: function() { + if (Cu.isDeadWrapper(this.rawNode)) { + return ""; + } + return findAllCssSelectors(this.rawNode); + }, + /** * Get the full CSS path for this node. * @@ -671,6 +681,23 @@ const NodeActor = protocol.ActorClassWithSpec(nodeSpec, { innerHeight: win.innerHeight, }; }, + + /** + * If the current node is an iframe, wait for the content window to be loaded. + */ + async waitForFrameLoad() { + if (Cu.isDeadWrapper(this.rawNode)) { + return; + } + + const { contentDocument, contentWindow } = this.rawNode; + if (contentDocument && contentDocument.readyState !== "complete") { + await new Promise(resolve => { + const domHelper = new DOMHelpers(contentWindow); + domHelper.onceDOMReady(resolve); + }); + } + }, }); /** diff --git a/devtools/server/actors/inspector/utils.js b/devtools/server/actors/inspector/utils.js index 3f8470db54..9f4f6b8a7b 100644 --- a/devtools/server/actors/inspector/utils.js +++ b/devtools/server/actors/inspector/utils.js @@ -19,20 +19,12 @@ loader.lazyRequireGetter( "nodeFilterConstants", "devtools/shared/dom-node-filter-constants" ); - loader.lazyRequireGetter( this, "isNativeAnonymous", "devtools/shared/layout/utils", true ); -loader.lazyRequireGetter( - this, - "isXBLAnonymous", - "devtools/shared/layout/utils", - true -); - loader.lazyRequireGetter( this, "CssLogic", @@ -107,9 +99,7 @@ function isNodeDead(node) { function isInXULDocument(el) { const doc = nodeDocument(el); - return ( - doc && doc.documentElement && doc.documentElement.namespaceURI === XUL_NS - ); + return doc?.documentElement && doc.documentElement.namespaceURI === XUL_NS; } /** @@ -135,13 +125,10 @@ function standardTreeWalkerFilter(node) { : nodeFilterConstants.FILTER_SKIP; } - // Ignore all native and XBL anonymous content inside a non-XUL document. + // Ignore all native anonymous content inside a non-XUL document. // We need to do this to skip things like form controls, scrollbars, // video controls, etc (see bug 1187482). - if ( - !isInXULDocument(node) && - (isXBLAnonymous(node) || isNativeAnonymous(node)) - ) { + if (!isInXULDocument(node) && isNativeAnonymous(node)) { return nodeFilterConstants.FILTER_SKIP; } diff --git a/devtools/server/actors/inspector/walker.js b/devtools/server/actors/inspector/walker.js index ddb8c05de4..e008e54352 100644 --- a/devtools/server/actors/inspector/walker.js +++ b/devtools/server/actors/inspector/walker.js @@ -8,7 +8,7 @@ const { Cc, Ci, Cu } = require("chrome"); const Services = require("Services"); const protocol = require("devtools/shared/protocol"); -const { walkerSpec } = require("devtools/shared/specs/inspector"); +const { walkerSpec } = require("devtools/shared/specs/walker"); const { LongStringActor } = require("devtools/server/actors/string"); const InspectorUtils = require("InspectorUtils"); const { @@ -170,6 +170,16 @@ loader.lazyRequireGetter( true ); +// ContentDOMReference requires ChromeUtils, which isn't available in worker context. +if (!isWorker) { + loader.lazyRequireGetter( + this, + "ContentDOMReference", + "resource://gre/modules/ContentDOMReference.jsm", + true + ); +} + loader.lazyServiceGetter( this, "eventListenerService", @@ -177,8 +187,6 @@ loader.lazyServiceGetter( "nsIEventListenerService" ); -loader.lazyRequireGetter(this, "ChromeUtils"); - // Minimum delay between two "new-mutations" events. const MUTATIONS_THROTTLING_DELAY = 100; // List of mutation types that should -not- be throttled. @@ -373,7 +381,10 @@ var WalkerActor = protocol.ActorClassWithSpec(walkerSpec, { return { actor: this.actorID, root: this.rootNode.form(), - traits: {}, + traits: { + // Firefox 71: getNodeActorFromContentDomReference is available. + retrieveNodeFromContentDomReference: true, + }, }; }, @@ -741,7 +752,7 @@ var WalkerActor = protocol.ActorClassWithSpec(walkerSpec, { rawNode.nodeName === "SLOT" && isDirectShadowHostChild(firstChild); - const isFlexItem = !!(firstChild && firstChild.parentFlexElement); + const isFlexItem = !!firstChild?.parentFlexElement; if ( !firstChild || @@ -2413,16 +2424,9 @@ var WalkerActor = protocol.ActorClassWithSpec(walkerSpec, { const { readyState } = window.document; if (readyState != "interactive" && readyState != "complete") { // The document is not loaded, so we want to register to fire again when the - // DOM has been loaded. To do this, we need to know if this is a XUL document. - // We listen for "DOMContentLoaded" on HTML documents, but XUL documents don't - // fire this event, so we fallback to the "load" event for XUL. Unfortunately, - // since the document isn't loaded yet, we can't check its namespace declaration - // to determine if it is XUL. Instead, we use ChromeUtils to see if the document - // object class is XULDocument. - const isXULDocument = - ChromeUtils.getClassName(window.document) == "XULDocument"; + // DOM has been loaded. window.addEventListener( - isXULDocument ? "load" : "DOMContentLoaded", + "DOMContentLoaded", this.onFrameLoad.bind(this, { window, isTopLevel }), { once: true } ); @@ -2641,6 +2645,24 @@ var WalkerActor = protocol.ActorClassWithSpec(walkerSpec, { return this.attachElement(win.frameElement); }, + /** + * Given a contentDomReference return the NodeActor for the corresponding frameElement. + */ + getNodeActorFromContentDomReference: function(contentDomReference) { + let rawNode = ContentDOMReference.resolve(contentDomReference); + if (!rawNode || !this._isInDOMTree(rawNode)) { + return null; + } + + // This is a special case for the document object whereby it is considered + // as document.documentElement (the node) + if (rawNode.defaultView && rawNode === rawNode.defaultView.document) { + rawNode = rawNode.documentElement; + } + + return this.attachElement(rawNode); + }, + /** * Given a StyleSheetActor (identified by its ID), commonly used in the * style-editor, get its ownerNode and return the corresponding walker's @@ -2660,10 +2682,9 @@ var WalkerActor = protocol.ActorClassWithSpec(walkerSpec, { * first retrieve a reference to the walkerFront: * * // Make sure the inspector/walker have been initialized first. - * toolbox.initInspector().then(() => { - * // Retrieve the walker. - * let walker = toolbox.walker; - * }); + * const inspectorFront = await toolbox.target.getFront("inspector"); + * // Retrieve the walker. + * const walker = inspectorFront.walker; * * And then call this method: * diff --git a/devtools/server/actors/moz.build b/devtools/server/actors/moz.build index 9ca8e73e7b..03f2cbc9df 100644 --- a/devtools/server/actors/moz.build +++ b/devtools/server/actors/moz.build @@ -48,7 +48,6 @@ DevToolsModules( 'performance.js', 'preference.js', 'process.js', - 'promises.js', 'reflow.js', 'root.js', 'screenshot.js', diff --git a/devtools/server/actors/network-monitor/network-observer.js b/devtools/server/actors/network-monitor/network-observer.js index d0346e230d..19c2f6b1a5 100644 --- a/devtools/server/actors/network-monitor/network-observer.js +++ b/devtools/server/actors/network-monitor/network-observer.js @@ -1336,7 +1336,6 @@ const LOAD_CAUSE_STRINGS = { [Ci.nsIContentPolicy.TYPE_DOCUMENT]: "document", [Ci.nsIContentPolicy.TYPE_SUBDOCUMENT]: "subdocument", [Ci.nsIContentPolicy.TYPE_REFRESH]: "refresh", - [Ci.nsIContentPolicy.TYPE_XBL]: "xbl", [Ci.nsIContentPolicy.TYPE_PING]: "ping", [Ci.nsIContentPolicy.TYPE_XMLHTTPREQUEST]: "xhr", [Ci.nsIContentPolicy.TYPE_OBJECT_SUBREQUEST]: "objectSubdoc", diff --git a/devtools/server/actors/object.js b/devtools/server/actors/object.js index d39004d346..9600edd04e 100644 --- a/devtools/server/actors/object.js +++ b/devtools/server/actors/object.js @@ -34,6 +34,16 @@ loader.lazyRequireGetter( "devtools/server/actors/object/stringifiers" ); +// ContentDOMReference requires ChromeUtils, which isn't available in worker context. +if (!isWorker) { + loader.lazyRequireGetter( + this, + "ContentDOMReference", + "resource://gre/modules/ContentDOMReference.jsm", + true + ); +} + const { getArrayLength, getPromiseState, @@ -192,6 +202,7 @@ const proto = { g.class = "InvisibleToDebugger: " + this.obj.class; return g; } + if (unwrapped && unwrapped.isProxy) { // Proxy objects can run traps when accessed, so just create a preview with // the target and the handler. @@ -202,47 +213,73 @@ const proto = { return g; } - // If the debuggee does not subsume the object's compartment, most properties won't - // be accessible. Cross-orgin Window and Location objects might expose some, though. - // Change the displayed class, but when creating the preview use the original one. - if (unwrapped === null) { - g.class = "Restricted"; - } else { - g.class = this.obj.class; - } + const ownPropertyLength = this._getOwnPropertyLength(); + + Object.assign(g, { + // If the debuggee does not subsume the object's compartment, most properties won't + // be accessible. Cross-orgin Window and Location objects might expose some, though. + // Change the displayed class, but when creating the preview use the original one. + class: unwrapped === null ? "Restricted" : this.obj.class, + ownPropertyLength: Number.isFinite(ownPropertyLength) + ? ownPropertyLength + : undefined, + extensible: this.obj.isExtensible(), + frozen: this.obj.isFrozen(), + sealed: this.obj.isSealed(), + }); this.hooks.incrementGripDepth(); - g.extensible = this.obj.isExtensible(); - g.frozen = this.obj.isFrozen(); - g.sealed = this.obj.isSealed(); - if (g.class == "Promise") { g.promiseState = this._createPromiseState(); } - // FF40+: Allow to know how many properties an object has to lazily display them - // when there is a bunch. - if (isTypedArray(g)) { - // Bug 1348761: getOwnPropertyNames is unnecessary slow on TypedArrays - g.ownPropertyLength = getArrayLength(this.obj); - } else if (isStorage(g)) { - g.ownPropertyLength = getStorageLength(this.obj); - } else { + const raw = this.getRawObject(); + this._populateGripPreview(g, raw); + this.hooks.decrementGripDepth(); + + if (raw && Node.isInstance(raw) && ContentDOMReference) { + // ContentDOMReference.get takes a DOM element and returns an object with + // its browsing context id, as well as a unique identifier. We are putting it in + // the grip here in order to be able to retrieve the node later, potentially from a + // different DebuggerServer running in the same process. + // If ContentDOMReference.get throws, we simply don't add the property to the grip. try { - g.ownPropertyLength = this.obj.getOwnPropertyNames().length; - } catch (err) { - // The above can throw when the debuggee does not subsume the object's - // compartment, or for some WrappedNatives like Cu.Sandbox. - } + g.contentDomReference = ContentDOMReference.get(raw); + } catch (e) {} } + return g; + }, + + _getOwnPropertyLength: function() { + // FF40+: Allow to know how many properties an object has to lazily display them + // when there is a bunch. + if (isTypedArray(this.obj)) { + // Bug 1348761: getOwnPropertyNames is unnecessary slow on TypedArrays + return getArrayLength(this.obj); + } + + if (isStorage(this.obj)) { + return getStorageLength(this.obj); + } + + try { + return this.obj.getOwnPropertyNames().length; + } catch (err) { + // The above can throw when the debuggee does not subsume the object's + // compartment, or for some WrappedNatives like Cu.Sandbox. + } + + return null; + }, + + getRawObject: function() { let raw = this.obj.unsafeDereference(); // If Cu is not defined, we are running on a worker thread, where xrays - // don't exist. The raw object will be null/unavailable when interacting - // with a replaying execution. - if (Cu) { + // don't exist. + if (raw && Cu) { raw = Cu.unwaiveXrays(raw); } @@ -250,19 +287,25 @@ const proto = { raw = null; } - for (const fn of previewers[this.obj.class] || previewers.Object) { + return raw; + }, + + /** + * Populate the `preview` property on `grip` given its type. + */ + _populateGripPreview: function(grip, raw) { + for (const previewer of previewers[this.obj.class] || previewers.Object) { try { - if (fn(this, g, raw)) { - break; + const previewerResult = previewer(this, grip, raw); + if (previewerResult) { + return; } } catch (e) { - const msg = "ObjectActor.prototype.grip previewer function"; + const msg = + "ObjectActor.prototype._populateGripPreview previewer function"; DevToolsUtils.reportException(msg, e); } } - - this.hooks.decrementGripDepth(); - return g; }, /** @@ -857,144 +900,6 @@ const proto = { }; }, - /** - * Handle a protocol request to get the list of dependent promises of a - * promise. - * - * @return object - * Returns an object containing an array of object grips of the - * dependent promises - */ - dependentPromises: function() { - if (this.obj.class != "Promise") { - return this.throwError( - "objectNotPromise", - "'dependentPromises' request is only valid for grips with a 'Promise' class." - ); - } - - const promises = this.obj.promiseDependentPromises.map(p => - this.hooks.createValueGrip(p) - ); - - return { promises }; - }, - - /** - * Handle a protocol request to get the allocation stack of a promise. - */ - allocationStack: function() { - if (this.obj.class != "Promise") { - return this.throwError( - "objectNotPromise", - "'allocationStack' request is only valid for grips with a 'Promise' class." - ); - } - - let stack = this.obj.promiseAllocationSite; - const allocationStacks = []; - - while (stack) { - if (stack.source) { - const source = this._getSourceOriginalLocation(stack); - - if (source) { - allocationStacks.push(source); - } - } - stack = stack.parent; - } - - return Promise.all(allocationStacks); - }, - - /** - * Handle a protocol request to get the fulfillment stack of a promise. - */ - fulfillmentStack: function() { - if (this.obj.class != "Promise") { - return this.throwError( - "objectNotPromise", - "'fulfillmentStack' request is only valid for grips with a 'Promise' class." - ); - } - - let stack = this.obj.promiseResolutionSite; - const fulfillmentStacks = []; - - while (stack) { - if (stack.source) { - const source = this._getSourceOriginalLocation(stack); - - if (source) { - fulfillmentStacks.push(source); - } - } - stack = stack.parent; - } - - return Promise.all(fulfillmentStacks); - }, - - /** - * Handle a protocol request to get the rejection stack of a promise. - */ - rejectionStack: function() { - if (this.obj.class != "Promise") { - return this.throwError( - "objectNotPromise", - "'rejectionStack' request is only valid for grips with a 'Promise' class." - ); - } - - let stack = this.obj.promiseResolutionSite; - const rejectionStacks = []; - - while (stack) { - if (stack.source) { - const source = this._getSourceOriginalLocation(stack); - - if (source) { - rejectionStacks.push(source); - } - } - stack = stack.parent; - } - - return Promise.all(rejectionStacks); - }, - - /** - * Helper function for fetching the source location of a SavedFrame stack. - * - * @param SavedFrame stack - * The promise allocation stack frame - * @return object - * Returns an object containing the source location of the SavedFrame - * stack. - */ - _getSourceOriginalLocation: function(stack) { - let source; - - // Catch any errors if the source actor cannot be found - try { - source = this.hooks.sources().getSourceActorsByURL(stack.source)[0]; - } catch (e) { - // ignored - } - - if (!source) { - return null; - } - - return { - source, - line: stack.line, - column: stack.column, - functionDisplayName: stack.functionDisplayName, - }; - }, - /** * Handle a protocol request to get the target and handler internal slots of a proxy. */ diff --git a/devtools/server/actors/object/long-string.js b/devtools/server/actors/object/long-string.js deleted file mode 100644 index d9a408b6fd..0000000000 --- a/devtools/server/actors/object/long-string.js +++ /dev/null @@ -1,115 +0,0 @@ -/* 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 { DebuggerServer } = require("devtools/server/debugger-server"); - -/** - * Creates an actor for the specified "very long" string. "Very long" is specified - * at the server's discretion. - * There is a newer, protocol.js based LongString actor in - * devtools/server/actors/string.js and if you can you should use this one. - * - * @param string String - * The string. - */ -function LongStringActor(string) { - this.string = string; - this.stringLength = string.length; -} - -LongStringActor.prototype = { - actorPrefix: "longString", - - rawValue: function() { - return this.string; - }, - - destroy: function() { - // Because longStringActors is not a weak map, we won't automatically leave - // it so we need to manually leave on destroy so that we don't leak - // memory. - this._releaseActor(); - }, - - /** - * Returns a grip for this actor for returning in a protocol message. - */ - grip: function() { - return { - type: "longString", - initial: this.string.substring( - 0, - DebuggerServer.LONG_STRING_INITIAL_LENGTH - ), - length: this.stringLength, - actor: this.actorID, - }; - }, - - /** - * Handle a request to extract part of this actor's string. - * - * @param request object - * The protocol request object. - */ - onSubstring: function(request) { - return { - from: this.actorID, - substring: this.string.substring(request.start, request.end), - }; - }, - - /** - * Handle a request to release this LongStringActor instance. - */ - onRelease: function() { - // TODO: also check if registeredPool === threadActor.threadLifetimePool - // when the web console moves away from manually releasing pause-scoped - // actors. - this._releaseActor(); - this.registeredPool.removeActor(this); - return {}; - }, - - _releaseActor: function() { - if (this.registeredPool && this.registeredPool.longStringActors) { - delete this.registeredPool.longStringActors[this.string]; - } - }, -}; - -LongStringActor.prototype.requestTypes = { - substring: LongStringActor.prototype.onSubstring, - release: LongStringActor.prototype.onRelease, -}; - -/** - * Create a grip for the given string. - * - * @param str String - * The string we are creating a grip for. - * @param pool ActorPool - * The actor pool where the new actor will be added. - */ -function longStringGrip(str, pool) { - if (!pool.longStringActors) { - pool.longStringActors = {}; - } - - if (pool.longStringActors.hasOwnProperty(str)) { - return pool.longStringActors[str].grip(); - } - - const actor = new LongStringActor(str); - pool.addActor(actor); - pool.longStringActors[str] = actor; - return actor.grip(); -} - -module.exports = { - LongStringActor, - longStringGrip, -}; diff --git a/devtools/server/actors/object/moz.build b/devtools/server/actors/object/moz.build index af092939f1..056d4c3a31 100644 --- a/devtools/server/actors/object/moz.build +++ b/devtools/server/actors/object/moz.build @@ -3,7 +3,6 @@ # file, You can obtain one at http://mozilla.org/MPL/2.0/. DevToolsModules( - 'long-string.js', 'previewers.js', 'property-iterator.js', 'stringifiers.js', diff --git a/devtools/server/actors/object/property-iterator.js b/devtools/server/actors/object/property-iterator.js index e38105b3cc..8d188e0e70 100644 --- a/devtools/server/actors/object/property-iterator.js +++ b/devtools/server/actors/object/property-iterator.js @@ -224,7 +224,7 @@ function enumObjectProperties(objectActor, options) { // Calling getOwnPropertyDescriptor on wrapped native prototypes is not // allowed (bug 560072). } - if (desc && desc.value && String(desc.value).includes(query)) { + if (desc?.value && String(desc.value).includes(query)) { return true; } return false; diff --git a/devtools/server/actors/object/utils.js b/devtools/server/actors/object/utils.js index ea3a843363..e1dd20ba62 100644 --- a/devtools/server/actors/object/utils.js +++ b/devtools/server/actors/object/utils.js @@ -10,10 +10,11 @@ const { assert } = DevToolsUtils; loader.lazyRequireGetter( this, - "longStringGrip", - "devtools/server/actors/object/long-string", + "LongStringActor", + "devtools/server/actors/string", true ); + loader.lazyRequireGetter( this, "symbolGrip", @@ -88,7 +89,15 @@ function createValueGrip(value, pool, makeObjectGrip) { case "string": if (stringIsLong(value)) { - return longStringGrip(value, pool); + for (const child of pool.poolChildren()) { + if (child instanceof LongStringActor && child.str == value) { + return child.form(); + } + } + + const actor = new LongStringActor(pool.conn, value); + pool.addActor(actor); + return actor.form(); } return value; diff --git a/devtools/server/actors/perf.js b/devtools/server/actors/perf.js index d9c0e05fbf..b3c32bd9c8 100644 --- a/devtools/server/actors/perf.js +++ b/devtools/server/actors/perf.js @@ -5,183 +5,57 @@ const protocol = require("devtools/shared/protocol"); const { ActorClassWithSpec, Actor } = protocol; +const { actorBridgeWithSpec } = require("devtools/server/actors/common"); const { perfSpec } = require("devtools/shared/specs/perf"); -const { Ci } = require("chrome"); -const Services = require("Services"); +const { + ActorReadyGeckoProfilerInterface, +} = require("devtools/server/performance-new/gecko-profiler-interface"); -loader.lazyImporter( - this, - "PrivateBrowsingUtils", - "resource://gre/modules/PrivateBrowsingUtils.jsm" -); - -// Some platforms are built without the Gecko Profiler. -const IS_SUPPORTED_PLATFORM = "nsIProfiler" in Ci; +/** + * Pass on the events from the bridge to the actor. + * @param {Object} actor The perf actor + * @param {Array} names The event names + */ +function _bridgeEvents(actor, names) { + for (const name of names) { + actor.bridge.on(name, (...args) => actor.emit(name, ...args)); + } +} /** * The PerfActor wraps the Gecko Profiler interface */ exports.PerfActor = ActorClassWithSpec(perfSpec, { - initialize(conn) { + initialize: function(conn, targetActor) { Actor.prototype.initialize.call(this, conn); + // The "bridge" is the actual implementation of the actor. It is abstracted + // out into its own class so that it can be re-used with the profiler popup. + this.bridge = new ActorReadyGeckoProfilerInterface({ + // Do not use the gzipped API from the Profiler to capture profiles. + gzipped: false, + }); - // Only setup the observers on a supported platform. - if (IS_SUPPORTED_PLATFORM) { - this._observer = { - observe: this._observe.bind(this), - }; - Services.obs.addObserver(this._observer, "profiler-started"); - Services.obs.addObserver(this._observer, "profiler-stopped"); - Services.obs.addObserver( - this._observer, - "chrome-document-global-created" - ); - Services.obs.addObserver(this._observer, "last-pb-context-exited"); - } + _bridgeEvents(this, [ + "profile-locked-by-private-browsing", + "profile-unlocked-from-private-browsing", + "profiler-started", + "profiler-stopped", + ]); }, - destroy() { - if (!IS_SUPPORTED_PLATFORM) { - return; - } - Services.obs.removeObserver(this._observer, "profiler-started"); - Services.obs.removeObserver(this._observer, "profiler-stopped"); - Services.obs.removeObserver( - this._observer, - "chrome-document-global-created" - ); - Services.obs.removeObserver(this._observer, "last-pb-context-exited"); - Actor.prototype.destroy.call(this); + destroy: function(conn) { + Actor.prototype.destroy.call(this, conn); + this.bridge.destroy(); }, - startProfiler(options) { - if (!IS_SUPPORTED_PLATFORM) { - return false; - } - - // For a quick implementation, decide on some default values. These may need - // to be tweaked or made configurable as needed. - const settings = { - entries: options.entries || 1000000, - // Window length should be Infinite if nothing's been passed. - // options.duration is supported for `perfActorVersion > 0`. - duration: options.duration || 0, - interval: options.interval || 1, - features: options.features || [ - "js", - "stackwalk", - "responsiveness", - "threads", - "leaf", - ], - threads: options.threads || ["GeckoMain", "Compositor"], - }; - - try { - // This can throw an error if the profiler is in the wrong state. - Services.profiler.StartProfiler( - settings.entries, - settings.interval, - settings.features, - settings.threads, - settings.duration - ); - } catch (e) { - // In case any errors get triggered, bailout with a false. - return false; - } - - return true; - }, - - stopProfilerAndDiscardProfile() { - if (!IS_SUPPORTED_PLATFORM) { - return; - } - Services.profiler.StopProfiler(); - }, - - async getSymbolTable(debugPath, breakpadId) { - const [addr, index, buffer] = await Services.profiler.getSymbolTable( - debugPath, - breakpadId - ); - // The protocol does not support the transfer of typed arrays, so we convert - // these typed arrays to plain JS arrays of numbers now. - // Our return value type is declared as "array:array:number". - return [Array.from(addr), Array.from(index), Array.from(buffer)]; - }, - - async getProfileAndStopProfiler() { - if (!IS_SUPPORTED_PLATFORM) { - return null; - } - - let profile; - try { - // Attempt to pull out the data. - profile = await Services.profiler.getProfileDataAsync(); - - // Stop and discard the buffers. - Services.profiler.StopProfiler(); - } catch (e) { - // If there was any kind of error, bailout with no profile. - return null; - } - - // Gecko Profiler errors can return an empty object, return null for this case - // as well. - if (Object.keys(profile).length === 0) { - return null; - } - return profile; - }, - - isActive() { - if (!IS_SUPPORTED_PLATFORM) { - return false; - } - return Services.profiler.IsActive(); - }, - - isSupportedPlatform() { - return IS_SUPPORTED_PLATFORM; - }, - - isLockedForPrivateBrowsing() { - if (!IS_SUPPORTED_PLATFORM) { - return false; - } - return !Services.profiler.CanProfile(); - }, - - /** - * Watch for events that happen within the browser. These can affect the current - * availability and state of the Gecko Profiler. - */ - _observe(subject, topic, _data) { - switch (topic) { - case "chrome-document-global-created": - if (PrivateBrowsingUtils.isWindowPrivate(subject)) { - this.emit("profile-locked-by-private-browsing"); - } - break; - case "last-pb-context-exited": - this.emit("profile-unlocked-from-private-browsing"); - break; - case "profiler-started": - const param = subject.QueryInterface(Ci.nsIProfilerStartParams); - this.emit( - topic, - param.entries, - param.interval, - param.features, - param.duration - ); - break; - case "profiler-stopped": - this.emit(topic); - break; - } - }, + // Connect the rest of the ActorReadyGeckoProfilerInterface's methods to the PerfActor. + startProfiler: actorBridgeWithSpec("startProfiler"), + stopProfilerAndDiscardProfile: actorBridgeWithSpec( + "stopProfilerAndDiscardProfile" + ), + getSymbolTable: actorBridgeWithSpec("getSymbolTable"), + getProfileAndStopProfiler: actorBridgeWithSpec("getProfileAndStopProfiler"), + isActive: actorBridgeWithSpec("isActive"), + isSupportedPlatform: actorBridgeWithSpec("isSupportedPlatform"), + isLockedForPrivateBrowsing: actorBridgeWithSpec("isLockedForPrivateBrowsing"), }); diff --git a/devtools/server/actors/promises.js b/devtools/server/actors/promises.js deleted file mode 100644 index 9d5731cc6d..0000000000 --- a/devtools/server/actors/promises.js +++ /dev/null @@ -1,215 +0,0 @@ -/* 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 protocol = require("devtools/shared/protocol"); -const { promisesSpec } = require("devtools/shared/specs/promises"); -const { expectState, ActorPool } = require("devtools/server/actors/common"); -const { ObjectActor } = require("devtools/server/actors/object"); -const { createValueGrip } = require("devtools/server/actors/object/utils"); -const DevToolsUtils = require("devtools/shared/DevToolsUtils"); -const EventEmitter = require("devtools/shared/event-emitter"); - -/** - * The Promises Actor provides support for getting the list of live promises and - * observing changes to their settlement state. - */ -var PromisesActor = protocol.ActorClassWithSpec(promisesSpec, { - /** - * @param conn DebuggerServerConnection. - * @param parentActor BrowsingContextTargetActor|RootActor - */ - initialize: function(conn, parentActor) { - protocol.Actor.prototype.initialize.call(this, conn); - - this.conn = conn; - this.parentActor = parentActor; - this.state = "detached"; - this._dbg = null; - this._gripDepth = 0; - this._navigationLifetimePool = null; - this._newPromises = null; - this._promisesSettled = null; - - this.objectGrip = this.objectGrip.bind(this); - this._makePromiseEventHandler = this._makePromiseEventHandler.bind(this); - this._onWindowReady = this._onWindowReady.bind(this); - }, - - destroy: function() { - if (this.state === "attached") { - this.detach(); - } - - protocol.Actor.prototype.destroy.call(this, this.conn); - }, - - get dbg() { - if (!this._dbg) { - this._dbg = this.parentActor.makeDebugger(); - } - return this._dbg; - }, - - /** - * Attach to the PromisesActor. - */ - attach: expectState( - "detached", - function() { - this.dbg.addDebuggees(); - - this._navigationLifetimePool = this._createActorPool(); - this.conn.addActorPool(this._navigationLifetimePool); - - this._newPromises = []; - this._promisesSettled = []; - - this.dbg.findScripts().forEach(s => { - this.parentActor.sources.createSourceActor(s.source); - }); - - this.dbg.onNewScript = s => { - this.parentActor.sources.createSourceActor(s.source); - }; - - EventEmitter.on(this.parentActor, "window-ready", this._onWindowReady); - - this.state = "attached"; - }, - "attaching to the PromisesActor" - ), - - /** - * Detach from the PromisesActor upon Debugger closing. - */ - detach: expectState("attached", function() { - this.dbg.removeAllDebuggees(); - this.dbg.disable(); - this._dbg = null; - this._newPromises = null; - this._promisesSettled = null; - - if (this._navigationLifetimePool) { - this.conn.removeActorPool(this._navigationLifetimePool); - this._navigationLifetimePool = null; - } - - EventEmitter.off(this.parentActor, "window-ready", this._onWindowReady); - - this.state = "detached"; - }), - - _createActorPool: function() { - const pool = new ActorPool(this.conn); - pool.objectActors = new WeakMap(); - return pool; - }, - - /** - * Create an ObjectActor for the given Promise object. - * - * @param object promise - * The promise object - * @return object - * An ObjectActor object that wraps the given Promise object - */ - _createObjectActorForPromise: function(promise) { - if (this._navigationLifetimePool.objectActors.has(promise)) { - return this._navigationLifetimePool.objectActors.get(promise); - } - - const actor = new ObjectActor( - promise, - { - getGripDepth: () => this._gripDepth, - incrementGripDepth: () => this._gripDepth++, - decrementGripDepth: () => this._gripDepth--, - createValueGrip: v => - createValueGrip(v, this._navigationLifetimePool, this.objectGrip), - sources: () => this.parentActor.sources, - createEnvironmentActor: () => - DevToolsUtils.reportException( - "PromisesActor", - Error("createEnvironmentActor not yet implemented") - ), - getGlobalDebugObject: () => null, - }, - this.conn - ); - - this._navigationLifetimePool.addActor(actor); - this._navigationLifetimePool.objectActors.set(promise, actor); - - return actor; - }, - - /** - * Get a grip for the given Promise object. - * - * @param object value - * The Promise object - * @return object - * The grip for the given Promise object - */ - objectGrip: function(value) { - return this._createObjectActorForPromise(value).form(); - }, - - /** - * Get a list of ObjectActors for all live Promise Objects. - */ - listPromises: function() { - const promises = this.dbg.findObjects({ class: "Promise" }); - - this.dbg.onNewPromise = this._makePromiseEventHandler( - this._newPromises, - "new-promises" - ); - this.dbg.onPromiseSettled = this._makePromiseEventHandler( - this._promisesSettled, - "promises-settled" - ); - - return promises.map(p => this._createObjectActorForPromise(p)); - }, - - /** - * Creates an event handler for onNewPromise that will add the new - * Promise ObjectActor to the array and schedule it to be emitted as a - * batch for the provided event. - * - * @param array array - * The list of Promise ObjectActors to emit - * @param string eventName - * The event name - */ - _makePromiseEventHandler: function(array, eventName) { - return promise => { - const actor = this._createObjectActorForPromise(promise); - const needsScheduling = array.length == 0; - - array.push(actor); - - if (needsScheduling) { - DevToolsUtils.executeSoon(() => { - this.emit(eventName, array.splice(0, array.length)); - }); - } - }; - }, - - _onWindowReady: expectState("attached", function({ isTopLevel }) { - if (!isTopLevel) { - return; - } - - this._navigationLifetimePool.destroy(); - this.dbg.removeAllDebuggees(); - this.dbg.addDebuggees(); - }), -}); - -exports.PromisesActor = PromisesActor; diff --git a/devtools/server/actors/storage.js b/devtools/server/actors/storage.js index 9351c5b293..d7fc9fe99c 100644 --- a/devtools/server/actors/storage.js +++ b/devtools/server/actors/storage.js @@ -12,9 +12,24 @@ const Services = require("Services"); const defer = require("devtools/shared/defer"); const { isWindowIncluded } = require("devtools/shared/layout/utils"); const specs = require("devtools/shared/specs/storage"); +loader.lazyGetter(this, "ExtensionProcessScript", () => { + return require("resource://gre/modules/ExtensionProcessScript.jsm") + .ExtensionProcessScript; +}); +loader.lazyGetter(this, "ExtensionStorageIDB", () => { + return require("resource://gre/modules/ExtensionStorageIDB.jsm") + .ExtensionStorageIDB; +}); +loader.lazyGetter( + this, + "WebExtensionPolicy", + () => Cu.getGlobalForObject(ExtensionProcessScript).WebExtensionPolicy +); const CHROME_ENABLED_PREF = "devtools.chrome.enabled"; const REMOTE_ENABLED_PREF = "devtools.debugger.remote-enabled"; +const EXTENSION_STORAGE_ENABLED_PREF = + "devtools.storage.extensionStorage.enabled"; const DEFAULT_VALUE = "value"; @@ -428,7 +443,7 @@ StorageActors.defaults = function(typeName, observationTopics) { getPrincipal(win) { if (win) { - return win.document.nodePrincipal; + return win.document.effectiveStoragePrincipal; } // We are running in the browser toolbox and viewing system DBs so we // need to use system principal. @@ -599,7 +614,7 @@ StorageActors.createActor( const cookies = this.getCookiesFromHost( host, - doc.nodePrincipal.originAttributes + doc.effectiveStoragePrincipal.originAttributes ); for (const cookie of cookies) { @@ -723,7 +738,7 @@ StorageActors.createActor( */ async editItem(data) { const doc = this.storageActor.document; - data.originAttributes = doc.nodePrincipal.originAttributes; + data.originAttributes = doc.effectiveStoragePrincipal.originAttributes; this.editCookie(data); }, @@ -737,12 +752,20 @@ StorageActors.createActor( async removeItem(host, name) { const doc = this.storageActor.document; - this.removeCookie(host, name, doc.nodePrincipal.originAttributes); + this.removeCookie( + host, + name, + doc.effectiveStoragePrincipal.originAttributes + ); }, async removeAll(host, domain) { const doc = this.storageActor.document; - this.removeAllCookies(host, domain, doc.nodePrincipal.originAttributes); + this.removeAllCookies( + host, + domain, + doc.effectiveStoragePrincipal.originAttributes + ); }, async removeAllSessionCookies(host, domain) { @@ -750,7 +773,7 @@ StorageActors.createActor( this.removeAllSessionCookies( host, domain, - doc.nodePrincipal.originAttributes + doc.effectiveStoragePrincipal.originAttributes ); }, @@ -1371,6 +1394,483 @@ StorageActors.createActor( getObjectForLocalOrSessionStorage("sessionStorage") ); +const extensionStorageHelpers = { + unresolvedPromises: new Map(), + // Map of addonId => onStorageChange listeners in the parent process. Each addon toolbox targets + // a single addon, and multiple addon toolboxes could be open at the same time. + onChangedParentListeners: new Map(), + // Set of onStorageChange listeners in the extension child process. Each addon toolbox will create + // a separate extensionStorage actor targeting that addon. The addonId is passed into the listener, + // so that changes propagate only if the storage actor has a matching addonId. + onChangedChildListeners: new Set(), + + // Sets the parent process message manager + setPpmm(ppmm) { + this.ppmm = ppmm; + }, + + // A promise in the main process has resolved, and we need to pass the return value(s) + // back to the child process + backToChild(...args) { + Services.mm.broadcastAsyncMessage( + "debug:storage-extensionStorage-request-child", + { + method: "backToChild", + args: args, + } + ); + }, + + // Send a message from the main process to a listener in the child process that the + // extension has modified storage local data + fireStorageOnChanged({ addonId, changes }) { + Services.mm.broadcastAsyncMessage( + "debug:storage-extensionStorage-request-child", + { + addonId, + changes, + method: "storageOnChanged", + } + ); + }, + + // Subscribe a listener for event notifications from the WE storage API when + // storage local data has been changed by the extension, and keep track of the + // listener to remove it when the debugger is being disconnected. + subscribeOnChangedListenerInParent(addonId) { + if (!this.onChangedParentListeners.has(addonId)) { + const onChangedListener = changes => { + this.fireStorageOnChanged({ addonId, changes }); + }; + ExtensionStorageIDB.addOnChangedListener(addonId, onChangedListener); + this.onChangedParentListeners.set(addonId, onChangedListener); + } + }, + + // The main process does not require an extension context to select the backend + // Bug 1542038, 1542039: Each storage area will need its own implementation, as + // they use different storage backends. + async setupStorageInParent(addonId) { + const { extension } = WebExtensionPolicy.getByID(addonId); + const parentResult = await ExtensionStorageIDB.selectBackend({ extension }); + const result = { + ...parentResult, + // Received as a StructuredCloneHolder, so we need to deserialize + storagePrincipal: parentResult.storagePrincipal.deserialize(this, true), + }; + + this.subscribeOnChangedListenerInParent(addonId); + return this.backToChild("setupStorageInParent", result); + }, + + onDisconnected() { + for (const [addonId, listener] of this.onChangedParentListeners) { + ExtensionStorageIDB.removeOnChangedListener(addonId, listener); + } + this.onChangedParentListeners.clear(); + }, + + // Runs in the main process. This determines what code to execute based on the message + // received from the child process. + async handleChildRequest(msg) { + switch (msg.json.method) { + case "setupStorageInParent": + const addonId = msg.data.args[0]; + const result = await extensionStorageHelpers.setupStorageInParent( + addonId + ); + return result; + default: + console.error("ERR_DIRECTOR_PARENT_UNKNOWN_METHOD", msg.json.method); + throw new Error("ERR_DIRECTOR_PARENT_UNKNOWN_METHOD"); + } + }, + + // Runs in the child process. This determines what code to execute based on the message + // received from the parent process. + handleParentRequest(msg) { + switch (msg.json.method) { + case "backToChild": { + const [func, rv] = msg.json.args; + const deferred = this.unresolvedPromises.get(func); + if (deferred) { + this.unresolvedPromises.delete(func); + deferred.resolve(rv); + } + break; + } + case "storageOnChanged": { + const { addonId, changes } = msg.data; + for (const listener of this.onChangedChildListeners) { + try { + listener({ addonId, changes }); + } catch (err) { + console.error(err); + // Ignore errors raised from listeners. + } + } + break; + } + default: + console.error("ERR_DIRECTOR_CLIENT_UNKNOWN_METHOD", msg.json.method); + throw new Error("ERR_DIRECTOR_CLIENT_UNKNOWN_METHOD"); + } + }, + + callParentProcessAsync(methodName, ...args) { + const deferred = defer(); + + this.unresolvedPromises.set(methodName, deferred); + + this.ppmm.sendAsyncMessage( + "debug:storage-extensionStorage-request-parent", + { + method: methodName, + args: args, + } + ); + + return deferred.promise; + }, +}; + +/** + * E10S parent/child setup helpers + * Add a message listener in the parent process to receive messages from the child + * process. + */ +exports.setupParentProcessForExtensionStorage = function({ mm, prefix }) { + // listen for director-script requests from the child process + setMessageManager(mm); + + function setMessageManager(newMM) { + if (mm) { + mm.removeMessageListener( + "debug:storage-extensionStorage-request-parent", + extensionStorageHelpers.handleChildRequest + ); + } + mm = newMM; + if (mm) { + mm.addMessageListener( + "debug:storage-extensionStorage-request-parent", + extensionStorageHelpers.handleChildRequest + ); + } + } + + return { + onBrowserSwap: setMessageManager, + onDisconnected: () => { + // Although "disconnected-from-child" implies that the child is already + // disconnected this is not the case. The disconnection takes place after + // this method has finished. This gives us chance to clean up items within + // the parent process e.g. observers. + setMessageManager(null); + extensionStorageHelpers.onDisconnected(); + }, + }; +}; + +/** + * The Extension Storage actor. + */ +if (Services.prefs.getBoolPref(EXTENSION_STORAGE_ENABLED_PREF, false)) { + StorageActors.createActor( + { + typeName: "extensionStorage", + }, + { + initialize(storageActor) { + protocol.Actor.prototype.initialize.call(this, null); + + this.storageActor = storageActor; + + this.addonId = this.storageActor.parentActor.addonId; + + // Retrieve the base moz-extension url for the extension + // (and also remove the final '/' from it). + this.extensionHostURL = this.getExtensionPolicy() + .getURL() + .slice(0, -1); + + // Map + // Bug 1542038, 1542039: Each storage area will need its own + // dbConnectionForHost, as they each have different storage backends. + // Anywhere dbConnectionForHost is used, we need to know the storage + // area to access the correct database. + this.dbConnectionForHost = new Map(); + + // Bug 1542038, 1542039: Each storage area will need its own + // this.hostVsStores or this actor will need to deviate from how + // this.hostVsStores is defined in the framework to associate each + // storage item with a storage area. Any methods that use it will also + // need to be updated (e.g. getNamesForHost). + this.hostVsStores = new Map(); + + this.onStorageChange = this.onStorageChange.bind(this); + + this.setupChildProcess(); + + this.onWindowReady = this.onWindowReady.bind(this); + this.onWindowDestroyed = this.onWindowDestroyed.bind(this); + this.storageActor.on("window-ready", this.onWindowReady); + this.storageActor.on("window-destroyed", this.onWindowDestroyed); + }, + + getExtensionPolicy() { + return WebExtensionPolicy.getByID(this.addonId); + }, + + destroy() { + extensionStorageHelpers.onChangedChildListeners.delete( + this.onStorageChange + ); + + this.storageActor.off("window-ready", this.onWindowReady); + this.storageActor.off("window-destroyed", this.onWindowDestroyed); + + this.hostVsStores.clear(); + + protocol.Actor.prototype.destroy.call(this); + + this.storageActor = null; + }, + + setupChildProcess() { + const ppmm = this.conn.parentMessageManager; + extensionStorageHelpers.setPpmm(ppmm); + + // eslint-disable-next-line no-restricted-properties + this.conn.setupInParent({ + module: "devtools/server/actors/storage", + setupParent: "setupParentProcessForExtensionStorage", + }); + + extensionStorageHelpers.onChangedChildListeners.add( + this.onStorageChange + ); + this.setupStorageInParent = extensionStorageHelpers.callParentProcessAsync.bind( + extensionStorageHelpers, + "setupStorageInParent" + ); + + // Add a message listener in the child process to receive messages from the parent + // process + ppmm.addMessageListener( + "debug:storage-extensionStorage-request-child", + extensionStorageHelpers.handleParentRequest.bind( + extensionStorageHelpers + ) + ); + }, + + /** + * This fires when the extension changes storage data while the storage + * inspector is open. Ensures this.hostVsStores stays up-to-date and + * passes the changes on to update the client. + */ + onStorageChange({ addonId, changes }) { + if (addonId !== this.addonId) { + return; + } + + const host = this.extensionHostURL; + const storeMap = this.hostVsStores.get(host); + + function isStructuredCloneHolder(value) { + return ( + value && + typeof value === "object" && + Cu.getClassName(value, true) === "StructuredCloneHolder" + ); + } + + for (const key in changes) { + const storageChange = changes[key]; + let { newValue, oldValue } = storageChange; + if (isStructuredCloneHolder(newValue)) { + newValue = newValue.deserialize(this); + } + if (isStructuredCloneHolder(oldValue)) { + oldValue = oldValue.deserialize(this); + } + + let action; + if (typeof newValue === "undefined") { + action = "deleted"; + storeMap.delete(key); + } else if (typeof oldValue === "undefined") { + action = "added"; + storeMap.set(key, newValue); + } else { + action = "changed"; + storeMap.set(key, newValue); + } + + this.storageActor.update(action, this.typeName, { [host]: [key] }); + } + }, + + /** + * Purpose of this method is same as populateStoresForHosts but this is async. + * This exact same operation cannot be performed in populateStoresForHosts + * method, as that method is called in initialize method of the actor, which + * cannot be asynchronous. + */ + async preListStores() { + // Ensure the actor's target is an extension and it is enabled + if (!this.addonId || !WebExtensionPolicy.getByID(this.addonId)) { + return; + } + + await this.populateStoresForHost(this.extensionHostURL); + }, + + /** + * This method is overriden and left blank as for extensionStorage, this operation + * cannot be performed synchronously. Thus, the preListStores method exists to + * do the same task asynchronously. + */ + populateStoresForHosts() {}, + + /** + * This method asynchronously reads the storage data for the target extension + * and caches this data into this.hostVsStores. + * @param {String} host - the hostname for the extension + */ + async populateStoresForHost(host) { + if (host !== this.extensionHostURL) { + return; + } + + const extension = ExtensionProcessScript.getExtensionChild( + this.addonId + ); + if (!extension || !extension.hasPermission("storage")) { + return; + } + + // Make sure storeMap is defined and set in this.hostVsStores before subscribing + // a storage onChanged listener in the parent process + const storeMap = new Map(); + this.hostVsStores.set(host, storeMap); + + const storagePrincipal = await this.getStoragePrincipal(extension.id); + + if (!storagePrincipal) { + // This could happen if the extension fails to be migrated to the + // IndexedDB backend + return; + } + + const db = await ExtensionStorageIDB.open(storagePrincipal); + this.dbConnectionForHost.set(host, db); + const data = await db.get(); + + for (const [key, value] of Object.entries(data)) { + storeMap.set(key, value); + } + + // Show the storage actor in the add-on storage inspector even when there + // is no extension page currently open + const storageData = {}; + storageData[host] = this.getNamesForHost(host); + this.storageActor.update("added", this.typeName, storageData); + }, + + async getStoragePrincipal(addonId) { + const { + backendEnabled, + storagePrincipal, + } = await this.setupStorageInParent(addonId); + + if (!backendEnabled) { + // IDB backend disabled; give up. + return null; + } + return storagePrincipal; + }, + + getValuesForHost(host, name) { + const result = []; + + if (!this.hostVsStores.has(host)) { + return result; + } + + if (name) { + return [{ name, value: this.hostVsStores.get(host).get(name) }]; + } + + for (const [key, value] of Array.from( + this.hostVsStores.get(host).entries() + )) { + result.push({ name: key, value }); + } + return result; + }, + + /** + * Converts a storage item to an "extensionobject" as defined in + * devtools/shared/specs/storage.js + * @param {Object} item - The storage item to convert + * @param {String} item.name - The storage item key + * @param {*} item.value - The storage item value + * @return {extensionobject} + */ + toStoreObject(item) { + if (!item) { + return null; + } + + const { name, value } = item; + + let newValue; + if (typeof value === "string") { + newValue = value; + } else { + try { + newValue = JSON.stringify(value) || String(value); + } catch (error) { + // throws for bigint + newValue = String(value); + } + + // JavaScript objects that are not JSON stringifiable will be represented + // by the string "Object" + if (newValue === "{}") { + newValue = "Object"; + } + } + + // FIXME: Bug 1318029 - Due to a bug that is thrown whenever a + // LongStringActor string reaches DebuggerServer.LONG_STRING_LENGTH we need + // to trim the value. When the bug is fixed we should stop trimming the + // string here. + const maxLength = DebuggerServer.LONG_STRING_LENGTH - 1; + if (newValue.length > maxLength) { + newValue = newValue.substr(0, maxLength); + } + + return { + name, + value: new LongStringActor(this.conn, newValue || ""), + area: "local", // Bug 1542038, 1542039: set the correct storage area + }; + }, + + getFields() { + return [ + { name: "name", editable: false }, + { name: "value", editable: false }, + { name: "area", editable: false }, + ]; + }, + } + ); +} + StorageActors.createActor( { typeName: "Cache", @@ -1378,7 +1878,8 @@ StorageActors.createActor( { async getCachesForHost(host) { const uri = Services.io.newURI(host); - const attrs = this.storageActor.document.nodePrincipal.originAttributes; + const attrs = this.storageActor.document.effectiveStoragePrincipal + .originAttributes; const principal = Services.scriptSecurityManager.createCodebasePrincipal( uri, attrs @@ -1729,7 +2230,7 @@ StorageActors.createActor( return { error: `Window for host ${host} not found` }; } - const principal = win.document.nodePrincipal; + const principal = win.document.effectiveStoragePrincipal; return this.removeDB(host, principal, name); }, @@ -1741,7 +2242,7 @@ StorageActors.createActor( return; } - const principal = win.document.nodePrincipal; + const principal = win.document.effectiveStoragePrincipal; this.clearDBStore(host, principal, db, store); }, @@ -1753,7 +2254,7 @@ StorageActors.createActor( return; } - const principal = win.document.nodePrincipal; + const principal = win.document.effectiveStoragePrincipal; this.removeDBRecord(host, principal, db, store, id); }, @@ -2713,6 +3214,11 @@ const StorageActor = protocol.ActorClassWithSpec(specs.storageSpec, { // Initialize the registered store types for (const [store, ActorConstructor] of storageTypePool) { + // Only create the extensionStorage actor when the debugging target + // is an extension. + if (store === "extensionStorage" && !this.parentActor.addonId) { + continue; + } this.childActorPool.set(store, new ActorConstructor(this)); } @@ -2793,6 +3299,14 @@ const StorageActor = protocol.ActorClassWithSpec(specs.storageSpec, { return null; }, + isIncludedInTargetExtension(subject) { + const { document } = subject; + return ( + document.nodePrincipal.addonId && + document.nodePrincipal.addonId === this.parentActor.addonId + ); + }, + isIncludedInTopLevelWindow(window) { return isWindowIncluded(this.window, window); }, @@ -2831,9 +3345,13 @@ const StorageActor = protocol.ActorClassWithSpec(specs.storageSpec, { return null; } + // We don't want to try to find a top level window for an extension page, as + // in many cases (e.g. background page), it is not loaded in a tab, and + // 'isIncludedInTopLevelWindow' throws an error if ( topic == "content-document-global-created" && - this.isIncludedInTopLevelWindow(subject) + (this.isIncludedInTargetExtension(subject) || + this.isIncludedInTopLevelWindow(subject)) ) { this.childWindowPool.add(subject); this.emit("window-ready", subject); diff --git a/devtools/server/actors/styles.js b/devtools/server/actors/styles.js index 665d93034e..4ab715c501 100644 --- a/devtools/server/actors/styles.js +++ b/devtools/server/actors/styles.js @@ -103,8 +103,6 @@ const BOLD_FONT_WEIGHT = 700; // Offset (in px) to avoid cutting off text edges of italic fonts. const FONT_PREVIEW_OFFSET = 4; -const NS_EVENT_STATE_VISITED = 1 << 24; - /** * The PageStyle actor lets the client look at the styles on a page, as * they are applied to a given node. @@ -133,6 +131,9 @@ var PageStyleActor = protocol.ActorClassWithSpec(pageStyleSpec, { // Stores the association of DOM objects -> actors this.refMap = new Map(); + // Latest node queried for its applied styles. + this.selectedElement = null; + // Maps document elements to style elements, used to add new rules. this.styleElements = new WeakMap(); @@ -142,6 +143,7 @@ var PageStyleActor = protocol.ActorClassWithSpec(pageStyleSpec, { this.inspector.targetActor.on("will-navigate", this.onFrameUnload); this.inspector.targetActor.on("stylesheet-added", this.onStyleSheetAdded); + this._observedRules = []; this._styleApplied = this._styleApplied.bind(this); this._watchedSheets = new Set(); }, @@ -156,12 +158,15 @@ var PageStyleActor = protocol.ActorClassWithSpec(pageStyleSpec, { this.inspector = null; this.walker = null; this.refMap = null; + this.selectedElement = null; this.cssLogic = null; this.styleElements = null; for (const sheet of this._watchedSheets) { sheet.off("style-applied", this._styleApplied); } + + this._observedRules = []; this._watchedSheets.clear(); }, @@ -575,6 +580,12 @@ var PageStyleActor = protocol.ActorClassWithSpec(pageStyleSpec, { * `skipPseudo`: Exclude styles applied to pseudo elements of the provided node. */ async getApplied(node, options) { + // Clear any previous references to StyleRuleActor instances for CSS rules. + // Assume the consumer has switched context to a new node and no longer + // interested in state changes of previous rules. + this._observedRules = []; + this.selectedElement = node.rawNode; + if (!node) { return { entries: [], rules: [], sheets: [] }; } @@ -590,6 +601,12 @@ var PageStyleActor = protocol.ActorClassWithSpec(pageStyleSpec, { // See the comment in |form| to understand this. await rule.getAuthoredCssText(); } + + // Reference to instances of StyleRuleActor for CSS rules matching the node. + // Assume these are used by a consumer which wants to be notified when their + // state or declarations change either directly or indirectly. + this._observedRules = result.rules; + return result; }, @@ -773,7 +790,7 @@ var PageStyleActor = protocol.ActorClassWithSpec(pageStyleSpec, { const domRules = InspectorUtils.getCSSStyleRules( node, pseudo, - _hasVisitedState(node) + CssLogic.hasVisitedState(node) ); if (!domRules) { @@ -891,7 +908,7 @@ var PageStyleActor = protocol.ActorClassWithSpec(pageStyleSpec, { const { bindingElement, pseudo } = CssLogic.getBindingElementAndPseudo( element ); - const relevantLinkVisited = _hasVisitedState(bindingElement); + const relevantLinkVisited = CssLogic.hasVisitedState(bindingElement); entry.matchedSelectors = []; for (let i = 0; i < selectors.length; i++) { @@ -1159,6 +1176,26 @@ var PageStyleActor = protocol.ActorClassWithSpec(pageStyleSpec, { return this.getNewAppliedProps(node, cssRule); }, + + /** + * Cause all StyleRuleActor instances of observed CSS rules to check whether the + * states of their declarations have changed. + * + * Observed rules are the latest rules returned by a call to PageStyleActor.getApplied() + * + * This is necessary because changes in one rule can cause the declarations in another + * to not be applicable (inactive CSS). The observers of those rules should be notified. + * Rules will fire a "declarations-updated" event if their declarations changed states. + * + * Call this method whenever a CSS rule is mutated: + * - a CSS declaration is added/changed/disabled/removed + * - a selector is added/changed/removed + */ + refreshObservedRules() { + for (const rule of this._observedRules) { + rule.refresh(); + } + }, }); exports.PageStyleActor = PageStyleActor; @@ -1487,7 +1524,7 @@ var StyleRuleActor = protocol.ActorClassWithSpec(styleRuleSpec, { cssText, true ); - const el = this.pageStyle.cssLogic.viewedElement; + const el = this.pageStyle.selectedElement; const style = this.pageStyle.cssLogic.computedStyle; // We need to grab CSS from the window, since calling supports() on the @@ -1497,6 +1534,7 @@ var StyleRuleActor = protocol.ActorClassWithSpec(styleRuleSpec, { // Use the 1-arg CSS.supports() call so that we also accept !important // in the value. decl.isValid = CSS.supports(`${decl.name}:${decl.value}`); + // TODO: convert from Object to Boolean. See Bug 1574471 decl.isUsed = inactivePropertyHelper.isPropertyUsed( el, style, @@ -1750,6 +1788,7 @@ var StyleRuleActor = protocol.ActorClassWithSpec(styleRuleSpec, { } this.authoredText = newText; + this.pageStyle.refreshObservedRules(); return this; }, @@ -1805,6 +1844,8 @@ var StyleRuleActor = protocol.ActorClassWithSpec(styleRuleSpec, { } } + this.pageStyle.refreshObservedRules(); + return this; }, @@ -2048,6 +2089,39 @@ var StyleRuleActor = protocol.ActorClassWithSpec(styleRuleSpec, { return { ruleProps, isMatching }; }); }, + + /** + * Using the latest computed style applicable to the selected element, + * check the states of declarations in this CSS rule. + * + * If any have changed their used/unused state, potentially as a result of changes in + * another rule, fire a "declarations-updated" event with all declarations and their + * updated states. + */ + refresh() { + let hasChanged = false; + const el = this.pageStyle.selectedElement; + const style = CssLogic.getComputedStyle(el); + + for (const decl of this._declarations) { + // TODO: convert from Object to Boolean. See Bug 1574471 + const isUsed = inactivePropertyHelper.isPropertyUsed( + el, + style, + this.rawRule, + decl.name + ); + + if (decl.isUsed.used !== isUsed.used) { + decl.isUsed = isUsed; + hasChanged = true; + } + } + + if (hasChanged) { + this.emit("declarations-updated", this._declarations); + } + }, }); /** @@ -2264,10 +2338,3 @@ function getTextAtLineColumn(text, line, column) { } exports.getTextAtLineColumn = getTextAtLineColumn; - -function _hasVisitedState(node) { - return ( - !!(InspectorUtils.getContentState(node) & NS_EVENT_STATE_VISITED) || - InspectorUtils.hasPseudoClassLock(node, ":visited") - ); -} diff --git a/devtools/server/actors/stylesheets.js b/devtools/server/actors/stylesheets.js index c50a577e53..a3701c306e 100644 --- a/devtools/server/actors/stylesheets.js +++ b/devtools/server/actors/stylesheets.js @@ -162,7 +162,7 @@ exports.getSheetText = getSheetText; function getCSSCharset(sheet) { if (sheet) { // charset attribute of or