summaryrefslogtreecommitdiffstats
path: root/devtools/client/debugger
diff options
context:
space:
mode:
authorMatt A. Tobin <mattatobin@localhost.localdomain>2018-02-02 04:16:08 -0500
committerMatt A. Tobin <mattatobin@localhost.localdomain>2018-02-02 04:16:08 -0500
commit5f8de423f190bbb79a62f804151bc24824fa32d8 (patch)
tree10027f336435511475e392454359edea8e25895d /devtools/client/debugger
parent49ee0794b5d912db1f95dce6eb52d781dc210db5 (diff)
downloadUXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar
UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.gz
UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.lz
UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.xz
UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.zip
Add m-esr52 at 52.6.0
Diffstat (limited to 'devtools/client/debugger')
-rw-r--r--devtools/client/debugger/content/actions/breakpoints.js191
-rw-r--r--devtools/client/debugger/content/actions/event-listeners.js118
-rw-r--r--devtools/client/debugger/content/actions/moz.build10
-rw-r--r--devtools/client/debugger/content/actions/sources.js280
-rw-r--r--devtools/client/debugger/content/constants.js25
-rw-r--r--devtools/client/debugger/content/globalActions.js18
-rw-r--r--devtools/client/debugger/content/moz.build17
-rw-r--r--devtools/client/debugger/content/queries.js70
-rw-r--r--devtools/client/debugger/content/reducers/async-requests.js31
-rw-r--r--devtools/client/debugger/content/reducers/breakpoints.js153
-rw-r--r--devtools/client/debugger/content/reducers/event-listeners.js37
-rw-r--r--devtools/client/debugger/content/reducers/index.js16
-rw-r--r--devtools/client/debugger/content/reducers/moz.build12
-rw-r--r--devtools/client/debugger/content/reducers/sources.js128
-rw-r--r--devtools/client/debugger/content/utils.js88
-rw-r--r--devtools/client/debugger/content/views/event-listeners-view.js295
-rw-r--r--devtools/client/debugger/content/views/moz.build9
-rw-r--r--devtools/client/debugger/content/views/sources-view.js1370
-rw-r--r--devtools/client/debugger/debugger-commands.js633
-rw-r--r--devtools/client/debugger/debugger-controller.js1276
-rw-r--r--devtools/client/debugger/debugger-view.js982
-rw-r--r--devtools/client/debugger/debugger.css69
-rw-r--r--devtools/client/debugger/debugger.xul474
-rw-r--r--devtools/client/debugger/moz.build20
-rw-r--r--devtools/client/debugger/new/bundle.js58335
-rw-r--r--devtools/client/debugger/new/images/Icons.js46
-rw-r--r--devtools/client/debugger/new/images/Svg.js43
-rw-r--r--devtools/client/debugger/new/images/angle-brackets.svg9
-rw-r--r--devtools/client/debugger/new/images/arrow.svg6
-rw-r--r--devtools/client/debugger/new/images/blackBox.svg9
-rw-r--r--devtools/client/debugger/new/images/breakpoint.svg6
-rw-r--r--devtools/client/debugger/new/images/close.svg7
-rw-r--r--devtools/client/debugger/new/images/disableBreakpoints.svg8
-rw-r--r--devtools/client/debugger/new/images/domain.svg7
-rw-r--r--devtools/client/debugger/new/images/favicon.png0
-rw-r--r--devtools/client/debugger/new/images/file.svg7
-rw-r--r--devtools/client/debugger/new/images/folder.svg6
-rw-r--r--devtools/client/debugger/new/images/globe.svg10
-rw-r--r--devtools/client/debugger/new/images/magnifying-glass.svg4
-rw-r--r--devtools/client/debugger/new/images/pause-circle.svg10
-rw-r--r--devtools/client/debugger/new/images/pause-exceptions.svg7
-rw-r--r--devtools/client/debugger/new/images/pause.svg8
-rw-r--r--devtools/client/debugger/new/images/play.svg6
-rw-r--r--devtools/client/debugger/new/images/plus.svg6
-rw-r--r--devtools/client/debugger/new/images/prettyPrint.svg6
-rw-r--r--devtools/client/debugger/new/images/resume.svg6
-rw-r--r--devtools/client/debugger/new/images/sad-face.svg9
-rw-r--r--devtools/client/debugger/new/images/settings.svg6
-rw-r--r--devtools/client/debugger/new/images/stepIn.svg8
-rw-r--r--devtools/client/debugger/new/images/stepOut.svg8
-rw-r--r--devtools/client/debugger/new/images/stepOver.svg9
-rw-r--r--devtools/client/debugger/new/images/subSettings.svg6
-rw-r--r--devtools/client/debugger/new/images/toggle-breakpoints.svg8
-rw-r--r--devtools/client/debugger/new/images/worker.svg6
-rw-r--r--devtools/client/debugger/new/index.html31
-rw-r--r--devtools/client/debugger/new/moz.build12
-rw-r--r--devtools/client/debugger/new/panel.js77
-rw-r--r--devtools/client/debugger/new/pretty-print-worker.js5904
-rw-r--r--devtools/client/debugger/new/source-map-worker.js5831
-rw-r--r--devtools/client/debugger/new/styles.css1724
-rw-r--r--devtools/client/debugger/new/test/mochitest/.eslintrc80
-rw-r--r--devtools/client/debugger/new/test/mochitest/browser.ini60
-rw-r--r--devtools/client/debugger/new/test/mochitest/browser_dbg-breaking-from-console.js31
-rw-r--r--devtools/client/debugger/new/test/mochitest/browser_dbg-breaking.js32
-rw-r--r--devtools/client/debugger/new/test/mochitest/browser_dbg-breakpoints-cond.js50
-rw-r--r--devtools/client/debugger/new/test/mochitest/browser_dbg-breakpoints.js101
-rw-r--r--devtools/client/debugger/new/test/mochitest/browser_dbg-call-stack.js62
-rw-r--r--devtools/client/debugger/new/test/mochitest/browser_dbg-chrome-create.js72
-rw-r--r--devtools/client/debugger/new/test/mochitest/browser_dbg-chrome-debugging.js88
-rw-r--r--devtools/client/debugger/new/test/mochitest/browser_dbg-console.js34
-rw-r--r--devtools/client/debugger/new/test/mochitest/browser_dbg-debugger-buttons.js54
-rw-r--r--devtools/client/debugger/new/test/mochitest/browser_dbg-editor-gutter.js64
-rw-r--r--devtools/client/debugger/new/test/mochitest/browser_dbg-editor-highlight.js46
-rw-r--r--devtools/client/debugger/new/test/mochitest/browser_dbg-editor-mode.js14
-rw-r--r--devtools/client/debugger/new/test/mochitest/browser_dbg-editor-select.js54
-rw-r--r--devtools/client/debugger/new/test/mochitest/browser_dbg-iframes.js26
-rw-r--r--devtools/client/debugger/new/test/mochitest/browser_dbg-navigation.js47
-rw-r--r--devtools/client/debugger/new/test/mochitest/browser_dbg-pause-exceptions.js46
-rw-r--r--devtools/client/debugger/new/test/mochitest/browser_dbg-pretty-print-paused.js22
-rw-r--r--devtools/client/debugger/new/test/mochitest/browser_dbg-pretty-print.js31
-rw-r--r--devtools/client/debugger/new/test/mochitest/browser_dbg-scopes.js27
-rw-r--r--devtools/client/debugger/new/test/mochitest/browser_dbg-searching.js28
-rw-r--r--devtools/client/debugger/new/test/mochitest/browser_dbg-sourcemaps-bogus.js23
-rw-r--r--devtools/client/debugger/new/test/mochitest/browser_dbg-sourcemaps.js44
-rw-r--r--devtools/client/debugger/new/test/mochitest/browser_dbg-sources.js58
-rw-r--r--devtools/client/debugger/new/test/mochitest/browser_dbg_keyboard-shortcuts.js46
-rw-r--r--devtools/client/debugger/new/test/mochitest/examples/README.md7
-rw-r--r--devtools/client/debugger/new/test/mochitest/examples/bogus-map.js8
-rw-r--r--devtools/client/debugger/new/test/mochitest/examples/bundle.js96
-rw-r--r--devtools/client/debugger/new/test/mochitest/examples/bundle.js.map1
-rw-r--r--devtools/client/debugger/new/test/mochitest/examples/doc-debugger-statements.html27
-rw-r--r--devtools/client/debugger/new/test/mochitest/examples/doc-exceptions.html7
-rw-r--r--devtools/client/debugger/new/test/mochitest/examples/doc-frames.html17
-rw-r--r--devtools/client/debugger/new/test/mochitest/examples/doc-iframes.html17
-rw-r--r--devtools/client/debugger/new/test/mochitest/examples/doc-minified.html14
-rw-r--r--devtools/client/debugger/new/test/mochitest/examples/doc-script-switching.html18
-rw-r--r--devtools/client/debugger/new/test/mochitest/examples/doc-scripts.html21
-rw-r--r--devtools/client/debugger/new/test/mochitest/examples/doc-sourcemap-bogus.html13
-rw-r--r--devtools/client/debugger/new/test/mochitest/examples/doc-sourcemaps.html13
-rw-r--r--devtools/client/debugger/new/test/mochitest/examples/doc-sources.html23
-rw-r--r--devtools/client/debugger/new/test/mochitest/examples/entry.js16
-rw-r--r--devtools/client/debugger/new/test/mochitest/examples/exceptions.js19
-rw-r--r--devtools/client/debugger/new/test/mochitest/examples/frames.js24
-rw-r--r--devtools/client/debugger/new/test/mochitest/examples/long.js76
-rw-r--r--devtools/client/debugger/new/test/mochitest/examples/math.min.js3
-rw-r--r--devtools/client/debugger/new/test/mochitest/examples/nested/nested-source.js3
-rw-r--r--devtools/client/debugger/new/test/mochitest/examples/opts.js3
-rw-r--r--devtools/client/debugger/new/test/mochitest/examples/output.js5
-rw-r--r--devtools/client/debugger/new/test/mochitest/examples/script-switching-01.js6
-rw-r--r--devtools/client/debugger/new/test/mochitest/examples/script-switching-02.js13
-rw-r--r--devtools/client/debugger/new/test/mochitest/examples/simple1.js31
-rw-r--r--devtools/client/debugger/new/test/mochitest/examples/simple2.js6
-rw-r--r--devtools/client/debugger/new/test/mochitest/examples/times2.js3
-rw-r--r--devtools/client/debugger/new/test/mochitest/examples/webpack.config.js8
-rw-r--r--devtools/client/debugger/new/test/mochitest/head.js684
-rw-r--r--devtools/client/debugger/panel.js180
-rw-r--r--devtools/client/debugger/test/.eslintrc.js6
-rw-r--r--devtools/client/debugger/test/mochitest/addon-source/browser_dbg_addon3/lib/main.js13
-rw-r--r--devtools/client/debugger/test/mochitest/addon-source/browser_dbg_addon3/package.json9
-rw-r--r--devtools/client/debugger/test/mochitest/addon-source/browser_dbg_addon4/bootstrap.js36
-rw-r--r--devtools/client/debugger/test/mochitest/addon-source/browser_dbg_addon4/chrome.manifest1
-rw-r--r--devtools/client/debugger/test/mochitest/addon-source/browser_dbg_addon4/install.rdf19
-rw-r--r--devtools/client/debugger/test/mochitest/addon-source/browser_dbg_addon4/test.jsm6
-rw-r--r--devtools/client/debugger/test/mochitest/addon-source/browser_dbg_addon4/test.xul8
-rw-r--r--devtools/client/debugger/test/mochitest/addon-source/browser_dbg_addon4/test2.jsm6
-rw-r--r--devtools/client/debugger/test/mochitest/addon-source/browser_dbg_addon4/test2.xul8
-rw-r--r--devtools/client/debugger/test/mochitest/addon-source/browser_dbg_addon4/testxul.js4
-rw-r--r--devtools/client/debugger/test/mochitest/addon-source/browser_dbg_addon4/testxul2.js4
-rw-r--r--devtools/client/debugger/test/mochitest/addon-source/browser_dbg_addon5/bootstrap.js23
-rw-r--r--devtools/client/debugger/test/mochitest/addon-source/browser_dbg_addon5/chrome.manifest1
-rw-r--r--devtools/client/debugger/test/mochitest/addon-source/browser_dbg_addon5/install.rdf20
-rw-r--r--devtools/client/debugger/test/mochitest/addon-source/browser_dbg_addon5/test.jsm6
-rw-r--r--devtools/client/debugger/test/mochitest/addon-source/browser_dbg_addon5/test.xul8
-rw-r--r--devtools/client/debugger/test/mochitest/addon-source/browser_dbg_addon5/test2.jsm6
-rw-r--r--devtools/client/debugger/test/mochitest/addon-source/browser_dbg_addon5/test2.xul8
-rw-r--r--devtools/client/debugger/test/mochitest/addon-source/browser_dbg_addon5/testxul.js4
-rw-r--r--devtools/client/debugger/test/mochitest/addon-source/browser_dbg_addon5/testxul2.js4
-rw-r--r--devtools/client/debugger/test/mochitest/addon-source/browser_dbg_addon_webext_contentscript/manifest.json18
-rw-r--r--devtools/client/debugger/test/mochitest/addon-source/browser_dbg_addon_webext_contentscript/webext-content-script.js1
-rw-r--r--devtools/client/debugger/test/mochitest/addon-webext-contentscript.xpibin0 -> 4648 bytes
-rw-r--r--devtools/client/debugger/test/mochitest/addon1.xpibin0 -> 5577 bytes
-rw-r--r--devtools/client/debugger/test/mochitest/addon2.xpibin0 -> 5578 bytes
-rw-r--r--devtools/client/debugger/test/mochitest/addon3.xpibin0 -> 12718 bytes
-rw-r--r--devtools/client/debugger/test/mochitest/addon4.xpibin0 -> 7340 bytes
-rw-r--r--devtools/client/debugger/test/mochitest/addon5.xpibin0 -> 7224 bytes
-rw-r--r--devtools/client/debugger/test/mochitest/browser.ini317
-rw-r--r--devtools/client/debugger/test/mochitest/browser2.ini460
-rw-r--r--devtools/client/debugger/test/mochitest/browser_dbg_WorkerActor.attach.js62
-rw-r--r--devtools/client/debugger/test/mochitest/browser_dbg_WorkerActor.attachThread.js100
-rw-r--r--devtools/client/debugger/test/mochitest/browser_dbg_aaa_run_first_leaktest.js33
-rw-r--r--devtools/client/debugger/test/mochitest/browser_dbg_addon-console.js47
-rw-r--r--devtools/client/debugger/test/mochitest/browser_dbg_addon-modules-unpacked.js67
-rw-r--r--devtools/client/debugger/test/mochitest/browser_dbg_addon-modules.js66
-rw-r--r--devtools/client/debugger/test/mochitest/browser_dbg_addon-panels.js49
-rw-r--r--devtools/client/debugger/test/mochitest/browser_dbg_addon-sources.js42
-rw-r--r--devtools/client/debugger/test/mochitest/browser_dbg_addon-workers-dbg-enabled.js41
-rw-r--r--devtools/client/debugger/test/mochitest/browser_dbg_addonactor.js95
-rw-r--r--devtools/client/debugger/test/mochitest/browser_dbg_auto-pretty-print-01.js117
-rw-r--r--devtools/client/debugger/test/mochitest/browser_dbg_auto-pretty-print-02.js126
-rw-r--r--devtools/client/debugger/test/mochitest/browser_dbg_auto-pretty-print-03.js58
-rw-r--r--devtools/client/debugger/test/mochitest/browser_dbg_bfcache.js95
-rw-r--r--devtools/client/debugger/test/mochitest/browser_dbg_blackboxing-01.js57
-rw-r--r--devtools/client/debugger/test/mochitest/browser_dbg_blackboxing-02.js60
-rw-r--r--devtools/client/debugger/test/mochitest/browser_dbg_blackboxing-03.js65
-rw-r--r--devtools/client/debugger/test/mochitest/browser_dbg_blackboxing-04.js65
-rw-r--r--devtools/client/debugger/test/mochitest/browser_dbg_blackboxing-05.js74
-rw-r--r--devtools/client/debugger/test/mochitest/browser_dbg_blackboxing-06.js61
-rw-r--r--devtools/client/debugger/test/mochitest/browser_dbg_blackboxing-07.js53
-rw-r--r--devtools/client/debugger/test/mochitest/browser_dbg_breadcrumbs-access.js98
-rw-r--r--devtools/client/debugger/test/mochitest/browser_dbg_break-in-anon.js40
-rw-r--r--devtools/client/debugger/test/mochitest/browser_dbg_break-on-dom-01.js58
-rw-r--r--devtools/client/debugger/test/mochitest/browser_dbg_break-on-dom-02.js135
-rw-r--r--devtools/client/debugger/test/mochitest/browser_dbg_break-on-dom-03.js102
-rw-r--r--devtools/client/debugger/test/mochitest/browser_dbg_break-on-dom-04.js101
-rw-r--r--devtools/client/debugger/test/mochitest/browser_dbg_break-on-dom-05.js128
-rw-r--r--devtools/client/debugger/test/mochitest/browser_dbg_break-on-dom-06.js130
-rw-r--r--devtools/client/debugger/test/mochitest/browser_dbg_break-on-dom-07.js106
-rw-r--r--devtools/client/debugger/test/mochitest/browser_dbg_break-on-dom-08.js61
-rw-r--r--devtools/client/debugger/test/mochitest/browser_dbg_break-on-dom-event-01.js225
-rw-r--r--devtools/client/debugger/test/mochitest/browser_dbg_break-on-dom-event-02.js105
-rw-r--r--devtools/client/debugger/test/mochitest/browser_dbg_break-on-dom-event-03.js97
-rw-r--r--devtools/client/debugger/test/mochitest/browser_dbg_break-on-next-console.js61
-rw-r--r--devtools/client/debugger/test/mochitest/browser_dbg_break-on-next.js103
-rw-r--r--devtools/client/debugger/test/mochitest/browser_dbg_break-unselected.js48
-rw-r--r--devtools/client/debugger/test/mochitest/browser_dbg_breakpoints-actual-location.js55
-rw-r--r--devtools/client/debugger/test/mochitest/browser_dbg_breakpoints-actual-location2.js88
-rw-r--r--devtools/client/debugger/test/mochitest/browser_dbg_breakpoints-break-on-last-line-of-script-on-reload.js116
-rw-r--r--devtools/client/debugger/test/mochitest/browser_dbg_breakpoints-button-01.js55
-rw-r--r--devtools/client/debugger/test/mochitest/browser_dbg_breakpoints-button-02.js64
-rw-r--r--devtools/client/debugger/test/mochitest/browser_dbg_breakpoints-condition-thrown-message.js107
-rw-r--r--devtools/client/debugger/test/mochitest/browser_dbg_breakpoints-contextmenu-add.js84
-rw-r--r--devtools/client/debugger/test/mochitest/browser_dbg_breakpoints-contextmenu.js252
-rw-r--r--devtools/client/debugger/test/mochitest/browser_dbg_breakpoints-disabled-reload.js124
-rw-r--r--devtools/client/debugger/test/mochitest/browser_dbg_breakpoints-editor.js241
-rw-r--r--devtools/client/debugger/test/mochitest/browser_dbg_breakpoints-eval.js47
-rw-r--r--devtools/client/debugger/test/mochitest/browser_dbg_breakpoints-highlight.js90
-rw-r--r--devtools/client/debugger/test/mochitest/browser_dbg_breakpoints-new-script.js92
-rw-r--r--devtools/client/debugger/test/mochitest/browser_dbg_breakpoints-other-tabs.js41
-rw-r--r--devtools/client/debugger/test/mochitest/browser_dbg_breakpoints-pane.js238
-rw-r--r--devtools/client/debugger/test/mochitest/browser_dbg_breakpoints-reload.js39
-rw-r--r--devtools/client/debugger/test/mochitest/browser_dbg_bug-896139.js48
-rw-r--r--devtools/client/debugger/test/mochitest/browser_dbg_chrome-create.js64
-rw-r--r--devtools/client/debugger/test/mochitest/browser_dbg_chrome-debugging.js102
-rw-r--r--devtools/client/debugger/test/mochitest/browser_dbg_clean-exit-window.js86
-rw-r--r--devtools/client/debugger/test/mochitest/browser_dbg_clean-exit.js44
-rw-r--r--devtools/client/debugger/test/mochitest/browser_dbg_closure-inspection.js153
-rw-r--r--devtools/client/debugger/test/mochitest/browser_dbg_cmd-blackbox.js117
-rw-r--r--devtools/client/debugger/test/mochitest/browser_dbg_cmd-break.js225
-rw-r--r--devtools/client/debugger/test/mochitest/browser_dbg_cmd-dbg.js102
-rw-r--r--devtools/client/debugger/test/mochitest/browser_dbg_conditional-breakpoints-01.js218
-rw-r--r--devtools/client/debugger/test/mochitest/browser_dbg_conditional-breakpoints-02.js219
-rw-r--r--devtools/client/debugger/test/mochitest/browser_dbg_conditional-breakpoints-03.js78
-rw-r--r--devtools/client/debugger/test/mochitest/browser_dbg_conditional-breakpoints-04.js52
-rw-r--r--devtools/client/debugger/test/mochitest/browser_dbg_conditional-breakpoints-05.js141
-rw-r--r--devtools/client/debugger/test/mochitest/browser_dbg_console-eval.js41
-rw-r--r--devtools/client/debugger/test/mochitest/browser_dbg_console-named-eval.js42
-rw-r--r--devtools/client/debugger/test/mochitest/browser_dbg_controller-evaluate-01.js106
-rw-r--r--devtools/client/debugger/test/mochitest/browser_dbg_controller-evaluate-02.js78
-rw-r--r--devtools/client/debugger/test/mochitest/browser_dbg_debugger-statement.js87
-rw-r--r--devtools/client/debugger/test/mochitest/browser_dbg_editor-contextmenu.js68
-rw-r--r--devtools/client/debugger/test/mochitest/browser_dbg_editor-mode.js97
-rw-r--r--devtools/client/debugger/test/mochitest/browser_dbg_event-listeners-01.js147
-rw-r--r--devtools/client/debugger/test/mochitest/browser_dbg_event-listeners-02.js123
-rw-r--r--devtools/client/debugger/test/mochitest/browser_dbg_event-listeners-03.js82
-rw-r--r--devtools/client/debugger/test/mochitest/browser_dbg_event-listeners-04.js55
-rw-r--r--devtools/client/debugger/test/mochitest/browser_dbg_file-reload.js72
-rw-r--r--devtools/client/debugger/test/mochitest/browser_dbg_function-display-name.js68
-rw-r--r--devtools/client/debugger/test/mochitest/browser_dbg_global-method-override.js26
-rw-r--r--devtools/client/debugger/test/mochitest/browser_dbg_globalactor.js61
-rw-r--r--devtools/client/debugger/test/mochitest/browser_dbg_hide-toolbar-buttons.js34
-rw-r--r--devtools/client/debugger/test/mochitest/browser_dbg_host-layout.js166
-rw-r--r--devtools/client/debugger/test/mochitest/browser_dbg_iframes.js72
-rw-r--r--devtools/client/debugger/test/mochitest/browser_dbg_instruments-pane-collapse.js167
-rw-r--r--devtools/client/debugger/test/mochitest/browser_dbg_instruments-pane-collapse_keyboard.js40
-rw-r--r--devtools/client/debugger/test/mochitest/browser_dbg_interrupts.js123
-rw-r--r--devtools/client/debugger/test/mochitest/browser_dbg_jump-to-function-definition.js50
-rw-r--r--devtools/client/debugger/test/mochitest/browser_dbg_listaddons.js112
-rw-r--r--devtools/client/debugger/test/mochitest/browser_dbg_listtabs-01.js98
-rw-r--r--devtools/client/debugger/test/mochitest/browser_dbg_listtabs-02.js219
-rw-r--r--devtools/client/debugger/test/mochitest/browser_dbg_listtabs-03.js61
-rw-r--r--devtools/client/debugger/test/mochitest/browser_dbg_listworkers.js59
-rw-r--r--devtools/client/debugger/test/mochitest/browser_dbg_location-changes-01-simple.js60
-rw-r--r--devtools/client/debugger/test/mochitest/browser_dbg_location-changes-02-blank.js57
-rw-r--r--devtools/client/debugger/test/mochitest/browser_dbg_location-changes-03-new.js59
-rw-r--r--devtools/client/debugger/test/mochitest/browser_dbg_location-changes-04-breakpoint.js165
-rw-r--r--devtools/client/debugger/test/mochitest/browser_dbg_multiple-windows.js165
-rw-r--r--devtools/client/debugger/test/mochitest/browser_dbg_navigation.js75
-rw-r--r--devtools/client/debugger/test/mochitest/browser_dbg_no-dangling-breakpoints.js25
-rw-r--r--devtools/client/debugger/test/mochitest/browser_dbg_no-page-sources.js54
-rw-r--r--devtools/client/debugger/test/mochitest/browser_dbg_on-pause-highlight.js86
-rw-r--r--devtools/client/debugger/test/mochitest/browser_dbg_on-pause-raise.js120
-rw-r--r--devtools/client/debugger/test/mochitest/browser_dbg_optimized-out-vars.js50
-rw-r--r--devtools/client/debugger/test/mochitest/browser_dbg_panel-size.js88
-rw-r--r--devtools/client/debugger/test/mochitest/browser_dbg_parser-01.js33
-rw-r--r--devtools/client/debugger/test/mochitest/browser_dbg_parser-02.js30
-rw-r--r--devtools/client/debugger/test/mochitest/browser_dbg_parser-03.js79
-rw-r--r--devtools/client/debugger/test/mochitest/browser_dbg_parser-04.js58
-rw-r--r--devtools/client/debugger/test/mochitest/browser_dbg_parser-05.js45
-rw-r--r--devtools/client/debugger/test/mochitest/browser_dbg_parser-06.js80
-rw-r--r--devtools/client/debugger/test/mochitest/browser_dbg_parser-07.js57
-rw-r--r--devtools/client/debugger/test/mochitest/browser_dbg_parser-08.js291
-rw-r--r--devtools/client/debugger/test/mochitest/browser_dbg_parser-09.js292
-rw-r--r--devtools/client/debugger/test/mochitest/browser_dbg_parser-10.js129
-rw-r--r--devtools/client/debugger/test/mochitest/browser_dbg_parser-11.js41
-rw-r--r--devtools/client/debugger/test/mochitest/browser_dbg_parser-computed-name.js32
-rw-r--r--devtools/client/debugger/test/mochitest/browser_dbg_parser-function-defaults.js31
-rw-r--r--devtools/client/debugger/test/mochitest/browser_dbg_parser-spread-expression.js32
-rw-r--r--devtools/client/debugger/test/mochitest/browser_dbg_parser-template-strings.js29
-rw-r--r--devtools/client/debugger/test/mochitest/browser_dbg_pause-exceptions-01.js246
-rw-r--r--devtools/client/debugger/test/mochitest/browser_dbg_pause-exceptions-02.js204
-rw-r--r--devtools/client/debugger/test/mochitest/browser_dbg_pause-no-step.js94
-rw-r--r--devtools/client/debugger/test/mochitest/browser_dbg_pause-resume.js91
-rw-r--r--devtools/client/debugger/test/mochitest/browser_dbg_pause-warning.js109
-rw-r--r--devtools/client/debugger/test/mochitest/browser_dbg_paused-keybindings.js50
-rw-r--r--devtools/client/debugger/test/mochitest/browser_dbg_post-page.js53
-rw-r--r--devtools/client/debugger/test/mochitest/browser_dbg_pretty-print-01.js52
-rw-r--r--devtools/client/debugger/test/mochitest/browser_dbg_pretty-print-02.js41
-rw-r--r--devtools/client/debugger/test/mochitest/browser_dbg_pretty-print-03.js40
-rw-r--r--devtools/client/debugger/test/mochitest/browser_dbg_pretty-print-04.js50
-rw-r--r--devtools/client/debugger/test/mochitest/browser_dbg_pretty-print-05.js66
-rw-r--r--devtools/client/debugger/test/mochitest/browser_dbg_pretty-print-06.js80
-rw-r--r--devtools/client/debugger/test/mochitest/browser_dbg_pretty-print-07.js62
-rw-r--r--devtools/client/debugger/test/mochitest/browser_dbg_pretty-print-08.js99
-rw-r--r--devtools/client/debugger/test/mochitest/browser_dbg_pretty-print-09.js92
-rw-r--r--devtools/client/debugger/test/mochitest/browser_dbg_pretty-print-10.js48
-rw-r--r--devtools/client/debugger/test/mochitest/browser_dbg_pretty-print-11.js65
-rw-r--r--devtools/client/debugger/test/mochitest/browser_dbg_pretty-print-12.js51
-rw-r--r--devtools/client/debugger/test/mochitest/browser_dbg_pretty-print-13.js53
-rw-r--r--devtools/client/debugger/test/mochitest/browser_dbg_pretty-print-on-paused.js69
-rw-r--r--devtools/client/debugger/test/mochitest/browser_dbg_progress-listener-bug.js89
-rw-r--r--devtools/client/debugger/test/mochitest/browser_dbg_promises-allocation-stack.js87
-rw-r--r--devtools/client/debugger/test/mochitest/browser_dbg_promises-chrome-allocation-stack.js100
-rw-r--r--devtools/client/debugger/test/mochitest/browser_dbg_promises-fulfillment-stack.js106
-rw-r--r--devtools/client/debugger/test/mochitest/browser_dbg_promises-rejection-stack.js106
-rw-r--r--devtools/client/debugger/test/mochitest/browser_dbg_reload-preferred-script-02.js50
-rw-r--r--devtools/client/debugger/test/mochitest/browser_dbg_reload-preferred-script-03.js62
-rw-r--r--devtools/client/debugger/test/mochitest/browser_dbg_reload-same-script.js88
-rw-r--r--devtools/client/debugger/test/mochitest/browser_dbg_scripts-switching-01.js162
-rw-r--r--devtools/client/debugger/test/mochitest/browser_dbg_scripts-switching-02.js163
-rw-r--r--devtools/client/debugger/test/mochitest/browser_dbg_scripts-switching-03.js63
-rw-r--r--devtools/client/debugger/test/mochitest/browser_dbg_search-autofill-identifier.js138
-rw-r--r--devtools/client/debugger/test/mochitest/browser_dbg_search-basic-01.js330
-rw-r--r--devtools/client/debugger/test/mochitest/browser_dbg_search-basic-02.js129
-rw-r--r--devtools/client/debugger/test/mochitest/browser_dbg_search-basic-03.js123
-rw-r--r--devtools/client/debugger/test/mochitest/browser_dbg_search-basic-04.js132
-rw-r--r--devtools/client/debugger/test/mochitest/browser_dbg_search-global-01.js278
-rw-r--r--devtools/client/debugger/test/mochitest/browser_dbg_search-global-02.js203
-rw-r--r--devtools/client/debugger/test/mochitest/browser_dbg_search-global-03.js110
-rw-r--r--devtools/client/debugger/test/mochitest/browser_dbg_search-global-04.js98
-rw-r--r--devtools/client/debugger/test/mochitest/browser_dbg_search-global-05.js160
-rw-r--r--devtools/client/debugger/test/mochitest/browser_dbg_search-global-06.js125
-rw-r--r--devtools/client/debugger/test/mochitest/browser_dbg_search-popup-jank.js128
-rw-r--r--devtools/client/debugger/test/mochitest/browser_dbg_search-sources-01.js232
-rw-r--r--devtools/client/debugger/test/mochitest/browser_dbg_search-sources-02.js281
-rw-r--r--devtools/client/debugger/test/mochitest/browser_dbg_search-sources-03.js103
-rw-r--r--devtools/client/debugger/test/mochitest/browser_dbg_search-symbols.js472
-rw-r--r--devtools/client/debugger/test/mochitest/browser_dbg_searchbox-help-popup-01.js64
-rw-r--r--devtools/client/debugger/test/mochitest/browser_dbg_searchbox-help-popup-02.js90
-rw-r--r--devtools/client/debugger/test/mochitest/browser_dbg_searchbox-parse.js126
-rw-r--r--devtools/client/debugger/test/mochitest/browser_dbg_server-conditional-bp-01.js218
-rw-r--r--devtools/client/debugger/test/mochitest/browser_dbg_server-conditional-bp-02.js214
-rw-r--r--devtools/client/debugger/test/mochitest/browser_dbg_server-conditional-bp-03.js73
-rw-r--r--devtools/client/debugger/test/mochitest/browser_dbg_server-conditional-bp-04.js46
-rw-r--r--devtools/client/debugger/test/mochitest/browser_dbg_server-conditional-bp-05.js134
-rw-r--r--devtools/client/debugger/test/mochitest/browser_dbg_source-maps-01.js170
-rw-r--r--devtools/client/debugger/test/mochitest/browser_dbg_source-maps-02.js153
-rw-r--r--devtools/client/debugger/test/mochitest/browser_dbg_source-maps-03.js88
-rw-r--r--devtools/client/debugger/test/mochitest/browser_dbg_source-maps-04.js187
-rw-r--r--devtools/client/debugger/test/mochitest/browser_dbg_sources-bookmarklet.js53
-rw-r--r--devtools/client/debugger/test/mochitest/browser_dbg_sources-cache.js147
-rw-r--r--devtools/client/debugger/test/mochitest/browser_dbg_sources-contextmenu-01.js57
-rw-r--r--devtools/client/debugger/test/mochitest/browser_dbg_sources-contextmenu-02.js75
-rw-r--r--devtools/client/debugger/test/mochitest/browser_dbg_sources-eval-01.js44
-rw-r--r--devtools/client/debugger/test/mochitest/browser_dbg_sources-eval-02.js55
-rw-r--r--devtools/client/debugger/test/mochitest/browser_dbg_sources-iframe-reload.js35
-rw-r--r--devtools/client/debugger/test/mochitest/browser_dbg_sources-keybindings.js40
-rw-r--r--devtools/client/debugger/test/mochitest/browser_dbg_sources-labels.js172
-rw-r--r--devtools/client/debugger/test/mochitest/browser_dbg_sources-large.js80
-rw-r--r--devtools/client/debugger/test/mochitest/browser_dbg_sources-sorting.js141
-rw-r--r--devtools/client/debugger/test/mochitest/browser_dbg_sources-webext-contentscript.js63
-rw-r--r--devtools/client/debugger/test/mochitest/browser_dbg_split-console-keypress.js108
-rw-r--r--devtools/client/debugger/test/mochitest/browser_dbg_split-console-paused-reload.js67
-rw-r--r--devtools/client/debugger/test/mochitest/browser_dbg_stack-01.js49
-rw-r--r--devtools/client/debugger/test/mochitest/browser_dbg_stack-02.js115
-rw-r--r--devtools/client/debugger/test/mochitest/browser_dbg_stack-03.js64
-rw-r--r--devtools/client/debugger/test/mochitest/browser_dbg_stack-04.js58
-rw-r--r--devtools/client/debugger/test/mochitest/browser_dbg_stack-05.js102
-rw-r--r--devtools/client/debugger/test/mochitest/browser_dbg_stack-06.js92
-rw-r--r--devtools/client/debugger/test/mochitest/browser_dbg_stack-07.js113
-rw-r--r--devtools/client/debugger/test/mochitest/browser_dbg_stack-contextmenu-01.js58
-rw-r--r--devtools/client/debugger/test/mochitest/browser_dbg_stack-contextmenu-02.js58
-rw-r--r--devtools/client/debugger/test/mochitest/browser_dbg_step-out.js91
-rw-r--r--devtools/client/debugger/test/mochitest/browser_dbg_tabactor-01.js65
-rw-r--r--devtools/client/debugger/test/mochitest/browser_dbg_tabactor-02.js79
-rw-r--r--devtools/client/debugger/test/mochitest/browser_dbg_terminate-on-tab-close.js34
-rw-r--r--devtools/client/debugger/test/mochitest/browser_dbg_variables-view-01.js132
-rw-r--r--devtools/client/debugger/test/mochitest/browser_dbg_variables-view-02.js227
-rw-r--r--devtools/client/debugger/test/mochitest/browser_dbg_variables-view-03.js157
-rw-r--r--devtools/client/debugger/test/mochitest/browser_dbg_variables-view-04.js156
-rw-r--r--devtools/client/debugger/test/mochitest/browser_dbg_variables-view-05.js234
-rw-r--r--devtools/client/debugger/test/mochitest/browser_dbg_variables-view-06.js125
-rw-r--r--devtools/client/debugger/test/mochitest/browser_dbg_variables-view-07.js69
-rw-r--r--devtools/client/debugger/test/mochitest/browser_dbg_variables-view-08.js61
-rw-r--r--devtools/client/debugger/test/mochitest/browser_dbg_variables-view-accessibility.js557
-rw-r--r--devtools/client/debugger/test/mochitest/browser_dbg_variables-view-data.js611
-rw-r--r--devtools/client/debugger/test/mochitest/browser_dbg_variables-view-edit-cancel.js58
-rw-r--r--devtools/client/debugger/test/mochitest/browser_dbg_variables-view-edit-click.js58
-rw-r--r--devtools/client/debugger/test/mochitest/browser_dbg_variables-view-edit-getset-01.js300
-rw-r--r--devtools/client/debugger/test/mochitest/browser_dbg_variables-view-edit-getset-02.js107
-rw-r--r--devtools/client/debugger/test/mochitest/browser_dbg_variables-view-edit-value.js91
-rw-r--r--devtools/client/debugger/test/mochitest/browser_dbg_variables-view-edit-watch.js510
-rw-r--r--devtools/client/debugger/test/mochitest/browser_dbg_variables-view-filter-01.js241
-rw-r--r--devtools/client/debugger/test/mochitest/browser_dbg_variables-view-filter-02.js249
-rw-r--r--devtools/client/debugger/test/mochitest/browser_dbg_variables-view-filter-03.js178
-rw-r--r--devtools/client/debugger/test/mochitest/browser_dbg_variables-view-filter-04.js243
-rw-r--r--devtools/client/debugger/test/mochitest/browser_dbg_variables-view-filter-05.js254
-rw-r--r--devtools/client/debugger/test/mochitest/browser_dbg_variables-view-filter-pref.js85
-rw-r--r--devtools/client/debugger/test/mochitest/browser_dbg_variables-view-filter-searchbox.js150
-rw-r--r--devtools/client/debugger/test/mochitest/browser_dbg_variables-view-frame-parameters-01.js270
-rw-r--r--devtools/client/debugger/test/mochitest/browser_dbg_variables-view-frame-parameters-02.js552
-rw-r--r--devtools/client/debugger/test/mochitest/browser_dbg_variables-view-frame-parameters-03.js157
-rw-r--r--devtools/client/debugger/test/mochitest/browser_dbg_variables-view-frame-with.js212
-rw-r--r--devtools/client/debugger/test/mochitest/browser_dbg_variables-view-frozen-sealed-nonext.js93
-rw-r--r--devtools/client/debugger/test/mochitest/browser_dbg_variables-view-hide-non-enums.js111
-rw-r--r--devtools/client/debugger/test/mochitest/browser_dbg_variables-view-large-array-buffer.js253
-rw-r--r--devtools/client/debugger/test/mochitest/browser_dbg_variables-view-map-set.js117
-rw-r--r--devtools/client/debugger/test/mochitest/browser_dbg_variables-view-override-01.js240
-rw-r--r--devtools/client/debugger/test/mochitest/browser_dbg_variables-view-override-02.js75
-rw-r--r--devtools/client/debugger/test/mochitest/browser_dbg_variables-view-popup-01.js67
-rw-r--r--devtools/client/debugger/test/mochitest/browser_dbg_variables-view-popup-02.js53
-rw-r--r--devtools/client/debugger/test/mochitest/browser_dbg_variables-view-popup-03.js49
-rw-r--r--devtools/client/debugger/test/mochitest/browser_dbg_variables-view-popup-04.js38
-rw-r--r--devtools/client/debugger/test/mochitest/browser_dbg_variables-view-popup-05.js57
-rw-r--r--devtools/client/debugger/test/mochitest/browser_dbg_variables-view-popup-06.js83
-rw-r--r--devtools/client/debugger/test/mochitest/browser_dbg_variables-view-popup-07.js70
-rw-r--r--devtools/client/debugger/test/mochitest/browser_dbg_variables-view-popup-08.js75
-rw-r--r--devtools/client/debugger/test/mochitest/browser_dbg_variables-view-popup-09.js39
-rw-r--r--devtools/client/debugger/test/mochitest/browser_dbg_variables-view-popup-10.js67
-rw-r--r--devtools/client/debugger/test/mochitest/browser_dbg_variables-view-popup-11.js84
-rw-r--r--devtools/client/debugger/test/mochitest/browser_dbg_variables-view-popup-12.js77
-rw-r--r--devtools/client/debugger/test/mochitest/browser_dbg_variables-view-popup-13.js68
-rw-r--r--devtools/client/debugger/test/mochitest/browser_dbg_variables-view-popup-14.js55
-rw-r--r--devtools/client/debugger/test/mochitest/browser_dbg_variables-view-popup-15.js39
-rw-r--r--devtools/client/debugger/test/mochitest/browser_dbg_variables-view-popup-16.js77
-rw-r--r--devtools/client/debugger/test/mochitest/browser_dbg_variables-view-popup-17.js80
-rw-r--r--devtools/client/debugger/test/mochitest/browser_dbg_variables-view-reexpand-01.js211
-rw-r--r--devtools/client/debugger/test/mochitest/browser_dbg_variables-view-reexpand-02.js226
-rw-r--r--devtools/client/debugger/test/mochitest/browser_dbg_variables-view-reexpand-03.js120
-rw-r--r--devtools/client/debugger/test/mochitest/browser_dbg_variables-view-webidl.js262
-rw-r--r--devtools/client/debugger/test/mochitest/browser_dbg_watch-expressions-01.js227
-rw-r--r--devtools/client/debugger/test/mochitest/browser_dbg_watch-expressions-02.js383
-rw-r--r--devtools/client/debugger/test/mochitest/browser_dbg_worker-console-01.js21
-rw-r--r--devtools/client/debugger/test/mochitest/browser_dbg_worker-console-02.js58
-rw-r--r--devtools/client/debugger/test/mochitest/browser_dbg_worker-console-03.js46
-rw-r--r--devtools/client/debugger/test/mochitest/browser_dbg_worker-source-map.js89
-rw-r--r--devtools/client/debugger/test/mochitest/browser_dbg_worker-window.js61
-rw-r--r--devtools/client/debugger/test/mochitest/code_WorkerActor.attach-worker1.js5
-rw-r--r--devtools/client/debugger/test/mochitest/code_WorkerActor.attach-worker2.js5
-rw-r--r--devtools/client/debugger/test/mochitest/code_WorkerActor.attachThread-worker.js16
-rw-r--r--devtools/client/debugger/test/mochitest/code_binary_search.coffee18
-rw-r--r--devtools/client/debugger/test/mochitest/code_binary_search.js29
-rw-r--r--devtools/client/debugger/test/mochitest/code_binary_search.map10
-rw-r--r--devtools/client/debugger/test/mochitest/code_blackboxing_blackboxme.js9
-rw-r--r--devtools/client/debugger/test/mochitest/code_blackboxing_one.js4
-rw-r--r--devtools/client/debugger/test/mochitest/code_blackboxing_three.js4
-rw-r--r--devtools/client/debugger/test/mochitest/code_blackboxing_two.js4
-rw-r--r--devtools/client/debugger/test/mochitest/code_blackboxing_unblackbox.min.js1
-rw-r--r--devtools/client/debugger/test/mochitest/code_breakpoints-break-on-last-line-of-script-on-reload.js6
-rw-r--r--devtools/client/debugger/test/mochitest/code_breakpoints-other-tabs.js4
-rw-r--r--devtools/client/debugger/test/mochitest/code_bug-896139.js8
-rw-r--r--devtools/client/debugger/test/mochitest/code_frame-script.js106
-rw-r--r--devtools/client/debugger/test/mochitest/code_function-jump-01.js6
-rw-r--r--devtools/client/debugger/test/mochitest/code_function-search-01.js42
-rw-r--r--devtools/client/debugger/test/mochitest/code_function-search-02.js21
-rw-r--r--devtools/client/debugger/test/mochitest/code_function-search-03.js32
-rw-r--r--devtools/client/debugger/test/mochitest/code_listworkers-worker1.js3
-rw-r--r--devtools/client/debugger/test/mochitest/code_listworkers-worker2.js3
-rw-r--r--devtools/client/debugger/test/mochitest/code_location-changes.js7
-rw-r--r--devtools/client/debugger/test/mochitest/code_math.js45
-rw-r--r--devtools/client/debugger/test/mochitest/code_math.map8
-rw-r--r--devtools/client/debugger/test/mochitest/code_math.min.js2
-rw-r--r--devtools/client/debugger/test/mochitest/code_math_bogus_map.js4
-rw-r--r--devtools/client/debugger/test/mochitest/code_same-line-functions.js1
-rw-r--r--devtools/client/debugger/test/mochitest/code_script-eval.js14
-rw-r--r--devtools/client/debugger/test/mochitest/code_script-switching-01.js6
-rw-r--r--devtools/client/debugger/test/mochitest/code_script-switching-02.js13
-rw-r--r--devtools/client/debugger/test/mochitest/code_test-editor-mode6
-rw-r--r--devtools/client/debugger/test/mochitest/code_ugly-2.js1
-rw-r--r--devtools/client/debugger/test/mochitest/code_ugly-3.js1
-rw-r--r--devtools/client/debugger/test/mochitest/code_ugly-4.js25
-rw-r--r--devtools/client/debugger/test/mochitest/code_ugly-5.js14
-rw-r--r--devtools/client/debugger/test/mochitest/code_ugly-6.js5
-rw-r--r--devtools/client/debugger/test/mochitest/code_ugly-7.js5
-rw-r--r--devtools/client/debugger/test/mochitest/code_ugly-83
-rw-r--r--devtools/client/debugger/test/mochitest/code_ugly-8^headers^1
-rw-r--r--devtools/client/debugger/test/mochitest/code_ugly.js3
-rw-r--r--devtools/client/debugger/test/mochitest/code_worker-source-map.coffee22
-rw-r--r--devtools/client/debugger/test/mochitest/code_worker-source-map.js35
-rw-r--r--devtools/client/debugger/test/mochitest/code_worker-source-map.js.map10
-rw-r--r--devtools/client/debugger/test/mochitest/code_workeractor-worker.js5
-rw-r--r--devtools/client/debugger/test/mochitest/doc_WorkerActor.attach-tab1.html8
-rw-r--r--devtools/client/debugger/test/mochitest/doc_WorkerActor.attach-tab2.html8
-rw-r--r--devtools/client/debugger/test/mochitest/doc_WorkerActor.attachThread-tab.html8
-rw-r--r--devtools/client/debugger/test/mochitest/doc_auto-pretty-print-01.html14
-rw-r--r--devtools/client/debugger/test/mochitest/doc_auto-pretty-print-02.html14
-rw-r--r--devtools/client/debugger/test/mochitest/doc_binary_search.html15
-rw-r--r--devtools/client/debugger/test/mochitest/doc_blackboxing.html26
-rw-r--r--devtools/client/debugger/test/mochitest/doc_blackboxing_unblackbox.html11
-rw-r--r--devtools/client/debugger/test/mochitest/doc_breakpoint-move.html25
-rw-r--r--devtools/client/debugger/test/mochitest/doc_breakpoints-break-on-last-line-of-script-on-reload.html8
-rw-r--r--devtools/client/debugger/test/mochitest/doc_breakpoints-other-tabs.html8
-rw-r--r--devtools/client/debugger/test/mochitest/doc_breakpoints-reload.html13
-rw-r--r--devtools/client/debugger/test/mochitest/doc_bug-896139.html18
-rw-r--r--devtools/client/debugger/test/mochitest/doc_closure-optimized-out.html34
-rw-r--r--devtools/client/debugger/test/mochitest/doc_closures.html32
-rw-r--r--devtools/client/debugger/test/mochitest/doc_cmd-break.html22
-rw-r--r--devtools/client/debugger/test/mochitest/doc_cmd-dbg.html40
-rw-r--r--devtools/client/debugger/test/mochitest/doc_conditional-breakpoints.html35
-rw-r--r--devtools/client/debugger/test/mochitest/doc_domnode-variables.html24
-rw-r--r--devtools/client/debugger/test/mochitest/doc_editor-mode.html20
-rw-r--r--devtools/client/debugger/test/mochitest/doc_empty-tab-01.html14
-rw-r--r--devtools/client/debugger/test/mochitest/doc_empty-tab-02.html14
-rw-r--r--devtools/client/debugger/test/mochitest/doc_event-listeners-01.html43
-rw-r--r--devtools/client/debugger/test/mochitest/doc_event-listeners-02.html53
-rw-r--r--devtools/client/debugger/test/mochitest/doc_event-listeners-03.html63
-rw-r--r--devtools/client/debugger/test/mochitest/doc_event-listeners-04.html23
-rw-r--r--devtools/client/debugger/test/mochitest/doc_frame-parameters.html37
-rw-r--r--devtools/client/debugger/test/mochitest/doc_function-display-name.html31
-rw-r--r--devtools/client/debugger/test/mochitest/doc_function-jump.html17
-rw-r--r--devtools/client/debugger/test/mochitest/doc_function-search.html30
-rw-r--r--devtools/client/debugger/test/mochitest/doc_global-method-override.html16
-rw-r--r--devtools/client/debugger/test/mochitest/doc_iframes.html15
-rw-r--r--devtools/client/debugger/test/mochitest/doc_included-script.html22
-rw-r--r--devtools/client/debugger/test/mochitest/doc_inline-debugger-statement.html21
-rw-r--r--devtools/client/debugger/test/mochitest/doc_inline-script.html25
-rw-r--r--devtools/client/debugger/test/mochitest/doc_large-array-buffer.html32
-rw-r--r--devtools/client/debugger/test/mochitest/doc_listworkers-tab.html8
-rw-r--r--devtools/client/debugger/test/mochitest/doc_map-set.html42
-rw-r--r--devtools/client/debugger/test/mochitest/doc_minified.html14
-rw-r--r--devtools/client/debugger/test/mochitest/doc_minified_bogus_map.html14
-rw-r--r--devtools/client/debugger/test/mochitest/doc_native-event-handler.html22
-rw-r--r--devtools/client/debugger/test/mochitest/doc_no-page-sources.html11
-rw-r--r--devtools/client/debugger/test/mochitest/doc_pause-exceptions.html35
-rw-r--r--devtools/client/debugger/test/mochitest/doc_pretty-print-2.html15
-rw-r--r--devtools/client/debugger/test/mochitest/doc_pretty-print-3.html8
-rw-r--r--devtools/client/debugger/test/mochitest/doc_pretty-print-on-paused.html14
-rw-r--r--devtools/client/debugger/test/mochitest/doc_pretty-print.html8
-rw-r--r--devtools/client/debugger/test/mochitest/doc_promise-get-allocation-stack.html24
-rw-r--r--devtools/client/debugger/test/mochitest/doc_promise-get-fulfillment-stack.html24
-rw-r--r--devtools/client/debugger/test/mochitest/doc_promise-get-rejection-stack.html24
-rw-r--r--devtools/client/debugger/test/mochitest/doc_promise.html30
-rw-r--r--devtools/client/debugger/test/mochitest/doc_proxy.html39
-rw-r--r--devtools/client/debugger/test/mochitest/doc_random-javascript.html15
-rw-r--r--devtools/client/debugger/test/mochitest/doc_recursion-stack.html35
-rw-r--r--devtools/client/debugger/test/mochitest/doc_scope-variable-2.html30
-rw-r--r--devtools/client/debugger/test/mochitest/doc_scope-variable-3.html23
-rw-r--r--devtools/client/debugger/test/mochitest/doc_scope-variable-4.html25
-rw-r--r--devtools/client/debugger/test/mochitest/doc_scope-variable.html25
-rw-r--r--devtools/client/debugger/test/mochitest/doc_script-bookmarklet.html14
-rw-r--r--devtools/client/debugger/test/mochitest/doc_script-eval.html16
-rw-r--r--devtools/client/debugger/test/mochitest/doc_script-switching-01.html18
-rw-r--r--devtools/client/debugger/test/mochitest/doc_script-switching-02.html18
-rw-r--r--devtools/client/debugger/test/mochitest/doc_script_webext_contentscript.html13
-rw-r--r--devtools/client/debugger/test/mochitest/doc_split-console-paused-reload.html22
-rw-r--r--devtools/client/debugger/test/mochitest/doc_step-many-statements.html50
-rw-r--r--devtools/client/debugger/test/mochitest/doc_step-out.html42
-rw-r--r--devtools/client/debugger/test/mochitest/doc_terminate-on-tab-close.html20
-rw-r--r--devtools/client/debugger/test/mochitest/doc_watch-expression-button.html31
-rw-r--r--devtools/client/debugger/test/mochitest/doc_watch-expressions.html29
-rw-r--r--devtools/client/debugger/test/mochitest/doc_whitespace-property-names.html29
-rw-r--r--devtools/client/debugger/test/mochitest/doc_with-frame.html29
-rw-r--r--devtools/client/debugger/test/mochitest/doc_worker-source-map.html18
-rw-r--r--devtools/client/debugger/test/mochitest/head.js1351
-rw-r--r--devtools/client/debugger/test/mochitest/sjs_post-page.sjs16
-rw-r--r--devtools/client/debugger/test/mochitest/sjs_random-javascript.sjs11
-rw-r--r--devtools/client/debugger/test/mochitest/testactors.js33
-rw-r--r--devtools/client/debugger/utils.js378
-rw-r--r--devtools/client/debugger/views/filter-view.js925
-rw-r--r--devtools/client/debugger/views/global-search-view.js756
-rw-r--r--devtools/client/debugger/views/options-view.js215
-rw-r--r--devtools/client/debugger/views/stack-frames-classic-view.js141
-rw-r--r--devtools/client/debugger/views/stack-frames-view.js283
-rw-r--r--devtools/client/debugger/views/toolbar-view.js287
-rw-r--r--devtools/client/debugger/views/variable-bubble-view.js321
-rw-r--r--devtools/client/debugger/views/watch-expressions-view.js303
-rw-r--r--devtools/client/debugger/views/workers-view.js55
546 files changed, 119687 insertions, 0 deletions
diff --git a/devtools/client/debugger/content/actions/breakpoints.js b/devtools/client/debugger/content/actions/breakpoints.js
new file mode 100644
index 000000000..5c5552d78
--- /dev/null
+++ b/devtools/client/debugger/content/actions/breakpoints.js
@@ -0,0 +1,191 @@
+/* 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 constants = require("../constants");
+const promise = require("promise");
+const { asPaused } = require("../utils");
+const { PROMISE } = require("devtools/client/shared/redux/middleware/promise");
+const {
+ getSource, getBreakpoint, getBreakpoints, makeLocationId
+} = require("../queries");
+const { Task } = require("devtools/shared/task");
+
+// Because breakpoints are just simple data structures, we still need
+// a way to lookup the actual client instance to talk to the server.
+// We keep an internal database of clients based off of actor ID.
+const BREAKPOINT_CLIENT_STORE = new Map();
+
+function setBreakpointClient(actor, client) {
+ BREAKPOINT_CLIENT_STORE.set(actor, client);
+}
+
+function getBreakpointClient(actor) {
+ return BREAKPOINT_CLIENT_STORE.get(actor);
+}
+
+function enableBreakpoint(location) {
+ // Enabling is exactly the same as adding. It will use the existing
+ // breakpoint that still stored.
+ return addBreakpoint(location);
+}
+
+function _breakpointExists(state, location) {
+ const currentBp = getBreakpoint(state, location);
+ return currentBp && !currentBp.disabled;
+}
+
+function _getOrCreateBreakpoint(state, location, condition) {
+ return getBreakpoint(state, location) || { location, condition };
+}
+
+function addBreakpoint(location, condition) {
+ return (dispatch, getState) => {
+ if (_breakpointExists(getState(), location)) {
+ return;
+ }
+
+ const bp = _getOrCreateBreakpoint(getState(), location, condition);
+
+ return dispatch({
+ type: constants.ADD_BREAKPOINT,
+ breakpoint: bp,
+ condition: condition,
+ [PROMISE]: Task.spawn(function* () {
+ const sourceClient = gThreadClient.source(
+ getSource(getState(), bp.location.actor)
+ );
+ const [response, bpClient] = yield sourceClient.setBreakpoint({
+ line: bp.location.line,
+ column: bp.location.column,
+ condition: bp.condition
+ });
+ const { isPending, actualLocation } = response;
+
+ // Save the client instance
+ setBreakpointClient(bpClient.actor, bpClient);
+
+ return {
+ text: DebuggerView.editor.getText(
+ (actualLocation ? actualLocation.line : bp.location.line) - 1
+ ).trim(),
+
+ // If the breakpoint response has an "actualLocation" attached, then
+ // the original requested placement for the breakpoint wasn't
+ // accepted.
+ actualLocation: isPending ? null : actualLocation,
+ actor: bpClient.actor
+ };
+ })
+ });
+ };
+}
+
+function disableBreakpoint(location) {
+ return _removeOrDisableBreakpoint(location, true);
+}
+
+function removeBreakpoint(location) {
+ return _removeOrDisableBreakpoint(location);
+}
+
+function _removeOrDisableBreakpoint(location, isDisabled) {
+ return (dispatch, getState) => {
+ let bp = getBreakpoint(getState(), location);
+ if (!bp) {
+ throw new Error("attempt to remove breakpoint that does not exist");
+ }
+ if (bp.loading) {
+ // TODO(jwl): make this wait until the breakpoint is saved if it
+ // is still loading
+ throw new Error("attempt to remove unsaved breakpoint");
+ }
+
+ const bpClient = getBreakpointClient(bp.actor);
+ const action = {
+ type: constants.REMOVE_BREAKPOINT,
+ breakpoint: bp,
+ disabled: isDisabled
+ };
+
+ // If the breakpoint is already disabled, we don't need to remove
+ // it from the server. We just need to dispatch an action
+ // simulating a successful server request to remove it, and it
+ // will be removed completely from the state.
+ if (!bp.disabled) {
+ return dispatch(Object.assign({}, action, {
+ [PROMISE]: bpClient.remove()
+ }));
+ } else {
+ return dispatch(Object.assign({}, action, { status: "done" }));
+ }
+ };
+}
+
+function removeAllBreakpoints() {
+ return (dispatch, getState) => {
+ const breakpoints = getBreakpoints(getState());
+ const activeBreakpoints = breakpoints.filter(bp => !bp.disabled);
+ activeBreakpoints.forEach(bp => removeBreakpoint(bp.location));
+ };
+}
+
+/**
+ * Update the condition of a breakpoint.
+ *
+ * @param object aLocation
+ * @see DebuggerController.Breakpoints.addBreakpoint
+ * @param string aClients
+ * The condition to set on the breakpoint
+ * @return object
+ * A promise that will be resolved with the breakpoint client
+ */
+function setBreakpointCondition(location, condition) {
+ return (dispatch, getState) => {
+ const bp = getBreakpoint(getState(), location);
+ if (!bp) {
+ throw new Error("Breakpoint does not exist at the specified location");
+ }
+ if (bp.loading) {
+ // TODO(jwl): when this function is called, make sure the action
+ // creator waits for the breakpoint to exist
+ throw new Error("breakpoint must be saved");
+ }
+
+ const bpClient = getBreakpointClient(bp.actor);
+ const action = {
+ type: constants.SET_BREAKPOINT_CONDITION,
+ breakpoint: bp,
+ condition: condition
+ };
+
+ // If it's not disabled, we need to update the condition on the
+ // server. Otherwise, just dispatch a non-remote action that
+ // updates the condition locally.
+ if (!bp.disabled) {
+ return dispatch(Object.assign({}, action, {
+ [PROMISE]: Task.spawn(function* () {
+ const newClient = yield bpClient.setCondition(gThreadClient, condition);
+
+ // Remove the old instance and save the new one
+ setBreakpointClient(bpClient.actor, null);
+ setBreakpointClient(newClient.actor, newClient);
+
+ return { actor: newClient.actor };
+ })
+ }));
+ } else {
+ return dispatch(action);
+ }
+ };
+}
+
+module.exports = {
+ enableBreakpoint,
+ addBreakpoint,
+ disableBreakpoint,
+ removeBreakpoint,
+ removeAllBreakpoints,
+ setBreakpointCondition
+};
diff --git a/devtools/client/debugger/content/actions/event-listeners.js b/devtools/client/debugger/content/actions/event-listeners.js
new file mode 100644
index 000000000..4bca557fe
--- /dev/null
+++ b/devtools/client/debugger/content/actions/event-listeners.js
@@ -0,0 +1,118 @@
+/* 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 constants = require("../constants");
+const { asPaused } = require("../utils");
+const { reportException } = require("devtools/shared/DevToolsUtils");
+const { setNamedTimeout } = require("devtools/client/shared/widgets/view-helpers");
+const { Task } = require("devtools/shared/task");
+
+const FETCH_EVENT_LISTENERS_DELAY = 200; // ms
+
+function fetchEventListeners() {
+ return (dispatch, getState) => {
+ // Make sure we"re not sending a batch of closely repeated requests.
+ // This can easily happen whenever new sources are fetched.
+ setNamedTimeout("event-listeners-fetch", FETCH_EVENT_LISTENERS_DELAY, () => {
+ // In case there is still a request of listeners going on (it
+ // takes several RDP round trips right now), make sure we wait
+ // on a currently running request
+ if (getState().eventListeners.fetchingListeners) {
+ dispatch({
+ type: services.WAIT_UNTIL,
+ predicate: action => (
+ action.type === constants.FETCH_EVENT_LISTENERS &&
+ action.status === "done"
+ ),
+ run: dispatch => dispatch(fetchEventListeners())
+ });
+ return;
+ }
+
+ dispatch({
+ type: constants.FETCH_EVENT_LISTENERS,
+ status: "begin"
+ });
+
+ asPaused(gThreadClient, _getListeners).then(listeners => {
+ // Notify that event listeners were fetched and shown in the view,
+ // and callback to resume the active thread if necessary.
+ window.emit(EVENTS.EVENT_LISTENERS_FETCHED);
+
+ dispatch({
+ type: constants.FETCH_EVENT_LISTENERS,
+ status: "done",
+ listeners: listeners
+ });
+ });
+ });
+ };
+}
+
+const _getListeners = Task.async(function* () {
+ const response = yield gThreadClient.eventListeners();
+
+ // Make sure all the listeners are sorted by the event type, since
+ // they"re not guaranteed to be clustered together.
+ response.listeners.sort((a, b) => a.type > b.type ? 1 : -1);
+
+ // Add all the listeners in the debugger view event linsteners container.
+ let fetchedDefinitions = new Map();
+ let listeners = [];
+ for (let listener of response.listeners) {
+ let definitionSite;
+ if (fetchedDefinitions.has(listener.function.actor)) {
+ definitionSite = fetchedDefinitions.get(listener.function.actor);
+ } else if (listener.function.class == "Function") {
+ definitionSite = yield _getDefinitionSite(listener.function);
+ if (!definitionSite) {
+ // We don"t know where this listener comes from so don"t show it in
+ // the UI as breaking on it doesn"t work (bug 942899).
+ continue;
+ }
+
+ fetchedDefinitions.set(listener.function.actor, definitionSite);
+ }
+ listener.function.url = definitionSite;
+ listeners.push(listener);
+ }
+ fetchedDefinitions.clear();
+
+ return listeners;
+});
+
+const _getDefinitionSite = Task.async(function* (aFunction) {
+ const grip = gThreadClient.pauseGrip(aFunction);
+ let response;
+
+ try {
+ response = yield grip.getDefinitionSite();
+ }
+ catch (e) {
+ // Don't make this error fatal, because it would break the entire events pane.
+ reportException("_getDefinitionSite", e);
+ return null;
+ }
+
+ return response.source.url;
+});
+
+function updateEventBreakpoints(eventNames) {
+ return dispatch => {
+ setNamedTimeout("event-breakpoints-update", 0, () => {
+ gThreadClient.pauseOnDOMEvents(eventNames, function () {
+ // Notify that event breakpoints were added/removed on the server.
+ window.emit(EVENTS.EVENT_BREAKPOINTS_UPDATED);
+
+ dispatch({
+ type: constants.UPDATE_EVENT_BREAKPOINTS,
+ eventNames: eventNames
+ });
+ });
+ });
+ };
+}
+
+module.exports = { updateEventBreakpoints, fetchEventListeners };
diff --git a/devtools/client/debugger/content/actions/moz.build b/devtools/client/debugger/content/actions/moz.build
new file mode 100644
index 000000000..13a2dd9ad
--- /dev/null
+++ b/devtools/client/debugger/content/actions/moz.build
@@ -0,0 +1,10 @@
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+DevToolsModules(
+ 'breakpoints.js',
+ 'event-listeners.js',
+ 'sources.js'
+)
diff --git a/devtools/client/debugger/content/actions/sources.js b/devtools/client/debugger/content/actions/sources.js
new file mode 100644
index 000000000..d7e0728e7
--- /dev/null
+++ b/devtools/client/debugger/content/actions/sources.js
@@ -0,0 +1,280 @@
+/* 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 constants = require("../constants");
+const promise = require("promise");
+const Services = require("Services");
+const { dumpn } = require("devtools/shared/DevToolsUtils");
+const { PROMISE, HISTOGRAM_ID } = require("devtools/client/shared/redux/middleware/promise");
+const { getSource, getSourceText } = require("../queries");
+const { Task } = require("devtools/shared/task");
+
+const NEW_SOURCE_IGNORED_URLS = ["debugger eval code", "XStringBundle"];
+const FETCH_SOURCE_RESPONSE_DELAY = 200; // ms
+
+function getSourceClient(source) {
+ return gThreadClient.source(source);
+}
+
+/**
+ * Handler for the debugger client's unsolicited newSource notification.
+ */
+function newSource(source) {
+ return dispatch => {
+ // Ignore bogus scripts, e.g. generated from 'clientEvaluate' packets.
+ if (NEW_SOURCE_IGNORED_URLS.indexOf(source.url) != -1) {
+ return;
+ }
+
+ // Signal that a new source has been added.
+ window.emit(EVENTS.NEW_SOURCE);
+
+ return dispatch({
+ type: constants.ADD_SOURCE,
+ source: source
+ });
+ };
+}
+
+function selectSource(source, opts) {
+ return (dispatch, getState) => {
+ if (!gThreadClient) {
+ // No connection, do nothing. This happens when the debugger is
+ // shut down too fast and it tries to display a default source.
+ return;
+ }
+
+ source = getSource(getState(), source.actor);
+
+ // Make sure to start a request to load the source text.
+ dispatch(loadSourceText(source));
+
+ dispatch({
+ type: constants.SELECT_SOURCE,
+ source: source,
+ opts: opts
+ });
+ };
+}
+
+function loadSources() {
+ return {
+ type: constants.LOAD_SOURCES,
+ [PROMISE]: Task.spawn(function* () {
+ const response = yield gThreadClient.getSources();
+
+ // Top-level breakpoints may pause the entire loading process
+ // because scripts are executed as they are loaded, so the
+ // engine may pause in the middle of loading all the sources.
+ // This is relatively harmless, as individual `newSource`
+ // notifications are fired for each script and they will be
+ // added to the UI through that.
+ if (!response.sources) {
+ dumpn(
+ "Error getting sources, probably because a top-level " +
+ "breakpoint was hit while executing them"
+ );
+ return;
+ }
+
+ // Ignore bogus scripts, e.g. generated from 'clientEvaluate' packets.
+ return response.sources.filter(source => {
+ return NEW_SOURCE_IGNORED_URLS.indexOf(source.url) === -1;
+ });
+ })
+ };
+}
+
+/**
+ * Set the black boxed status of the given source.
+ *
+ * @param Object aSource
+ * The source form.
+ * @param bool aBlackBoxFlag
+ * True to black box the source, false to un-black box it.
+ * @returns Promise
+ * A promize that resolves to [aSource, isBlackBoxed] or rejects to
+ * [aSource, error].
+ */
+function blackbox(source, shouldBlackBox) {
+ const client = getSourceClient(source);
+
+ return {
+ type: constants.BLACKBOX,
+ source: source,
+ [PROMISE]: Task.spawn(function* () {
+ yield shouldBlackBox ? client.blackBox() : client.unblackBox();
+ return {
+ isBlackBoxed: shouldBlackBox
+ };
+ })
+ };
+}
+
+/**
+ * Toggle the pretty printing of a source's text. All subsequent calls to
+ * |getText| will return the pretty-toggled text. Nothing will happen for
+ * non-javascript files.
+ *
+ * @param Object aSource
+ * The source form from the RDP.
+ * @returns Promise
+ * A promise that resolves to [aSource, prettyText] or rejects to
+ * [aSource, error].
+ */
+function togglePrettyPrint(source) {
+ return (dispatch, getState) => {
+ const sourceClient = getSourceClient(source);
+ const wantPretty = !source.isPrettyPrinted;
+
+ return dispatch({
+ type: constants.TOGGLE_PRETTY_PRINT,
+ source: source,
+ [PROMISE]: Task.spawn(function* () {
+ let response;
+
+ // Only attempt to pretty print JavaScript sources.
+ const sourceText = getSourceText(getState(), source.actor);
+ const contentType = sourceText ? sourceText.contentType : null;
+ if (!SourceUtils.isJavaScript(source.url, contentType)) {
+ throw new Error("Can't prettify non-javascript files.");
+ }
+
+ if (wantPretty) {
+ response = yield sourceClient.prettyPrint(Prefs.editorTabSize);
+ }
+ else {
+ response = yield sourceClient.disablePrettyPrint();
+ }
+
+ // Remove the cached source AST from the Parser, to avoid getting
+ // wrong locations when searching for functions.
+ DebuggerController.Parser.clearSource(source.url);
+
+ return {
+ isPrettyPrinted: wantPretty,
+ text: response.source,
+ contentType: response.contentType
+ };
+ })
+ });
+ };
+}
+
+function loadSourceText(source) {
+ return (dispatch, getState) => {
+ // Fetch the source text only once.
+ let textInfo = getSourceText(getState(), source.actor);
+ if (textInfo) {
+ // It's already loaded or is loading
+ return promise.resolve(textInfo);
+ }
+
+ const sourceClient = getSourceClient(source);
+
+ return dispatch({
+ type: constants.LOAD_SOURCE_TEXT,
+ source: source,
+ [PROMISE]: Task.spawn(function* () {
+ let transportType = gClient.localTransport ? "_LOCAL" : "_REMOTE";
+ let histogramId = "DEVTOOLS_DEBUGGER_DISPLAY_SOURCE" + transportType + "_MS";
+ let histogram = Services.telemetry.getHistogramById(histogramId);
+ let startTime = Date.now();
+
+ const response = yield sourceClient.source();
+
+ histogram.add(Date.now() - startTime);
+
+ // Automatically pretty print if enabled and the test is
+ // detected to be "minified"
+ if (Prefs.autoPrettyPrint &&
+ !source.isPrettyPrinted &&
+ SourceUtils.isMinified(source.actor, response.source)) {
+ dispatch(togglePrettyPrint(source));
+ }
+
+ return { text: response.source,
+ contentType: response.contentType };
+ })
+ });
+ };
+}
+
+/**
+ * Starts fetching all the sources, silently.
+ *
+ * @param array aUrls
+ * The urls for the sources to fetch. If fetching a source's text
+ * takes too long, it will be discarded.
+ * @return object
+ * A promise that is resolved after source texts have been fetched.
+ */
+function getTextForSources(actors) {
+ return (dispatch, getState) => {
+ let deferred = promise.defer();
+ let pending = new Set(actors);
+ let fetched = [];
+
+ // Can't use promise.all, because if one fetch operation is rejected, then
+ // everything is considered rejected, thus no other subsequent source will
+ // be getting fetched. We don't want that. Something like Q's allSettled
+ // would work like a charm here.
+
+ // Try to fetch as many sources as possible.
+ for (let actor of actors) {
+ let source = getSource(getState(), actor);
+ dispatch(loadSourceText(source)).then(({ text, contentType }) => {
+ onFetch([source, text, contentType]);
+ }, err => {
+ onError(source, err);
+ });
+ }
+
+ setTimeout(onTimeout, FETCH_SOURCE_RESPONSE_DELAY);
+
+ /* Called if fetching a source takes too long. */
+ function onTimeout() {
+ pending = new Set();
+ maybeFinish();
+ }
+
+ /* Called if fetching a source finishes successfully. */
+ function onFetch([aSource, aText, aContentType]) {
+ // If fetching the source has previously timed out, discard it this time.
+ if (!pending.has(aSource.actor)) {
+ return;
+ }
+ pending.delete(aSource.actor);
+ fetched.push([aSource.actor, aText, aContentType]);
+ maybeFinish();
+ }
+
+ /* Called if fetching a source failed because of an error. */
+ function onError([aSource, aError]) {
+ pending.delete(aSource.actor);
+ maybeFinish();
+ }
+
+ /* Called every time something interesting happens while fetching sources. */
+ function maybeFinish() {
+ if (pending.size == 0) {
+ // Sort the fetched sources alphabetically by their url.
+ deferred.resolve(fetched.sort(([aFirst], [aSecond]) => aFirst > aSecond));
+ }
+ }
+
+ return deferred.promise;
+ };
+}
+
+module.exports = {
+ newSource,
+ selectSource,
+ loadSources,
+ blackbox,
+ togglePrettyPrint,
+ loadSourceText,
+ getTextForSources
+};
diff --git a/devtools/client/debugger/content/constants.js b/devtools/client/debugger/content/constants.js
new file mode 100644
index 000000000..0099477b7
--- /dev/null
+++ b/devtools/client/debugger/content/constants.js
@@ -0,0 +1,25 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* 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";
+
+exports.UPDATE_EVENT_BREAKPOINTS = "UPDATE_EVENT_BREAKPOINTS";
+exports.FETCH_EVENT_LISTENERS = "FETCH_EVENT_LISTENERS";
+
+exports.TOGGLE_PRETTY_PRINT = "TOGGLE_PRETTY_PRINT";
+exports.BLACKBOX = "BLACKBOX";
+
+exports.ADD_BREAKPOINT = "ADD_BREAKPOINT";
+exports.REMOVE_BREAKPOINT = "REMOVE_BREAKPOINT";
+exports.ENABLE_BREAKPOINT = "ENABLE_BREAKPOINT";
+exports.DISABLE_BREAKPOINT = "DISABLE_BREAKPOINT";
+exports.SET_BREAKPOINT_CONDITION = "SET_BREAKPOINT_CONDITION";
+
+exports.ADD_SOURCE = "ADD_SOURCE";
+exports.LOAD_SOURCES = "LOAD_SOURCES";
+exports.LOAD_SOURCE_TEXT = "LOAD_SOURCE_TEXT";
+exports.SELECT_SOURCE = "SELECT_SOURCE";
+exports.UNLOAD = "UNLOAD";
+exports.RELOAD = "RELOAD";
diff --git a/devtools/client/debugger/content/globalActions.js b/devtools/client/debugger/content/globalActions.js
new file mode 100644
index 000000000..3f02be36e
--- /dev/null
+++ b/devtools/client/debugger/content/globalActions.js
@@ -0,0 +1,18 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* 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 constants = require("./constants");
+
+// Fired when the page is being unloaded, for example when it's being
+// navigated away from.
+function unload() {
+ return {
+ type: constants.UNLOAD
+ };
+}
+
+module.exports = { unload };
diff --git a/devtools/client/debugger/content/moz.build b/devtools/client/debugger/content/moz.build
new file mode 100644
index 000000000..fcca58e65
--- /dev/null
+++ b/devtools/client/debugger/content/moz.build
@@ -0,0 +1,17 @@
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+DIRS += [
+ 'actions',
+ 'reducers',
+ 'views',
+]
+
+DevToolsModules(
+ 'constants.js',
+ 'globalActions.js',
+ 'queries.js',
+ 'utils.js'
+)
diff --git a/devtools/client/debugger/content/queries.js b/devtools/client/debugger/content/queries.js
new file mode 100644
index 000000000..3a0c54b88
--- /dev/null
+++ b/devtools/client/debugger/content/queries.js
@@ -0,0 +1,70 @@
+
+function getSource(state, actor) {
+ return state.sources.sources[actor];
+}
+
+function getSources(state) {
+ return state.sources.sources;
+}
+
+function getSourceCount(state) {
+ return Object.keys(state.sources.sources).length;
+}
+
+function getSourceByURL(state, url) {
+ for (let k in state.sources.sources) {
+ const source = state.sources.sources[k];
+ if (source.url === url) {
+ return source;
+ }
+ }
+}
+
+function getSourceByActor(state, actor) {
+ for (let k in state.sources.sources) {
+ const source = state.sources.sources[k];
+ if (source.actor === actor) {
+ return source;
+ }
+ }
+}
+
+function getSelectedSource(state) {
+ return state.sources.sources[state.sources.selectedSource];
+}
+
+function getSelectedSourceOpts(state) {
+ return state.sources.selectedSourceOpts;
+}
+
+function getSourceText(state, actor) {
+ return state.sources.sourcesText[actor];
+}
+
+function getBreakpoints(state) {
+ return Object.keys(state.breakpoints.breakpoints).map(k => {
+ return state.breakpoints.breakpoints[k];
+ });
+}
+
+function getBreakpoint(state, location) {
+ return state.breakpoints.breakpoints[makeLocationId(location)];
+}
+
+function makeLocationId(location) {
+ return location.actor + ":" + location.line.toString();
+}
+
+module.exports = {
+ getSource,
+ getSources,
+ getSourceCount,
+ getSourceByURL,
+ getSourceByActor,
+ getSelectedSource,
+ getSelectedSourceOpts,
+ getSourceText,
+ getBreakpoint,
+ getBreakpoints,
+ makeLocationId
+};
diff --git a/devtools/client/debugger/content/reducers/async-requests.js b/devtools/client/debugger/content/reducers/async-requests.js
new file mode 100644
index 000000000..206e1cf60
--- /dev/null
+++ b/devtools/client/debugger/content/reducers/async-requests.js
@@ -0,0 +1,31 @@
+/* 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 constants = require("../constants");
+const initialState = [];
+
+function update(state = initialState, action, emitChange) {
+ const { seqId } = action;
+
+ if (action.type === constants.UNLOAD) {
+ return initialState;
+ }
+ else if (seqId) {
+ let newState;
+ if (action.status === "start") {
+ newState = [...state, seqId];
+ }
+ else if (action.status === "error" || action.status === "done") {
+ newState = state.filter(id => id !== seqId);
+ }
+
+ emitChange("open-requests", newState);
+ return newState;
+ }
+
+ return state;
+}
+
+module.exports = update;
diff --git a/devtools/client/debugger/content/reducers/breakpoints.js b/devtools/client/debugger/content/reducers/breakpoints.js
new file mode 100644
index 000000000..7e42098e8
--- /dev/null
+++ b/devtools/client/debugger/content/reducers/breakpoints.js
@@ -0,0 +1,153 @@
+/* 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 constants = require("../constants");
+const Immutable = require("devtools/client/shared/vendor/seamless-immutable");
+const { mergeIn, setIn, deleteIn } = require("../utils");
+const { makeLocationId } = require("../queries");
+
+const initialState = Immutable({
+ breakpoints: {}
+});
+
+// Return the first argument that is a string, or null if nothing is a
+// string.
+function firstString(...args) {
+ for (var arg of args) {
+ if (typeof arg === "string") {
+ return arg;
+ }
+ }
+ return null;
+}
+
+function update(state = initialState, action, emitChange) {
+ switch (action.type) {
+ case constants.ADD_BREAKPOINT: {
+ const id = makeLocationId(action.breakpoint.location);
+
+ if (action.status === "start") {
+ const existingBp = state.breakpoints[id];
+ const bp = existingBp || Immutable(action.breakpoint);
+
+ state = setIn(state, ["breakpoints", id], bp.merge({
+ disabled: false,
+ loading: true,
+ // We want to do an OR here, but we can't because we need
+ // empty strings to be truthy, i.e. an empty string is a valid
+ // condition.
+ condition: firstString(action.condition, bp.condition)
+ }));
+
+ emitChange(existingBp ? "breakpoint-enabled" : "breakpoint-added",
+ state.breakpoints[id]);
+ return state;
+ }
+ else if (action.status === "done") {
+ const { actor, text } = action.value;
+ let { actualLocation } = action.value;
+
+ // If the breakpoint moved, update the map
+ if (actualLocation) {
+ // XXX Bug 1227417: The `setBreakpoint` RDP request rdp
+ // request returns an `actualLocation` field that doesn't
+ // conform to the regular { actor, line } location shape, but
+ // it has a `source` field. We should fix that.
+ actualLocation = { actor: actualLocation.source.actor,
+ line: actualLocation.line };
+
+ state = deleteIn(state, ["breakpoints", id]);
+
+ const movedId = makeLocationId(actualLocation);
+ const currentBp = state.breakpoints[movedId] || Immutable(action.breakpoint);
+ const prevLocation = action.breakpoint.location;
+ const newBp = currentBp.merge({ location: actualLocation });
+ state = setIn(state, ["breakpoints", movedId], newBp);
+
+ emitChange("breakpoint-moved", {
+ breakpoint: newBp,
+ prevLocation: prevLocation
+ });
+ }
+
+ const finalLocation = (
+ actualLocation ? actualLocation : action.breakpoint.location
+ );
+ const finalLocationId = makeLocationId(finalLocation);
+ state = mergeIn(state, ["breakpoints", finalLocationId], {
+ disabled: false,
+ loading: false,
+ actor: actor,
+ text: text
+ });
+ emitChange("breakpoint-updated", state.breakpoints[finalLocationId]);
+ return state;
+ }
+ else if (action.status === "error") {
+ // Remove the optimistic update
+ emitChange("breakpoint-removed", state.breakpoints[id]);
+ return deleteIn(state, ["breakpoints", id]);
+ }
+ break;
+ }
+
+ case constants.REMOVE_BREAKPOINT: {
+ if (action.status === "done") {
+ const id = makeLocationId(action.breakpoint.location);
+ const bp = state.breakpoints[id];
+
+ if (action.disabled) {
+ state = mergeIn(state, ["breakpoints", id],
+ { loading: false, disabled: true });
+ emitChange("breakpoint-disabled", state.breakpoints[id]);
+ return state;
+ }
+
+ state = deleteIn(state, ["breakpoints", id]);
+ emitChange("breakpoint-removed", bp);
+ return state;
+ }
+ break;
+ }
+
+ case constants.SET_BREAKPOINT_CONDITION: {
+ const id = makeLocationId(action.breakpoint.location);
+ const bp = state.breakpoints[id];
+ emitChange("breakpoint-condition-updated", bp);
+
+ if (!action.status) {
+ // No status means that it wasn't a remote request. Just update
+ // the condition locally.
+ return mergeIn(state, ["breakpoints", id], {
+ condition: action.condition
+ });
+ }
+ else if (action.status === "start") {
+ return mergeIn(state, ["breakpoints", id], {
+ loading: true,
+ condition: action.condition
+ });
+ }
+ else if (action.status === "done") {
+ return mergeIn(state, ["breakpoints", id], {
+ loading: false,
+ condition: action.condition,
+ // Setting a condition creates a new breakpoint client as of
+ // now, so we need to update the actor
+ actor: action.value.actor
+ });
+ }
+ else if (action.status === "error") {
+ emitChange("breakpoint-removed", bp);
+ return deleteIn(state, ["breakpoints", id]);
+ }
+
+ break;
+ }}
+
+ return state;
+}
+
+module.exports = update;
diff --git a/devtools/client/debugger/content/reducers/event-listeners.js b/devtools/client/debugger/content/reducers/event-listeners.js
new file mode 100644
index 000000000..fdd3da99d
--- /dev/null
+++ b/devtools/client/debugger/content/reducers/event-listeners.js
@@ -0,0 +1,37 @@
+/* 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 constants = require("../constants");
+
+const FETCH_EVENT_LISTENERS_DELAY = 200; // ms
+
+const initialState = {
+ activeEventNames: [],
+ listeners: [],
+ fetchingListeners: false,
+};
+
+function update(state = initialState, action, emit) {
+ switch (action.type) {
+ case constants.UPDATE_EVENT_BREAKPOINTS:
+ state.activeEventNames = action.eventNames;
+ emit("activeEventNames", state.activeEventNames);
+ break;
+ case constants.FETCH_EVENT_LISTENERS:
+ if (action.status === "begin") {
+ state.fetchingListeners = true;
+ }
+ else if (action.status === "done") {
+ state.fetchingListeners = false;
+ state.listeners = action.listeners;
+ emit("event-listeners", state.listeners);
+ }
+ break;
+ }
+
+ return state;
+}
+
+module.exports = update;
diff --git a/devtools/client/debugger/content/reducers/index.js b/devtools/client/debugger/content/reducers/index.js
new file mode 100644
index 000000000..27f2059f9
--- /dev/null
+++ b/devtools/client/debugger/content/reducers/index.js
@@ -0,0 +1,16 @@
+/* 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 eventListeners = require("./event-listeners");
+const sources = require("./sources");
+const breakpoints = require("./breakpoints");
+const asyncRequests = require("./async-requests");
+
+module.exports = {
+ eventListeners,
+ sources,
+ breakpoints,
+ asyncRequests
+};
diff --git a/devtools/client/debugger/content/reducers/moz.build b/devtools/client/debugger/content/reducers/moz.build
new file mode 100644
index 000000000..0433a099c
--- /dev/null
+++ b/devtools/client/debugger/content/reducers/moz.build
@@ -0,0 +1,12 @@
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+DevToolsModules(
+ 'async-requests.js',
+ 'breakpoints.js',
+ 'event-listeners.js',
+ 'index.js',
+ 'sources.js'
+)
diff --git a/devtools/client/debugger/content/reducers/sources.js b/devtools/client/debugger/content/reducers/sources.js
new file mode 100644
index 000000000..963a52fb5
--- /dev/null
+++ b/devtools/client/debugger/content/reducers/sources.js
@@ -0,0 +1,128 @@
+/* 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 constants = require("../constants");
+const Immutable = require("devtools/client/shared/vendor/seamless-immutable");
+const { mergeIn, setIn } = require("../utils");
+
+const initialState = Immutable({
+ sources: {},
+ selectedSource: null,
+ selectedSourceOpts: null,
+ sourcesText: {}
+});
+
+function update(state = initialState, action, emitChange) {
+ switch (action.type) {
+ case constants.ADD_SOURCE:
+ emitChange("source", action.source);
+ return mergeIn(state, ["sources", action.source.actor], action.source);
+
+ case constants.LOAD_SOURCES:
+ if (action.status === "done") {
+ const sources = action.value;
+ if (!sources) {
+ return state;
+ }
+ const sourcesByActor = {};
+ sources.forEach(source => {
+ if (!state.sources[source.actor]) {
+ emitChange("source", source);
+ }
+ sourcesByActor[source.actor] = source;
+ });
+ return mergeIn(state, ["sources"], state.sources.merge(sourcesByActor));
+ }
+ break;
+
+ case constants.SELECT_SOURCE:
+ emitChange("source-selected", action.source);
+ return state.merge({
+ selectedSource: action.source.actor,
+ selectedSourceOpts: action.opts
+ });
+
+ case constants.LOAD_SOURCE_TEXT: {
+ const s = _updateText(state, action);
+ emitChange("source-text-loaded", s.sources[action.source.actor]);
+ return s;
+ }
+
+ case constants.BLACKBOX:
+ if (action.status === "done") {
+ const s = mergeIn(state,
+ ["sources", action.source.actor, "isBlackBoxed"],
+ action.value.isBlackBoxed);
+ emitChange("blackboxed", s.sources[action.source.actor]);
+ return s;
+ }
+ break;
+
+ case constants.TOGGLE_PRETTY_PRINT:
+ let s = state;
+ if (action.status === "error") {
+ s = mergeIn(state, ["sourcesText", action.source.actor], {
+ loading: false
+ });
+
+ // If it errored, just display the source as it was before, but
+ // only if there is existing text already. If auto-prettifying
+ // is on, the original text may still be coming in and we don't
+ // have it yet. If we try to set empty text we confuse the
+ // editor because it thinks it's already displaying the source's
+ // text and won't load the text when it actually comes in.
+ if (s.sourcesText[action.source.actor].text != null) {
+ emitChange("prettyprinted", s.sources[action.source.actor]);
+ }
+ }
+ else {
+ s = _updateText(state, action);
+ // Don't do this yet, the progress bar is still imperatively shown
+ // from the source view. We will fix in the next iteration.
+ // emitChange('source-text-loaded', s.sources[action.source.actor]);
+
+ if (action.status === "done") {
+ s = mergeIn(s,
+ ["sources", action.source.actor, "isPrettyPrinted"],
+ action.value.isPrettyPrinted);
+ emitChange("prettyprinted", s.sources[action.source.actor]);
+ }
+ }
+ return s;
+
+ case constants.UNLOAD:
+ // Reset the entire state to just the initial state, a blank state
+ // if you will.
+ return initialState;
+ }
+
+ return state;
+}
+
+function _updateText(state, action) {
+ const { source } = action;
+
+ if (action.status === "start") {
+ // Merge this in, don't set it. That way the previous value is
+ // still stored here, and we can retrieve it if whatever we're
+ // doing fails.
+ return mergeIn(state, ["sourcesText", source.actor], {
+ loading: true
+ });
+ }
+ else if (action.status === "error") {
+ return setIn(state, ["sourcesText", source.actor], {
+ error: action.error
+ });
+ }
+ else {
+ return setIn(state, ["sourcesText", source.actor], {
+ text: action.value.text,
+ contentType: action.value.contentType
+ });
+ }
+}
+
+module.exports = update;
diff --git a/devtools/client/debugger/content/utils.js b/devtools/client/debugger/content/utils.js
new file mode 100644
index 000000000..59993e9b4
--- /dev/null
+++ b/devtools/client/debugger/content/utils.js
@@ -0,0 +1,88 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* 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 { reportException } = require("devtools/shared/DevToolsUtils");
+const { Task } = require("devtools/shared/task");
+
+function asPaused(client, func) {
+ if (client.state != "paused") {
+ return Task.spawn(function* () {
+ yield client.interrupt();
+ let result;
+
+ try {
+ result = yield func();
+ }
+ catch (e) {
+ // Try to put the debugger back in a working state by resuming
+ // it
+ yield client.resume();
+ throw e;
+ }
+
+ yield client.resume();
+ return result;
+ });
+ } else {
+ return func();
+ }
+}
+
+function handleError(err) {
+ reportException("promise", err.toString());
+}
+
+function onReducerEvents(controller, listeners, thisContext) {
+ Object.keys(listeners).forEach(name => {
+ const listener = listeners[name];
+ controller.onChange(name, payload => {
+ listener.call(thisContext, payload);
+ });
+ });
+}
+
+function _getIn(destObj, path) {
+ return path.reduce(function (acc, name) {
+ return acc[name];
+ }, destObj);
+}
+
+function mergeIn(destObj, path, value) {
+ path = [...path];
+ path.reverse();
+ var obj = path.reduce(function (acc, name) {
+ return { [name]: acc };
+ }, value);
+
+ return destObj.merge(obj, { deep: true });
+}
+
+function setIn(destObj, path, value) {
+ destObj = mergeIn(destObj, path, null);
+ return mergeIn(destObj, path, value);
+}
+
+function updateIn(destObj, path, fn) {
+ return setIn(destObj, path, fn(_getIn(destObj, path)));
+}
+
+function deleteIn(destObj, path) {
+ const objPath = path.slice(0, -1);
+ const propName = path[path.length - 1];
+ const obj = _getIn(destObj, objPath);
+ return setIn(destObj, objPath, obj.without(propName));
+}
+
+module.exports = {
+ asPaused,
+ handleError,
+ onReducerEvents,
+ mergeIn,
+ setIn,
+ updateIn,
+ deleteIn
+};
diff --git a/devtools/client/debugger/content/views/event-listeners-view.js b/devtools/client/debugger/content/views/event-listeners-view.js
new file mode 100644
index 000000000..993d6506e
--- /dev/null
+++ b/devtools/client/debugger/content/views/event-listeners-view.js
@@ -0,0 +1,295 @@
+/* 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";
+
+/* import-globals-from ../../debugger-controller.js */
+
+const actions = require("../actions/event-listeners");
+const { bindActionCreators } = require("devtools/client/shared/vendor/redux");
+const { Heritage, WidgetMethods } = require("devtools/client/shared/widgets/view-helpers");
+const { SideMenuWidget } = require("resource://devtools/client/shared/widgets/SideMenuWidget.jsm");
+
+/**
+ * Functions handling the event listeners UI.
+ */
+function EventListenersView(controller) {
+ dumpn("EventListenersView was instantiated");
+
+ this.actions = bindActionCreators(actions, controller.dispatch);
+ this.getState = () => controller.getState().eventListeners;
+
+ this._onCheck = this._onCheck.bind(this);
+ this._onClick = this._onClick.bind(this);
+
+ controller.onChange("event-listeners", this.renderListeners.bind(this));
+}
+
+EventListenersView.prototype = Heritage.extend(WidgetMethods, {
+ /**
+ * Initialization function, called when the debugger is started.
+ */
+ initialize: function () {
+ dumpn("Initializing the EventListenersView");
+
+ this.widget = new SideMenuWidget(document.getElementById("event-listeners"), {
+ showItemCheckboxes: true,
+ showGroupCheckboxes: true
+ });
+
+ this.emptyText = L10N.getStr("noEventListenersText");
+ this._eventCheckboxTooltip = L10N.getStr("eventCheckboxTooltip");
+ this._onSelectorString = " " + L10N.getStr("eventOnSelector") + " ";
+ this._inSourceString = " " + L10N.getStr("eventInSource") + " ";
+ this._inNativeCodeString = L10N.getStr("eventNative");
+
+ this.widget.addEventListener("check", this._onCheck, false);
+ this.widget.addEventListener("click", this._onClick, false);
+ },
+
+ /**
+ * Destruction function, called when the debugger is closed.
+ */
+ destroy: function () {
+ dumpn("Destroying the EventListenersView");
+
+ this.widget.removeEventListener("check", this._onCheck, false);
+ this.widget.removeEventListener("click", this._onClick, false);
+ },
+
+ renderListeners: function (listeners) {
+ listeners.forEach(listener => {
+ this.addListener(listener, { staged: true });
+ });
+
+ // Flushes all the prepared events into the event listeners container.
+ this.commit();
+ },
+
+ /**
+ * Adds an event to this event listeners container.
+ *
+ * @param object aListener
+ * The listener object coming from the active thread.
+ * @param object aOptions [optional]
+ * Additional options for adding the source. Supported options:
+ * - staged: true to stage the item to be appended later
+ */
+ addListener: function (aListener, aOptions = {}) {
+ let { node: { selector }, function: { url }, type } = aListener;
+ if (!type) return;
+
+ // Some listener objects may be added from plugins, thus getting
+ // translated to native code.
+ if (!url) {
+ url = this._inNativeCodeString;
+ }
+
+ // If an event item for this listener's url and type was already added,
+ // avoid polluting the view and simply increase the "targets" count.
+ let eventItem = this.getItemForPredicate(aItem =>
+ aItem.attachment.url == url &&
+ aItem.attachment.type == type);
+
+ if (eventItem) {
+ let { selectors, view: { targets } } = eventItem.attachment;
+ if (selectors.indexOf(selector) == -1) {
+ selectors.push(selector);
+ targets.setAttribute("value", L10N.getFormatStr("eventNodes", selectors.length));
+ }
+ return;
+ }
+
+ // There's no easy way of grouping event types into higher-level groups,
+ // so we need to do this by hand.
+ let is = (...args) => args.indexOf(type) != -1;
+ let has = str => type.includes(str);
+ let starts = str => type.startsWith(str);
+ let group;
+
+ if (starts("animation")) {
+ group = L10N.getStr("animationEvents");
+ } else if (starts("audio")) {
+ group = L10N.getStr("audioEvents");
+ } else if (is("levelchange")) {
+ group = L10N.getStr("batteryEvents");
+ } else if (is("cut", "copy", "paste")) {
+ group = L10N.getStr("clipboardEvents");
+ } else if (starts("composition")) {
+ group = L10N.getStr("compositionEvents");
+ } else if (starts("device")) {
+ group = L10N.getStr("deviceEvents");
+ } else if (is("fullscreenchange", "fullscreenerror", "orientationchange",
+ "overflow", "resize", "scroll", "underflow", "zoom")) {
+ group = L10N.getStr("displayEvents");
+ } else if (starts("drag") || starts("drop")) {
+ group = L10N.getStr("dragAndDropEvents");
+ } else if (starts("gamepad")) {
+ group = L10N.getStr("gamepadEvents");
+ } else if (is("canplay", "canplaythrough", "durationchange", "emptied",
+ "ended", "loadeddata", "loadedmetadata", "pause", "play", "playing",
+ "ratechange", "seeked", "seeking", "stalled", "suspend", "timeupdate",
+ "volumechange", "waiting")) {
+ group = L10N.getStr("mediaEvents");
+ } else if (is("blocked", "complete", "success", "upgradeneeded", "versionchange")) {
+ group = L10N.getStr("indexedDBEvents");
+ } else if (is("blur", "change", "focus", "focusin", "focusout", "invalid",
+ "reset", "select", "submit")) {
+ group = L10N.getStr("interactionEvents");
+ } else if (starts("key") || is("input")) {
+ group = L10N.getStr("keyboardEvents");
+ } else if (starts("mouse") || has("click") || is("contextmenu", "show", "wheel")) {
+ group = L10N.getStr("mouseEvents");
+ } else if (starts("DOM")) {
+ group = L10N.getStr("mutationEvents");
+ } else if (is("abort", "error", "hashchange", "load", "loadend", "loadstart",
+ "pagehide", "pageshow", "progress", "timeout", "unload", "uploadprogress",
+ "visibilitychange")) {
+ group = L10N.getStr("navigationEvents");
+ } else if (is("pointerlockchange", "pointerlockerror")) {
+ group = L10N.getStr("pointerLockEvents");
+ } else if (is("compassneedscalibration", "userproximity")) {
+ group = L10N.getStr("sensorEvents");
+ } else if (starts("storage")) {
+ group = L10N.getStr("storageEvents");
+ } else if (is("beginEvent", "endEvent", "repeatEvent")) {
+ group = L10N.getStr("timeEvents");
+ } else if (starts("touch")) {
+ group = L10N.getStr("touchEvents");
+ } else {
+ group = L10N.getStr("otherEvents");
+ }
+
+ // Create the element node for the event listener item.
+ const itemView = this._createItemView(type, selector, url);
+
+ // Event breakpoints survive target navigations. Make sure the newly
+ // inserted event item is correctly checked.
+ const activeEventNames = this.getState().activeEventNames;
+ const checkboxState = activeEventNames.indexOf(type) != -1;
+
+ // Append an event listener item to this container.
+ this.push([itemView.container], {
+ staged: aOptions.staged, /* stage the item to be appended later? */
+ attachment: {
+ url: url,
+ type: type,
+ view: itemView,
+ selectors: [selector],
+ group: group,
+ checkboxState: checkboxState,
+ checkboxTooltip: this._eventCheckboxTooltip
+ }
+ });
+ },
+
+ /**
+ * Gets all the event types known to this container.
+ *
+ * @return array
+ * List of event types, for example ["load", "click"...]
+ */
+ getAllEvents: function () {
+ return this.attachments.map(e => e.type);
+ },
+
+ /**
+ * Gets the checked event types in this container.
+ *
+ * @return array
+ * List of event types, for example ["load", "click"...]
+ */
+ getCheckedEvents: function () {
+ return this.attachments.filter(e => e.checkboxState).map(e => e.type);
+ },
+
+ /**
+ * Customization function for creating an item's UI.
+ *
+ * @param string aType
+ * The event type, for example "click".
+ * @param string aSelector
+ * The target element's selector.
+ * @param string url
+ * The source url in which the event listener is located.
+ * @return object
+ * An object containing the event listener view nodes.
+ */
+ _createItemView: function (aType, aSelector, aUrl) {
+ let container = document.createElement("hbox");
+ container.className = "dbg-event-listener";
+
+ let eventType = document.createElement("label");
+ eventType.className = "plain dbg-event-listener-type";
+ eventType.setAttribute("value", aType);
+ container.appendChild(eventType);
+
+ let typeSeparator = document.createElement("label");
+ typeSeparator.className = "plain dbg-event-listener-separator";
+ typeSeparator.setAttribute("value", this._onSelectorString);
+ container.appendChild(typeSeparator);
+
+ let eventTargets = document.createElement("label");
+ eventTargets.className = "plain dbg-event-listener-targets";
+ eventTargets.setAttribute("value", aSelector);
+ container.appendChild(eventTargets);
+
+ let selectorSeparator = document.createElement("label");
+ selectorSeparator.className = "plain dbg-event-listener-separator";
+ selectorSeparator.setAttribute("value", this._inSourceString);
+ container.appendChild(selectorSeparator);
+
+ let eventLocation = document.createElement("label");
+ eventLocation.className = "plain dbg-event-listener-location";
+ eventLocation.setAttribute("value", SourceUtils.getSourceLabel(aUrl));
+ eventLocation.setAttribute("flex", "1");
+ eventLocation.setAttribute("crop", "center");
+ container.appendChild(eventLocation);
+
+ return {
+ container: container,
+ type: eventType,
+ targets: eventTargets,
+ location: eventLocation
+ };
+ },
+
+ /**
+ * The check listener for the event listeners container.
+ */
+ _onCheck: function ({ detail: { description, checked }, target }) {
+ if (description == "item") {
+ this.getItemForElement(target).attachment.checkboxState = checked;
+
+ this.actions.updateEventBreakpoints(this.getCheckedEvents());
+ return;
+ }
+
+ // Check all the event items in this group.
+ this.items
+ .filter(e => e.attachment.group == description)
+ .forEach(e => this.callMethod("checkItem", e.target, checked));
+ },
+
+ /**
+ * The select listener for the event listeners container.
+ */
+ _onClick: function ({ target }) {
+ // Changing the checkbox state is handled by the _onCheck event. Avoid
+ // handling that again in this click event, so pass in "noSiblings"
+ // when retrieving the target's item, to ignore the checkbox.
+ let eventItem = this.getItemForElement(target, { noSiblings: true });
+ if (eventItem) {
+ let newState = eventItem.attachment.checkboxState ^= 1;
+ this.callMethod("checkItem", eventItem.target, newState);
+ }
+ },
+
+ _eventCheckboxTooltip: "",
+ _onSelectorString: "",
+ _inSourceString: "",
+ _inNativeCodeString: ""
+});
+
+module.exports = EventListenersView;
diff --git a/devtools/client/debugger/content/views/moz.build b/devtools/client/debugger/content/views/moz.build
new file mode 100644
index 000000000..de1ecc184
--- /dev/null
+++ b/devtools/client/debugger/content/views/moz.build
@@ -0,0 +1,9 @@
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+DevToolsModules(
+ 'event-listeners-view.js',
+ 'sources-view.js'
+)
diff --git a/devtools/client/debugger/content/views/sources-view.js b/devtools/client/debugger/content/views/sources-view.js
new file mode 100644
index 000000000..bb68afcf4
--- /dev/null
+++ b/devtools/client/debugger/content/views/sources-view.js
@@ -0,0 +1,1370 @@
+/* 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";
+
+/* import-globals-from ../../debugger-controller.js */
+
+const utils = require("../utils");
+const {
+ getSelectedSource,
+ getSourceByURL,
+ getBreakpoint,
+ getBreakpoints,
+ makeLocationId
+} = require("../queries");
+const actions = Object.assign(
+ {},
+ require("../actions/sources"),
+ require("../actions/breakpoints")
+);
+const { bindActionCreators } = require("devtools/client/shared/vendor/redux");
+const {
+ Heritage,
+ WidgetMethods,
+ setNamedTimeout
+} = require("devtools/client/shared/widgets/view-helpers");
+const { Task } = require("devtools/shared/task");
+const { SideMenuWidget } = require("resource://devtools/client/shared/widgets/SideMenuWidget.jsm");
+const { gDevTools } = require("devtools/client/framework/devtools");
+const {KeyCodes} = require("devtools/client/shared/keycodes");
+
+const NEW_SOURCE_DISPLAY_DELAY = 200; // ms
+const FUNCTION_SEARCH_POPUP_POSITION = "topcenter bottomleft";
+const BREAKPOINT_LINE_TOOLTIP_MAX_LENGTH = 1000; // chars
+const BREAKPOINT_CONDITIONAL_POPUP_POSITION = "before_start";
+const BREAKPOINT_CONDITIONAL_POPUP_OFFSET_X = 7; // px
+const BREAKPOINT_CONDITIONAL_POPUP_OFFSET_Y = -3; // px
+
+/**
+ * Functions handling the sources UI.
+ */
+function SourcesView(controller, DebuggerView) {
+ dumpn("SourcesView was instantiated");
+
+ utils.onReducerEvents(controller, {
+ "source": this.renderSource,
+ "blackboxed": this.renderBlackBoxed,
+ "prettyprinted": this.updateToolbarButtonsState,
+ "source-selected": this.renderSourceSelected,
+ "breakpoint-updated": bp => this.renderBreakpoint(bp),
+ "breakpoint-enabled": bp => this.renderBreakpoint(bp),
+ "breakpoint-disabled": bp => this.renderBreakpoint(bp),
+ "breakpoint-removed": bp => this.renderBreakpoint(bp, true),
+ }, this);
+
+ this.getState = controller.getState;
+ this.actions = bindActionCreators(actions, controller.dispatch);
+ this.DebuggerView = DebuggerView;
+ this.Parser = DebuggerController.Parser;
+
+ this.togglePrettyPrint = this.togglePrettyPrint.bind(this);
+ this.toggleBlackBoxing = this.toggleBlackBoxing.bind(this);
+ this.toggleBreakpoints = this.toggleBreakpoints.bind(this);
+
+ this._onEditorCursorActivity = this._onEditorCursorActivity.bind(this);
+ this._onMouseDown = this._onMouseDown.bind(this);
+ this._onSourceSelect = this._onSourceSelect.bind(this);
+ this._onStopBlackBoxing = this._onStopBlackBoxing.bind(this);
+ this._onBreakpointRemoved = this._onBreakpointRemoved.bind(this);
+ this._onBreakpointClick = this._onBreakpointClick.bind(this);
+ this._onBreakpointCheckboxClick = this._onBreakpointCheckboxClick.bind(this);
+ this._onConditionalPopupShowing = this._onConditionalPopupShowing.bind(this);
+ this._onConditionalPopupShown = this._onConditionalPopupShown.bind(this);
+ this._onConditionalPopupHiding = this._onConditionalPopupHiding.bind(this);
+ this._onConditionalTextboxKeyPress = this._onConditionalTextboxKeyPress.bind(this);
+ this._onEditorContextMenuOpen = this._onEditorContextMenuOpen.bind(this);
+ this._onCopyUrlCommand = this._onCopyUrlCommand.bind(this);
+ this._onNewTabCommand = this._onNewTabCommand.bind(this);
+ this._onConditionalPopupHidden = this._onConditionalPopupHidden.bind(this);
+}
+
+SourcesView.prototype = Heritage.extend(WidgetMethods, {
+ /**
+ * Initialization function, called when the debugger is started.
+ */
+ initialize: function (isWorker) {
+ dumpn("Initializing the SourcesView");
+
+ this.widget = new SideMenuWidget(document.getElementById("sources"), {
+ contextMenu: document.getElementById("debuggerSourcesContextMenu"),
+ showArrows: true
+ });
+
+ this._preferredSourceURL = null;
+ this._unnamedSourceIndex = 0;
+ this.emptyText = L10N.getStr("noSourcesText");
+ this._blackBoxCheckboxTooltip = L10N.getStr("blackBoxCheckboxTooltip");
+
+ this._commandset = document.getElementById("debuggerCommands");
+ this._popupset = document.getElementById("debuggerPopupset");
+ this._cmPopup = document.getElementById("sourceEditorContextMenu");
+ this._cbPanel = document.getElementById("conditional-breakpoint-panel");
+ this._cbTextbox = document.getElementById("conditional-breakpoint-panel-textbox");
+ this._blackBoxButton = document.getElementById("black-box");
+ this._stopBlackBoxButton = document.getElementById("black-boxed-message-button");
+ this._prettyPrintButton = document.getElementById("pretty-print");
+ this._toggleBreakpointsButton = document.getElementById("toggle-breakpoints");
+ this._newTabMenuItem = document.getElementById("debugger-sources-context-newtab");
+ this._copyUrlMenuItem = document.getElementById("debugger-sources-context-copyurl");
+
+ this._noResultsFoundToolTip = new Tooltip(document);
+ this._noResultsFoundToolTip.defaultPosition = FUNCTION_SEARCH_POPUP_POSITION;
+
+ // We don't show the pretty print button if debugger a worker
+ // because it simply doesn't work yet. (bug 1273730)
+ if (Prefs.prettyPrintEnabled && !isWorker) {
+ this._prettyPrintButton.removeAttribute("hidden");
+ }
+
+ this._editorContainer = document.getElementById("editor");
+ this._editorContainer.addEventListener("mousedown", this._onMouseDown, false);
+
+ this.widget.addEventListener("select", this._onSourceSelect, false);
+
+ this._stopBlackBoxButton.addEventListener("click", this._onStopBlackBoxing, false);
+ this._cbPanel.addEventListener("popupshowing", this._onConditionalPopupShowing, false);
+ this._cbPanel.addEventListener("popupshown", this._onConditionalPopupShown, false);
+ this._cbPanel.addEventListener("popuphiding", this._onConditionalPopupHiding, false);
+ this._cbPanel.addEventListener("popuphidden", this._onConditionalPopupHidden, false);
+ this._cbTextbox.addEventListener("keypress", this._onConditionalTextboxKeyPress, false);
+ this._copyUrlMenuItem.addEventListener("command", this._onCopyUrlCommand, false);
+ this._newTabMenuItem.addEventListener("command", this._onNewTabCommand, false);
+
+ this._cbPanel.hidden = true;
+ this.allowFocusOnRightClick = true;
+ this.autoFocusOnSelection = false;
+ this.autoFocusOnFirstItem = false;
+
+ // Sort the contents by the displayed label.
+ this.sortContents((aFirst, aSecond) => {
+ return +(aFirst.attachment.label.toLowerCase() >
+ aSecond.attachment.label.toLowerCase());
+ });
+
+ // Sort known source groups towards the end of the list
+ this.widget.groupSortPredicate = function (a, b) {
+ if ((a in KNOWN_SOURCE_GROUPS) == (b in KNOWN_SOURCE_GROUPS)) {
+ return a.localeCompare(b);
+ }
+ return (a in KNOWN_SOURCE_GROUPS) ? 1 : -1;
+ };
+
+ this.DebuggerView.editor.on("popupOpen", this._onEditorContextMenuOpen);
+
+ this._addCommands();
+ },
+
+ /**
+ * Destruction function, called when the debugger is closed.
+ */
+ destroy: function () {
+ dumpn("Destroying the SourcesView");
+
+ this.widget.removeEventListener("select", this._onSourceSelect, false);
+ this._stopBlackBoxButton.removeEventListener("click", this._onStopBlackBoxing, false);
+ this._cbPanel.removeEventListener("popupshowing", this._onConditionalPopupShowing, false);
+ this._cbPanel.removeEventListener("popupshown", this._onConditionalPopupShown, false);
+ this._cbPanel.removeEventListener("popuphiding", this._onConditionalPopupHiding, false);
+ this._cbPanel.removeEventListener("popuphidden", this._onConditionalPopupHidden, false);
+ this._cbTextbox.removeEventListener("keypress", this._onConditionalTextboxKeyPress, false);
+ this._copyUrlMenuItem.removeEventListener("command", this._onCopyUrlCommand, false);
+ this._newTabMenuItem.removeEventListener("command", this._onNewTabCommand, false);
+ this.DebuggerView.editor.off("popupOpen", this._onEditorContextMenuOpen, false);
+ },
+
+ empty: function () {
+ WidgetMethods.empty.call(this);
+ this._unnamedSourceIndex = 0;
+ this._selectedBreakpoint = null;
+ },
+
+ /**
+ * Add commands that XUL can fire.
+ */
+ _addCommands: function () {
+ XULUtils.addCommands(this._commandset, {
+ addBreakpointCommand: e => this._onCmdAddBreakpoint(e),
+ addConditionalBreakpointCommand: e => this._onCmdAddConditionalBreakpoint(e),
+ blackBoxCommand: () => this.toggleBlackBoxing(),
+ unBlackBoxButton: () => this._onStopBlackBoxing(),
+ prettyPrintCommand: () => this.togglePrettyPrint(),
+ toggleBreakpointsCommand: () =>this.toggleBreakpoints(),
+ nextSourceCommand: () => this.selectNextItem(),
+ prevSourceCommand: () => this.selectPrevItem()
+ });
+ },
+
+ /**
+ * Sets the preferred location to be selected in this sources container.
+ * @param string aUrl
+ */
+ set preferredSource(aUrl) {
+ this._preferredValue = aUrl;
+
+ // Selects the element with the specified value in this sources container,
+ // if already inserted.
+ if (this.containsValue(aUrl)) {
+ this.selectedValue = aUrl;
+ }
+ },
+
+ sourcesDidUpdate: function () {
+ if (!getSelectedSource(this.getState())) {
+ let url = this._preferredSourceURL;
+ let source = url && getSourceByURL(this.getState(), url);
+ if (source) {
+ this.actions.selectSource(source);
+ }
+ else {
+ setNamedTimeout("new-source", NEW_SOURCE_DISPLAY_DELAY, () => {
+ if (!getSelectedSource(this.getState()) && this.itemCount > 0) {
+ this.actions.selectSource(this.getItemAtIndex(0).attachment.source);
+ }
+ });
+ }
+ }
+ },
+
+ renderSource: function (source) {
+ this.addSource(source, { staged: false });
+ for (let bp of getBreakpoints(this.getState())) {
+ if (bp.location.actor === source.actor) {
+ this.renderBreakpoint(bp);
+ }
+ }
+ this.sourcesDidUpdate();
+ },
+
+ /**
+ * Adds a source to this sources container.
+ *
+ * @param object aSource
+ * The source object coming from the active thread.
+ * @param object aOptions [optional]
+ * Additional options for adding the source. Supported options:
+ * - staged: true to stage the item to be appended later
+ */
+ addSource: function (aSource, aOptions = {}) {
+ if (!aSource.url && !aOptions.force) {
+ // We don't show any unnamed eval scripts yet (see bug 1124106)
+ return;
+ }
+
+ let { label, group, unicodeUrl } = this._parseUrl(aSource);
+
+ let contents = document.createElement("label");
+ contents.className = "plain dbg-source-item";
+ contents.setAttribute("value", label);
+ contents.setAttribute("crop", "start");
+ contents.setAttribute("flex", "1");
+ contents.setAttribute("tooltiptext", unicodeUrl);
+
+ if (aSource.introductionType === "wasm") {
+ const wasm = document.createElement("box");
+ wasm.className = "dbg-wasm-item";
+ const icon = document.createElement("box");
+ icon.setAttribute("tooltiptext", L10N.getStr("experimental"));
+ icon.className = "icon";
+ wasm.appendChild(icon);
+ wasm.appendChild(contents);
+
+ contents = wasm;
+ }
+
+ // If the source is blackboxed, apply the appropriate style.
+ if (gThreadClient.source(aSource).isBlackBoxed) {
+ contents.classList.add("black-boxed");
+ }
+
+ // Append a source item to this container.
+ this.push([contents, aSource.actor], {
+ staged: aOptions.staged, /* stage the item to be appended later? */
+ attachment: {
+ label: label,
+ group: group,
+ checkboxState: !aSource.isBlackBoxed,
+ checkboxTooltip: this._blackBoxCheckboxTooltip,
+ source: aSource
+ }
+ });
+ },
+
+ _parseUrl: function (aSource) {
+ let fullUrl = aSource.url;
+ let url, unicodeUrl, label, group;
+
+ if (!fullUrl) {
+ unicodeUrl = "SCRIPT" + this._unnamedSourceIndex++;
+ label = unicodeUrl;
+ group = L10N.getStr("anonymousSourcesLabel");
+ }
+ else {
+ let url = fullUrl.split(" -> ").pop();
+ label = aSource.addonPath ? aSource.addonPath : SourceUtils.getSourceLabel(url);
+ group = aSource.addonID ? aSource.addonID : SourceUtils.getSourceGroup(url);
+ unicodeUrl = NetworkHelper.convertToUnicode(unescape(fullUrl));
+ }
+
+ return {
+ label: label,
+ group: group,
+ unicodeUrl: unicodeUrl
+ };
+ },
+
+ renderBreakpoint: function (breakpoint, removed) {
+ if (removed) {
+ // Be defensive about the breakpoint not existing.
+ if (this._getBreakpoint(breakpoint)) {
+ this._removeBreakpoint(breakpoint);
+ }
+ }
+ else {
+ if (this._getBreakpoint(breakpoint)) {
+ this._updateBreakpointStatus(breakpoint);
+ }
+ else {
+ this._addBreakpoint(breakpoint);
+ }
+ }
+ },
+
+ /**
+ * Adds a breakpoint to this sources container.
+ *
+ * @param object aBreakpointClient
+ * See Breakpoints.prototype._showBreakpoint
+ * @param object aOptions [optional]
+ * @see DebuggerController.Breakpoints.addBreakpoint
+ */
+ _addBreakpoint: function (breakpoint, options = {}) {
+ let disabled = breakpoint.disabled;
+ let location = breakpoint.location;
+
+ // Get the source item to which the breakpoint should be attached.
+ let sourceItem = this.getItemByValue(location.actor);
+ if (!sourceItem) {
+ return;
+ }
+
+ // Create the element node and menu popup for the breakpoint item.
+ let breakpointArgs = Heritage.extend(breakpoint.asMutable(), options);
+ let breakpointView = this._createBreakpointView.call(this, breakpointArgs);
+ let contextMenu = this._createContextMenu.call(this, breakpointArgs);
+
+ // Append a breakpoint child item to the corresponding source item.
+ sourceItem.append(breakpointView.container, {
+ attachment: Heritage.extend(breakpointArgs, {
+ actor: location.actor,
+ line: location.line,
+ view: breakpointView,
+ popup: contextMenu
+ }),
+ attributes: [
+ ["contextmenu", contextMenu.menupopupId]
+ ],
+ // Make sure that when the breakpoint item is removed, the corresponding
+ // menupopup and commandset are also destroyed.
+ finalize: this._onBreakpointRemoved
+ });
+
+ if (typeof breakpoint.condition === "string") {
+ this.highlightBreakpoint(breakpoint.location, {
+ openPopup: true,
+ noEditorUpdate: true
+ });
+ }
+
+ window.emit(EVENTS.BREAKPOINT_SHOWN_IN_PANE);
+ },
+
+ /**
+ * Removes a breakpoint from this sources container.
+ * It does not also remove the breakpoint from the controller. Be careful.
+ *
+ * @param object aLocation
+ * @see DebuggerController.Breakpoints.addBreakpoint
+ */
+ _removeBreakpoint: function (breakpoint) {
+ // When a parent source item is removed, all the child breakpoint items are
+ // also automagically removed.
+ let sourceItem = this.getItemByValue(breakpoint.location.actor);
+ if (!sourceItem) {
+ return;
+ }
+
+ // Clear the breakpoint view.
+ sourceItem.remove(this._getBreakpoint(breakpoint));
+
+ if (this._selectedBreakpoint &&
+ (queries.makeLocationId(this._selectedBreakpoint.location) ===
+ queries.makeLocationId(breakpoint.location))) {
+ this._selectedBreakpoint = null;
+ }
+
+ window.emit(EVENTS.BREAKPOINT_HIDDEN_IN_PANE);
+ },
+
+ _getBreakpoint: function (bp) {
+ return this.getItemForPredicate(item => {
+ return item.attachment.actor === bp.location.actor &&
+ item.attachment.line === bp.location.line;
+ });
+ },
+
+ /**
+ * Updates a breakpoint.
+ *
+ * @param object breakpoint
+ */
+ _updateBreakpointStatus: function (breakpoint) {
+ let location = breakpoint.location;
+ let breakpointItem = this._getBreakpoint(getBreakpoint(this.getState(), location));
+ if (!breakpointItem) {
+ return promise.reject(new Error("No breakpoint found."));
+ }
+
+ // Breakpoint will now be enabled.
+ let attachment = breakpointItem.attachment;
+
+ // Update the corresponding menu items to reflect the enabled state.
+ let prefix = "bp-cMenu-"; // "breakpoints context menu"
+ let identifier = makeLocationId(location);
+ let enableSelfId = prefix + "enableSelf-" + identifier + "-menuitem";
+ let disableSelfId = prefix + "disableSelf-" + identifier + "-menuitem";
+ let enableSelf = document.getElementById(enableSelfId);
+ let disableSelf = document.getElementById(disableSelfId);
+
+ if (breakpoint.disabled) {
+ enableSelf.removeAttribute("hidden");
+ disableSelf.setAttribute("hidden", true);
+ attachment.view.checkbox.removeAttribute("checked");
+ }
+ else {
+ enableSelf.setAttribute("hidden", true);
+ disableSelf.removeAttribute("hidden");
+ attachment.view.checkbox.setAttribute("checked", "true");
+
+ // Update the breakpoint toggle button checked state.
+ this._toggleBreakpointsButton.removeAttribute("checked");
+ }
+
+ },
+
+ /**
+ * Highlights a breakpoint in this sources container.
+ *
+ * @param object aLocation
+ * @see DebuggerController.Breakpoints.addBreakpoint
+ * @param object aOptions [optional]
+ * An object containing some of the following boolean properties:
+ * - openPopup: tells if the expression popup should be shown.
+ * - noEditorUpdate: tells if you want to skip editor updates.
+ */
+ highlightBreakpoint: function (aLocation, aOptions = {}) {
+ let breakpoint = getBreakpoint(this.getState(), aLocation);
+ if (!breakpoint) {
+ return;
+ }
+
+ // Breakpoint will now be selected.
+ this._selectBreakpoint(breakpoint);
+
+ // Update the editor location if necessary.
+ if (!aOptions.noEditorUpdate) {
+ this.DebuggerView.setEditorLocation(aLocation.actor, aLocation.line, { noDebug: true });
+ }
+
+ // If the breakpoint requires a new conditional expression, display
+ // the panel to input the corresponding expression.
+ if (aOptions.openPopup) {
+ return this._openConditionalPopup();
+ } else {
+ return this._hideConditionalPopup();
+ }
+ },
+
+ /**
+ * Highlight the breakpoint on the current currently focused line/column
+ * if it exists.
+ */
+ highlightBreakpointAtCursor: function () {
+ let actor = this.selectedValue;
+ let line = this.DebuggerView.editor.getCursor().line + 1;
+
+ let location = { actor: actor, line: line };
+ this.highlightBreakpoint(location, { noEditorUpdate: true });
+ },
+
+ /**
+ * Unhighlights the current breakpoint in this sources container.
+ */
+ unhighlightBreakpoint: function () {
+ this._hideConditionalPopup();
+ this._unselectBreakpoint();
+ },
+
+ /**
+ * Display the message thrown on breakpoint condition
+ */
+ showBreakpointConditionThrownMessage: function (aLocation, aMessage = "") {
+ let breakpointItem = this._getBreakpoint(getBreakpoint(this.getState(), aLocation));
+ if (!breakpointItem) {
+ return;
+ }
+ let attachment = breakpointItem.attachment;
+ attachment.view.container.classList.add("dbg-breakpoint-condition-thrown");
+ attachment.view.message.setAttribute("value", aMessage);
+ },
+
+ /**
+ * Update the checked/unchecked and enabled/disabled states of the buttons in
+ * the sources toolbar based on the currently selected source's state.
+ */
+ updateToolbarButtonsState: function (source) {
+ if (source.isBlackBoxed) {
+ this._blackBoxButton.setAttribute("checked", true);
+ this._prettyPrintButton.setAttribute("checked", true);
+ } else {
+ this._blackBoxButton.removeAttribute("checked");
+ this._prettyPrintButton.removeAttribute("checked");
+ }
+
+ if (source.isPrettyPrinted) {
+ this._prettyPrintButton.setAttribute("checked", true);
+ } else {
+ this._prettyPrintButton.removeAttribute("checked");
+ }
+ },
+
+ /**
+ * Toggle the pretty printing of the selected source.
+ */
+ togglePrettyPrint: function () {
+ if (this._prettyPrintButton.hasAttribute("disabled")) {
+ return;
+ }
+
+ this.DebuggerView.showProgressBar();
+ const source = getSelectedSource(this.getState());
+ const sourceClient = gThreadClient.source(source);
+ const shouldPrettyPrint = !source.isPrettyPrinted;
+
+ // This is only here to give immediate feedback,
+ // `renderPrettyPrinted` will set the final status of the buttons
+ if (shouldPrettyPrint) {
+ this._prettyPrintButton.setAttribute("checked", true);
+ } else {
+ this._prettyPrintButton.removeAttribute("checked");
+ }
+
+ this.actions.togglePrettyPrint(source);
+ },
+
+ /**
+ * Toggle the black boxed state of the selected source.
+ */
+ toggleBlackBoxing: Task.async(function* () {
+ const source = getSelectedSource(this.getState());
+ const shouldBlackBox = !source.isBlackBoxed;
+
+ // Be optimistic that the (un-)black boxing will succeed, so
+ // enable/disable the pretty print button and check/uncheck the
+ // black box button immediately.
+ if (shouldBlackBox) {
+ this._prettyPrintButton.setAttribute("disabled", true);
+ this._blackBoxButton.setAttribute("checked", true);
+ } else {
+ this._prettyPrintButton.removeAttribute("disabled");
+ this._blackBoxButton.removeAttribute("checked");
+ }
+
+ this.actions.blackbox(source, shouldBlackBox);
+ }),
+
+ renderBlackBoxed: function (source) {
+ const sourceItem = this.getItemByValue(source.actor);
+ sourceItem.prebuiltNode.classList.toggle(
+ "black-boxed",
+ source.isBlackBoxed
+ );
+
+ if (getSelectedSource(this.getState()).actor === source.actor) {
+ this.updateToolbarButtonsState(source);
+ }
+ },
+
+ /**
+ * Toggles all breakpoints enabled/disabled.
+ */
+ toggleBreakpoints: function () {
+ let breakpoints = getBreakpoints(this.getState());
+ let hasBreakpoints = breakpoints.length > 0;
+ let hasEnabledBreakpoints = breakpoints.some(bp => !bp.disabled);
+
+ if (hasBreakpoints && hasEnabledBreakpoints) {
+ this._toggleBreakpointsButton.setAttribute("checked", true);
+ this._onDisableAll();
+ } else {
+ this._toggleBreakpointsButton.removeAttribute("checked");
+ this._onEnableAll();
+ }
+ },
+
+ hidePrettyPrinting: function () {
+ this._prettyPrintButton.style.display = "none";
+
+ if (this._blackBoxButton.style.display === "none") {
+ let sep = document.querySelector("#sources-toolbar .devtools-separator");
+ sep.style.display = "none";
+ }
+ },
+
+ hideBlackBoxing: function () {
+ this._blackBoxButton.style.display = "none";
+
+ if (this._prettyPrintButton.style.display === "none") {
+ let sep = document.querySelector("#sources-toolbar .devtools-separator");
+ sep.style.display = "none";
+ }
+ },
+
+ getDisplayURL: function (source) {
+ if (!source.url) {
+ return this.getItemByValue(source.actor).attachment.label;
+ }
+ return NetworkHelper.convertToUnicode(unescape(source.url));
+ },
+
+ /**
+ * Marks a breakpoint as selected in this sources container.
+ *
+ * @param object aItem
+ * The breakpoint item to select.
+ */
+ _selectBreakpoint: function (bp) {
+ if (this._selectedBreakpoint === bp) {
+ return;
+ }
+ this._unselectBreakpoint();
+ this._selectedBreakpoint = bp;
+
+ const item = this._getBreakpoint(bp);
+ item.target.classList.add("selected");
+
+ // Ensure the currently selected breakpoint is visible.
+ this.widget.ensureElementIsVisible(item.target);
+ },
+
+ /**
+ * Marks the current breakpoint as unselected in this sources container.
+ */
+ _unselectBreakpoint: function () {
+ if (!this._selectedBreakpoint) {
+ return;
+ }
+
+ const item = this._getBreakpoint(this._selectedBreakpoint);
+ item.target.classList.remove("selected");
+
+ this._selectedBreakpoint = null;
+ },
+
+ /**
+ * Opens a conditional breakpoint's expression input popup.
+ */
+ _openConditionalPopup: function () {
+ let breakpointItem = this._getBreakpoint(this._selectedBreakpoint);
+ let attachment = breakpointItem.attachment;
+ // Check if this is an enabled conditional breakpoint, and if so,
+ // retrieve the current conditional epression.
+ let bp = getBreakpoint(this.getState(), attachment);
+ let expr = (bp ? (bp.condition || "") : "");
+ let cbPanel = this._cbPanel;
+
+ // Update the conditional expression textbox. If no expression was
+ // previously set, revert to using an empty string by default.
+ this._cbTextbox.value = expr;
+
+ function openPopup() {
+ // Show the conditional expression panel. The popup arrow should be pointing
+ // at the line number node in the breakpoint item view.
+ cbPanel.hidden = false;
+ cbPanel.openPopup(breakpointItem.attachment.view.lineNumber,
+ BREAKPOINT_CONDITIONAL_POPUP_POSITION,
+ BREAKPOINT_CONDITIONAL_POPUP_OFFSET_X,
+ BREAKPOINT_CONDITIONAL_POPUP_OFFSET_Y);
+
+ cbPanel.removeEventListener("popuphidden", openPopup, false);
+ }
+
+ // Wait until the other cb panel is closed
+ if (!this._cbPanel.hidden) {
+ this._cbPanel.addEventListener("popuphidden", openPopup, false);
+ } else {
+ openPopup();
+ }
+ },
+
+ /**
+ * Hides a conditional breakpoint's expression input popup.
+ */
+ _hideConditionalPopup: function () {
+ // Sometimes this._cbPanel doesn't have hidePopup method which doesn't
+ // break anything but simply outputs an exception to the console.
+ if (this._cbPanel.hidePopup) {
+ this._cbPanel.hidePopup();
+ }
+ },
+
+ /**
+ * Customization function for creating a breakpoint item's UI.
+ *
+ * @param object aOptions
+ * A couple of options or flags supported by this operation:
+ * - location: the breakpoint's source location and line number
+ * - disabled: the breakpoint's disabled state, boolean
+ * - text: the breakpoint's line text to be displayed
+ * - message: thrown string when the breakpoint condition throws
+ * @return object
+ * An object containing the breakpoint container, checkbox,
+ * line number and line text nodes.
+ */
+ _createBreakpointView: function (aOptions) {
+ let { location, disabled, text, message } = aOptions;
+ let identifier = makeLocationId(location);
+
+ let checkbox = document.createElement("checkbox");
+ if (!disabled) {
+ checkbox.setAttribute("checked", true);
+ }
+ checkbox.className = "dbg-breakpoint-checkbox";
+
+ let lineNumberNode = document.createElement("label");
+ lineNumberNode.className = "plain dbg-breakpoint-line";
+ lineNumberNode.setAttribute("value", location.line);
+
+ let lineTextNode = document.createElement("label");
+ lineTextNode.className = "plain dbg-breakpoint-text";
+ lineTextNode.setAttribute("value", text);
+ lineTextNode.setAttribute("crop", "end");
+ lineTextNode.setAttribute("flex", "1");
+
+ let tooltip = text ? text.substr(0, BREAKPOINT_LINE_TOOLTIP_MAX_LENGTH) : "";
+ lineTextNode.setAttribute("tooltiptext", tooltip);
+
+ let thrownNode = document.createElement("label");
+ thrownNode.className = "plain dbg-breakpoint-condition-thrown-message dbg-breakpoint-text";
+ thrownNode.setAttribute("value", message);
+ thrownNode.setAttribute("crop", "end");
+ thrownNode.setAttribute("flex", "1");
+
+ let bpLineContainer = document.createElement("hbox");
+ bpLineContainer.className = "plain dbg-breakpoint-line-container";
+ bpLineContainer.setAttribute("flex", "1");
+
+ bpLineContainer.appendChild(lineNumberNode);
+ bpLineContainer.appendChild(lineTextNode);
+
+ let bpDetailContainer = document.createElement("vbox");
+ bpDetailContainer.className = "plain dbg-breakpoint-detail-container";
+ bpDetailContainer.setAttribute("flex", "1");
+
+ bpDetailContainer.appendChild(bpLineContainer);
+ bpDetailContainer.appendChild(thrownNode);
+
+ let container = document.createElement("hbox");
+ container.id = "breakpoint-" + identifier;
+ container.className = "dbg-breakpoint side-menu-widget-item-other";
+ container.classList.add("devtools-monospace");
+ container.setAttribute("align", "center");
+ container.setAttribute("flex", "1");
+
+ container.addEventListener("click", this._onBreakpointClick, false);
+ checkbox.addEventListener("click", this._onBreakpointCheckboxClick, false);
+
+ container.appendChild(checkbox);
+ container.appendChild(bpDetailContainer);
+
+ return {
+ container: container,
+ checkbox: checkbox,
+ lineNumber: lineNumberNode,
+ lineText: lineTextNode,
+ message: thrownNode
+ };
+ },
+
+ /**
+ * Creates a context menu for a breakpoint element.
+ *
+ * @param object aOptions
+ * A couple of options or flags supported by this operation:
+ * - location: the breakpoint's source location and line number
+ * - disabled: the breakpoint's disabled state, boolean
+ * @return object
+ * An object containing the breakpoint commandset and menu popup ids.
+ */
+ _createContextMenu: function (aOptions) {
+ let { location, disabled } = aOptions;
+ let identifier = makeLocationId(location);
+
+ let commandset = document.createElement("commandset");
+ let menupopup = document.createElement("menupopup");
+ commandset.id = "bp-cSet-" + identifier;
+ menupopup.id = "bp-mPop-" + identifier;
+
+ createMenuItem.call(this, "enableSelf", !disabled);
+ createMenuItem.call(this, "disableSelf", disabled);
+ createMenuItem.call(this, "deleteSelf");
+ createMenuSeparator();
+ createMenuItem.call(this, "setConditional");
+ createMenuSeparator();
+ createMenuItem.call(this, "enableOthers");
+ createMenuItem.call(this, "disableOthers");
+ createMenuItem.call(this, "deleteOthers");
+ createMenuSeparator();
+ createMenuItem.call(this, "enableAll");
+ createMenuItem.call(this, "disableAll");
+ createMenuSeparator();
+ createMenuItem.call(this, "deleteAll");
+
+ this._popupset.appendChild(menupopup);
+ this._commandset.appendChild(commandset);
+
+ return {
+ commandsetId: commandset.id,
+ menupopupId: menupopup.id
+ };
+
+ /**
+ * Creates a menu item specified by a name with the appropriate attributes
+ * (label and handler).
+ *
+ * @param string aName
+ * A global identifier for the menu item.
+ * @param boolean aHiddenFlag
+ * True if this menuitem should be hidden.
+ */
+ function createMenuItem(aName, aHiddenFlag) {
+ let menuitem = document.createElement("menuitem");
+ let command = document.createElement("command");
+
+ let prefix = "bp-cMenu-"; // "breakpoints context menu"
+ let commandId = prefix + aName + "-" + identifier + "-command";
+ let menuitemId = prefix + aName + "-" + identifier + "-menuitem";
+
+ let label = L10N.getStr("breakpointMenuItem." + aName);
+ let func = "_on" + aName.charAt(0).toUpperCase() + aName.slice(1);
+
+ command.id = commandId;
+ command.setAttribute("label", label);
+ command.addEventListener("command", () => this[func](location), false);
+
+ menuitem.id = menuitemId;
+ menuitem.setAttribute("command", commandId);
+ aHiddenFlag && menuitem.setAttribute("hidden", "true");
+
+ commandset.appendChild(command);
+ menupopup.appendChild(menuitem);
+ }
+
+ /**
+ * Creates a simple menu separator element and appends it to the current
+ * menupopup hierarchy.
+ */
+ function createMenuSeparator() {
+ let menuseparator = document.createElement("menuseparator");
+ menupopup.appendChild(menuseparator);
+ }
+ },
+
+ /**
+ * Copy the source url from the currently selected item.
+ */
+ _onCopyUrlCommand: function () {
+ let selected = this.selectedItem && this.selectedItem.attachment;
+ if (!selected) {
+ return;
+ }
+ clipboardHelper.copyString(selected.source.url);
+ },
+
+ /**
+ * Opens selected item source in a new tab.
+ */
+ _onNewTabCommand: function () {
+ let win = Services.wm.getMostRecentWindow(gDevTools.chromeWindowType);
+ let selected = this.selectedItem.attachment;
+ win.openUILinkIn(selected.source.url, "tab", { relatedToCurrent: true });
+ },
+
+ /**
+ * Function called each time a breakpoint item is removed.
+ *
+ * @param object aItem
+ * The corresponding item.
+ */
+ _onBreakpointRemoved: function (aItem) {
+ dumpn("Finalizing breakpoint item: " + aItem.stringify());
+
+ // Destroy the context menu for the breakpoint.
+ let contextMenu = aItem.attachment.popup;
+ document.getElementById(contextMenu.commandsetId).remove();
+ document.getElementById(contextMenu.menupopupId).remove();
+ },
+
+ _onMouseDown: function (e) {
+ this.hideNoResultsTooltip();
+
+ if (!e.metaKey) {
+ return;
+ }
+
+ let editor = this.DebuggerView.editor;
+ let identifier = this._findIdentifier(e.clientX, e.clientY);
+
+ if (!identifier) {
+ return;
+ }
+
+ let foundDefinitions = this._getFunctionDefinitions(identifier);
+
+ if (!foundDefinitions || !foundDefinitions.definitions) {
+ return;
+ }
+
+ this._showFunctionDefinitionResults(identifier, foundDefinitions.definitions, editor);
+ },
+
+ /**
+ * Searches for function definition of a function in a given source file
+ */
+
+ _findDefinition: function (parsedSource, aName) {
+ let functionDefinitions = parsedSource.getNamedFunctionDefinitions(aName);
+
+ let resultList = [];
+
+ if (!functionDefinitions || !functionDefinitions.length || !functionDefinitions[0].length) {
+ return {
+ definitions: resultList
+ };
+ }
+
+ // functionDefinitions is a list with an object full of metadata,
+ // extract the data and use to construct a more useful, less
+ // cluttered, contextual list
+ for (let i = 0; i < functionDefinitions.length; i++) {
+ let functionDefinition = {
+ source: functionDefinitions[i].sourceUrl,
+ startLine: functionDefinitions[i][0].functionLocation.start.line,
+ startColumn: functionDefinitions[i][0].functionLocation.start.column,
+ name: functionDefinitions[i][0].functionName
+ };
+
+ resultList.push(functionDefinition);
+ }
+
+ return {
+ definitions: resultList
+ };
+ },
+
+ /**
+ * Searches for an identifier underneath the specified position in the
+ * source editor.
+ *
+ * @param number x, y
+ * The left/top coordinates where to look for an identifier.
+ */
+ _findIdentifier: function (x, y) {
+ let parsedSource = SourceUtils.parseSource(this.DebuggerView, this.Parser);
+ let identifierInfo = SourceUtils.findIdentifier(this.DebuggerView.editor, parsedSource, x, y);
+
+ // Not hovering over an identifier
+ if (!identifierInfo) {
+ return;
+ }
+
+ return identifierInfo;
+ },
+
+ /**
+ * The selection listener for the source editor.
+ */
+ _onEditorCursorActivity: function (e) {
+ let editor = this.DebuggerView.editor;
+ let start = editor.getCursor("start").line + 1;
+ let end = editor.getCursor().line + 1;
+ let source = getSelectedSource(this.getState());
+
+ if (source) {
+ let location = { actor: source.actor, line: start };
+ if (getBreakpoint(this.getState(), location) && start == end) {
+ this.highlightBreakpoint(location, { noEditorUpdate: true });
+ } else {
+ this.unhighlightBreakpoint();
+ }
+ }
+ },
+
+ /*
+ * Uses function definition data to perform actions in different
+ * cases of how many locations were found: zero, one, or multiple definitions
+ */
+ _showFunctionDefinitionResults: function (aHoveredFunction, aDefinitionList, aEditor) {
+ let definitions = aDefinitionList;
+ let hoveredFunction = aHoveredFunction;
+
+ // show a popup saying no results were found
+ if (definitions.length == 0) {
+ this._noResultsFoundToolTip.setTextContent({
+ messages: [L10N.getStr("noMatchingStringsText")]
+ });
+
+ this._markedIdentifier = aEditor.markText(
+ { line: hoveredFunction.location.start.line - 1, ch: hoveredFunction.location.start.column },
+ { line: hoveredFunction.location.end.line - 1, ch: hoveredFunction.location.end.column });
+
+ this._noResultsFoundToolTip.show(this._markedIdentifier.anchor);
+
+ } else if (definitions.length == 1) {
+ this.DebuggerView.setEditorLocation(definitions[0].source, definitions[0].startLine);
+ } else {
+ // TODO: multiple definitions found, do something else
+ this.DebuggerView.setEditorLocation(definitions[0].source, definitions[0].startLine);
+ }
+ },
+
+ /**
+ * Hides the tooltip and clear marked text popup.
+ */
+ hideNoResultsTooltip: function () {
+ this._noResultsFoundToolTip.hide();
+ if (this._markedIdentifier) {
+ this._markedIdentifier.clear();
+ this._markedIdentifier = null;
+ }
+ },
+
+ /*
+ * Gets the definition locations from function metadata
+ */
+ _getFunctionDefinitions: function (aIdentifierInfo) {
+ let parsedSource = SourceUtils.parseSource(this.DebuggerView, this.Parser);
+ let definition_info = this._findDefinition(parsedSource, aIdentifierInfo.name);
+
+ // Did not find any definitions for the identifier
+ if (!definition_info) {
+ return;
+ }
+
+ return definition_info;
+ },
+
+ /**
+ * The select listener for the sources container.
+ */
+ _onSourceSelect: function ({ detail: sourceItem }) {
+ if (!sourceItem) {
+ return;
+ }
+
+ const { source } = sourceItem.attachment;
+ this.actions.selectSource(source);
+ },
+
+ renderSourceSelected: function (source) {
+ if (source.url) {
+ this._preferredSourceURL = source.url;
+ }
+ this.updateToolbarButtonsState(source);
+ this._selectItem(this.getItemByValue(source.actor));
+ },
+
+ /**
+ * The click listener for the "stop black boxing" button.
+ */
+ _onStopBlackBoxing: Task.async(function* () {
+ this.actions.blackbox(getSelectedSource(this.getState()), false);
+ }),
+
+ /**
+ * The source editor's contextmenu handler.
+ * - Toggles "Add Conditional Breakpoint" and "Edit Conditional Breakpoint" items
+ */
+ _onEditorContextMenuOpen: function (message, ev, popup) {
+ let actor = this.selectedValue;
+ let line = this.DebuggerView.editor.getCursor().line + 1;
+ let location = { actor, line };
+
+ let breakpoint = getBreakpoint(this.getState(), location);
+ let addConditionalBreakpointMenuItem = popup.querySelector("#se-dbg-cMenu-addConditionalBreakpoint");
+ let editConditionalBreakpointMenuItem = popup.querySelector("#se-dbg-cMenu-editConditionalBreakpoint");
+
+ if (breakpoint && !!breakpoint.condition) {
+ editConditionalBreakpointMenuItem.removeAttribute("hidden");
+ addConditionalBreakpointMenuItem.setAttribute("hidden", true);
+ }
+ else {
+ addConditionalBreakpointMenuItem.removeAttribute("hidden");
+ editConditionalBreakpointMenuItem.setAttribute("hidden", true);
+ }
+ },
+
+ /**
+ * The click listener for a breakpoint container.
+ */
+ _onBreakpointClick: function (e) {
+ let sourceItem = this.getItemForElement(e.target);
+ let breakpointItem = this.getItemForElement.call(sourceItem, e.target);
+ let attachment = breakpointItem.attachment;
+ let bp = getBreakpoint(this.getState(), attachment);
+ if (bp) {
+ this.highlightBreakpoint(bp.location, {
+ openPopup: bp.condition && e.button == 0
+ });
+ } else {
+ this.highlightBreakpoint(bp.location);
+ }
+ },
+
+ /**
+ * The click listener for a breakpoint checkbox.
+ */
+ _onBreakpointCheckboxClick: function (e) {
+ let sourceItem = this.getItemForElement(e.target);
+ let breakpointItem = this.getItemForElement.call(sourceItem, e.target);
+ let bp = getBreakpoint(this.getState(), breakpointItem.attachment);
+
+ if (bp.disabled) {
+ this.actions.enableBreakpoint(bp.location);
+ }
+ else {
+ this.actions.disableBreakpoint(bp.location);
+ }
+
+ // Don't update the editor location (avoid propagating into _onBreakpointClick).
+ e.preventDefault();
+ e.stopPropagation();
+ },
+
+ /**
+ * The popup showing listener for the breakpoints conditional expression panel.
+ */
+ _onConditionalPopupShowing: function () {
+ this._conditionalPopupVisible = true; // Used in tests.
+ },
+
+ /**
+ * The popup shown listener for the breakpoints conditional expression panel.
+ */
+ _onConditionalPopupShown: function () {
+ this._cbTextbox.focus();
+ this._cbTextbox.select();
+ window.emit(EVENTS.CONDITIONAL_BREAKPOINT_POPUP_SHOWN);
+ },
+
+ /**
+ * The popup hiding listener for the breakpoints conditional expression panel.
+ */
+ _onConditionalPopupHiding: function () {
+ this._conditionalPopupVisible = false; // Used in tests.
+
+ // Check if this is an enabled conditional breakpoint, and if so,
+ // save the current conditional expression.
+ let bp = this._selectedBreakpoint;
+ if (bp) {
+ let condition = this._cbTextbox.value;
+ this.actions.setBreakpointCondition(bp.location, condition);
+ }
+ },
+
+ /**
+ * The popup hidden listener for the breakpoints conditional expression panel.
+ */
+ _onConditionalPopupHidden: function () {
+ this._cbPanel.hidden = true;
+ window.emit(EVENTS.CONDITIONAL_BREAKPOINT_POPUP_HIDDEN);
+ },
+
+ /**
+ * The keypress listener for the breakpoints conditional expression textbox.
+ */
+ _onConditionalTextboxKeyPress: function (e) {
+ if (e.keyCode == KeyCodes.DOM_VK_RETURN) {
+ this._hideConditionalPopup();
+ }
+ },
+
+ /**
+ * Called when the add breakpoint key sequence was pressed.
+ */
+ _onCmdAddBreakpoint: function (e) {
+ let actor = this.selectedValue;
+ let line = (this.DebuggerView.clickedLine ?
+ this.DebuggerView.clickedLine + 1 :
+ this.DebuggerView.editor.getCursor().line + 1);
+ let location = { actor, line };
+ let bp = getBreakpoint(this.getState(), location);
+
+ // If a breakpoint already existed, remove it now.
+ if (bp) {
+ this.actions.removeBreakpoint(bp.location);
+ }
+ // No breakpoint existed at the required location, add one now.
+ else {
+ this.actions.addBreakpoint(location);
+ }
+ },
+
+ /**
+ * Called when the add conditional breakpoint key sequence was pressed.
+ */
+ _onCmdAddConditionalBreakpoint: function (e) {
+ let actor = this.selectedValue;
+ let line = (this.DebuggerView.clickedLine ?
+ this.DebuggerView.clickedLine + 1 :
+ this.DebuggerView.editor.getCursor().line + 1);
+
+ let location = { actor, line };
+ let bp = getBreakpoint(this.getState(), location);
+
+ // If a breakpoint already existed or wasn't a conditional, morph it now.
+ if (bp) {
+ this.highlightBreakpoint(bp.location, { openPopup: true });
+ }
+ // No breakpoint existed at the required location, add one now.
+ else {
+ this.actions.addBreakpoint(location, "");
+ }
+ },
+
+ getOtherBreakpoints: function (location) {
+ const bps = getBreakpoints(this.getState());
+ if (location) {
+ return bps.filter(bp => {
+ return (bp.location.actor !== location.actor ||
+ bp.location.line !== location.line);
+ });
+ }
+ return bps;
+ },
+
+ /**
+ * Function invoked on the "setConditional" menuitem command.
+ *
+ * @param object aLocation
+ * @see DebuggerController.Breakpoints.addBreakpoint
+ */
+ _onSetConditional: function (aLocation) {
+ // Highlight the breakpoint and show a conditional expression popup.
+ this.highlightBreakpoint(aLocation, { openPopup: true });
+ },
+
+ /**
+ * Function invoked on the "enableSelf" menuitem command.
+ *
+ * @param object aLocation
+ * @see DebuggerController.Breakpoints.addBreakpoint
+ */
+ _onEnableSelf: function (aLocation) {
+ // Enable the breakpoint, in this container and the controller store.
+ this.actions.enableBreakpoint(aLocation);
+ },
+
+ /**
+ * Function invoked on the "disableSelf" menuitem command.
+ *
+ * @param object aLocation
+ * @see DebuggerController.Breakpoints.addBreakpoint
+ */
+ _onDisableSelf: function (aLocation) {
+ const bp = getBreakpoint(this.getState(), aLocation);
+ if (!bp.disabled) {
+ this.actions.disableBreakpoint(aLocation);
+ }
+ },
+
+ /**
+ * Function invoked on the "deleteSelf" menuitem command.
+ *
+ * @param object aLocation
+ * @see DebuggerController.Breakpoints.addBreakpoint
+ */
+ _onDeleteSelf: function (aLocation) {
+ this.actions.removeBreakpoint(aLocation);
+ },
+
+ /**
+ * Function invoked on the "enableOthers" menuitem command.
+ *
+ * @param object aLocation
+ * @see DebuggerController.Breakpoints.addBreakpoint
+ */
+ _onEnableOthers: function (aLocation) {
+ let other = this.getOtherBreakpoints(aLocation);
+ // TODO(jwl): batch these and interrupt the thread for all of them
+ other.forEach(bp => this._onEnableSelf(bp.location));
+ },
+
+ /**
+ * Function invoked on the "disableOthers" menuitem command.
+ *
+ * @param object aLocation
+ * @see DebuggerController.Breakpoints.addBreakpoint
+ */
+ _onDisableOthers: function (aLocation) {
+ let other = this.getOtherBreakpoints(aLocation);
+ other.forEach(bp => this._onDisableSelf(bp.location));
+ },
+
+ /**
+ * Function invoked on the "deleteOthers" menuitem command.
+ *
+ * @param object aLocation
+ * @see DebuggerController.Breakpoints.addBreakpoint
+ */
+ _onDeleteOthers: function (aLocation) {
+ let other = this.getOtherBreakpoints(aLocation);
+ other.forEach(bp => this._onDeleteSelf(bp.location));
+ },
+
+ /**
+ * Function invoked on the "enableAll" menuitem command.
+ */
+ _onEnableAll: function () {
+ this._onEnableOthers(undefined);
+ },
+
+ /**
+ * Function invoked on the "disableAll" menuitem command.
+ */
+ _onDisableAll: function () {
+ this._onDisableOthers(undefined);
+ },
+
+ /**
+ * Function invoked on the "deleteAll" menuitem command.
+ */
+ _onDeleteAll: function () {
+ this._onDeleteOthers(undefined);
+ },
+
+ _commandset: null,
+ _popupset: null,
+ _cmPopup: null,
+ _cbPanel: null,
+ _cbTextbox: null,
+ _selectedBreakpointItem: null,
+ _conditionalPopupVisible: false,
+ _noResultsFoundToolTip: null,
+ _markedIdentifier: null,
+ _selectedBreakpoint: null,
+ _conditionalPopupVisible: false
+});
+
+module.exports = SourcesView;
diff --git a/devtools/client/debugger/debugger-commands.js b/devtools/client/debugger/debugger-commands.js
new file mode 100644
index 000000000..3229646f8
--- /dev/null
+++ b/devtools/client/debugger/debugger-commands.js
@@ -0,0 +1,633 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* 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 { Cc, Ci, Cu } = require("chrome");
+const l10n = require("gcli/l10n");
+loader.lazyRequireGetter(this, "gDevTools",
+ "devtools/client/framework/devtools", true);
+
+/**
+ * The commands and converters that are exported to GCLI
+ */
+exports.items = [];
+
+/**
+ * Utility to get access to the current breakpoint list.
+ *
+ * @param DebuggerPanel dbg
+ * The debugger panel.
+ * @return array
+ * An array of objects, one for each breakpoint, where each breakpoint
+ * object has the following properties:
+ * - url: the URL of the source file.
+ * - label: a unique string identifier designed to be user visible.
+ * - lineNumber: the line number of the breakpoint in the source file.
+ * - lineText: the text of the line at the breakpoint.
+ * - truncatedLineText: lineText truncated to MAX_LINE_TEXT_LENGTH.
+ */
+function getAllBreakpoints(dbg) {
+ let breakpoints = [];
+ let sources = dbg._view.Sources;
+ let { trimUrlLength: trim } = dbg.panelWin.SourceUtils;
+
+ for (let source of sources) {
+ for (let { attachment: breakpoint } of source) {
+ breakpoints.push({
+ url: source.attachment.source.url,
+ label: source.attachment.label + ":" + breakpoint.line,
+ lineNumber: breakpoint.line,
+ lineText: breakpoint.text,
+ truncatedLineText: trim(breakpoint.text, MAX_LINE_TEXT_LENGTH, "end")
+ });
+ }
+ }
+
+ return breakpoints;
+}
+
+function getAllSources(dbg) {
+ if (!dbg) {
+ return [];
+ }
+
+ let items = dbg._view.Sources.items;
+ return items
+ .filter(item => !!item.attachment.source.url)
+ .map(item => ({
+ name: item.attachment.source.url,
+ value: item.attachment.source.actor
+ }));
+}
+
+/**
+ * 'break' command
+ */
+exports.items.push({
+ name: "break",
+ description: l10n.lookup("breakDesc"),
+ manual: l10n.lookup("breakManual")
+});
+
+/**
+ * 'break list' command
+ */
+exports.items.push({
+ name: "break list",
+ item: "command",
+ runAt: "client",
+ description: l10n.lookup("breaklistDesc"),
+ returnType: "breakpoints",
+ exec: function (args, context) {
+ let dbg = getPanel(context, "jsdebugger", { ensureOpened: true });
+ return dbg.then(getAllBreakpoints);
+ }
+});
+
+exports.items.push({
+ item: "converter",
+ from: "breakpoints",
+ to: "view",
+ exec: function (breakpoints, context) {
+ let dbg = getPanel(context, "jsdebugger");
+ if (dbg && breakpoints.length) {
+ return context.createView({
+ html: breakListHtml,
+ data: {
+ breakpoints: breakpoints,
+ onclick: context.update,
+ ondblclick: context.updateExec
+ }
+ });
+ } else {
+ return context.createView({
+ html: "<p>${message}</p>",
+ data: { message: l10n.lookup("breaklistNone") }
+ });
+ }
+ }
+});
+
+var breakListHtml = "" +
+ "<table>" +
+ " <thead>" +
+ " <th>Source</th>" +
+ " <th>Line</th>" +
+ " <th>Actions</th>" +
+ " </thead>" +
+ " <tbody>" +
+ " <tr foreach='breakpoint in ${breakpoints}'>" +
+ " <td class='gcli-breakpoint-label'>${breakpoint.label}</td>" +
+ " <td class='gcli-breakpoint-lineText'>" +
+ " ${breakpoint.truncatedLineText}" +
+ " </td>" +
+ " <td>" +
+ " <span class='gcli-out-shortcut'" +
+ " data-command='break del ${breakpoint.label}'" +
+ " onclick='${onclick}'" +
+ " ondblclick='${ondblclick}'>" +
+ " " + l10n.lookup("breaklistOutRemove") + "</span>" +
+ " </td>" +
+ " </tr>" +
+ " </tbody>" +
+ "</table>" +
+ "";
+
+var MAX_LINE_TEXT_LENGTH = 30;
+var MAX_LABEL_LENGTH = 20;
+
+/**
+ * 'break add' command
+ */
+exports.items.push({
+ name: "break add",
+ description: l10n.lookup("breakaddDesc"),
+ manual: l10n.lookup("breakaddManual")
+});
+
+/**
+ * 'break add line' command
+ */
+exports.items.push({
+ item: "command",
+ runAt: "client",
+ name: "break add line",
+ description: l10n.lookup("breakaddlineDesc"),
+ params: [
+ {
+ name: "file",
+ type: {
+ name: "selection",
+ lookup: function (context) {
+ return getAllSources(getPanel(context, "jsdebugger"));
+ }
+ },
+ description: l10n.lookup("breakaddlineFileDesc")
+ },
+ {
+ name: "line",
+ type: { name: "number", min: 1, step: 10 },
+ description: l10n.lookup("breakaddlineLineDesc")
+ }
+ ],
+ returnType: "string",
+ exec: function (args, context) {
+ let dbg = getPanel(context, "jsdebugger");
+ if (!dbg) {
+ return l10n.lookup("debuggerStopped");
+ }
+
+ let deferred = context.defer();
+ let item = dbg._view.Sources.getItemForAttachment(a => {
+ return a.source && a.source.actor === args.file;
+ });
+ let position = { actor: item.value, line: args.line };
+
+ dbg.addBreakpoint(position).then(() => {
+ deferred.resolve(l10n.lookup("breakaddAdded"));
+ }, aError => {
+ deferred.resolve(l10n.lookupFormat("breakaddFailed", [aError]));
+ });
+
+ return deferred.promise;
+ }
+});
+
+/**
+ * 'break del' command
+ */
+exports.items.push({
+ item: "command",
+ runAt: "client",
+ name: "break del",
+ description: l10n.lookup("breakdelDesc"),
+ params: [
+ {
+ name: "breakpoint",
+ type: {
+ name: "selection",
+ lookup: function (context) {
+ let dbg = getPanel(context, "jsdebugger");
+ if (!dbg) {
+ return [];
+ }
+ return getAllBreakpoints(dbg).map(breakpoint => ({
+ name: breakpoint.label,
+ value: breakpoint,
+ description: breakpoint.truncatedLineText
+ }));
+ }
+ },
+ description: l10n.lookup("breakdelBreakidDesc")
+ }
+ ],
+ returnType: "string",
+ exec: function (args, context) {
+ let dbg = getPanel(context, "jsdebugger");
+ if (!dbg) {
+ return l10n.lookup("debuggerStopped");
+ }
+
+ let source = dbg._view.Sources.getItemForAttachment(a => {
+ return a.source && a.source.url === args.breakpoint.url;
+ });
+
+ let deferred = context.defer();
+ let position = { actor: source.attachment.source.actor,
+ line: args.breakpoint.lineNumber };
+
+ dbg.removeBreakpoint(position).then(() => {
+ deferred.resolve(l10n.lookup("breakdelRemoved"));
+ }, () => {
+ deferred.resolve(l10n.lookup("breakNotFound"));
+ });
+
+ return deferred.promise;
+ }
+});
+
+/**
+ * 'dbg' command
+ */
+exports.items.push({
+ name: "dbg",
+ description: l10n.lookup("dbgDesc"),
+ manual: l10n.lookup("dbgManual")
+});
+
+/**
+ * 'dbg open' command
+ */
+exports.items.push({
+ item: "command",
+ runAt: "client",
+ name: "dbg open",
+ description: l10n.lookup("dbgOpen"),
+ params: [],
+ exec: function (args, context) {
+ let target = context.environment.target;
+ return gDevTools.showToolbox(target, "jsdebugger").then(() => null);
+ }
+});
+
+/**
+ * 'dbg close' command
+ */
+exports.items.push({
+ item: "command",
+ runAt: "client",
+ name: "dbg close",
+ description: l10n.lookup("dbgClose"),
+ params: [],
+ exec: function (args, context) {
+ if (!getPanel(context, "jsdebugger")) {
+ return;
+ }
+ let target = context.environment.target;
+ return gDevTools.closeToolbox(target).then(() => null);
+ }
+});
+
+/**
+ * 'dbg interrupt' command
+ */
+exports.items.push({
+ item: "command",
+ runAt: "client",
+ name: "dbg interrupt",
+ description: l10n.lookup("dbgInterrupt"),
+ params: [],
+ exec: function (args, context) {
+ let dbg = getPanel(context, "jsdebugger");
+ if (!dbg) {
+ return l10n.lookup("debuggerStopped");
+ }
+
+ let controller = dbg._controller;
+ let thread = controller.activeThread;
+ if (!thread.paused) {
+ thread.interrupt();
+ }
+ }
+});
+
+/**
+ * 'dbg continue' command
+ */
+exports.items.push({
+ item: "command",
+ runAt: "client",
+ name: "dbg continue",
+ description: l10n.lookup("dbgContinue"),
+ params: [],
+ exec: function (args, context) {
+ let dbg = getPanel(context, "jsdebugger");
+ if (!dbg) {
+ return l10n.lookup("debuggerStopped");
+ }
+
+ let controller = dbg._controller;
+ let thread = controller.activeThread;
+ if (thread.paused) {
+ thread.resume();
+ }
+ }
+});
+
+/**
+ * 'dbg step' command
+ */
+exports.items.push({
+ item: "command",
+ runAt: "client",
+ name: "dbg step",
+ description: l10n.lookup("dbgStepDesc"),
+ manual: l10n.lookup("dbgStepManual")
+});
+
+/**
+ * 'dbg step over' command
+ */
+exports.items.push({
+ item: "command",
+ runAt: "client",
+ name: "dbg step over",
+ description: l10n.lookup("dbgStepOverDesc"),
+ params: [],
+ exec: function (args, context) {
+ let dbg = getPanel(context, "jsdebugger");
+ if (!dbg) {
+ return l10n.lookup("debuggerStopped");
+ }
+
+ let controller = dbg._controller;
+ let thread = controller.activeThread;
+ if (thread.paused) {
+ thread.stepOver();
+ }
+ }
+});
+
+/**
+ * 'dbg step in' command
+ */
+exports.items.push({
+ item: "command",
+ runAt: "client",
+ name: "dbg step in",
+ description: l10n.lookup("dbgStepInDesc"),
+ params: [],
+ exec: function (args, context) {
+ let dbg = getPanel(context, "jsdebugger");
+ if (!dbg) {
+ return l10n.lookup("debuggerStopped");
+ }
+
+ let controller = dbg._controller;
+ let thread = controller.activeThread;
+ if (thread.paused) {
+ thread.stepIn();
+ }
+ }
+});
+
+/**
+ * 'dbg step over' command
+ */
+exports.items.push({
+ item: "command",
+ runAt: "client",
+ name: "dbg step out",
+ description: l10n.lookup("dbgStepOutDesc"),
+ params: [],
+ exec: function (args, context) {
+ let dbg = getPanel(context, "jsdebugger");
+ if (!dbg) {
+ return l10n.lookup("debuggerStopped");
+ }
+
+ let controller = dbg._controller;
+ let thread = controller.activeThread;
+ if (thread.paused) {
+ thread.stepOut();
+ }
+ }
+});
+
+/**
+ * 'dbg list' command
+ */
+exports.items.push({
+ item: "command",
+ runAt: "client",
+ name: "dbg list",
+ description: l10n.lookup("dbgListSourcesDesc"),
+ params: [],
+ returnType: "dom",
+ exec: function (args, context) {
+ let dbg = getPanel(context, "jsdebugger");
+ if (!dbg) {
+ return l10n.lookup("debuggerClosed");
+ }
+
+ let sources = getAllSources(dbg);
+ let doc = context.environment.chromeDocument;
+ let div = createXHTMLElement(doc, "div");
+ let ol = createXHTMLElement(doc, "ol");
+
+ sources.forEach(source => {
+ let li = createXHTMLElement(doc, "li");
+ li.textContent = source.name;
+ ol.appendChild(li);
+ });
+ div.appendChild(ol);
+
+ return div;
+ }
+});
+
+/**
+ * Define the 'dbg blackbox' and 'dbg unblackbox' commands.
+ */
+[
+ {
+ name: "blackbox",
+ clientMethod: "blackBox",
+ l10nPrefix: "dbgBlackBox"
+ },
+ {
+ name: "unblackbox",
+ clientMethod: "unblackBox",
+ l10nPrefix: "dbgUnBlackBox"
+ }
+].forEach(function (cmd) {
+ const lookup = function (id) {
+ return l10n.lookup(cmd.l10nPrefix + id);
+ };
+
+ exports.items.push({
+ item: "command",
+ runAt: "client",
+ name: "dbg " + cmd.name,
+ description: lookup("Desc"),
+ params: [
+ {
+ name: "source",
+ type: {
+ name: "selection",
+ lookup: function (context) {
+ return getAllSources(getPanel(context, "jsdebugger"));
+ }
+ },
+ description: lookup("SourceDesc"),
+ defaultValue: null
+ },
+ {
+ name: "glob",
+ type: "string",
+ description: lookup("GlobDesc"),
+ defaultValue: null
+ },
+ {
+ name: "invert",
+ type: "boolean",
+ description: lookup("InvertDesc")
+ }
+ ],
+ returnType: "dom",
+ exec: function (args, context) {
+ const dbg = getPanel(context, "jsdebugger");
+ const doc = context.environment.chromeDocument;
+ if (!dbg) {
+ throw new Error(l10n.lookup("debuggerClosed"));
+ }
+
+ const { promise, resolve, reject } = context.defer();
+ const { activeThread } = dbg._controller;
+ const globRegExp = args.glob ? globToRegExp(args.glob) : null;
+
+ // Filter the sources down to those that we will need to black box.
+
+ function shouldBlackBox(source) {
+ var value = globRegExp && globRegExp.test(source.url)
+ || args.source && source.actor == args.source;
+ return args.invert ? !value : value;
+ }
+
+ const toBlackBox = [];
+ for (let {attachment: {source}} of dbg._view.Sources.items) {
+ if (shouldBlackBox(source)) {
+ toBlackBox.push(source);
+ }
+ }
+
+ // If we aren't black boxing any sources, bail out now.
+
+ if (toBlackBox.length === 0) {
+ const empty = createXHTMLElement(doc, "div");
+ empty.textContent = lookup("EmptyDesc");
+ return void resolve(empty);
+ }
+
+ // Send the black box request to each source we are black boxing. As we
+ // get responses, accumulate the results in `blackBoxed`.
+
+ const blackBoxed = [];
+
+ for (let source of toBlackBox) {
+ dbg.blackbox(source, cmd.clientMethod === "blackBox").then(() => {
+ blackBoxed.push(source.url);
+ }, err => {
+ blackBoxed.push(lookup("ErrorDesc") + " " + source.url);
+ }).then(() => {
+ if (toBlackBox.length === blackBoxed.length) {
+ displayResults();
+ }
+ });
+ }
+
+ // List the results for the user.
+
+ function displayResults() {
+ const results = doc.createElement("div");
+ results.textContent = lookup("NonEmptyDesc");
+
+ const list = createXHTMLElement(doc, "ul");
+ results.appendChild(list);
+
+ for (let result of blackBoxed) {
+ const item = createXHTMLElement(doc, "li");
+ item.textContent = result;
+ list.appendChild(item);
+ }
+ resolve(results);
+ }
+
+ return promise;
+ }
+ });
+});
+
+/**
+ * A helper to create xhtml namespaced elements.
+ */
+function createXHTMLElement(document, tagname) {
+ return document.createElementNS("http://www.w3.org/1999/xhtml", tagname);
+}
+
+/**
+ * A helper to go from a command context to a debugger panel.
+ */
+function getPanel(context, id, options = {}) {
+ if (!context) {
+ return undefined;
+ }
+
+ let target = context.environment.target;
+
+ if (options.ensureOpened) {
+ return gDevTools.showToolbox(target, id).then(toolbox => {
+ return toolbox.getPanel(id);
+ });
+ } else {
+ let toolbox = gDevTools.getToolbox(target);
+ if (toolbox) {
+ return toolbox.getPanel(id);
+ } else {
+ return undefined;
+ }
+ }
+}
+
+/**
+ * Converts a glob to a regular expression.
+ */
+function globToRegExp(glob) {
+ const reStr = glob
+ // Escape existing regular expression syntax.
+ .replace(/\\/g, "\\\\")
+ .replace(/\//g, "\\/")
+ .replace(/\^/g, "\\^")
+ .replace(/\$/g, "\\$")
+ .replace(/\+/g, "\\+")
+ .replace(/\?/g, "\\?")
+ .replace(/\./g, "\\.")
+ .replace(/\(/g, "\\(")
+ .replace(/\)/g, "\\)")
+ .replace(/\=/g, "\\=")
+ .replace(/\!/g, "\\!")
+ .replace(/\|/g, "\\|")
+ .replace(/\{/g, "\\{")
+ .replace(/\}/g, "\\}")
+ .replace(/\,/g, "\\,")
+ .replace(/\[/g, "\\[")
+ .replace(/\]/g, "\\]")
+ .replace(/\-/g, "\\-")
+ // Turn * into the match everything wildcard.
+ .replace(/\*/g, ".*");
+ return new RegExp("^" + reStr + "$");
+}
diff --git a/devtools/client/debugger/debugger-controller.js b/devtools/client/debugger/debugger-controller.js
new file mode 100644
index 000000000..ce6a467bc
--- /dev/null
+++ b/devtools/client/debugger/debugger-controller.js
@@ -0,0 +1,1276 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* 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";
+
+var { classes: Cc, interfaces: Ci, utils: Cu } = Components;
+
+const DBG_STRINGS_URI = "devtools/client/locales/debugger.properties";
+const NEW_SOURCE_IGNORED_URLS = ["debugger eval code", "XStringBundle"];
+const NEW_SOURCE_DISPLAY_DELAY = 200; // ms
+const FETCH_SOURCE_RESPONSE_DELAY = 200; // ms
+const FRAME_STEP_CLEAR_DELAY = 100; // ms
+const CALL_STACK_PAGE_SIZE = 25; // frames
+
+// The panel's window global is an EventEmitter firing the following events:
+const EVENTS = {
+ // When the debugger's source editor instance finishes loading or unloading.
+ EDITOR_LOADED: "Debugger:EditorLoaded",
+ EDITOR_UNLOADED: "Debugger:EditorUnloaded",
+
+ // When new sources are received from the debugger server.
+ NEW_SOURCE: "Debugger:NewSource",
+ SOURCES_ADDED: "Debugger:SourcesAdded",
+
+ // When a source is shown in the source editor.
+ SOURCE_SHOWN: "Debugger:EditorSourceShown",
+ SOURCE_ERROR_SHOWN: "Debugger:EditorSourceErrorShown",
+
+ // When the editor has shown a source and set the line / column position
+ EDITOR_LOCATION_SET: "Debugger:EditorLocationSet",
+
+ // When scopes, variables, properties and watch expressions are fetched and
+ // displayed in the variables view.
+ FETCHED_SCOPES: "Debugger:FetchedScopes",
+ FETCHED_VARIABLES: "Debugger:FetchedVariables",
+ FETCHED_PROPERTIES: "Debugger:FetchedProperties",
+ FETCHED_BUBBLE_PROPERTIES: "Debugger:FetchedBubbleProperties",
+ FETCHED_WATCH_EXPRESSIONS: "Debugger:FetchedWatchExpressions",
+
+ // When a breakpoint has been added or removed on the debugger server.
+ BREAKPOINT_ADDED: "Debugger:BreakpointAdded",
+ BREAKPOINT_REMOVED: "Debugger:BreakpointRemoved",
+ BREAKPOINT_CLICKED: "Debugger:BreakpointClicked",
+
+ // When a breakpoint has been shown or hidden in the source editor
+ // or the pane.
+ BREAKPOINT_SHOWN_IN_EDITOR: "Debugger:BreakpointShownInEditor",
+ BREAKPOINT_SHOWN_IN_PANE: "Debugger:BreakpointShownInPane",
+ BREAKPOINT_HIDDEN_IN_EDITOR: "Debugger:BreakpointHiddenInEditor",
+ BREAKPOINT_HIDDEN_IN_PANE: "Debugger:BreakpointHiddenInPane",
+
+ // When a conditional breakpoint's popup is shown/hidden.
+ CONDITIONAL_BREAKPOINT_POPUP_SHOWN: "Debugger:ConditionalBreakpointPopupShown",
+ CONDITIONAL_BREAKPOINT_POPUP_HIDDEN: "Debugger:ConditionalBreakpointPopupHidden",
+
+ // When event listeners are fetched or event breakpoints are updated.
+ EVENT_LISTENERS_FETCHED: "Debugger:EventListenersFetched",
+ EVENT_BREAKPOINTS_UPDATED: "Debugger:EventBreakpointsUpdated",
+
+ // When a file search was performed.
+ FILE_SEARCH_MATCH_FOUND: "Debugger:FileSearch:MatchFound",
+ FILE_SEARCH_MATCH_NOT_FOUND: "Debugger:FileSearch:MatchNotFound",
+
+ // When a function search was performed.
+ FUNCTION_SEARCH_MATCH_FOUND: "Debugger:FunctionSearch:MatchFound",
+ FUNCTION_SEARCH_MATCH_NOT_FOUND: "Debugger:FunctionSearch:MatchNotFound",
+
+ // When a global text search was performed.
+ GLOBAL_SEARCH_MATCH_FOUND: "Debugger:GlobalSearch:MatchFound",
+ GLOBAL_SEARCH_MATCH_NOT_FOUND: "Debugger:GlobalSearch:MatchNotFound",
+
+ // After the the StackFrames object has been filled with frames
+ AFTER_FRAMES_REFILLED: "Debugger:AfterFramesRefilled",
+
+ // After the stackframes are cleared and debugger won't pause anymore.
+ AFTER_FRAMES_CLEARED: "Debugger:AfterFramesCleared",
+
+ // When the options popup is showing or hiding.
+ OPTIONS_POPUP_SHOWING: "Debugger:OptionsPopupShowing",
+ OPTIONS_POPUP_HIDDEN: "Debugger:OptionsPopupHidden",
+
+ // When the widgets layout has been changed.
+ LAYOUT_CHANGED: "Debugger:LayoutChanged",
+
+ // When a worker has been selected.
+ WORKER_SELECTED: "Debugger::WorkerSelected"
+};
+
+// Descriptions for what a stack frame represents after the debugger pauses.
+const FRAME_TYPE = {
+ NORMAL: 0,
+ CONDITIONAL_BREAKPOINT_EVAL: 1,
+ WATCH_EXPRESSIONS_EVAL: 2,
+ PUBLIC_CLIENT_EVAL: 3
+};
+
+const { BrowserLoader } = Cu.import("resource://devtools/client/shared/browser-loader.js", {});
+const { require } = BrowserLoader({
+ baseURI: "resource://devtools/client/debugger/",
+ window,
+});
+const { XPCOMUtils } = require("resource://gre/modules/XPCOMUtils.jsm");
+XPCOMUtils.defineConstant(this, "require", require);
+const { SimpleListWidget } = require("resource://devtools/client/shared/widgets/SimpleListWidget.jsm");
+const { BreadcrumbsWidget } = require("resource://devtools/client/shared/widgets/BreadcrumbsWidget.jsm");
+const { SideMenuWidget } = require("resource://devtools/client/shared/widgets/SideMenuWidget.jsm");
+const { VariablesView } = require("resource://devtools/client/shared/widgets/VariablesView.jsm");
+const { VariablesViewController, StackFrameUtils } = require("resource://devtools/client/shared/widgets/VariablesViewController.jsm");
+const EventEmitter = require("devtools/shared/event-emitter");
+const { gDevTools } = require("devtools/client/framework/devtools");
+const { ViewHelpers, Heritage, WidgetMethods, setNamedTimeout,
+ clearNamedTimeout } = require("devtools/client/shared/widgets/view-helpers");
+
+// React
+const React = require("devtools/client/shared/vendor/react");
+const ReactDOM = require("devtools/client/shared/vendor/react-dom");
+const { Provider } = require("devtools/client/shared/vendor/react-redux");
+
+// Used to create the Redux store
+const createStore = require("devtools/client/shared/redux/create-store")({
+ getTargetClient: () => DebuggerController.client,
+ log: false
+});
+const {
+ makeStateBroadcaster,
+ enhanceStoreWithBroadcaster,
+ combineBroadcastingReducers
+} = require("devtools/client/shared/redux/non-react-subscriber");
+const { bindActionCreators } = require("devtools/client/shared/vendor/redux");
+const reducers = require("./content/reducers/index");
+const { onReducerEvents } = require("./content/utils");
+
+const waitUntilService = require("devtools/client/shared/redux/middleware/wait-service");
+var services = {
+ WAIT_UNTIL: waitUntilService.NAME
+};
+
+var Services = require("Services");
+var {TargetFactory} = require("devtools/client/framework/target");
+var {Toolbox} = require("devtools/client/framework/toolbox");
+var DevToolsUtils = require("devtools/shared/DevToolsUtils");
+var promise = require("devtools/shared/deprecated-sync-thenables");
+var Editor = require("devtools/client/sourceeditor/editor");
+var DebuggerEditor = require("devtools/client/sourceeditor/debugger");
+var Tooltip = require("devtools/client/shared/widgets/tooltip/Tooltip");
+var FastListWidget = require("devtools/client/shared/widgets/FastListWidget");
+var {LocalizationHelper, ELLIPSIS} = require("devtools/shared/l10n");
+var {PrefsHelper} = require("devtools/client/shared/prefs");
+var {Task} = require("devtools/shared/task");
+
+XPCOMUtils.defineConstant(this, "EVENTS", EVENTS);
+
+XPCOMUtils.defineLazyModuleGetter(this, "Parser",
+ "resource://devtools/shared/Parser.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "ShortcutUtils",
+ "resource://gre/modules/ShortcutUtils.jsm");
+
+XPCOMUtils.defineLazyServiceGetter(this, "clipboardHelper",
+ "@mozilla.org/widget/clipboardhelper;1", "nsIClipboardHelper");
+
+Object.defineProperty(this, "NetworkHelper", {
+ get: function () {
+ return require("devtools/shared/webconsole/network-helper");
+ },
+ configurable: true,
+ enumerable: true
+});
+
+/**
+ * Localization convenience methods.
+ */
+var L10N = new LocalizationHelper(DBG_STRINGS_URI);
+
+/**
+ * Object defining the debugger controller components.
+ */
+var DebuggerController = {
+ /**
+ * Initializes the debugger controller.
+ */
+ initialize: function () {
+ dumpn("Initializing the DebuggerController");
+
+ this.startupDebugger = this.startupDebugger.bind(this);
+ this.shutdownDebugger = this.shutdownDebugger.bind(this);
+ this._onNavigate = this._onNavigate.bind(this);
+ this._onWillNavigate = this._onWillNavigate.bind(this);
+ this._onTabDetached = this._onTabDetached.bind(this);
+
+ const broadcaster = makeStateBroadcaster(() => !!this.activeThread);
+ const reducer = combineBroadcastingReducers(
+ reducers,
+ broadcaster.emitChange
+ );
+ // TODO: Bug 1228867, clean this up and probably abstract it out
+ // better.
+ //
+ // We only want to process async event that are appropriate for
+ // this page. The devtools are open across page reloads, so async
+ // requests from the last page might bleed through if reloading
+ // fast enough. We check to make sure the async action is part of
+ // a current request, and ignore it if not.
+ let store = createStore((state, action) => {
+ if (action.seqId &&
+ (action.status === "done" || action.status === "error") &&
+ state && state.asyncRequests.indexOf(action.seqId) === -1) {
+ return state;
+ }
+ return reducer(state, action);
+ });
+ store = enhanceStoreWithBroadcaster(store, broadcaster);
+
+ // This controller right now acts as the store that's globally
+ // available, so just copy the Redux API onto it.
+ Object.keys(store).forEach(name => {
+ this[name] = store[name];
+ });
+ },
+
+ /**
+ * Initializes the view.
+ *
+ * @return object
+ * A promise that is resolved when the debugger finishes startup.
+ */
+ startupDebugger: Task.async(function* () {
+ if (this._startup) {
+ return;
+ }
+
+ yield DebuggerView.initialize(this._target.isWorkerTarget);
+ this._startup = true;
+ }),
+
+ /**
+ * Destroys the view and disconnects the debugger client from the server.
+ *
+ * @return object
+ * A promise that is resolved when the debugger finishes shutdown.
+ */
+ shutdownDebugger: Task.async(function* () {
+ if (this._shutdown) {
+ return;
+ }
+
+ DebuggerView.destroy();
+ this.StackFrames.disconnect();
+ this.ThreadState.disconnect();
+ if (this._target.isTabActor) {
+ this.Workers.disconnect();
+ }
+
+ this.disconnect();
+
+ this._shutdown = true;
+ }),
+
+ /**
+ * Initiates remote debugging based on the current target, wiring event
+ * handlers as necessary.
+ *
+ * @return object
+ * A promise that is resolved when the debugger finishes connecting.
+ */
+ connect: Task.async(function* () {
+ let target = this._target;
+
+ let { client } = target;
+ target.on("close", this._onTabDetached);
+ target.on("navigate", this._onNavigate);
+ target.on("will-navigate", this._onWillNavigate);
+ this.client = client;
+ this.activeThread = this._toolbox.threadClient;
+
+ // Disable asm.js so that we can set breakpoints and other things
+ // on asm.js scripts
+ yield this.reconfigureThread({ observeAsmJS: true });
+ yield this.connectThread();
+
+ // We need to call this to sync the state of the resume
+ // button in the toolbar with the state of the thread.
+ this.ThreadState._update();
+
+ this._hideUnsupportedFeatures();
+ }),
+
+ connectThread: function () {
+ const { newSource, fetchEventListeners } = bindActionCreators(actions, this.dispatch);
+
+ // TODO: bug 806775, update the globals list using aPacket.hostAnnotations
+ // from bug 801084.
+ // this.client.addListener("newGlobal", ...);
+
+ this.activeThread.addListener("newSource", (event, packet) => {
+ newSource(packet.source);
+
+ // Make sure the events listeners are up to date.
+ if (DebuggerView.instrumentsPaneTab == "events-tab") {
+ fetchEventListeners();
+ }
+ });
+
+ if (this._target.isTabActor) {
+ this.Workers.connect();
+ }
+ this.ThreadState.connect();
+ this.StackFrames.connect();
+
+ // Load all of the sources. Note that the server will actually
+ // emit individual `newSource` notifications, which trigger
+ // separate actions, so this won't do anything other than force
+ // the server to traverse sources.
+ this.dispatch(actions.loadSources()).then(() => {
+ // If the engine is already paused, update the UI to represent the
+ // paused state
+ if (this.activeThread) {
+ const pausedPacket = this.activeThread.getLastPausePacket();
+ DebuggerView.Toolbar.toggleResumeButtonState(
+ this.activeThread.state,
+ !!pausedPacket
+ );
+ if (pausedPacket) {
+ this.StackFrames._onPaused("paused", pausedPacket);
+ }
+ }
+ });
+ },
+
+ /**
+ * Disconnects the debugger client and removes event handlers as necessary.
+ */
+ disconnect: function () {
+ // Return early if the client didn't even have a chance to instantiate.
+ if (!this.client) {
+ return;
+ }
+
+ this.client.removeListener("newGlobal");
+ this.activeThread.removeListener("newSource");
+ this.activeThread.removeListener("blackboxchange");
+
+ this._connected = false;
+ this.client = null;
+ this.activeThread = null;
+ },
+
+ _hideUnsupportedFeatures: function () {
+ if (this.client.mainRoot.traits.noPrettyPrinting) {
+ DebuggerView.Sources.hidePrettyPrinting();
+ }
+
+ if (this.client.mainRoot.traits.noBlackBoxing) {
+ DebuggerView.Sources.hideBlackBoxing();
+ }
+ },
+
+ _onWillNavigate: function (opts = {}) {
+ // Reset UI.
+ DebuggerView.handleTabNavigation();
+ if (!opts.noUnload) {
+ this.dispatch(actions.unload());
+ }
+
+ // Discard all the cached parsed sources *before* the target
+ // starts navigating. Sources may be fetched during navigation, in
+ // which case we don't want to hang on to the old source contents.
+ DebuggerController.Parser.clearCache();
+ SourceUtils.clearCache();
+
+ // Prevent performing any actions that were scheduled before
+ // navigation.
+ clearNamedTimeout("new-source");
+ clearNamedTimeout("event-breakpoints-update");
+ clearNamedTimeout("event-listeners-fetch");
+ },
+
+ _onNavigate: function () {
+ this.ThreadState.handleTabNavigation();
+ this.StackFrames.handleTabNavigation();
+ },
+
+ /**
+ * Called when the debugged tab is closed.
+ */
+ _onTabDetached: function () {
+ this.shutdownDebugger();
+ },
+
+ /**
+ * Warn if resuming execution produced a wrongOrder error.
+ */
+ _ensureResumptionOrder: function (aResponse) {
+ if (aResponse.error == "wrongOrder") {
+ DebuggerView.Toolbar.showResumeWarning(aResponse.lastPausedUrl);
+ }
+ },
+
+ /**
+ * Detach and reattach to the thread actor with useSourceMaps true, blow
+ * away old sources and get them again.
+ */
+ reconfigureThread: function (opts) {
+ const deferred = promise.defer();
+ this.activeThread.reconfigure(
+ opts,
+ aResponse => {
+ if (aResponse.error) {
+ deferred.reject(aResponse.error);
+ return;
+ }
+
+ if (("useSourceMaps" in opts) || ("autoBlackBox" in opts)) {
+ // Reset the view and fetch all the sources again.
+ DebuggerView.handleTabNavigation();
+ this.dispatch(actions.unload());
+ this.dispatch(actions.loadSources());
+
+ // Update the stack frame list.
+ if (this.activeThread.paused) {
+ this.activeThread._clearFrames();
+ this.activeThread.fillFrames(CALL_STACK_PAGE_SIZE);
+ }
+ }
+
+ deferred.resolve();
+ }
+ );
+ return deferred.promise;
+ },
+
+ waitForSourcesLoaded: function () {
+ const deferred = promise.defer();
+ this.dispatch({
+ type: services.WAIT_UNTIL,
+ predicate: action => (action.type === constants.LOAD_SOURCES &&
+ action.status === "done"),
+ run: deferred.resolve
+ });
+ return deferred.promise;
+ },
+
+ waitForSourceShown: function (name) {
+ const deferred = promise.defer();
+ window.on(EVENTS.SOURCE_SHOWN, function onShown(_, source) {
+ if (source.url.includes(name)) {
+ window.off(EVENTS.SOURCE_SHOWN, onShown);
+ deferred.resolve();
+ }
+ });
+ return deferred.promise;
+ },
+
+ _startup: false,
+ _shutdown: false,
+ _connected: false,
+ client: null,
+ activeThread: null
+};
+
+function Workers() {
+ this._workerForms = Object.create(null);
+ this._onWorkerListChanged = this._onWorkerListChanged.bind(this);
+ this._onWorkerSelect = this._onWorkerSelect.bind(this);
+}
+
+Workers.prototype = {
+ get _tabClient() {
+ return DebuggerController._target.activeTab;
+ },
+
+ connect: function () {
+ if (!Prefs.workersEnabled) {
+ return;
+ }
+
+ this._updateWorkerList();
+ this._tabClient.addListener("workerListChanged", this._onWorkerListChanged);
+ },
+
+ disconnect: function () {
+ this._tabClient.removeListener("workerListChanged", this._onWorkerListChanged);
+ },
+
+ _updateWorkerList: function () {
+ if (!this._tabClient.listWorkers) {
+ return;
+ }
+
+ this._tabClient.listWorkers((response) => {
+ let workerForms = Object.create(null);
+ for (let worker of response.workers) {
+ workerForms[worker.actor] = worker;
+ }
+
+ for (let workerActor in this._workerForms) {
+ if (!(workerActor in workerForms)) {
+ DebuggerView.Workers.removeWorker(this._workerForms[workerActor]);
+ delete this._workerForms[workerActor];
+ }
+ }
+
+ for (let workerActor in workerForms) {
+ if (!(workerActor in this._workerForms)) {
+ let workerForm = workerForms[workerActor];
+ this._workerForms[workerActor] = workerForm;
+ DebuggerView.Workers.addWorker(workerForm);
+ }
+ }
+ });
+ },
+
+ _onWorkerListChanged: function () {
+ this._updateWorkerList();
+ },
+
+ _onWorkerSelect: function (workerForm) {
+ DebuggerController.client.attachWorker(workerForm.actor, (response, workerClient) => {
+ let toolbox = gDevTools.showToolbox(TargetFactory.forWorker(workerClient),
+ "jsdebugger", Toolbox.HostType.WINDOW);
+ window.emit(EVENTS.WORKER_SELECTED, toolbox);
+ });
+ }
+};
+
+/**
+ * ThreadState keeps the UI up to date with the state of the
+ * thread (paused/attached/etc.).
+ */
+function ThreadState() {
+ this._update = this._update.bind(this);
+ this.interruptedByResumeButton = false;
+}
+
+ThreadState.prototype = {
+ get activeThread() {
+ return DebuggerController.activeThread;
+ },
+
+ /**
+ * Connect to the current thread client.
+ */
+ connect: function () {
+ dumpn("ThreadState is connecting...");
+ this.activeThread.addListener("paused", this._update);
+ this.activeThread.addListener("resumed", this._update);
+ },
+
+ /**
+ * Disconnect from the client.
+ */
+ disconnect: function () {
+ if (!this.activeThread) {
+ return;
+ }
+ dumpn("ThreadState is disconnecting...");
+ this.activeThread.removeListener("paused", this._update);
+ this.activeThread.removeListener("resumed", this._update);
+ },
+
+ /**
+ * Handles any initialization on a tab navigation event issued by the client.
+ */
+ handleTabNavigation: function () {
+ if (!this.activeThread) {
+ return;
+ }
+ dumpn("Handling tab navigation in the ThreadState");
+ this._update();
+ },
+
+ /**
+ * Update the UI after a thread state change.
+ */
+ _update: function (aEvent, aPacket) {
+ if (aEvent == "paused") {
+ if (aPacket.why.type == "interrupted" &&
+ this.interruptedByResumeButton) {
+ // Interrupt requests suppressed by default, but if this is an
+ // explicit interrupt by the pause button we want to emit it.
+ gTarget.emit("thread-paused", aPacket);
+ } else if (aPacket.why.type == "breakpointConditionThrown" && aPacket.why.message) {
+ let where = aPacket.frame.where;
+ let aLocation = {
+ line: where.line,
+ column: where.column,
+ actor: where.source ? where.source.actor : null
+ };
+ DebuggerView.Sources.showBreakpointConditionThrownMessage(
+ aLocation,
+ aPacket.why.message
+ );
+ }
+ }
+
+ this.interruptedByResumeButton = false;
+ DebuggerView.Toolbar.toggleResumeButtonState(
+ this.activeThread.state,
+ aPacket ? aPacket.frame : false
+ );
+ }
+};
+
+/**
+ * Keeps the stack frame list up-to-date, using the thread client's
+ * stack frame cache.
+ */
+function StackFrames() {
+ this._onPaused = this._onPaused.bind(this);
+ this._onResumed = this._onResumed.bind(this);
+ this._onFrames = this._onFrames.bind(this);
+ this._onFramesCleared = this._onFramesCleared.bind(this);
+ this._onBlackBoxChange = this._onBlackBoxChange.bind(this);
+ this._onPrettyPrintChange = this._onPrettyPrintChange.bind(this);
+ this._afterFramesCleared = this._afterFramesCleared.bind(this);
+ this.evaluate = this.evaluate.bind(this);
+}
+
+StackFrames.prototype = {
+ get activeThread() {
+ return DebuggerController.activeThread;
+ },
+
+ currentFrameDepth: -1,
+ _currentFrameDescription: FRAME_TYPE.NORMAL,
+ _syncedWatchExpressions: null,
+ _currentWatchExpressions: null,
+ _currentBreakpointLocation: null,
+ _currentEvaluation: null,
+ _currentException: null,
+ _currentReturnedValue: null,
+
+ /**
+ * Connect to the current thread client.
+ */
+ connect: function () {
+ dumpn("StackFrames is connecting...");
+ this.activeThread.addListener("paused", this._onPaused);
+ this.activeThread.addListener("resumed", this._onResumed);
+ this.activeThread.addListener("framesadded", this._onFrames);
+ this.activeThread.addListener("framescleared", this._onFramesCleared);
+ this.activeThread.addListener("blackboxchange", this._onBlackBoxChange);
+ this.activeThread.addListener("prettyprintchange", this._onPrettyPrintChange);
+ this.handleTabNavigation();
+ },
+
+ /**
+ * Disconnect from the client.
+ */
+ disconnect: function () {
+ if (!this.activeThread) {
+ return;
+ }
+ dumpn("StackFrames is disconnecting...");
+ this.activeThread.removeListener("paused", this._onPaused);
+ this.activeThread.removeListener("resumed", this._onResumed);
+ this.activeThread.removeListener("framesadded", this._onFrames);
+ this.activeThread.removeListener("framescleared", this._onFramesCleared);
+ this.activeThread.removeListener("blackboxchange", this._onBlackBoxChange);
+ this.activeThread.removeListener("prettyprintchange", this._onPrettyPrintChange);
+ clearNamedTimeout("frames-cleared");
+ },
+
+ /**
+ * Handles any initialization on a tab navigation event issued by the client.
+ */
+ handleTabNavigation: function () {
+ dumpn("Handling tab navigation in the StackFrames");
+ // Nothing to do here yet.
+ },
+
+ /**
+ * Handler for the thread client's paused notification.
+ *
+ * @param string aEvent
+ * The name of the notification ("paused" in this case).
+ * @param object aPacket
+ * The response packet.
+ */
+ _onPaused: function (aEvent, aPacket) {
+ switch (aPacket.why.type) {
+ // If paused by a breakpoint, store the breakpoint location.
+ case "breakpoint":
+ this._currentBreakpointLocation = aPacket.frame.where;
+ break;
+ case "breakpointConditionThrown":
+ this._currentBreakpointLocation = aPacket.frame.where;
+ this._conditionThrowMessage = aPacket.why.message;
+ break;
+ // If paused by a client evaluation, store the evaluated value.
+ case "clientEvaluated":
+ this._currentEvaluation = aPacket.why.frameFinished;
+ break;
+ // If paused by an exception, store the exception value.
+ case "exception":
+ this._currentException = aPacket.why.exception;
+ break;
+ // If paused while stepping out of a frame, store the returned value or
+ // thrown exception.
+ case "resumeLimit":
+ if (!aPacket.why.frameFinished) {
+ break;
+ } else if (aPacket.why.frameFinished.throw) {
+ this._currentException = aPacket.why.frameFinished.throw;
+ } else if (aPacket.why.frameFinished.return) {
+ this._currentReturnedValue = aPacket.why.frameFinished.return;
+ }
+ break;
+ // If paused by an explicit interrupt, which are generated by the slow
+ // script dialog and internal events such as setting breakpoints, ignore
+ // the event to avoid UI flicker.
+ case "interrupted":
+ if (!aPacket.why.onNext) {
+ return;
+ }
+ break;
+ }
+
+ this.activeThread.fillFrames(CALL_STACK_PAGE_SIZE);
+ // Focus the editor, but don't steal focus from the split console.
+ if (!DebuggerController._toolbox.isSplitConsoleFocused()) {
+ DebuggerView.editor.focus();
+ }
+ },
+
+ /**
+ * Handler for the thread client's resumed notification.
+ */
+ _onResumed: function () {
+ // Prepare the watch expression evaluation string for the next pause.
+ if (this._currentFrameDescription != FRAME_TYPE.WATCH_EXPRESSIONS_EVAL) {
+ this._currentWatchExpressions = this._syncedWatchExpressions;
+ }
+ },
+
+ /**
+ * Handler for the thread client's framesadded notification.
+ */
+ _onFrames: Task.async(function* () {
+ // Ignore useless notifications.
+ if (!this.activeThread || !this.activeThread.cachedFrames.length) {
+ return;
+ }
+ if (this._currentFrameDescription != FRAME_TYPE.NORMAL &&
+ this._currentFrameDescription != FRAME_TYPE.PUBLIC_CLIENT_EVAL) {
+ return;
+ }
+
+ // TODO: remove all of this deprecated code: Bug 990137.
+ yield this._handleConditionalBreakpoint();
+
+ // TODO: handle all of this server-side: Bug 832470, comment 14.
+ yield this._handleWatchExpressions();
+
+ // Make sure the debugger view panes are visible, then refill the frames.
+ DebuggerView.showInstrumentsPane();
+ this._refillFrames();
+
+ // No additional processing is necessary for this stack frame.
+ if (this._currentFrameDescription != FRAME_TYPE.NORMAL) {
+ this._currentFrameDescription = FRAME_TYPE.NORMAL;
+ }
+ }),
+
+ /**
+ * Fill the StackFrames view with the frames we have in the cache, compressing
+ * frames which have black boxed sources into single frames.
+ */
+ _refillFrames: function () {
+ // Make sure all the previous stackframes are removed before re-adding them.
+ DebuggerView.StackFrames.empty();
+
+ for (let frame of this.activeThread.cachedFrames) {
+ let { depth, source, where: { line, column } } = frame;
+
+ let isBlackBoxed = source ? this.activeThread.source(source).isBlackBoxed : false;
+ DebuggerView.StackFrames.addFrame(frame, line, column, depth, isBlackBoxed);
+ }
+
+ DebuggerView.StackFrames.selectedDepth = Math.max(this.currentFrameDepth, 0);
+ DebuggerView.StackFrames.dirty = this.activeThread.moreFrames;
+
+ DebuggerView.StackFrames.addCopyContextMenu();
+
+ window.emit(EVENTS.AFTER_FRAMES_REFILLED);
+ },
+
+ /**
+ * Handler for the thread client's framescleared notification.
+ */
+ _onFramesCleared: function () {
+ switch (this._currentFrameDescription) {
+ case FRAME_TYPE.NORMAL:
+ this._currentEvaluation = null;
+ this._currentException = null;
+ this._currentReturnedValue = null;
+ break;
+ case FRAME_TYPE.CONDITIONAL_BREAKPOINT_EVAL:
+ this._currentBreakpointLocation = null;
+ break;
+ case FRAME_TYPE.WATCH_EXPRESSIONS_EVAL:
+ this._currentWatchExpressions = null;
+ break;
+ }
+
+ // After each frame step (in, over, out), framescleared is fired, which
+ // forces the UI to be emptied and rebuilt on framesadded. Most of the times
+ // this is not necessary, and will result in a brief redraw flicker.
+ // To avoid it, invalidate the UI only after a short time if necessary.
+ setNamedTimeout("frames-cleared", FRAME_STEP_CLEAR_DELAY, this._afterFramesCleared);
+ },
+
+ /**
+ * Handler for the debugger's blackboxchange notification.
+ */
+ _onBlackBoxChange: function () {
+ if (this.activeThread.state == "paused") {
+ // Hack to avoid selecting the topmost frame after blackboxing a source.
+ this.currentFrameDepth = NaN;
+ this._refillFrames();
+ }
+ },
+
+ /**
+ * Handler for the debugger's prettyprintchange notification.
+ */
+ _onPrettyPrintChange: function () {
+ if (this.activeThread.state != "paused") {
+ return;
+ }
+ // Makes sure the selected source remains selected
+ // after the fillFrames is called.
+ const source = DebuggerView.Sources.selectedValue;
+
+ this.activeThread.fillFrames(CALL_STACK_PAGE_SIZE, () => {
+ DebuggerView.Sources.selectedValue = source;
+ });
+ },
+
+ /**
+ * Called soon after the thread client's framescleared notification.
+ */
+ _afterFramesCleared: function () {
+ // Ignore useless notifications.
+ if (this.activeThread.cachedFrames.length) {
+ return;
+ }
+ DebuggerView.editor.clearDebugLocation();
+ DebuggerView.StackFrames.empty();
+ DebuggerView.Sources.unhighlightBreakpoint();
+ DebuggerView.WatchExpressions.toggleContents(true);
+ DebuggerView.Variables.empty(0);
+
+ window.emit(EVENTS.AFTER_FRAMES_CLEARED);
+ },
+
+ /**
+ * Marks the stack frame at the specified depth as selected and updates the
+ * properties view with the stack frame's data.
+ *
+ * @param number aDepth
+ * The depth of the frame in the stack.
+ */
+ selectFrame: function (aDepth) {
+ // Make sure the frame at the specified depth exists first.
+ let frame = this.activeThread.cachedFrames[this.currentFrameDepth = aDepth];
+ if (!frame) {
+ return;
+ }
+
+ // Check if the frame does not represent the evaluation of debuggee code.
+ let { environment, where, source } = frame;
+ if (!environment) {
+ return;
+ }
+
+ // Don't change the editor's location if the execution was paused by a
+ // public client evaluation. This is useful for adding overlays on
+ // top of the editor, like a variable inspection popup.
+ let isClientEval = this._currentFrameDescription == FRAME_TYPE.PUBLIC_CLIENT_EVAL;
+ let isPopupShown = DebuggerView.VariableBubble.contentsShown();
+ if (!isClientEval && !isPopupShown) {
+ // Move the editor's caret to the proper url and line.
+ DebuggerView.setEditorLocation(source.actor, where.line);
+ } else {
+ // Highlight the line where the execution is paused in the editor.
+ DebuggerView.setEditorLocation(source.actor, where.line, { noCaret: true });
+ }
+
+ // Highlight the breakpoint at the line and column if it exists.
+ DebuggerView.Sources.highlightBreakpointAtCursor();
+
+ // Don't display the watch expressions textbox inputs in the pane.
+ DebuggerView.WatchExpressions.toggleContents(false);
+
+ // Start recording any added variables or properties in any scope and
+ // clear existing scopes to create each one dynamically.
+ DebuggerView.Variables.empty();
+
+ // If watch expressions evaluation results are available, create a scope
+ // to contain all the values.
+ if (this._syncedWatchExpressions && aDepth == 0) {
+ let label = L10N.getStr("watchExpressionsScopeLabel");
+ let scope = DebuggerView.Variables.addScope(label,
+ "variables-view-watch-expressions");
+
+ // Customize the scope for holding watch expressions evaluations.
+ scope.descriptorTooltip = false;
+ scope.contextMenuId = "debuggerWatchExpressionsContextMenu";
+ scope.separatorStr = L10N.getStr("watchExpressionsSeparatorLabel2");
+ scope.switch = DebuggerView.WatchExpressions.switchExpression;
+ scope.delete = DebuggerView.WatchExpressions.deleteExpression;
+
+ // The evaluation hasn't thrown, so fetch and add the returned results.
+ this._fetchWatchExpressions(scope, this._currentEvaluation.return);
+
+ // The watch expressions scope is always automatically expanded.
+ scope.expand();
+ }
+
+ do {
+ // Create a scope to contain all the inspected variables in the
+ // current environment.
+ let label = StackFrameUtils.getScopeLabel(environment);
+ let scope = DebuggerView.Variables.addScope(label);
+ let innermost = environment == frame.environment;
+
+ // Handle special additions to the innermost scope.
+ if (innermost) {
+ this._insertScopeFrameReferences(scope, frame);
+ }
+
+ // Handle the expansion of the scope, lazily populating it with the
+ // variables in the current environment.
+ DebuggerView.Variables.controller.addExpander(scope, environment);
+
+ // The innermost scope is always automatically expanded, because it
+ // contains the variables in the current stack frame which are likely to
+ // be inspected. The previously expanded scopes are also reexpanded here.
+ if (innermost || DebuggerView.Variables.wasExpanded(scope)) {
+ scope.expand();
+ }
+ } while ((environment = environment.parent));
+
+ // Signal that scope environments have been shown.
+ window.emit(EVENTS.FETCHED_SCOPES);
+ },
+
+ /**
+ * Loads more stack frames from the debugger server cache.
+ */
+ addMoreFrames: function () {
+ this.activeThread.fillFrames(
+ this.activeThread.cachedFrames.length + CALL_STACK_PAGE_SIZE);
+ },
+
+ /**
+ * Evaluate an expression in the context of the selected frame.
+ *
+ * @param string aExpression
+ * The expression to evaluate.
+ * @param object aOptions [optional]
+ * Additional options for this client evaluation:
+ * - depth: the frame depth used for evaluation, 0 being the topmost.
+ * - meta: some meta-description for what this evaluation represents.
+ * @return object
+ * A promise that is resolved when the evaluation finishes,
+ * or rejected if there was no stack frame available or some
+ * other error occurred.
+ */
+ evaluate: function (aExpression, aOptions = {}) {
+ let depth = "depth" in aOptions ? aOptions.depth : this.currentFrameDepth;
+ let frame = this.activeThread.cachedFrames[depth];
+ if (frame == null) {
+ return promise.reject(new Error("No stack frame available."));
+ }
+
+ let deferred = promise.defer();
+
+ this.activeThread.addOneTimeListener("paused", (aEvent, aPacket) => {
+ let { type, frameFinished } = aPacket.why;
+ if (type == "clientEvaluated") {
+ deferred.resolve(frameFinished);
+ } else {
+ deferred.reject(new Error("Active thread paused unexpectedly."));
+ }
+ });
+
+ let meta = "meta" in aOptions ? aOptions.meta : FRAME_TYPE.PUBLIC_CLIENT_EVAL;
+ this._currentFrameDescription = meta;
+ this.activeThread.eval(frame.actor, aExpression);
+
+ return deferred.promise;
+ },
+
+ /**
+ * Add nodes for special frame references in the innermost scope.
+ *
+ * @param Scope aScope
+ * The scope where the references will be placed into.
+ * @param object aFrame
+ * The frame to get some references from.
+ */
+ _insertScopeFrameReferences: function (aScope, aFrame) {
+ // Add any thrown exception.
+ if (this._currentException) {
+ let excRef = aScope.addItem("<exception>", { value: this._currentException },
+ { internalItem: true });
+ DebuggerView.Variables.controller.addExpander(excRef, this._currentException);
+ }
+ // Add any returned value.
+ if (this._currentReturnedValue) {
+ let retRef = aScope.addItem("<return>",
+ { value: this._currentReturnedValue },
+ { internalItem: true });
+ DebuggerView.Variables.controller.addExpander(retRef, this._currentReturnedValue);
+ }
+ // Add "this".
+ if (aFrame.this) {
+ let thisRef = aScope.addItem("this", { value: aFrame.this });
+ DebuggerView.Variables.controller.addExpander(thisRef, aFrame.this);
+ }
+ },
+
+ /**
+ * Handles conditional breakpoints when the debugger pauses and the
+ * stackframes are received.
+ *
+ * We moved conditional breakpoint handling to the server, but
+ * need to support it in the client for a while until most of the
+ * server code in production is updated with it.
+ * TODO: remove all of this deprecated code: Bug 990137.
+ *
+ * @return object
+ * A promise that is resolved after a potential breakpoint's
+ * conditional expression is evaluated. If there's no breakpoint
+ * where the debugger is paused, the promise is resolved immediately.
+ */
+ _handleConditionalBreakpoint: Task.async(function* () {
+ if (gClient.mainRoot.traits.conditionalBreakpoints) {
+ return;
+ }
+ let breakLocation = this._currentBreakpointLocation;
+ if (!breakLocation) {
+ return;
+ }
+
+ let bp = queries.getBreakpoint(DebuggerController.getState(), {
+ actor: breakLocation.source.actor,
+ line: breakLocation.line
+ });
+ let conditionalExpression = bp.condition;
+ if (!conditionalExpression) {
+ return;
+ }
+
+ // Evaluating the current breakpoint's conditional expression will
+ // cause the stack frames to be cleared and active thread to pause,
+ // sending a 'clientEvaluated' packed and adding the frames again.
+ let evaluationOptions = { depth: 0, meta: FRAME_TYPE.CONDITIONAL_BREAKPOINT_EVAL };
+ yield this.evaluate(conditionalExpression, evaluationOptions);
+ this._currentFrameDescription = FRAME_TYPE.NORMAL;
+
+ // If the breakpoint's conditional expression evaluation is falsy
+ // and there is no exception, automatically resume execution.
+ if (!this._currentEvaluation.throw &&
+ VariablesView.isFalsy({ value: this._currentEvaluation.return })) {
+ this.activeThread.resume(DebuggerController._ensureResumptionOrder);
+ }
+ }),
+
+ /**
+ * Handles watch expressions when the debugger pauses and the stackframes
+ * are received.
+ *
+ * @return object
+ * A promise that is resolved after the potential watch expressions
+ * are evaluated. If there are no watch expressions where the debugger
+ * is paused, the promise is resolved immediately.
+ */
+ _handleWatchExpressions: Task.async(function* () {
+ // Ignore useless notifications.
+ if (!this.activeThread || !this.activeThread.cachedFrames.length) {
+ return;
+ }
+
+ let watchExpressions = this._currentWatchExpressions;
+ if (!watchExpressions) {
+ return;
+ }
+
+ // Evaluation causes the stack frames to be cleared and active thread to
+ // pause, sending a 'clientEvaluated' packet and adding the frames again.
+ let evaluationOptions = { depth: 0, meta: FRAME_TYPE.WATCH_EXPRESSIONS_EVAL };
+ yield this.evaluate(watchExpressions, evaluationOptions);
+ this._currentFrameDescription = FRAME_TYPE.NORMAL;
+
+ // If an error was thrown during the evaluation of the watch expressions
+ // or the evaluation was terminated from the slow script dialog, then at
+ // least one expression evaluation could not be performed. So remove the
+ // most recent watch expression and try again.
+ if (this._currentEvaluation.throw || this._currentEvaluation.terminated) {
+ DebuggerView.WatchExpressions.removeAt(0);
+ yield DebuggerController.StackFrames.syncWatchExpressions();
+ }
+ }),
+
+ /**
+ * Adds the watch expressions evaluation results to a scope in the view.
+ *
+ * @param Scope aScope
+ * The scope where the watch expressions will be placed into.
+ * @param object aExp
+ * The grip of the evaluation results.
+ */
+ _fetchWatchExpressions: function (aScope, aExp) {
+ // Fetch the expressions only once.
+ if (aScope._fetched) {
+ return;
+ }
+ aScope._fetched = true;
+
+ // Add nodes for every watch expression in scope.
+ this.activeThread.pauseGrip(aExp).getPrototypeAndProperties(aResponse => {
+ let ownProperties = aResponse.ownProperties;
+ let totalExpressions = DebuggerView.WatchExpressions.itemCount;
+
+ for (let i = 0; i < totalExpressions; i++) {
+ let name = DebuggerView.WatchExpressions.getString(i);
+ let expVal = ownProperties[i].value;
+ let expRef = aScope.addItem(name, ownProperties[i]);
+ DebuggerView.Variables.controller.addExpander(expRef, expVal);
+
+ // Revert some of the custom watch expressions scope presentation flags,
+ // so that they don't propagate to child items.
+ expRef.switch = null;
+ expRef.delete = null;
+ expRef.descriptorTooltip = true;
+ expRef.separatorStr = L10N.getStr("variablesSeparatorLabel");
+ }
+
+ // Signal that watch expressions have been fetched.
+ window.emit(EVENTS.FETCHED_WATCH_EXPRESSIONS);
+ });
+ },
+
+ /**
+ * Updates a list of watch expressions to evaluate on each pause.
+ * TODO: handle all of this server-side: Bug 832470, comment 14.
+ */
+ syncWatchExpressions: function () {
+ let list = DebuggerView.WatchExpressions.getAllStrings();
+
+ // Sanity check all watch expressions before syncing them. To avoid
+ // having the whole watch expressions array throw because of a single
+ // faulty expression, simply convert it to a string describing the error.
+ // There's no other information necessary to be offered in such cases.
+ let sanitizedExpressions = list.map(aString => {
+ // Reflect.parse throws when it encounters a syntax error.
+ try {
+ Parser.reflectionAPI.parse(aString);
+ return aString; // Watch expression can be executed safely.
+ } catch (e) {
+ return "\"" + e.name + ": " + e.message + "\""; // Syntax error.
+ }
+ });
+
+ if (!sanitizedExpressions.length) {
+ this._currentWatchExpressions = null;
+ this._syncedWatchExpressions = null;
+ } else {
+ this._syncedWatchExpressions =
+ this._currentWatchExpressions = "[" +
+ sanitizedExpressions.map(aString =>
+ "eval(\"" +
+ "try {" +
+ // Make sure all quotes are escaped in the expression's syntax,
+ // and add a newline after the statement to avoid comments
+ // breaking the code integrity inside the eval block.
+ aString.replace(/\\/g, "\\\\").replace(/"/g, "\\$&") + "\" + " + "'\\n'" + " + \"" +
+ "} catch (e) {" +
+ "e.name + ': ' + e.message;" + // TODO: Bug 812765, 812764.
+ "}" +
+ "\")"
+ ).join(",") +
+ "]";
+ }
+
+ this.currentFrameDepth = -1;
+ return this._onFrames();
+ }
+};
+
+/**
+ * Shortcuts for accessing various debugger preferences.
+ */
+var Prefs = new PrefsHelper("devtools", {
+ workersAndSourcesWidth: ["Int", "debugger.ui.panes-workers-and-sources-width"],
+ instrumentsWidth: ["Int", "debugger.ui.panes-instruments-width"],
+ panesVisibleOnStartup: ["Bool", "debugger.ui.panes-visible-on-startup"],
+ variablesSortingEnabled: ["Bool", "debugger.ui.variables-sorting-enabled"],
+ variablesOnlyEnumVisible: ["Bool", "debugger.ui.variables-only-enum-visible"],
+ variablesSearchboxVisible: ["Bool", "debugger.ui.variables-searchbox-visible"],
+ pauseOnExceptions: ["Bool", "debugger.pause-on-exceptions"],
+ ignoreCaughtExceptions: ["Bool", "debugger.ignore-caught-exceptions"],
+ sourceMapsEnabled: ["Bool", "debugger.source-maps-enabled"],
+ prettyPrintEnabled: ["Bool", "debugger.pretty-print-enabled"],
+ autoPrettyPrint: ["Bool", "debugger.auto-pretty-print"],
+ workersEnabled: ["Bool", "debugger.workers"],
+ editorTabSize: ["Int", "editor.tabsize"],
+ autoBlackBox: ["Bool", "debugger.auto-black-box"],
+});
+
+/**
+ * Convenient way of emitting events from the panel window.
+ */
+EventEmitter.decorate(this);
+
+/**
+ * Preliminary setup for the DebuggerController object.
+ */
+DebuggerController.initialize();
+DebuggerController.Parser = new Parser();
+DebuggerController.Workers = new Workers();
+DebuggerController.ThreadState = new ThreadState();
+DebuggerController.StackFrames = new StackFrames();
+
+/**
+ * Export some properties to the global scope for easier access.
+ */
+Object.defineProperties(window, {
+ "gTarget": {
+ get: function () {
+ return DebuggerController._target;
+ },
+ configurable: true
+ },
+ "gHostType": {
+ get: function () {
+ return DebuggerView._hostType;
+ },
+ configurable: true
+ },
+ "gClient": {
+ get: function () {
+ return DebuggerController.client;
+ },
+ configurable: true
+ },
+ "gThreadClient": {
+ get: function () {
+ return DebuggerController.activeThread;
+ },
+ configurable: true
+ },
+ "gCallStackPageSize": {
+ get: function () {
+ return CALL_STACK_PAGE_SIZE;
+ },
+ configurable: true
+ }
+});
+
+/**
+ * Helper method for debugging.
+ * @param string
+ */
+function dumpn(str) {
+ if (wantLogging) {
+ dump("DBG-FRONTEND: " + str + "\n");
+ }
+}
+
+var wantLogging = Services.prefs.getBoolPref("devtools.debugger.log");
diff --git a/devtools/client/debugger/debugger-view.js b/devtools/client/debugger/debugger-view.js
new file mode 100644
index 000000000..b6a5850ff
--- /dev/null
+++ b/devtools/client/debugger/debugger-view.js
@@ -0,0 +1,982 @@
+/* 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 SOURCE_URL_DEFAULT_MAX_LENGTH = 64; // chars
+const STACK_FRAMES_SOURCE_URL_MAX_LENGTH = 15; // chars
+const STACK_FRAMES_SOURCE_URL_TRIM_SECTION = "center";
+const STACK_FRAMES_SCROLL_DELAY = 100; // ms
+const BREAKPOINT_SMALL_WINDOW_WIDTH = 850; // px
+const RESULTS_PANEL_POPUP_POSITION = "before_end";
+const RESULTS_PANEL_MAX_RESULTS = 10;
+const FILE_SEARCH_ACTION_MAX_DELAY = 300; // ms
+const GLOBAL_SEARCH_EXPAND_MAX_RESULTS = 50;
+const GLOBAL_SEARCH_LINE_MAX_LENGTH = 300; // chars
+const GLOBAL_SEARCH_ACTION_MAX_DELAY = 1500; // ms
+const FUNCTION_SEARCH_ACTION_MAX_DELAY = 400; // ms
+const SEARCH_GLOBAL_FLAG = "!";
+const SEARCH_FUNCTION_FLAG = "@";
+const SEARCH_TOKEN_FLAG = "#";
+const SEARCH_LINE_FLAG = ":";
+const SEARCH_VARIABLE_FLAG = "*";
+const SEARCH_AUTOFILL = [SEARCH_GLOBAL_FLAG, SEARCH_FUNCTION_FLAG, SEARCH_TOKEN_FLAG];
+const TOOLBAR_ORDER_POPUP_POSITION = "topcenter bottomleft";
+const RESIZE_REFRESH_RATE = 50; // ms
+
+const EventListenersView = require("./content/views/event-listeners-view");
+const SourcesView = require("./content/views/sources-view");
+var actions = Object.assign(
+ {},
+ require("./content/globalActions"),
+ require("./content/actions/breakpoints"),
+ require("./content/actions/sources"),
+ require("./content/actions/event-listeners")
+);
+var queries = require("./content/queries");
+var constants = require("./content/constants");
+
+/**
+ * Object defining the debugger view components.
+ */
+var DebuggerView = {
+
+ /**
+ * This is attached so tests can change it without needing to load an
+ * actual large file in automation
+ */
+ LARGE_FILE_SIZE: 1048576, // 1 MB in bytes
+
+ /**
+ * Initializes the debugger view.
+ *
+ * @return object
+ * A promise that is resolved when the view finishes initializing.
+ */
+ initialize: function (isWorker) {
+ if (this._startup) {
+ return this._startup;
+ }
+ const deferred = promise.defer();
+ this._startup = deferred.promise;
+
+ this._initializePanes();
+ this._initializeEditor(deferred.resolve);
+ this.Toolbar.initialize();
+ this.Options.initialize();
+ this.Filtering.initialize();
+ this.StackFrames.initialize();
+ this.StackFramesClassicList.initialize();
+ this.Workers.initialize();
+ this.Sources.initialize(isWorker);
+ this.VariableBubble.initialize();
+ this.WatchExpressions.initialize();
+ this.EventListeners.initialize();
+ this.GlobalSearch.initialize();
+ this._initializeVariablesView();
+
+ this._editorSource = {};
+ this._editorDocuments = {};
+
+ this.editor.on("cursorActivity", this.Sources._onEditorCursorActivity);
+
+ this.controller = DebuggerController;
+ const getState = this.controller.getState;
+
+ onReducerEvents(this.controller, {
+ "source-text-loaded": this.renderSourceText,
+ "source-selected": this.renderSourceText,
+ "blackboxed": this.renderBlackBoxed,
+ "prettyprinted": this.renderPrettyPrinted,
+ "breakpoint-added": this.addEditorBreakpoint,
+ "breakpoint-enabled": this.addEditorBreakpoint,
+ "breakpoint-disabled": this.removeEditorBreakpoint,
+ "breakpoint-removed": this.removeEditorBreakpoint,
+ "breakpoint-condition-updated": this.renderEditorBreakpointCondition,
+ "breakpoint-moved": ({ breakpoint, prevLocation }) => {
+ const selectedSource = queries.getSelectedSource(getState());
+ const { location } = breakpoint;
+
+ if (selectedSource &&
+ selectedSource.actor === location.actor) {
+ this.editor.moveBreakpoint(prevLocation.line - 1,
+ location.line - 1);
+ }
+ }
+ }, this);
+
+ return deferred.promise;
+ },
+
+ /**
+ * Destroys the debugger view.
+ *
+ * @return object
+ * A promise that is resolved when the view finishes destroying.
+ */
+ destroy: function () {
+ if (this._hasShutdown) {
+ return;
+ }
+ this._hasShutdown = true;
+
+ window.removeEventListener("resize", this._onResize, false);
+ this.editor.off("cursorActivity", this.Sources._onEditorCursorActivity);
+
+ this.Toolbar.destroy();
+ this.Options.destroy();
+ this.Filtering.destroy();
+ this.StackFrames.destroy();
+ this.StackFramesClassicList.destroy();
+ this.Sources.destroy();
+ this.VariableBubble.destroy();
+ this.WatchExpressions.destroy();
+ this.EventListeners.destroy();
+ this.GlobalSearch.destroy();
+ this._destroyPanes();
+
+ this.editor.destroy();
+ this.editor = null;
+
+ this.controller.dispatch(actions.removeAllBreakpoints());
+ },
+
+ /**
+ * Initializes the UI for all the displayed panes.
+ */
+ _initializePanes: function () {
+ dumpn("Initializing the DebuggerView panes");
+
+ this._body = document.getElementById("body");
+ this._editorDeck = document.getElementById("editor-deck");
+ this._workersAndSourcesPane = document.getElementById("workers-and-sources-pane");
+ this._instrumentsPane = document.getElementById("instruments-pane");
+ this._instrumentsPaneToggleButton = document.getElementById("instruments-pane-toggle");
+
+ this.showEditor = this.showEditor.bind(this);
+ this.showBlackBoxMessage = this.showBlackBoxMessage.bind(this);
+ this.showProgressBar = this.showProgressBar.bind(this);
+
+ this._onTabSelect = this._onInstrumentsPaneTabSelect.bind(this);
+ this._instrumentsPane.tabpanels.addEventListener("select", this._onTabSelect);
+
+ this._collapsePaneString = L10N.getStr("collapsePanes");
+ this._expandPaneString = L10N.getStr("expandPanes");
+
+ this._workersAndSourcesPane.setAttribute("width", Prefs.workersAndSourcesWidth);
+ this._instrumentsPane.setAttribute("width", Prefs.instrumentsWidth);
+ this.toggleInstrumentsPane({ visible: Prefs.panesVisibleOnStartup });
+
+ this.updateLayoutMode();
+
+ this._onResize = this._onResize.bind(this);
+ window.addEventListener("resize", this._onResize, false);
+ },
+
+ /**
+ * Destroys the UI for all the displayed panes.
+ */
+ _destroyPanes: function () {
+ dumpn("Destroying the DebuggerView panes");
+
+ if (gHostType != "side") {
+ Prefs.workersAndSourcesWidth = this._workersAndSourcesPane.getAttribute("width");
+ Prefs.instrumentsWidth = this._instrumentsPane.getAttribute("width");
+ }
+
+ this._workersAndSourcesPane = null;
+ this._instrumentsPane = null;
+ this._instrumentsPaneToggleButton = null;
+ },
+
+ /**
+ * Initializes the VariablesView instance and attaches a controller.
+ */
+ _initializeVariablesView: function () {
+ this.Variables = new VariablesView(document.getElementById("variables"), {
+ searchPlaceholder: L10N.getStr("emptyVariablesFilterText"),
+ emptyText: L10N.getStr("emptyVariablesText"),
+ onlyEnumVisible: Prefs.variablesOnlyEnumVisible,
+ searchEnabled: Prefs.variablesSearchboxVisible,
+ eval: (variable, value) => {
+ let string = variable.evaluationMacro(variable, value);
+ DebuggerController.StackFrames.evaluate(string);
+ },
+ lazyEmpty: true
+ });
+
+ // Attach the current toolbox to the VView so it can link DOMNodes to
+ // the inspector/highlighter
+ this.Variables.toolbox = DebuggerController._toolbox;
+
+ // Attach a controller that handles interfacing with the debugger protocol.
+ VariablesViewController.attach(this.Variables, {
+ getEnvironmentClient: aObject => gThreadClient.environment(aObject),
+ getObjectClient: aObject => {
+ return gThreadClient.pauseGrip(aObject);
+ }
+ });
+
+ // Relay events from the VariablesView.
+ this.Variables.on("fetched", (aEvent, aType) => {
+ switch (aType) {
+ case "scopes":
+ window.emit(EVENTS.FETCHED_SCOPES);
+ break;
+ case "variables":
+ window.emit(EVENTS.FETCHED_VARIABLES);
+ break;
+ case "properties":
+ window.emit(EVENTS.FETCHED_PROPERTIES);
+ break;
+ }
+ });
+ },
+
+ /**
+ * Initializes the Editor instance.
+ *
+ * @param function aCallback
+ * Called after the editor finishes initializing.
+ */
+ _initializeEditor: function (callback) {
+ dumpn("Initializing the DebuggerView editor");
+
+ let extraKeys = {};
+ bindKey("_doTokenSearch", "tokenSearchKey");
+ bindKey("_doGlobalSearch", "globalSearchKey", { alt: true });
+ bindKey("_doFunctionSearch", "functionSearchKey");
+ extraKeys[Editor.keyFor("jumpToLine")] = false;
+ extraKeys["Esc"] = false;
+
+ function bindKey(func, key, modifiers = {}) {
+ key = document.getElementById(key).getAttribute("key");
+ let shortcut = Editor.accel(key, modifiers);
+ extraKeys[shortcut] = () => DebuggerView.Filtering[func]();
+ }
+
+ let gutters = ["breakpoints"];
+
+ this.editor = new Editor({
+ mode: Editor.modes.text,
+ readOnly: true,
+ lineNumbers: true,
+ showAnnotationRuler: true,
+ gutters: gutters,
+ extraKeys: extraKeys,
+ contextMenu: "sourceEditorContextMenu",
+ enableCodeFolding: false
+ });
+
+ this.editor.appendTo(document.getElementById("editor")).then(() => {
+ this.editor.extend(DebuggerEditor);
+ this._loadingText = L10N.getStr("loadingText");
+ callback();
+ });
+
+ this.editor.on("gutterClick", (ev, line, button) => {
+ // A right-click shouldn't do anything but keep track of where
+ // it was clicked.
+ if (button == 2) {
+ this.clickedLine = line;
+ }
+ else {
+ const source = queries.getSelectedSource(this.controller.getState());
+ if (source) {
+ const location = { actor: source.actor, line: line + 1 };
+ if (this.editor.hasBreakpoint(line)) {
+ this.controller.dispatch(actions.removeBreakpoint(location));
+ } else {
+ this.controller.dispatch(actions.addBreakpoint(location));
+ }
+ }
+ }
+ });
+
+ this.editor.on("cursorActivity", () => {
+ this.clickedLine = null;
+ });
+ },
+
+ updateEditorBreakpoints: function (source) {
+ const breakpoints = queries.getBreakpoints(this.controller.getState());
+ const sources = queries.getSources(this.controller.getState());
+
+ for (let bp of breakpoints) {
+ if (sources[bp.location.actor] && !bp.disabled) {
+ this.addEditorBreakpoint(bp);
+ }
+ else {
+ this.removeEditorBreakpoint(bp);
+ }
+ }
+ },
+
+ addEditorBreakpoint: function (breakpoint) {
+ const { location, condition } = breakpoint;
+ const source = queries.getSelectedSource(this.controller.getState());
+
+ if (source &&
+ source.actor === location.actor &&
+ !breakpoint.disabled) {
+ this.editor.addBreakpoint(location.line - 1, condition);
+ }
+ },
+
+ removeEditorBreakpoint: function (breakpoint) {
+ const { location } = breakpoint;
+ const source = queries.getSelectedSource(this.controller.getState());
+
+ if (source && source.actor === location.actor) {
+ this.editor.removeBreakpoint(location.line - 1);
+ this.editor.removeBreakpointCondition(location.line - 1);
+ }
+ },
+
+ renderEditorBreakpointCondition: function (breakpoint) {
+ const { location, condition, disabled } = breakpoint;
+ const source = queries.getSelectedSource(this.controller.getState());
+
+ if (source && source.actor === location.actor && !disabled) {
+ if (condition) {
+ this.editor.setBreakpointCondition(location.line - 1);
+ } else {
+ this.editor.removeBreakpointCondition(location.line - 1);
+ }
+ }
+ },
+
+ /**
+ * Display the source editor.
+ */
+ showEditor: function () {
+ this._editorDeck.selectedIndex = 0;
+ },
+
+ /**
+ * Display the black box message.
+ */
+ showBlackBoxMessage: function () {
+ this._editorDeck.selectedIndex = 1;
+ },
+
+ /**
+ * Display the progress bar.
+ */
+ showProgressBar: function () {
+ this._editorDeck.selectedIndex = 2;
+ },
+
+ /**
+ * Sets the currently displayed text contents in the source editor.
+ * This resets the mode and undo stack.
+ *
+ * @param string documentKey
+ * Key to get the correct editor document
+ *
+ * @param string aTextContent
+ * The source text content.
+ *
+ * @param boolean shouldUpdateText
+ Forces a text and mode reset
+ */
+ _setEditorText: function (documentKey, aTextContent = "", shouldUpdateText = false) {
+ const isNew = this._setEditorDocument(documentKey);
+
+ this.editor.clearDebugLocation();
+ this.editor.clearHistory();
+ this.editor.removeBreakpoints();
+
+ // Only set editor's text and mode if it is a new document
+ if (isNew || shouldUpdateText) {
+ this.editor.setMode(Editor.modes.text);
+ this.editor.setText(aTextContent);
+ }
+ },
+
+ /**
+ * Sets the proper editor mode (JS or HTML) according to the specified
+ * content type, or by determining the type from the url or text content.
+ *
+ * @param string aUrl
+ * The source url.
+ * @param string aContentType [optional]
+ * The source content type.
+ * @param string aTextContent [optional]
+ * The source text content.
+ */
+ _setEditorMode: function (aUrl, aContentType = "", aTextContent = "") {
+ // Use JS mode for files with .js and .jsm extensions.
+ if (SourceUtils.isJavaScript(aUrl, aContentType)) {
+ return void this.editor.setMode(Editor.modes.js);
+ }
+
+ if (aContentType === "text/wasm") {
+ return void this.editor.setMode(Editor.modes.text);
+ }
+
+ // Use HTML mode for files in which the first non whitespace character is
+ // &lt;, regardless of extension.
+ if (aTextContent.match(/^\s*</)) {
+ return void this.editor.setMode(Editor.modes.html);
+ }
+
+ // Unknown language, use text.
+ this.editor.setMode(Editor.modes.text);
+ },
+
+ /**
+ * Sets the editor's displayed document.
+ * If there isn't a document for the source, create one
+ *
+ * @param string key - key used to access the editor document cache
+ *
+ * @return boolean isNew - was the document just created
+ */
+ _setEditorDocument: function (key) {
+ let isNew;
+
+ if (!this._editorDocuments[key]) {
+ isNew = true;
+ this._editorDocuments[key] = this.editor.createDocument();
+ } else {
+ isNew = false;
+ }
+
+ const doc = this._editorDocuments[key];
+ this.editor.replaceDocument(doc);
+ return isNew;
+ },
+
+ renderBlackBoxed: function (source) {
+ this._renderSourceText(
+ source,
+ queries.getSourceText(this.controller.getState(), source.actor)
+ );
+ },
+
+ renderPrettyPrinted: function (source) {
+ this._renderSourceText(
+ source,
+ queries.getSourceText(this.controller.getState(), source.actor)
+ );
+ },
+
+ renderSourceText: function (source) {
+ this._renderSourceText(
+ source,
+ queries.getSourceText(this.controller.getState(), source.actor),
+ queries.getSelectedSourceOpts(this.controller.getState())
+ );
+ },
+
+ _renderSourceText: function (source, textInfo, opts = {}) {
+ const selectedSource = queries.getSelectedSource(this.controller.getState());
+
+ // Exit early if we're attempting to render an unselected source
+ if (!selectedSource || selectedSource.actor !== source.actor) {
+ return;
+ }
+
+ if (source.isBlackBoxed) {
+ this.showBlackBoxMessage();
+ setTimeout(() => {
+ window.emit(EVENTS.SOURCE_SHOWN, source);
+ }, 0);
+ return;
+ }
+ else {
+ this.showEditor();
+ }
+
+ if (textInfo.loading) {
+ // TODO: bug 1228866, we need to update `_editorSource` here but
+ // still make the editor be updated when the full text comes
+ // through somehow.
+ this._setEditorText("loading", L10N.getStr("loadingText"));
+ return;
+ }
+ else if (textInfo.error) {
+ let msg = L10N.getFormatStr("errorLoadingText2", textInfo.error);
+ this._setEditorText("error", msg);
+ console.error(new Error(msg));
+ dumpn(msg);
+
+ this.showEditor();
+ window.emit(EVENTS.SOURCE_ERROR_SHOWN, source);
+ return;
+ }
+
+ // If the line is not specified, default to the current frame's position,
+ // if available and the frame's url corresponds to the requested url.
+ if (!("line" in opts)) {
+ let cachedFrames = DebuggerController.activeThread.cachedFrames;
+ let currentDepth = DebuggerController.StackFrames.currentFrameDepth;
+ let frame = cachedFrames[currentDepth];
+ if (frame && frame.source.actor == source.actor) {
+ opts.line = frame.where.line;
+ }
+ }
+
+ if (this._editorSource.actor === source.actor &&
+ this._editorSource.prettyPrinted === source.isPrettyPrinted &&
+ this._editorSource.blackboxed === source.isBlackBoxed) {
+ this.updateEditorPosition(opts);
+ return;
+ }
+
+ let { text, contentType } = textInfo;
+ let shouldUpdateText = this._editorSource.prettyPrinted != source.isPrettyPrinted;
+ this._setEditorText(source.actor, text, shouldUpdateText);
+
+ this._editorSource.actor = source.actor;
+ this._editorSource.prettyPrinted = source.isPrettyPrinted;
+ this._editorSource.blackboxed = source.isBlackBoxed;
+ this._editorSource.prettyPrinted = source.isPrettyPrinted;
+
+ this._setEditorMode(source.url, contentType, text);
+ this.updateEditorBreakpoints(source);
+
+ setTimeout(() => {
+ window.emit(EVENTS.SOURCE_SHOWN, source);
+ }, 0);
+
+ this.updateEditorPosition(opts);
+ },
+
+ updateEditorPosition: function (opts) {
+ let line = opts.line || 0;
+
+ // Line numbers in the source editor should start from 1. If
+ // invalid or not specified, then don't do anything.
+ if (line < 1) {
+ window.emit(EVENTS.EDITOR_LOCATION_SET);
+ return;
+ }
+
+ if (opts.charOffset) {
+ line += this.editor.getPosition(opts.charOffset).line;
+ }
+ if (opts.lineOffset) {
+ line += opts.lineOffset;
+ }
+ if (opts.moveCursor) {
+ let location = { line: line - 1, ch: opts.columnOffset || 0 };
+ this.editor.setCursor(location);
+ }
+ if (!opts.noDebug) {
+ this.editor.setDebugLocation(line - 1);
+ }
+ window.emit(EVENTS.EDITOR_LOCATION_SET);
+ },
+
+ /**
+ * Update the source editor's current caret and debug location based on
+ * a requested url and line.
+ *
+ * @param string aActor
+ * The target actor id.
+ * @param number aLine [optional]
+ * The target line in the source.
+ * @param object aFlags [optional]
+ * Additional options for showing the source. Supported options:
+ * - charOffset: character offset for the caret or debug location
+ * - lineOffset: line offset for the caret or debug location
+ * - columnOffset: column offset for the caret or debug location
+ * - noCaret: don't set the caret location at the specified line
+ * - noDebug: don't set the debug location at the specified line
+ * - align: string specifying whether to align the specified line
+ * at the "top", "center" or "bottom" of the editor
+ * - force: boolean forcing all text to be reshown in the editor
+ * @return object
+ * A promise that is resolved after the source text has been set.
+ */
+ setEditorLocation: function (aActor, aLine, aFlags = {}) {
+ // Avoid trying to set a source for a url that isn't known yet.
+ if (!this.Sources.containsValue(aActor)) {
+ throw new Error("Unknown source for the specified URL.");
+ }
+
+ let sourceItem = this.Sources.getItemByValue(aActor);
+ let source = sourceItem.attachment.source;
+
+ // Make sure the requested source client is shown in the editor,
+ // then update the source editor's caret position and debug
+ // location.
+ this.controller.dispatch(actions.selectSource(source, {
+ line: aLine,
+ charOffset: aFlags.charOffset,
+ lineOffset: aFlags.lineOffset,
+ columnOffset: aFlags.columnOffset,
+ moveCursor: !aFlags.noCaret,
+ noDebug: aFlags.noDebug,
+ forceUpdate: aFlags.force
+ }));
+ },
+
+ /**
+ * Gets the visibility state of the instruments pane.
+ * @return boolean
+ */
+ get instrumentsPaneHidden() {
+ return this._instrumentsPane.classList.contains("pane-collapsed");
+ },
+
+ /**
+ * Gets the currently selected tab in the instruments pane.
+ * @return string
+ */
+ get instrumentsPaneTab() {
+ return this._instrumentsPane.selectedTab.id;
+ },
+
+ /**
+ * Sets the instruments pane hidden or visible.
+ *
+ * @param object aFlags
+ * An object containing some of the following properties:
+ * - visible: true if the pane should be shown, false to hide
+ * - animated: true to display an animation on toggle
+ * - delayed: true to wait a few cycles before toggle
+ * - callback: a function to invoke when the toggle finishes
+ * @param number aTabIndex [optional]
+ * The index of the intended selected tab in the details pane.
+ */
+ toggleInstrumentsPane: function (aFlags, aTabIndex) {
+ let pane = this._instrumentsPane;
+ let button = this._instrumentsPaneToggleButton;
+
+ ViewHelpers.togglePane(aFlags, pane);
+
+ if (aFlags.visible) {
+ button.classList.remove("pane-collapsed");
+ button.setAttribute("tooltiptext", this._collapsePaneString);
+ } else {
+ button.classList.add("pane-collapsed");
+ button.setAttribute("tooltiptext", this._expandPaneString);
+ }
+
+ if (aTabIndex !== undefined) {
+ pane.selectedIndex = aTabIndex;
+ }
+ },
+
+ /**
+ * Sets the instruments pane visible after a short period of time.
+ *
+ * @param function aCallback
+ * A function to invoke when the toggle finishes.
+ */
+ showInstrumentsPane: function (aCallback) {
+ DebuggerView.toggleInstrumentsPane({
+ visible: true,
+ animated: true,
+ delayed: true,
+ callback: aCallback
+ }, 0);
+ },
+
+ /**
+ * Handles a tab selection event on the instruments pane.
+ */
+ _onInstrumentsPaneTabSelect: function () {
+ if (this._instrumentsPane.selectedTab.id == "events-tab") {
+ this.controller.dispatch(actions.fetchEventListeners());
+ }
+ },
+
+ /**
+ * Handles a host change event issued by the parent toolbox.
+ *
+ * @param string aType
+ * The host type, either "bottom", "side" or "window".
+ */
+ handleHostChanged: function (hostType) {
+ this._hostType = hostType;
+ this.updateLayoutMode();
+ },
+
+ /**
+ * Resize handler for this container's window.
+ */
+ _onResize: function (evt) {
+ // Allow requests to settle down first.
+ setNamedTimeout(
+ "resize-events", RESIZE_REFRESH_RATE, () => this.updateLayoutMode());
+ },
+
+ /**
+ * Set the layout to "vertical" or "horizontal" depending on the host type.
+ */
+ updateLayoutMode: function () {
+ if (this._isSmallWindowHost() || this._hostType == "side") {
+ this._setLayoutMode("vertical");
+ } else {
+ this._setLayoutMode("horizontal");
+ }
+ },
+
+ /**
+ * Check if the current host is in window mode and is
+ * too small for horizontal layout
+ */
+ _isSmallWindowHost: function () {
+ if (this._hostType != "window") {
+ return false;
+ }
+
+ return window.outerWidth <= BREAKPOINT_SMALL_WINDOW_WIDTH;
+ },
+
+ /**
+ * Enter the provided layoutMode. Do nothing if the layout is the same as the current one.
+ * @param {String} layoutMode new layout ("vertical" or "horizontal")
+ */
+ _setLayoutMode: function (layoutMode) {
+ if (this._body.getAttribute("layout") == layoutMode) {
+ return;
+ }
+
+ if (layoutMode == "vertical") {
+ this._enterVerticalLayout();
+ } else {
+ this._enterHorizontalLayout();
+ }
+
+ this._body.setAttribute("layout", layoutMode);
+ window.emit(EVENTS.LAYOUT_CHANGED, layoutMode);
+ },
+
+ /**
+ * Switches the debugger widgets to a vertical layout.
+ */
+ _enterVerticalLayout: function () {
+ let vertContainer = document.getElementById("vertical-layout-panes-container");
+
+ // Move the soruces and instruments panes in a different container.
+ let splitter = document.getElementById("sources-and-instruments-splitter");
+ vertContainer.insertBefore(this._workersAndSourcesPane, splitter);
+ vertContainer.appendChild(this._instrumentsPane);
+
+ // Make sure the vertical layout container's height doesn't repeatedly
+ // grow or shrink based on the displayed sources, variables etc.
+ vertContainer.setAttribute("height",
+ vertContainer.getBoundingClientRect().height);
+ },
+
+ /**
+ * Switches the debugger widgets to a horizontal layout.
+ */
+ _enterHorizontalLayout: function () {
+ let normContainer = document.getElementById("debugger-widgets");
+ let editorPane = document.getElementById("editor-and-instruments-pane");
+
+ // The sources and instruments pane need to be inserted at their
+ // previous locations in their normal container.
+ let splitter = document.getElementById("sources-and-editor-splitter");
+ normContainer.insertBefore(this._workersAndSourcesPane, splitter);
+ editorPane.appendChild(this._instrumentsPane);
+
+ // Revert to the preferred sources and instruments widths, because
+ // they flexed in the vertical layout.
+ this._workersAndSourcesPane.setAttribute("width", Prefs.workersAndSourcesWidth);
+ this._instrumentsPane.setAttribute("width", Prefs.instrumentsWidth);
+ },
+
+ /**
+ * Handles any initialization on a tab navigation event issued by the client.
+ */
+ handleTabNavigation: function () {
+ dumpn("Handling tab navigation in the DebuggerView");
+ this.Filtering.clearSearch();
+ this.GlobalSearch.clearView();
+ this.StackFrames.empty();
+ this.Sources.empty();
+ this.Variables.empty();
+ this.EventListeners.empty();
+
+ if (this.editor) {
+ this.editor.setMode(Editor.modes.text);
+ this.editor.setText("");
+ this.editor.clearHistory();
+ this._editorSource = {};
+ this._editorDocuments = {};
+ }
+ },
+
+ Toolbar: null,
+ Options: null,
+ Filtering: null,
+ GlobalSearch: null,
+ StackFrames: null,
+ Sources: null,
+ Variables: null,
+ VariableBubble: null,
+ WatchExpressions: null,
+ EventListeners: null,
+ editor: null,
+ _loadingText: "",
+ _body: null,
+ _editorDeck: null,
+ _workersAndSourcesPane: null,
+ _instrumentsPane: null,
+ _instrumentsPaneToggleButton: null,
+ _collapsePaneString: "",
+ _expandPaneString: ""
+};
+
+/**
+ * A custom items container, used for displaying views like the
+ * FilteredSources, FilteredFunctions etc., inheriting the generic WidgetMethods.
+ */
+function ResultsPanelContainer() {
+}
+
+ResultsPanelContainer.prototype = Heritage.extend(WidgetMethods, {
+ /**
+ * Sets the anchor node for this container panel.
+ * @param nsIDOMNode aNode
+ */
+ set anchor(aNode) {
+ this._anchor = aNode;
+
+ // If the anchor node is not null, create a panel to attach to the anchor
+ // when showing the popup.
+ if (aNode) {
+ if (!this._panel) {
+ this._panel = document.createElement("panel");
+ this._panel.id = "results-panel";
+ this._panel.setAttribute("level", "top");
+ this._panel.setAttribute("noautofocus", "true");
+ this._panel.setAttribute("consumeoutsideclicks", "false");
+ document.documentElement.appendChild(this._panel);
+ }
+ if (!this.widget) {
+ this.widget = new SimpleListWidget(this._panel);
+ this.autoFocusOnFirstItem = false;
+ this.autoFocusOnSelection = false;
+ this.maintainSelectionVisible = false;
+ }
+ }
+ // Cleanup the anchor and remove the previously created panel.
+ else {
+ this._panel.remove();
+ this._panel = null;
+ this.widget = null;
+ }
+ },
+
+ /**
+ * Gets the anchor node for this container panel.
+ * @return nsIDOMNode
+ */
+ get anchor() {
+ return this._anchor;
+ },
+
+ /**
+ * Sets the container panel hidden or visible. It's hidden by default.
+ * @param boolean aFlag
+ */
+ set hidden(aFlag) {
+ if (aFlag) {
+ this._panel.hidden = true;
+ this._panel.hidePopup();
+ } else {
+ this._panel.hidden = false;
+ this._panel.openPopup(this._anchor, this.position, this.left, this.top);
+ }
+ },
+
+ /**
+ * Gets this container's visibility state.
+ * @return boolean
+ */
+ get hidden() {
+ return this._panel.state == "closed" ||
+ this._panel.state == "hiding";
+ },
+
+ /**
+ * Removes all items from this container and hides it.
+ */
+ clearView: function () {
+ this.hidden = true;
+ this.empty();
+ },
+
+ /**
+ * Selects the next found item in this container.
+ * Does not change the currently focused node.
+ */
+ selectNext: function () {
+ let nextIndex = this.selectedIndex + 1;
+ if (nextIndex >= this.itemCount) {
+ nextIndex = 0;
+ }
+ this.selectedItem = this.getItemAtIndex(nextIndex);
+ },
+
+ /**
+ * Selects the previously found item in this container.
+ * Does not change the currently focused node.
+ */
+ selectPrev: function () {
+ let prevIndex = this.selectedIndex - 1;
+ if (prevIndex < 0) {
+ prevIndex = this.itemCount - 1;
+ }
+ this.selectedItem = this.getItemAtIndex(prevIndex);
+ },
+
+ /**
+ * Customization function for creating an item's UI.
+ *
+ * @param string aLabel
+ * The item's label string.
+ * @param string aBeforeLabel
+ * An optional string shown before the label.
+ * @param string aBelowLabel
+ * An optional string shown underneath the label.
+ */
+ _createItemView: function (aLabel, aBelowLabel, aBeforeLabel) {
+ let container = document.createElement("vbox");
+ container.className = "results-panel-item";
+
+ let firstRowLabels = document.createElement("hbox");
+ let secondRowLabels = document.createElement("hbox");
+
+ if (aBeforeLabel) {
+ let beforeLabelNode = document.createElement("label");
+ beforeLabelNode.className = "plain results-panel-item-label-before";
+ beforeLabelNode.setAttribute("value", aBeforeLabel);
+ firstRowLabels.appendChild(beforeLabelNode);
+ }
+
+ let labelNode = document.createElement("label");
+ labelNode.className = "plain results-panel-item-label";
+ labelNode.setAttribute("value", aLabel);
+ firstRowLabels.appendChild(labelNode);
+
+ if (aBelowLabel) {
+ let belowLabelNode = document.createElement("label");
+ belowLabelNode.className = "plain results-panel-item-label-below";
+ belowLabelNode.setAttribute("value", aBelowLabel);
+ secondRowLabels.appendChild(belowLabelNode);
+ }
+
+ container.appendChild(firstRowLabels);
+ container.appendChild(secondRowLabels);
+
+ return container;
+ },
+
+ _anchor: null,
+ _panel: null,
+ position: RESULTS_PANEL_POPUP_POSITION,
+ left: 0,
+ top: 0
+});
+
+DebuggerView.EventListeners = new EventListenersView(DebuggerController);
+DebuggerView.Sources = new SourcesView(DebuggerController, DebuggerView);
diff --git a/devtools/client/debugger/debugger.css b/devtools/client/debugger/debugger.css
new file mode 100644
index 000000000..9898e6bf0
--- /dev/null
+++ b/devtools/client/debugger/debugger.css
@@ -0,0 +1,69 @@
+/* -*- Mode: javascript; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* 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/. */
+
+/* Side pane views */
+
+#workers-pane > tabpanels > tabpanel,
+#sources-pane > tabpanels > tabpanel,
+#instruments-pane > tabpanels > tabpanel {
+ -moz-box-orient: vertical;
+}
+
+/* Toolbar controls */
+
+.devtools-toolbarbutton:not([label]) > .toolbarbutton-text {
+ display: none;
+}
+
+/* Horizontal vs. vertical layout */
+
+#body[layout=vertical] #debugger-widgets {
+ -moz-box-orient: vertical;
+}
+
+#body[layout=vertical] #workers-and-sources-pane {
+ -moz-box-flex: 1;
+}
+
+#body[layout=vertical] #instruments-pane {
+ -moz-box-flex: 2;
+}
+
+#body[layout=vertical] #instruments-pane-toggle {
+ display: none;
+}
+
+#body[layout=vertical] #sources-and-editor-splitter,
+#body[layout=vertical] #editor-and-instruments-splitter {
+ display: none;
+}
+
+#body[layout=horizontal] #vertical-layout-splitter,
+#body[layout=horizontal] #vertical-layout-panes-container {
+ display: none;
+}
+
+#body[layout=vertical] #stackframes {
+ visibility: hidden;
+}
+
+#source-progress-container {
+ display: flex;
+ flex-flow: column;
+ justify-content: center;
+}
+
+#source-progress {
+ flex: none;
+}
+
+#redux-devtools * {
+ display: block;
+}
+
+#redux-devtools span {
+ display: inline
+}
diff --git a/devtools/client/debugger/debugger.xul b/devtools/client/debugger/debugger.xul
new file mode 100644
index 000000000..5a22cf7f8
--- /dev/null
+++ b/devtools/client/debugger/debugger.xul
@@ -0,0 +1,474 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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/. -->
+<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
+<?xml-stylesheet href="chrome://devtools/content/shared/widgets/widgets.css" type="text/css"?>
+<?xml-stylesheet href="chrome://devtools/content/debugger/debugger.css" type="text/css"?>
+<?xml-stylesheet href="chrome://devtools/skin/widgets.css" type="text/css"?>
+<?xml-stylesheet href="chrome://devtools/skin/debugger.css" type="text/css"?>
+<!DOCTYPE window [
+ <!ENTITY % debuggerDTD SYSTEM "chrome://devtools/locale/debugger.dtd">
+ %debuggerDTD;
+]>
+<?xul-overlay href="chrome://global/content/editMenuOverlay.xul"?>
+
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ xmlns:html="http://www.w3.org/1999/xhtml"
+ macanimationtype="document"
+ fullscreenbutton="true"
+ screenX="4" screenY="4"
+ width="960" height="480"
+ persist="screenX screenY width height sizemode">
+
+ <script type="application/javascript;version=1.8"
+ src="chrome://devtools/content/shared/theme-switching.js"/>
+ <script type="text/javascript" src="chrome://global/content/globalOverlay.js"/>
+ <script type="text/javascript" src="debugger-controller.js"/>
+ <script type="text/javascript" src="debugger-view.js"/>
+ <script type="text/javascript" src="utils.js"/>
+ <script type="text/javascript" src="views/workers-view.js"/>
+ <script type="text/javascript" src="views/variable-bubble-view.js"/>
+ <script type="text/javascript" src="views/watch-expressions-view.js"/>
+ <script type="text/javascript" src="views/global-search-view.js"/>
+ <script type="text/javascript" src="views/toolbar-view.js"/>
+ <script type="text/javascript" src="views/options-view.js"/>
+ <script type="text/javascript" src="views/stack-frames-view.js"/>
+ <script type="text/javascript" src="views/stack-frames-classic-view.js"/>
+ <script type="text/javascript" src="views/filter-view.js"/>
+
+ <commandset id="editMenuCommands"/>
+
+ <commandset id="debuggerCommands"></commandset>
+
+ <popupset id="debuggerPopupset">
+ <menupopup id="sourceEditorContextMenu"
+ onpopupshowing="goUpdateGlobalEditMenuItems()">
+ <menuitem id="se-dbg-cMenu-addBreakpoint"
+ label="&debuggerUI.seMenuBreak;"
+ key="addBreakpointKey"
+ command="addBreakpointCommand"/>
+ <menuitem id="se-dbg-cMenu-addConditionalBreakpoint"
+ label="&debuggerUI.seMenuCondBreak;"
+ key="addConditionalBreakpointKey"
+ command="addConditionalBreakpointCommand"/>
+ <menuitem id="se-dbg-cMenu-editConditionalBreakpoint"
+ label="&debuggerUI.seEditMenuCondBreak;"
+ key="addConditionalBreakpointKey"
+ command="addConditionalBreakpointCommand"/>
+ <menuitem id="se-dbg-cMenu-addAsWatch"
+ label="&debuggerUI.seMenuAddWatch;"
+ key="addWatchExpressionKey"
+ command="addWatchExpressionCommand"/>
+ <menuseparator/>
+ <menuitem id="cMenu_copy"/>
+ <menuseparator/>
+ <menuitem id="cMenu_selectAll"/>
+ <menuseparator/>
+ <menuitem id="se-dbg-cMenu-findFile"
+ label="&debuggerUI.searchFile;"
+ accesskey="&debuggerUI.searchFile.accesskey;"
+ key="fileSearchKey"
+ command="fileSearchCommand"/>
+ <menuitem id="se-dbg-cMenu-findGlobal"
+ label="&debuggerUI.searchGlobal;"
+ accesskey="&debuggerUI.searchGlobal.accesskey;"
+ key="globalSearchKey"
+ command="globalSearchCommand"/>
+ <menuitem id="se-dbg-cMenu-findFunction"
+ label="&debuggerUI.searchFunction;"
+ accesskey="&debuggerUI.searchFunction.accesskey;"
+ key="functionSearchKey"
+ command="functionSearchCommand"/>
+ <menuseparator/>
+ <menuitem id="se-dbg-cMenu-findToken"
+ label="&debuggerUI.searchToken;"
+ accesskey="&debuggerUI.searchToken.accesskey;"
+ key="tokenSearchKey"
+ command="tokenSearchCommand"/>
+ <menuitem id="se-dbg-cMenu-findLine"
+ label="&debuggerUI.searchGoToLine;"
+ accesskey="&debuggerUI.searchGoToLine.accesskey;"
+ key="lineSearchKey"
+ command="lineSearchCommand"/>
+ <menuseparator/>
+ <menuitem id="se-dbg-cMenu-findVariable"
+ label="&debuggerUI.searchVariable;"
+ accesskey="&debuggerUI.searchVariable.accesskey;"
+ key="variableSearchKey"
+ command="variableSearchCommand"/>
+ <menuitem id="se-dbg-cMenu-focusVariables"
+ label="&debuggerUI.focusVariables;"
+ accesskey="&debuggerUI.focusVariables.accesskey;"
+ key="variablesFocusKey"
+ command="variablesFocusCommand"/>
+ <menuitem id="se-dbg-cMenu-prettyPrint"
+ label="&debuggerUI.sources.prettyPrint;"
+ command="prettyPrintCommand"/>
+ </menupopup>
+ <menupopup id="debuggerWatchExpressionsContextMenu">
+ <menuitem id="add-watch-expression"
+ label="&debuggerUI.addWatch;"
+ accesskey="&debuggerUI.addWatch.accesskey;"
+ key="addWatchExpressionKey"
+ command="addWatchExpressionCommand"/>
+ <menuitem id="removeAll-watch-expression"
+ label="&debuggerUI.removeAllWatch;"
+ accesskey="&debuggerUI.removeAllWatch.accesskey;"
+ key="removeAllWatchExpressionsKey"
+ command="removeAllWatchExpressionsCommand"/>
+ </menupopup>
+ <menupopup id="debuggerPrefsContextMenu"
+ position="before_end"
+ onpopupshowing="DebuggerView.Options._onPopupShowing()"
+ onpopuphiding="DebuggerView.Options._onPopupHiding()"
+ onpopuphidden="DebuggerView.Options._onPopupHidden()">
+ <menuitem id="auto-pretty-print"
+ type="checkbox"
+ label="&debuggerUI.autoPrettyPrint;"
+ accesskey="&debuggerUI.autoPrettyPrint.accesskey;"
+ command="toggleAutoPrettyPrint"/>
+ <menuitem id="pause-on-exceptions"
+ type="checkbox"
+ label="&debuggerUI.pauseExceptions;"
+ accesskey="&debuggerUI.pauseExceptions.accesskey;"
+ command="togglePauseOnExceptions"/>
+ <menuitem id="ignore-caught-exceptions"
+ type="checkbox"
+ label="&debuggerUI.ignoreCaughtExceptions;"
+ accesskey="&debuggerUI.ignoreCaughtExceptions.accesskey;"
+ command="toggleIgnoreCaughtExceptions"/>
+ <menuitem id="show-panes-on-startup"
+ type="checkbox"
+ label="&debuggerUI.showPanesOnInit;"
+ accesskey="&debuggerUI.showPanesOnInit.accesskey;"
+ command="toggleShowPanesOnStartup"/>
+ <menuitem id="show-vars-only-enum"
+ type="checkbox"
+ label="&debuggerUI.showOnlyEnum;"
+ accesskey="&debuggerUI.showOnlyEnum.accesskey;"
+ command="toggleShowOnlyEnum"/>
+ <menuitem id="show-vars-filter-box"
+ type="checkbox"
+ label="&debuggerUI.showVarsFilter;"
+ accesskey="&debuggerUI.showVarsFilter.accesskey;"
+ command="toggleShowVariablesFilterBox"/>
+ <menuitem id="show-original-source"
+ type="checkbox"
+ label="&debuggerUI.showOriginalSource;"
+ accesskey="&debuggerUI.showOriginalSource.accesskey;"
+ command="toggleShowOriginalSource"/>
+ <menuitem id="auto-black-box"
+ type="checkbox"
+ label="&debuggerUI.autoBlackBox;"
+ accesskey="&debuggerUI.autoBlackBox.accesskey;"
+ command="toggleAutoBlackBox"/>
+ </menupopup>
+ </popupset>
+
+ <popupset id="debuggerSourcesPopupset">
+ <menupopup id="debuggerSourcesContextMenu">
+ <menuitem id="debugger-sources-context-newtab"
+ label="&debuggerUI.context.newTab;"
+ accesskey="&debuggerUI.context.newTab.accesskey;"/>
+ <menuitem id="debugger-sources-context-copyurl"
+ label="&debuggerUI.context.copyUrl;"
+ accesskey="&debuggerUI.context.copyUrl.accesskey;"/>
+ </menupopup>
+ </popupset>
+
+ <keyset id="debuggerKeys">
+ <key id="nextSourceKey"
+ keycode="VK_DOWN"
+ modifiers="accel alt"
+ command="nextSourceCommand"/>
+ <key id="prevSourceKey"
+ keycode="VK_UP"
+ modifiers="accel alt"
+ command="prevSourceCommand"/>
+ <key id="resumeKey"
+ keycode="&debuggerUI.stepping.resume1;"
+ command="resumeCommand"/>
+ <key id="stepOverKey"
+ keycode="&debuggerUI.stepping.stepOver1;"
+ command="stepOverCommand"/>
+ <key id="stepInKey"
+ keycode="&debuggerUI.stepping.stepIn1;"
+ command="stepInCommand"/>
+ <key id="stepOutKey"
+ keycode="&debuggerUI.stepping.stepOut1;"
+ modifiers="shift"
+ command="stepOutCommand"/>
+ <key id="fileSearchKey"
+ key="&debuggerUI.searchFile.key;"
+ modifiers="accel"
+ command="fileSearchCommand"/>
+ <key id="fileSearchKey"
+ key="&debuggerUI.searchFile.altkey;"
+ modifiers="accel"
+ command="fileSearchCommand"/>
+ <key id="globalSearchKey"
+ key="&debuggerUI.searchGlobal.key;"
+ modifiers="accel alt"
+ command="globalSearchCommand"/>
+ <key id="functionSearchKey"
+ key="&debuggerUI.searchFunction.key;"
+ modifiers="accel"
+ command="functionSearchCommand"/>
+ <key id="tokenSearchKey"
+ key="&debuggerUI.searchToken.key;"
+ modifiers="accel"
+ command="tokenSearchCommand"/>
+ <key id="lineSearchKey"
+ key="&debuggerUI.searchGoToLine.key;"
+ modifiers="accel"
+ command="lineSearchCommand"/>
+ <key id="variableSearchKey"
+ key="&debuggerUI.searchVariable.key;"
+ modifiers="accel alt"
+ command="variableSearchCommand"/>
+ <key id="variablesFocusKey"
+ key="&debuggerUI.focusVariables.key;"
+ modifiers="accel shift"
+ command="variablesFocusCommand"/>
+ <key id="addBreakpointKey"
+ key="&debuggerUI.seMenuBreak.key;"
+ modifiers="accel"
+ command="addBreakpointCommand"/>
+ <key id="addConditionalBreakpointKey"
+ key="&debuggerUI.seMenuCondBreak.key;"
+ modifiers="accel shift"
+ command="addConditionalBreakpointCommand"/>
+ <key id="addWatchExpressionKey"
+ key="&debuggerUI.seMenuAddWatch.key;"
+ modifiers="accel shift"
+ command="addWatchExpressionCommand"/>
+ <key id="removeAllWatchExpressionsKey"
+ key="&debuggerUI.removeAllWatch.key;"
+ modifiers="accel alt"
+ command="removeAllWatchExpressionsCommand"/>
+ <key id="debuggerSourcesCopyUrl"
+ key="&debuggerUI.context.copyUrl.key;"
+ modifiers="accel"
+ oncommand="DebuggerView.Sources._onCopyUrlCommand()"/>
+ </keyset>
+
+ <vbox id="body"
+ class="theme-body"
+ layout="horizontal"
+ flex="1">
+ <toolbar id="debugger-toolbar"
+ class="devtools-toolbar">
+ <hbox id="debugger-controls"
+ class="devtools-toolbarbutton-group">
+ <toolbarbutton id="resume"
+ class="devtools-toolbarbutton"
+ tabindex="0"/>
+ <toolbarbutton id="step-over"
+ class="devtools-toolbarbutton"
+ tabindex="0"/>
+ <toolbarbutton id="step-in"
+ class="devtools-toolbarbutton"
+ tabindex="0"/>
+ <toolbarbutton id="step-out"
+ class="devtools-toolbarbutton"
+ tabindex="0"/>
+ </hbox>
+ <vbox id="stackframes" flex="1"/>
+ <textbox id="searchbox"
+ class="devtools-searchinput" type="search"/>
+ <toolbarbutton id="instruments-pane-toggle"
+ class="devtools-toolbarbutton"
+ tooltiptext="&debuggerUI.panesButton.tooltip;"
+ tabindex="0"/>
+ <toolbarbutton id="debugger-options"
+ class="devtools-toolbarbutton devtools-option-toolbarbutton"
+ tooltiptext="&debuggerUI.optsButton.tooltip;"
+ popup="debuggerPrefsContextMenu"
+ tabindex="0"/>
+ </toolbar>
+ <vbox id="globalsearch" orient="vertical" hidden="true"/>
+ <splitter class="devtools-horizontal-splitter" hidden="true"/>
+ <hbox id="debugger-widgets" flex="1">
+ <vbox id="workers-and-sources-pane">
+ <tabbox id="workers-pane"
+ class="devtools-sidebar-tabs"
+ flex="0"
+ hidden="true">
+ <tabs>
+ <tab id="workers-tab"
+ crop="end"
+ label="&debuggerUI.tabs.workers;"/>
+ </tabs>
+ <tabpanels flex="1">
+ <tabpanel>
+ <vbox id="workers" flex="1"/>
+ </tabpanel>
+ </tabpanels>
+ </tabbox>
+ <splitter id="workers-splitter"
+ class="devtools-horizontal-splitter"
+ hidden="true" />
+ <tabbox id="sources-pane"
+ class="devtools-sidebar-tabs"
+ flex="1">
+ <tabs>
+ <tab id="sources-tab"
+ crop="end"
+ label="&debuggerUI.tabs.sources;"/>
+ <tab id="callstack-tab"
+ crop="end"
+ label="&debuggerUI.tabs.callstack;"/>
+ </tabs>
+ <tabpanels flex="1">
+ <tabpanel id="sources-tabpanel">
+ <vbox id="sources" flex="1"/>
+ <toolbar id="sources-toolbar" class="devtools-toolbar">
+ <hbox id="sources-controls"
+ class="devtools-toolbarbutton-group">
+ <toolbarbutton id="black-box"
+ class="devtools-toolbarbutton"
+ tooltiptext="&debuggerUI.sources.blackBoxTooltip;"
+ command="blackBoxCommand"/>
+ <toolbarbutton id="pretty-print"
+ class="devtools-toolbarbutton"
+ tooltiptext="&debuggerUI.sources.prettyPrint;"
+ command="prettyPrintCommand"
+ hidden="true"/>
+ </hbox>
+ <vbox class="devtools-separator"/>
+ <toolbarbutton id="toggle-breakpoints"
+ class="devtools-toolbarbutton"
+ tooltiptext="&debuggerUI.sources.toggleBreakpoints;"
+ command="toggleBreakpointsCommand"/>
+ </toolbar>
+ </tabpanel>
+ <tabpanel id="callstack-tabpanel">
+ <vbox id="callstack-list" flex="1"/>
+ </tabpanel>
+ </tabpanels>
+ </tabbox>
+ </vbox>
+ <splitter id="sources-and-editor-splitter"
+ class="devtools-side-splitter"/>
+ <vbox id="debugger-content" flex="1">
+ <hbox id="editor-and-instruments-pane" flex="1">
+ <deck id="editor-deck" flex="1" class="devtools-main-content">
+ <vbox id="editor"/>
+ <vbox id="black-boxed-message"
+ align="center"
+ pack="center">
+ <description id="black-boxed-message-label">
+ &debuggerUI.blackBoxMessage.label;
+ </description>
+ <button id="black-boxed-message-button"
+ class="devtools-toolbarbutton"
+ label="&debuggerUI.blackBoxMessage.unBlackBoxButton;"
+ command="unBlackBoxCommand"/>
+ </vbox>
+ <html:div id="source-progress-container"
+ align="center">
+ <html:div id="hbox">
+ <html:progress id="source-progress"></html:progress>
+ </html:div>
+ </html:div>
+ </deck>
+ <splitter id="editor-and-instruments-splitter"
+ class="devtools-side-splitter"/>
+ <tabbox id="instruments-pane"
+ class="devtools-sidebar-tabs"
+ hidden="true">
+ <tabs>
+ <tab id="variables-tab"
+ crop="end"
+ label="&debuggerUI.tabs.variables;"/>
+ <tab id="events-tab"
+ crop="end"
+ label="&debuggerUI.tabs.events;"/>
+ </tabs>
+ <tabpanels flex="1">
+ <tabpanel id="variables-tabpanel">
+ <vbox id="expressions"/>
+ <splitter class="devtools-horizontal-splitter"/>
+ <vbox id="variables" flex="1"/>
+ </tabpanel>
+ <tabpanel id="events-tabpanel">
+ <vbox id="event-listeners" flex="1"/>
+ </tabpanel>
+ </tabpanels>
+ </tabbox>
+ </hbox>
+ </vbox>
+ <splitter id="vertical-layout-splitter"
+ class="devtools-horizontal-splitter"/>
+ <hbox id="vertical-layout-panes-container">
+ <splitter id="sources-and-instruments-splitter"
+ class="devtools-side-splitter"/>
+ <!-- The sources-pane and instruments-pane will be moved in this
+ container if the toolbox's host requires it. -->
+ </hbox>
+ </hbox>
+ </vbox>
+
+ <panel id="searchbox-help-panel"
+ level="top"
+ type="arrow"
+ position="before_start"
+ noautofocus="true"
+ consumeoutsideclicks="false">
+ <vbox>
+ <hbox>
+ <label id="filter-label"/>
+ </hbox>
+ <label id="searchbox-panel-operators"
+ value="&debuggerUI.searchPanelOperators;"/>
+ <hbox align="center">
+ <button id="global-operator-button"
+ class="searchbox-panel-operator-button devtools-monospace"
+ command="globalSearchCommand"/>
+ <label id="global-operator-label"
+ class="plain searchbox-panel-operator-label"/>
+ </hbox>
+ <hbox align="center">
+ <button id="function-operator-button"
+ class="searchbox-panel-operator-button devtools-monospace"
+ command="functionSearchCommand"/>
+ <label id="function-operator-label"
+ class="plain searchbox-panel-operator-label"/>
+ </hbox>
+ <hbox align="center">
+ <button id="token-operator-button"
+ class="searchbox-panel-operator-button devtools-monospace"
+ command="tokenSearchCommand"/>
+ <label id="token-operator-label"
+ class="plain searchbox-panel-operator-label"/>
+ </hbox>
+ <hbox align="center">
+ <button id="line-operator-button"
+ class="searchbox-panel-operator-button devtools-monospace"
+ command="lineSearchCommand"/>
+ <label id="line-operator-label"
+ class="plain searchbox-panel-operator-label"/>
+ </hbox>
+ <hbox align="center">
+ <button id="variable-operator-button"
+ class="searchbox-panel-operator-button devtools-monospace"
+ command="variableSearchCommand"/>
+ <label id="variable-operator-label"
+ class="plain searchbox-panel-operator-label"/>
+ </hbox>
+ </vbox>
+ </panel>
+
+ <panel id="conditional-breakpoint-panel"
+ level="top"
+ type="arrow"
+ noautofocus="true"
+ consumeoutsideclicks="false">
+ <vbox>
+ <label id="conditional-breakpoint-panel-description"
+ value="&debuggerUI.condBreakPanelTitle;"/>
+ <textbox id="conditional-breakpoint-panel-textbox"/>
+ </vbox>
+ </panel>
+</window>
diff --git a/devtools/client/debugger/moz.build b/devtools/client/debugger/moz.build
new file mode 100644
index 000000000..9719ec002
--- /dev/null
+++ b/devtools/client/debugger/moz.build
@@ -0,0 +1,20 @@
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+DIRS += [
+ 'content',
+ 'new'
+]
+
+DevToolsModules(
+ 'debugger-commands.js',
+ 'panel.js'
+)
+
+BROWSER_CHROME_MANIFESTS += [
+ 'new/test/mochitest/browser.ini',
+ 'test/mochitest/browser.ini',
+ 'test/mochitest/browser2.ini'
+]
diff --git a/devtools/client/debugger/new/bundle.js b/devtools/client/debugger/new/bundle.js
new file mode 100644
index 000000000..cbbd15a44
--- /dev/null
+++ b/devtools/client/debugger/new/bundle.js
@@ -0,0 +1,58335 @@
+// Generated from: 8175aacaec380ecf859183ad62bee2a9aef180d2 Disable searching test because it's timing out on try on certain platforms for some reason
+
+var Debugger =
+/******/ (function(modules) { // webpackBootstrap
+/******/ // The module cache
+/******/ var installedModules = {};
+/******/
+/******/ // The require function
+/******/ function __webpack_require__(moduleId) {
+/******/
+/******/ // Check if module is in cache
+/******/ if(installedModules[moduleId])
+/******/ return installedModules[moduleId].exports;
+/******/
+/******/ // Create a new module (and put it into the cache)
+/******/ var module = installedModules[moduleId] = {
+/******/ exports: {},
+/******/ id: moduleId,
+/******/ loaded: false
+/******/ };
+/******/
+/******/ // Execute the module function
+/******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
+/******/
+/******/ // Flag the module as loaded
+/******/ module.loaded = true;
+/******/
+/******/ // Return the exports of the module
+/******/ return module.exports;
+/******/ }
+/******/
+/******/
+/******/ // expose the modules object (__webpack_modules__)
+/******/ __webpack_require__.m = modules;
+/******/
+/******/ // expose the module cache
+/******/ __webpack_require__.c = installedModules;
+/******/
+/******/ // __webpack_public_path__
+/******/ __webpack_require__.p = "/public/build";
+/******/
+/******/ // Load entry module and return exports
+/******/ return __webpack_require__(0);
+/******/ })
+/************************************************************************/
+/******/ ([
+/* 0 */
+/***/ function(module, exports, __webpack_require__) {
+
+ module.exports = __webpack_require__(1);
+
+
+/***/ },
+/* 1 */
+/***/ function(module, exports, __webpack_require__) {
+
+ var React = __webpack_require__(2);
+
+ var _require = __webpack_require__(3);
+
+ var bindActionCreators = _require.bindActionCreators;
+ var combineReducers = _require.combineReducers;
+
+ var ReactDOM = __webpack_require__(16);
+
+ var _require2 = __webpack_require__(18);
+
+ var _require2$client = _require2.client;
+ var getClient = _require2$client.getClient;
+ var firefox = _require2$client.firefox;
+ var renderRoot = _require2.renderRoot;
+ var bootstrap = _require2.bootstrap;
+
+ var _require3 = __webpack_require__(89);
+
+ var getValue = _require3.getValue;
+ var isFirefoxPanel = _require3.isFirefoxPanel;
+
+
+ var configureStore = __webpack_require__(238);
+
+ var reducers = __webpack_require__(249);
+ var selectors = __webpack_require__(259);
+
+ var App = __webpack_require__(260);
+
+ var createStore = configureStore({
+ log: getValue("logging.actions"),
+ makeThunkArgs: (args, state) => {
+ return Object.assign({}, args, { client: getClient(state) });
+ }
+ });
+
+ var store = createStore(combineReducers(reducers));
+ var actions = bindActionCreators(__webpack_require__(262), store.dispatch);
+
+ if (!isFirefoxPanel()) {
+ L10N.setBundle(__webpack_require__(458));
+ }
+
+ window.appStore = store;
+
+ // Expose the bound actions so external things can do things like
+ // selecting a source.
+ window.actions = {
+ selectSource: actions.selectSource,
+ selectSourceURL: actions.selectSourceURL
+ };
+
+ function unmountRoot() {
+ var mount = document.querySelector("#mount");
+ ReactDOM.unmountComponentAtNode(mount);
+ }
+
+ if (isFirefoxPanel()) {
+ (function () {
+ var sourceMap = __webpack_require__(264);
+ var prettyPrint = __webpack_require__(276);
+
+ module.exports = {
+ bootstrap: (_ref) => {
+ var threadClient = _ref.threadClient;
+ var tabTarget = _ref.tabTarget;
+ var toolbox = _ref.toolbox;
+ var L10N = _ref.L10N;
+
+ // TODO (jlast) remove when the panel has L10N
+ if (L10N) {
+ window.L10N = L10N;
+ } else {
+ window.L10N = __webpack_require__(459);
+ window.L10N.setBundle(__webpack_require__(458));
+ }
+
+ firefox.setThreadClient(threadClient);
+ firefox.setTabTarget(tabTarget);
+ renderRoot(React, ReactDOM, App, store);
+ return firefox.initPage(actions);
+ },
+ destroy: () => {
+ unmountRoot();
+ sourceMap.destroyWorker();
+ prettyPrint.destroyWorker();
+ },
+ store: store,
+ actions: actions,
+ selectors: selectors,
+ client: firefox.clientCommands
+ };
+ })();
+ } else {
+ bootstrap(React, ReactDOM, App, actions, store);
+ }
+
+/***/ },
+/* 2 */
+/***/ function(module, exports) {
+
+ module.exports = devtoolsRequire("devtools/client/shared/vendor/react");
+
+/***/ },
+/* 3 */
+/***/ function(module, exports, __webpack_require__) {
+
+ 'use strict';
+
+ exports.__esModule = true;
+ exports.compose = exports.applyMiddleware = exports.bindActionCreators = exports.combineReducers = exports.createStore = undefined;
+
+ var _createStore = __webpack_require__(4);
+
+ var _createStore2 = _interopRequireDefault(_createStore);
+
+ var _combineReducers = __webpack_require__(11);
+
+ var _combineReducers2 = _interopRequireDefault(_combineReducers);
+
+ var _bindActionCreators = __webpack_require__(13);
+
+ var _bindActionCreators2 = _interopRequireDefault(_bindActionCreators);
+
+ var _applyMiddleware = __webpack_require__(14);
+
+ var _applyMiddleware2 = _interopRequireDefault(_applyMiddleware);
+
+ var _compose = __webpack_require__(15);
+
+ var _compose2 = _interopRequireDefault(_compose);
+
+ var _warning = __webpack_require__(12);
+
+ var _warning2 = _interopRequireDefault(_warning);
+
+ function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; }
+
+ /*
+ * This is a dummy function to check if the function name has been altered by minification.
+ * If the function has been minified and NODE_ENV !== 'production', warn the user.
+ */
+ function isCrushed() {}
+
+ if (false) {
+ (0, _warning2["default"])('You are currently using minified code outside of NODE_ENV === \'production\'. ' + 'This means that you are running a slower development build of Redux. ' + 'You can use loose-envify (https://github.com/zertosh/loose-envify) for browserify ' + 'or DefinePlugin for webpack (http://stackoverflow.com/questions/30030031) ' + 'to ensure you have the correct code for your production build.');
+ }
+
+ exports.createStore = _createStore2["default"];
+ exports.combineReducers = _combineReducers2["default"];
+ exports.bindActionCreators = _bindActionCreators2["default"];
+ exports.applyMiddleware = _applyMiddleware2["default"];
+ exports.compose = _compose2["default"];
+
+/***/ },
+/* 4 */
+/***/ function(module, exports, __webpack_require__) {
+
+ 'use strict';
+
+ exports.__esModule = true;
+ exports.ActionTypes = undefined;
+ exports["default"] = createStore;
+
+ var _isPlainObject = __webpack_require__(5);
+
+ var _isPlainObject2 = _interopRequireDefault(_isPlainObject);
+
+ var _symbolObservable = __webpack_require__(9);
+
+ var _symbolObservable2 = _interopRequireDefault(_symbolObservable);
+
+ function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; }
+
+ /**
+ * These are private action types reserved by Redux.
+ * For any unknown actions, you must return the current state.
+ * If the current state is undefined, you must return the initial state.
+ * Do not reference these action types directly in your code.
+ */
+ var ActionTypes = exports.ActionTypes = {
+ INIT: '@@redux/INIT'
+ };
+
+ /**
+ * Creates a Redux store that holds the state tree.
+ * The only way to change the data in the store is to call `dispatch()` on it.
+ *
+ * There should only be a single store in your app. To specify how different
+ * parts of the state tree respond to actions, you may combine several reducers
+ * into a single reducer function by using `combineReducers`.
+ *
+ * @param {Function} reducer A function that returns the next state tree, given
+ * the current state tree and the action to handle.
+ *
+ * @param {any} [initialState] The initial state. You may optionally specify it
+ * to hydrate the state from the server in universal apps, or to restore a
+ * previously serialized user session.
+ * If you use `combineReducers` to produce the root reducer function, this must be
+ * an object with the same shape as `combineReducers` keys.
+ *
+ * @param {Function} enhancer The store enhancer. You may optionally specify it
+ * to enhance the store with third-party capabilities such as middleware,
+ * time travel, persistence, etc. The only store enhancer that ships with Redux
+ * is `applyMiddleware()`.
+ *
+ * @returns {Store} A Redux store that lets you read the state, dispatch actions
+ * and subscribe to changes.
+ */
+ function createStore(reducer, initialState, enhancer) {
+ var _ref2;
+
+ if (typeof initialState === 'function' && typeof enhancer === 'undefined') {
+ enhancer = initialState;
+ initialState = undefined;
+ }
+
+ if (typeof enhancer !== 'undefined') {
+ if (typeof enhancer !== 'function') {
+ throw new Error('Expected the enhancer to be a function.');
+ }
+
+ return enhancer(createStore)(reducer, initialState);
+ }
+
+ if (typeof reducer !== 'function') {
+ throw new Error('Expected the reducer to be a function.');
+ }
+
+ var currentReducer = reducer;
+ var currentState = initialState;
+ var currentListeners = [];
+ var nextListeners = currentListeners;
+ var isDispatching = false;
+
+ function ensureCanMutateNextListeners() {
+ if (nextListeners === currentListeners) {
+ nextListeners = currentListeners.slice();
+ }
+ }
+
+ /**
+ * Reads the state tree managed by the store.
+ *
+ * @returns {any} The current state tree of your application.
+ */
+ function getState() {
+ return currentState;
+ }
+
+ /**
+ * Adds a change listener. It will be called any time an action is dispatched,
+ * and some part of the state tree may potentially have changed. You may then
+ * call `getState()` to read the current state tree inside the callback.
+ *
+ * You may call `dispatch()` from a change listener, with the following
+ * caveats:
+ *
+ * 1. The subscriptions are snapshotted just before every `dispatch()` call.
+ * If you subscribe or unsubscribe while the listeners are being invoked, this
+ * will not have any effect on the `dispatch()` that is currently in progress.
+ * However, the next `dispatch()` call, whether nested or not, will use a more
+ * recent snapshot of the subscription list.
+ *
+ * 2. The listener should not expect to see all state changes, as the state
+ * might have been updated multiple times during a nested `dispatch()` before
+ * the listener is called. It is, however, guaranteed that all subscribers
+ * registered before the `dispatch()` started will be called with the latest
+ * state by the time it exits.
+ *
+ * @param {Function} listener A callback to be invoked on every dispatch.
+ * @returns {Function} A function to remove this change listener.
+ */
+ function subscribe(listener) {
+ if (typeof listener !== 'function') {
+ throw new Error('Expected listener to be a function.');
+ }
+
+ var isSubscribed = true;
+
+ ensureCanMutateNextListeners();
+ nextListeners.push(listener);
+
+ return function unsubscribe() {
+ if (!isSubscribed) {
+ return;
+ }
+
+ isSubscribed = false;
+
+ ensureCanMutateNextListeners();
+ var index = nextListeners.indexOf(listener);
+ nextListeners.splice(index, 1);
+ };
+ }
+
+ /**
+ * Dispatches an action. It is the only way to trigger a state change.
+ *
+ * The `reducer` function, used to create the store, will be called with the
+ * current state tree and the given `action`. Its return value will
+ * be considered the **next** state of the tree, and the change listeners
+ * will be notified.
+ *
+ * The base implementation only supports plain object actions. If you want to
+ * dispatch a Promise, an Observable, a thunk, or something else, you need to
+ * wrap your store creating function into the corresponding middleware. For
+ * example, see the documentation for the `redux-thunk` package. Even the
+ * middleware will eventually dispatch plain object actions using this method.
+ *
+ * @param {Object} action A plain object representing “what changed”. It is
+ * a good idea to keep actions serializable so you can record and replay user
+ * sessions, or use the time travelling `redux-devtools`. An action must have
+ * a `type` property which may not be `undefined`. It is a good idea to use
+ * string constants for action types.
+ *
+ * @returns {Object} For convenience, the same action object you dispatched.
+ *
+ * Note that, if you use a custom middleware, it may wrap `dispatch()` to
+ * return something else (for example, a Promise you can await).
+ */
+ function dispatch(action) {
+ if (!(0, _isPlainObject2["default"])(action)) {
+ throw new Error('Actions must be plain objects. ' + 'Use custom middleware for async actions.');
+ }
+
+ if (typeof action.type === 'undefined') {
+ throw new Error('Actions may not have an undefined "type" property. ' + 'Have you misspelled a constant?');
+ }
+
+ if (isDispatching) {
+ throw new Error('Reducers may not dispatch actions.');
+ }
+
+ try {
+ isDispatching = true;
+ currentState = currentReducer(currentState, action);
+ } finally {
+ isDispatching = false;
+ }
+
+ var listeners = currentListeners = nextListeners;
+ for (var i = 0; i < listeners.length; i++) {
+ listeners[i]();
+ }
+
+ return action;
+ }
+
+ /**
+ * Replaces the reducer currently used by the store to calculate the state.
+ *
+ * You might need this if your app implements code splitting and you want to
+ * load some of the reducers dynamically. You might also need this if you
+ * implement a hot reloading mechanism for Redux.
+ *
+ * @param {Function} nextReducer The reducer for the store to use instead.
+ * @returns {void}
+ */
+ function replaceReducer(nextReducer) {
+ if (typeof nextReducer !== 'function') {
+ throw new Error('Expected the nextReducer to be a function.');
+ }
+
+ currentReducer = nextReducer;
+ dispatch({ type: ActionTypes.INIT });
+ }
+
+ /**
+ * Interoperability point for observable/reactive libraries.
+ * @returns {observable} A minimal observable of state changes.
+ * For more information, see the observable proposal:
+ * https://github.com/zenparsing/es-observable
+ */
+ function observable() {
+ var _ref;
+
+ var outerSubscribe = subscribe;
+ return _ref = {
+ /**
+ * The minimal observable subscription method.
+ * @param {Object} observer Any object that can be used as an observer.
+ * The observer object should have a `next` method.
+ * @returns {subscription} An object with an `unsubscribe` method that can
+ * be used to unsubscribe the observable from the store, and prevent further
+ * emission of values from the observable.
+ */
+
+ subscribe: function subscribe(observer) {
+ if (typeof observer !== 'object') {
+ throw new TypeError('Expected the observer to be an object.');
+ }
+
+ function observeState() {
+ if (observer.next) {
+ observer.next(getState());
+ }
+ }
+
+ observeState();
+ var unsubscribe = outerSubscribe(observeState);
+ return { unsubscribe: unsubscribe };
+ }
+ }, _ref[_symbolObservable2["default"]] = function () {
+ return this;
+ }, _ref;
+ }
+
+ // When a store is created, an "INIT" action is dispatched so that every
+ // reducer returns their initial state. This effectively populates
+ // the initial state tree.
+ dispatch({ type: ActionTypes.INIT });
+
+ return _ref2 = {
+ dispatch: dispatch,
+ subscribe: subscribe,
+ getState: getState,
+ replaceReducer: replaceReducer
+ }, _ref2[_symbolObservable2["default"]] = observable, _ref2;
+ }
+
+/***/ },
+/* 5 */
+/***/ function(module, exports, __webpack_require__) {
+
+ var getPrototype = __webpack_require__(6),
+ isObjectLike = __webpack_require__(8);
+
+ /** `Object#toString` result references. */
+ var objectTag = '[object Object]';
+
+ /** Used for built-in method references. */
+ var funcProto = Function.prototype,
+ objectProto = Object.prototype;
+
+ /** Used to resolve the decompiled source of functions. */
+ var funcToString = funcProto.toString;
+
+ /** Used to check objects for own properties. */
+ var hasOwnProperty = objectProto.hasOwnProperty;
+
+ /** Used to infer the `Object` constructor. */
+ var objectCtorString = funcToString.call(Object);
+
+ /**
+ * Used to resolve the
+ * [`toStringTag`](http://ecma-international.org/ecma-262/7.0/#sec-object.prototype.tostring)
+ * of values.
+ */
+ var objectToString = objectProto.toString;
+
+ /**
+ * Checks if `value` is a plain object, that is, an object created by the
+ * `Object` constructor or one with a `[[Prototype]]` of `null`.
+ *
+ * @static
+ * @memberOf _
+ * @since 0.8.0
+ * @category Lang
+ * @param {*} value The value to check.
+ * @returns {boolean} Returns `true` if `value` is a plain object, else `false`.
+ * @example
+ *
+ * function Foo() {
+ * this.a = 1;
+ * }
+ *
+ * _.isPlainObject(new Foo);
+ * // => false
+ *
+ * _.isPlainObject([1, 2, 3]);
+ * // => false
+ *
+ * _.isPlainObject({ 'x': 0, 'y': 0 });
+ * // => true
+ *
+ * _.isPlainObject(Object.create(null));
+ * // => true
+ */
+ function isPlainObject(value) {
+ if (!isObjectLike(value) || objectToString.call(value) != objectTag) {
+ return false;
+ }
+ var proto = getPrototype(value);
+ if (proto === null) {
+ return true;
+ }
+ var Ctor = hasOwnProperty.call(proto, 'constructor') && proto.constructor;
+ return (typeof Ctor == 'function' &&
+ Ctor instanceof Ctor && funcToString.call(Ctor) == objectCtorString);
+ }
+
+ module.exports = isPlainObject;
+
+
+/***/ },
+/* 6 */
+/***/ function(module, exports, __webpack_require__) {
+
+ var overArg = __webpack_require__(7);
+
+ /** Built-in value references. */
+ var getPrototype = overArg(Object.getPrototypeOf, Object);
+
+ module.exports = getPrototype;
+
+
+/***/ },
+/* 7 */
+/***/ function(module, exports) {
+
+ /**
+ * Creates a unary function that invokes `func` with its argument transformed.
+ *
+ * @private
+ * @param {Function} func The function to wrap.
+ * @param {Function} transform The argument transform.
+ * @returns {Function} Returns the new function.
+ */
+ function overArg(func, transform) {
+ return function(arg) {
+ return func(transform(arg));
+ };
+ }
+
+ module.exports = overArg;
+
+
+/***/ },
+/* 8 */
+/***/ function(module, exports) {
+
+ /**
+ * Checks if `value` is object-like. A value is object-like if it's not `null`
+ * and has a `typeof` result of "object".
+ *
+ * @static
+ * @memberOf _
+ * @since 4.0.0
+ * @category Lang
+ * @param {*} value The value to check.
+ * @returns {boolean} Returns `true` if `value` is object-like, else `false`.
+ * @example
+ *
+ * _.isObjectLike({});
+ * // => true
+ *
+ * _.isObjectLike([1, 2, 3]);
+ * // => true
+ *
+ * _.isObjectLike(_.noop);
+ * // => false
+ *
+ * _.isObjectLike(null);
+ * // => false
+ */
+ function isObjectLike(value) {
+ return value != null && typeof value == 'object';
+ }
+
+ module.exports = isObjectLike;
+
+
+/***/ },
+/* 9 */
+/***/ function(module, exports, __webpack_require__) {
+
+ /* WEBPACK VAR INJECTION */(function(global) {/* global window */
+ 'use strict';
+
+ module.exports = __webpack_require__(10)(global || window || this);
+
+ /* WEBPACK VAR INJECTION */}.call(exports, (function() { return this; }())))
+
+/***/ },
+/* 10 */
+/***/ function(module, exports) {
+
+ 'use strict';
+
+ module.exports = function symbolObservablePonyfill(root) {
+ var result;
+ var Symbol = root.Symbol;
+
+ if (typeof Symbol === 'function') {
+ if (Symbol.observable) {
+ result = Symbol.observable;
+ } else {
+ result = Symbol('observable');
+ Symbol.observable = result;
+ }
+ } else {
+ result = '@@observable';
+ }
+
+ return result;
+ };
+
+
+/***/ },
+/* 11 */
+/***/ function(module, exports, __webpack_require__) {
+
+ 'use strict';
+
+ exports.__esModule = true;
+ exports["default"] = combineReducers;
+
+ var _createStore = __webpack_require__(4);
+
+ var _isPlainObject = __webpack_require__(5);
+
+ var _isPlainObject2 = _interopRequireDefault(_isPlainObject);
+
+ var _warning = __webpack_require__(12);
+
+ var _warning2 = _interopRequireDefault(_warning);
+
+ function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; }
+
+ function getUndefinedStateErrorMessage(key, action) {
+ var actionType = action && action.type;
+ var actionName = actionType && '"' + actionType.toString() + '"' || 'an action';
+
+ return 'Given action ' + actionName + ', reducer "' + key + '" returned undefined. ' + 'To ignore an action, you must explicitly return the previous state.';
+ }
+
+ function getUnexpectedStateShapeWarningMessage(inputState, reducers, action) {
+ var reducerKeys = Object.keys(reducers);
+ var argumentName = action && action.type === _createStore.ActionTypes.INIT ? 'initialState argument passed to createStore' : 'previous state received by the reducer';
+
+ if (reducerKeys.length === 0) {
+ return 'Store does not have a valid reducer. Make sure the argument passed ' + 'to combineReducers is an object whose values are reducers.';
+ }
+
+ if (!(0, _isPlainObject2["default"])(inputState)) {
+ return 'The ' + argumentName + ' has unexpected type of "' + {}.toString.call(inputState).match(/\s([a-z|A-Z]+)/)[1] + '". Expected argument to be an object with the following ' + ('keys: "' + reducerKeys.join('", "') + '"');
+ }
+
+ var unexpectedKeys = Object.keys(inputState).filter(function (key) {
+ return !reducers.hasOwnProperty(key);
+ });
+
+ if (unexpectedKeys.length > 0) {
+ return 'Unexpected ' + (unexpectedKeys.length > 1 ? 'keys' : 'key') + ' ' + ('"' + unexpectedKeys.join('", "') + '" found in ' + argumentName + '. ') + 'Expected to find one of the known reducer keys instead: ' + ('"' + reducerKeys.join('", "') + '". Unexpected keys will be ignored.');
+ }
+ }
+
+ function assertReducerSanity(reducers) {
+ Object.keys(reducers).forEach(function (key) {
+ var reducer = reducers[key];
+ var initialState = reducer(undefined, { type: _createStore.ActionTypes.INIT });
+
+ if (typeof initialState === 'undefined') {
+ throw new Error('Reducer "' + key + '" returned undefined during initialization. ' + 'If the state passed to the reducer is undefined, you must ' + 'explicitly return the initial state. The initial state may ' + 'not be undefined.');
+ }
+
+ var type = '@@redux/PROBE_UNKNOWN_ACTION_' + Math.random().toString(36).substring(7).split('').join('.');
+ if (typeof reducer(undefined, { type: type }) === 'undefined') {
+ throw new Error('Reducer "' + key + '" returned undefined when probed with a random type. ' + ('Don\'t try to handle ' + _createStore.ActionTypes.INIT + ' or other actions in "redux/*" ') + 'namespace. They are considered private. Instead, you must return the ' + 'current state for any unknown actions, unless it is undefined, ' + 'in which case you must return the initial state, regardless of the ' + 'action type. The initial state may not be undefined.');
+ }
+ });
+ }
+
+ /**
+ * Turns an object whose values are different reducer functions, into a single
+ * reducer function. It will call every child reducer, and gather their results
+ * into a single state object, whose keys correspond to the keys of the passed
+ * reducer functions.
+ *
+ * @param {Object} reducers An object whose values correspond to different
+ * reducer functions that need to be combined into one. One handy way to obtain
+ * it is to use ES6 `import * as reducers` syntax. The reducers may never return
+ * undefined for any action. Instead, they should return their initial state
+ * if the state passed to them was undefined, and the current state for any
+ * unrecognized action.
+ *
+ * @returns {Function} A reducer function that invokes every reducer inside the
+ * passed object, and builds a state object with the same shape.
+ */
+ function combineReducers(reducers) {
+ var reducerKeys = Object.keys(reducers);
+ var finalReducers = {};
+ for (var i = 0; i < reducerKeys.length; i++) {
+ var key = reducerKeys[i];
+ if (typeof reducers[key] === 'function') {
+ finalReducers[key] = reducers[key];
+ }
+ }
+ var finalReducerKeys = Object.keys(finalReducers);
+
+ var sanityError;
+ try {
+ assertReducerSanity(finalReducers);
+ } catch (e) {
+ sanityError = e;
+ }
+
+ return function combination() {
+ var state = arguments.length <= 0 || arguments[0] === undefined ? {} : arguments[0];
+ var action = arguments[1];
+
+ if (sanityError) {
+ throw sanityError;
+ }
+
+ if (false) {
+ var warningMessage = getUnexpectedStateShapeWarningMessage(state, finalReducers, action);
+ if (warningMessage) {
+ (0, _warning2["default"])(warningMessage);
+ }
+ }
+
+ var hasChanged = false;
+ var nextState = {};
+ for (var i = 0; i < finalReducerKeys.length; i++) {
+ var key = finalReducerKeys[i];
+ var reducer = finalReducers[key];
+ var previousStateForKey = state[key];
+ var nextStateForKey = reducer(previousStateForKey, action);
+ if (typeof nextStateForKey === 'undefined') {
+ var errorMessage = getUndefinedStateErrorMessage(key, action);
+ throw new Error(errorMessage);
+ }
+ nextState[key] = nextStateForKey;
+ hasChanged = hasChanged || nextStateForKey !== previousStateForKey;
+ }
+ return hasChanged ? nextState : state;
+ };
+ }
+
+/***/ },
+/* 12 */
+/***/ function(module, exports) {
+
+ 'use strict';
+
+ exports.__esModule = true;
+ exports["default"] = warning;
+ /**
+ * Prints a warning in the console if it exists.
+ *
+ * @param {String} message The warning message.
+ * @returns {void}
+ */
+ function warning(message) {
+ /* eslint-disable no-console */
+ if (typeof console !== 'undefined' && typeof console.error === 'function') {
+ console.error(message);
+ }
+ /* eslint-enable no-console */
+ try {
+ // This error was thrown as a convenience so that if you enable
+ // "break on all exceptions" in your console,
+ // it would pause the execution at this line.
+ throw new Error(message);
+ /* eslint-disable no-empty */
+ } catch (e) {}
+ /* eslint-enable no-empty */
+ }
+
+/***/ },
+/* 13 */
+/***/ function(module, exports) {
+
+ 'use strict';
+
+ exports.__esModule = true;
+ exports["default"] = bindActionCreators;
+ function bindActionCreator(actionCreator, dispatch) {
+ return function () {
+ return dispatch(actionCreator.apply(undefined, arguments));
+ };
+ }
+
+ /**
+ * Turns an object whose values are action creators, into an object with the
+ * same keys, but with every function wrapped into a `dispatch` call so they
+ * may be invoked directly. This is just a convenience method, as you can call
+ * `store.dispatch(MyActionCreators.doSomething())` yourself just fine.
+ *
+ * For convenience, you can also pass a single function as the first argument,
+ * and get a function in return.
+ *
+ * @param {Function|Object} actionCreators An object whose values are action
+ * creator functions. One handy way to obtain it is to use ES6 `import * as`
+ * syntax. You may also pass a single function.
+ *
+ * @param {Function} dispatch The `dispatch` function available on your Redux
+ * store.
+ *
+ * @returns {Function|Object} The object mimicking the original object, but with
+ * every action creator wrapped into the `dispatch` call. If you passed a
+ * function as `actionCreators`, the return value will also be a single
+ * function.
+ */
+ function bindActionCreators(actionCreators, dispatch) {
+ if (typeof actionCreators === 'function') {
+ return bindActionCreator(actionCreators, dispatch);
+ }
+
+ if (typeof actionCreators !== 'object' || actionCreators === null) {
+ throw new Error('bindActionCreators expected an object or a function, instead received ' + (actionCreators === null ? 'null' : typeof actionCreators) + '. ' + 'Did you write "import ActionCreators from" instead of "import * as ActionCreators from"?');
+ }
+
+ var keys = Object.keys(actionCreators);
+ var boundActionCreators = {};
+ for (var i = 0; i < keys.length; i++) {
+ var key = keys[i];
+ var actionCreator = actionCreators[key];
+ if (typeof actionCreator === 'function') {
+ boundActionCreators[key] = bindActionCreator(actionCreator, dispatch);
+ }
+ }
+ return boundActionCreators;
+ }
+
+/***/ },
+/* 14 */
+/***/ function(module, exports, __webpack_require__) {
+
+ 'use strict';
+
+ exports.__esModule = true;
+
+ var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; };
+
+ exports["default"] = applyMiddleware;
+
+ var _compose = __webpack_require__(15);
+
+ var _compose2 = _interopRequireDefault(_compose);
+
+ function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; }
+
+ /**
+ * Creates a store enhancer that applies middleware to the dispatch method
+ * of the Redux store. This is handy for a variety of tasks, such as expressing
+ * asynchronous actions in a concise manner, or logging every action payload.
+ *
+ * See `redux-thunk` package as an example of the Redux middleware.
+ *
+ * Because middleware is potentially asynchronous, this should be the first
+ * store enhancer in the composition chain.
+ *
+ * Note that each middleware will be given the `dispatch` and `getState` functions
+ * as named arguments.
+ *
+ * @param {...Function} middlewares The middleware chain to be applied.
+ * @returns {Function} A store enhancer applying the middleware.
+ */
+ function applyMiddleware() {
+ for (var _len = arguments.length, middlewares = Array(_len), _key = 0; _key < _len; _key++) {
+ middlewares[_key] = arguments[_key];
+ }
+
+ return function (createStore) {
+ return function (reducer, initialState, enhancer) {
+ var store = createStore(reducer, initialState, enhancer);
+ var _dispatch = store.dispatch;
+ var chain = [];
+
+ var middlewareAPI = {
+ getState: store.getState,
+ dispatch: function dispatch(action) {
+ return _dispatch(action);
+ }
+ };
+ chain = middlewares.map(function (middleware) {
+ return middleware(middlewareAPI);
+ });
+ _dispatch = _compose2["default"].apply(undefined, chain)(store.dispatch);
+
+ return _extends({}, store, {
+ dispatch: _dispatch
+ });
+ };
+ };
+ }
+
+/***/ },
+/* 15 */
+/***/ function(module, exports) {
+
+ "use strict";
+
+ exports.__esModule = true;
+ exports["default"] = compose;
+ /**
+ * Composes single-argument functions from right to left. The rightmost
+ * function can take multiple arguments as it provides the signature for
+ * the resulting composite function.
+ *
+ * @param {...Function} funcs The functions to compose.
+ * @returns {Function} A function obtained by composing the argument functions
+ * from right to left. For example, compose(f, g, h) is identical to doing
+ * (...args) => f(g(h(...args))).
+ */
+
+ function compose() {
+ for (var _len = arguments.length, funcs = Array(_len), _key = 0; _key < _len; _key++) {
+ funcs[_key] = arguments[_key];
+ }
+
+ if (funcs.length === 0) {
+ return function (arg) {
+ return arg;
+ };
+ } else {
+ var _ret = function () {
+ var last = funcs[funcs.length - 1];
+ var rest = funcs.slice(0, -1);
+ return {
+ v: function v() {
+ return rest.reduceRight(function (composed, f) {
+ return f(composed);
+ }, last.apply(undefined, arguments));
+ }
+ };
+ }();
+
+ if (typeof _ret === "object") return _ret.v;
+ }
+ }
+
+/***/ },
+/* 16 */
+/***/ function(module, exports, __webpack_require__) {
+
+ 'use strict';
+
+ module.exports = __webpack_require__(17);
+
+
+/***/ },
+/* 17 */
+/***/ function(module, exports) {
+
+ module.exports = devtoolsRequire("devtools/client/shared/vendor/react-dom");
+
+/***/ },
+/* 18 */
+/***/ function(module, exports, __webpack_require__) {
+
+ /* global window, document, DebuggerConfig */
+
+ var _require = __webpack_require__(3);
+
+ var bindActionCreators = _require.bindActionCreators;
+ var combineReducers = _require.combineReducers;
+
+ var _require2 = __webpack_require__(19);
+
+ var Provider = _require2.Provider;
+
+ var _require3 = __webpack_require__(28);
+
+ var DevToolsUtils = _require3.DevToolsUtils;
+ var AppConstants = _require3.AppConstants;
+
+ var _require4 = __webpack_require__(88);
+
+ var injectGlobals = _require4.injectGlobals;
+ var debugGlobal = _require4.debugGlobal;
+
+ var _require5 = __webpack_require__(89);
+
+ var setConfig = _require5.setConfig;
+ var isEnabled = _require5.isEnabled;
+ var getValue = _require5.getValue;
+ var isDevelopment = _require5.isDevelopment;
+
+
+ setConfig(({"environment":"firefox-panel","baseWorkerURL":"resource://devtools/client/debugger/new/","logging":false,"clientLogging":false,"features":{"tabs":true,"sourceMaps":true,"prettyPrint":true}}));
+
+ // Set various flags before requiring app code.
+ if (isEnabled("logging.client")) {
+ DevToolsUtils.dumpn.wantLogging = true;
+ }
+
+ var client = __webpack_require__(141);
+ var getClient = client.getClient;
+ var connectClients = client.connectClients;
+ var startDebugging = client.startDebugging;
+
+
+ var Root = __webpack_require__(210);
+
+ // Using this static variable allows webpack to know at compile-time
+ // to avoid this require and not include it at all in the output.
+ if (false) {
+ var theme = getValue("theme");
+ switch (theme) {
+ case "dark":
+ require("./lib/themes/dark-theme.css");break;
+ case "light":
+ require("./lib/themes/light-theme.css");break;
+ case "firebug":
+ require("./lib/themes/firebug-theme.css");break;
+ }
+ document.body.parentNode.classList.add(`theme-${ theme }`);
+
+ window.L10N = require("./utils/L10N");
+ }
+
+ function initApp() {
+ var configureStore = __webpack_require__(216);
+ var reducers = __webpack_require__(226);
+ var LandingPage = __webpack_require__(231);
+
+ var createStore = configureStore({
+ log: getValue("logging.actions"),
+ makeThunkArgs: (args, state) => {
+ return Object.assign({}, args, { client: getClient(state) });
+ }
+ });
+
+ var store = createStore(combineReducers(reducers));
+ var actions = bindActionCreators(__webpack_require__(236), store.dispatch);
+
+ if (isDevelopment()) {
+ AppConstants.DEBUG_JS_MODULES = true;
+ injectGlobals({ store });
+ }
+
+ return { store, actions, LandingPage };
+ }
+
+ function renderRoot(_React, _ReactDOM, component, _store) {
+ var mount = document.querySelector("#mount");
+
+ // bail in test environments that do not have a mount
+ if (!mount) {
+ return;
+ }
+
+ _ReactDOM.render(_React.createElement(Provider, { store: _store }, Root(component)), mount);
+ }
+
+ function getTargetFromQuery() {
+ var href = window.location.href;
+ var nodeMatch = href.match(/ws=([^&#]*)/);
+ var firefoxMatch = href.match(/firefox-tab=([^&#]*)/);
+ var chromeMatch = href.match(/chrome-tab=([^&#]*)/);
+
+ if (nodeMatch) {
+ return { type: "node", param: nodeMatch[1] };
+ } else if (firefoxMatch) {
+ return { type: "firefox", param: firefoxMatch[1] };
+ } else if (chromeMatch) {
+ return { type: "chrome", param: chromeMatch[1] };
+ }
+
+ return null;
+ }
+
+ function bootstrap(React, ReactDOM, App, appActions, appStore) {
+ var connTarget = getTargetFromQuery();
+ if (connTarget) {
+ startDebugging(connTarget, appActions).then(tabs => {
+ renderRoot(React, ReactDOM, App, appStore);
+ });
+ } else {
+ (function () {
+ var _initApp = initApp();
+
+ var store = _initApp.store;
+ var actions = _initApp.actions;
+ var LandingPage = _initApp.LandingPage;
+
+ renderRoot(React, ReactDOM, LandingPage, store);
+ connectClients(tabs => actions.newTabs(tabs));
+ })();
+ }
+ }
+
+ module.exports = {
+ bootstrap,
+ renderRoot,
+ debugGlobal,
+ client
+ };
+
+/***/ },
+/* 19 */
+/***/ function(module, exports, __webpack_require__) {
+
+ 'use strict';
+
+ exports.__esModule = true;
+ exports.connect = exports.Provider = undefined;
+
+ var _Provider = __webpack_require__(20);
+
+ var _Provider2 = _interopRequireDefault(_Provider);
+
+ var _connect = __webpack_require__(23);
+
+ var _connect2 = _interopRequireDefault(_connect);
+
+ function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; }
+
+ exports.Provider = _Provider2["default"];
+ exports.connect = _connect2["default"];
+
+/***/ },
+/* 20 */
+/***/ function(module, exports, __webpack_require__) {
+
+ 'use strict';
+
+ exports.__esModule = true;
+ exports["default"] = undefined;
+
+ var _react = __webpack_require__(2);
+
+ var _storeShape = __webpack_require__(21);
+
+ var _storeShape2 = _interopRequireDefault(_storeShape);
+
+ var _warning = __webpack_require__(22);
+
+ var _warning2 = _interopRequireDefault(_warning);
+
+ function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; }
+
+ function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
+
+ function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; }
+
+ function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }
+
+ var didWarnAboutReceivingStore = false;
+ function warnAboutReceivingStore() {
+ if (didWarnAboutReceivingStore) {
+ return;
+ }
+ didWarnAboutReceivingStore = true;
+
+ (0, _warning2["default"])('<Provider> does not support changing `store` on the fly. ' + 'It is most likely that you see this error because you updated to ' + 'Redux 2.x and React Redux 2.x which no longer hot reload reducers ' + 'automatically. See https://github.com/reactjs/react-redux/releases/' + 'tag/v2.0.0 for the migration instructions.');
+ }
+
+ var Provider = function (_Component) {
+ _inherits(Provider, _Component);
+
+ Provider.prototype.getChildContext = function getChildContext() {
+ return { store: this.store };
+ };
+
+ function Provider(props, context) {
+ _classCallCheck(this, Provider);
+
+ var _this = _possibleConstructorReturn(this, _Component.call(this, props, context));
+
+ _this.store = props.store;
+ return _this;
+ }
+
+ Provider.prototype.render = function render() {
+ var children = this.props.children;
+
+ return _react.Children.only(children);
+ };
+
+ return Provider;
+ }(_react.Component);
+
+ exports["default"] = Provider;
+
+ if (false) {
+ Provider.prototype.componentWillReceiveProps = function (nextProps) {
+ var store = this.store;
+ var nextStore = nextProps.store;
+
+ if (store !== nextStore) {
+ warnAboutReceivingStore();
+ }
+ };
+ }
+
+ Provider.propTypes = {
+ store: _storeShape2["default"].isRequired,
+ children: _react.PropTypes.element.isRequired
+ };
+ Provider.childContextTypes = {
+ store: _storeShape2["default"].isRequired
+ };
+
+/***/ },
+/* 21 */
+/***/ function(module, exports, __webpack_require__) {
+
+ 'use strict';
+
+ exports.__esModule = true;
+
+ var _react = __webpack_require__(2);
+
+ exports["default"] = _react.PropTypes.shape({
+ subscribe: _react.PropTypes.func.isRequired,
+ dispatch: _react.PropTypes.func.isRequired,
+ getState: _react.PropTypes.func.isRequired
+ });
+
+/***/ },
+/* 22 */
+/***/ function(module, exports) {
+
+ 'use strict';
+
+ exports.__esModule = true;
+ exports["default"] = warning;
+ /**
+ * Prints a warning in the console if it exists.
+ *
+ * @param {String} message The warning message.
+ * @returns {void}
+ */
+ function warning(message) {
+ /* eslint-disable no-console */
+ if (typeof console !== 'undefined' && typeof console.error === 'function') {
+ console.error(message);
+ }
+ /* eslint-enable no-console */
+ try {
+ // This error was thrown as a convenience so that you can use this stack
+ // to find the callsite that caused this warning to fire.
+ throw new Error(message);
+ /* eslint-disable no-empty */
+ } catch (e) {}
+ /* eslint-enable no-empty */
+ }
+
+/***/ },
+/* 23 */
+/***/ function(module, exports, __webpack_require__) {
+
+ 'use strict';
+
+ var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; };
+
+ exports.__esModule = true;
+ exports["default"] = connect;
+
+ var _react = __webpack_require__(2);
+
+ var _storeShape = __webpack_require__(21);
+
+ var _storeShape2 = _interopRequireDefault(_storeShape);
+
+ var _shallowEqual = __webpack_require__(24);
+
+ var _shallowEqual2 = _interopRequireDefault(_shallowEqual);
+
+ var _wrapActionCreators = __webpack_require__(25);
+
+ var _wrapActionCreators2 = _interopRequireDefault(_wrapActionCreators);
+
+ var _warning = __webpack_require__(22);
+
+ var _warning2 = _interopRequireDefault(_warning);
+
+ var _isPlainObject = __webpack_require__(5);
+
+ var _isPlainObject2 = _interopRequireDefault(_isPlainObject);
+
+ var _hoistNonReactStatics = __webpack_require__(26);
+
+ var _hoistNonReactStatics2 = _interopRequireDefault(_hoistNonReactStatics);
+
+ var _invariant = __webpack_require__(27);
+
+ var _invariant2 = _interopRequireDefault(_invariant);
+
+ function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; }
+
+ function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
+
+ function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; }
+
+ function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }
+
+ var defaultMapStateToProps = function defaultMapStateToProps(state) {
+ return {};
+ }; // eslint-disable-line no-unused-vars
+ var defaultMapDispatchToProps = function defaultMapDispatchToProps(dispatch) {
+ return { dispatch: dispatch };
+ };
+ var defaultMergeProps = function defaultMergeProps(stateProps, dispatchProps, parentProps) {
+ return _extends({}, parentProps, stateProps, dispatchProps);
+ };
+
+ function getDisplayName(WrappedComponent) {
+ return WrappedComponent.displayName || WrappedComponent.name || 'Component';
+ }
+
+ var errorObject = { value: null };
+ function tryCatch(fn, ctx) {
+ try {
+ return fn.apply(ctx);
+ } catch (e) {
+ errorObject.value = e;
+ return errorObject;
+ }
+ }
+
+ // Helps track hot reloading.
+ var nextVersion = 0;
+
+ function connect(mapStateToProps, mapDispatchToProps, mergeProps) {
+ var options = arguments.length <= 3 || arguments[3] === undefined ? {} : arguments[3];
+
+ var shouldSubscribe = Boolean(mapStateToProps);
+ var mapState = mapStateToProps || defaultMapStateToProps;
+
+ var mapDispatch = undefined;
+ if (typeof mapDispatchToProps === 'function') {
+ mapDispatch = mapDispatchToProps;
+ } else if (!mapDispatchToProps) {
+ mapDispatch = defaultMapDispatchToProps;
+ } else {
+ mapDispatch = (0, _wrapActionCreators2["default"])(mapDispatchToProps);
+ }
+
+ var finalMergeProps = mergeProps || defaultMergeProps;
+ var _options$pure = options.pure;
+ var pure = _options$pure === undefined ? true : _options$pure;
+ var _options$withRef = options.withRef;
+ var withRef = _options$withRef === undefined ? false : _options$withRef;
+
+ var checkMergedEquals = pure && finalMergeProps !== defaultMergeProps;
+
+ // Helps track hot reloading.
+ var version = nextVersion++;
+
+ return function wrapWithConnect(WrappedComponent) {
+ var connectDisplayName = 'Connect(' + getDisplayName(WrappedComponent) + ')';
+
+ function checkStateShape(props, methodName) {
+ if (!(0, _isPlainObject2["default"])(props)) {
+ (0, _warning2["default"])(methodName + '() in ' + connectDisplayName + ' must return a plain object. ' + ('Instead received ' + props + '.'));
+ }
+ }
+
+ function computeMergedProps(stateProps, dispatchProps, parentProps) {
+ var mergedProps = finalMergeProps(stateProps, dispatchProps, parentProps);
+ if (false) {
+ checkStateShape(mergedProps, 'mergeProps');
+ }
+ return mergedProps;
+ }
+
+ var Connect = function (_Component) {
+ _inherits(Connect, _Component);
+
+ Connect.prototype.shouldComponentUpdate = function shouldComponentUpdate() {
+ return !pure || this.haveOwnPropsChanged || this.hasStoreStateChanged;
+ };
+
+ function Connect(props, context) {
+ _classCallCheck(this, Connect);
+
+ var _this = _possibleConstructorReturn(this, _Component.call(this, props, context));
+
+ _this.version = version;
+ _this.store = props.store || context.store;
+
+ (0, _invariant2["default"])(_this.store, 'Could not find "store" in either the context or ' + ('props of "' + connectDisplayName + '". ') + 'Either wrap the root component in a <Provider>, ' + ('or explicitly pass "store" as a prop to "' + connectDisplayName + '".'));
+
+ var storeState = _this.store.getState();
+ _this.state = { storeState: storeState };
+ _this.clearCache();
+ return _this;
+ }
+
+ Connect.prototype.computeStateProps = function computeStateProps(store, props) {
+ if (!this.finalMapStateToProps) {
+ return this.configureFinalMapState(store, props);
+ }
+
+ var state = store.getState();
+ var stateProps = this.doStatePropsDependOnOwnProps ? this.finalMapStateToProps(state, props) : this.finalMapStateToProps(state);
+
+ if (false) {
+ checkStateShape(stateProps, 'mapStateToProps');
+ }
+ return stateProps;
+ };
+
+ Connect.prototype.configureFinalMapState = function configureFinalMapState(store, props) {
+ var mappedState = mapState(store.getState(), props);
+ var isFactory = typeof mappedState === 'function';
+
+ this.finalMapStateToProps = isFactory ? mappedState : mapState;
+ this.doStatePropsDependOnOwnProps = this.finalMapStateToProps.length !== 1;
+
+ if (isFactory) {
+ return this.computeStateProps(store, props);
+ }
+
+ if (false) {
+ checkStateShape(mappedState, 'mapStateToProps');
+ }
+ return mappedState;
+ };
+
+ Connect.prototype.computeDispatchProps = function computeDispatchProps(store, props) {
+ if (!this.finalMapDispatchToProps) {
+ return this.configureFinalMapDispatch(store, props);
+ }
+
+ var dispatch = store.dispatch;
+
+ var dispatchProps = this.doDispatchPropsDependOnOwnProps ? this.finalMapDispatchToProps(dispatch, props) : this.finalMapDispatchToProps(dispatch);
+
+ if (false) {
+ checkStateShape(dispatchProps, 'mapDispatchToProps');
+ }
+ return dispatchProps;
+ };
+
+ Connect.prototype.configureFinalMapDispatch = function configureFinalMapDispatch(store, props) {
+ var mappedDispatch = mapDispatch(store.dispatch, props);
+ var isFactory = typeof mappedDispatch === 'function';
+
+ this.finalMapDispatchToProps = isFactory ? mappedDispatch : mapDispatch;
+ this.doDispatchPropsDependOnOwnProps = this.finalMapDispatchToProps.length !== 1;
+
+ if (isFactory) {
+ return this.computeDispatchProps(store, props);
+ }
+
+ if (false) {
+ checkStateShape(mappedDispatch, 'mapDispatchToProps');
+ }
+ return mappedDispatch;
+ };
+
+ Connect.prototype.updateStatePropsIfNeeded = function updateStatePropsIfNeeded() {
+ var nextStateProps = this.computeStateProps(this.store, this.props);
+ if (this.stateProps && (0, _shallowEqual2["default"])(nextStateProps, this.stateProps)) {
+ return false;
+ }
+
+ this.stateProps = nextStateProps;
+ return true;
+ };
+
+ Connect.prototype.updateDispatchPropsIfNeeded = function updateDispatchPropsIfNeeded() {
+ var nextDispatchProps = this.computeDispatchProps(this.store, this.props);
+ if (this.dispatchProps && (0, _shallowEqual2["default"])(nextDispatchProps, this.dispatchProps)) {
+ return false;
+ }
+
+ this.dispatchProps = nextDispatchProps;
+ return true;
+ };
+
+ Connect.prototype.updateMergedPropsIfNeeded = function updateMergedPropsIfNeeded() {
+ var nextMergedProps = computeMergedProps(this.stateProps, this.dispatchProps, this.props);
+ if (this.mergedProps && checkMergedEquals && (0, _shallowEqual2["default"])(nextMergedProps, this.mergedProps)) {
+ return false;
+ }
+
+ this.mergedProps = nextMergedProps;
+ return true;
+ };
+
+ Connect.prototype.isSubscribed = function isSubscribed() {
+ return typeof this.unsubscribe === 'function';
+ };
+
+ Connect.prototype.trySubscribe = function trySubscribe() {
+ if (shouldSubscribe && !this.unsubscribe) {
+ this.unsubscribe = this.store.subscribe(this.handleChange.bind(this));
+ this.handleChange();
+ }
+ };
+
+ Connect.prototype.tryUnsubscribe = function tryUnsubscribe() {
+ if (this.unsubscribe) {
+ this.unsubscribe();
+ this.unsubscribe = null;
+ }
+ };
+
+ Connect.prototype.componentDidMount = function componentDidMount() {
+ this.trySubscribe();
+ };
+
+ Connect.prototype.componentWillReceiveProps = function componentWillReceiveProps(nextProps) {
+ if (!pure || !(0, _shallowEqual2["default"])(nextProps, this.props)) {
+ this.haveOwnPropsChanged = true;
+ }
+ };
+
+ Connect.prototype.componentWillUnmount = function componentWillUnmount() {
+ this.tryUnsubscribe();
+ this.clearCache();
+ };
+
+ Connect.prototype.clearCache = function clearCache() {
+ this.dispatchProps = null;
+ this.stateProps = null;
+ this.mergedProps = null;
+ this.haveOwnPropsChanged = true;
+ this.hasStoreStateChanged = true;
+ this.haveStatePropsBeenPrecalculated = false;
+ this.statePropsPrecalculationError = null;
+ this.renderedElement = null;
+ this.finalMapDispatchToProps = null;
+ this.finalMapStateToProps = null;
+ };
+
+ Connect.prototype.handleChange = function handleChange() {
+ if (!this.unsubscribe) {
+ return;
+ }
+
+ var storeState = this.store.getState();
+ var prevStoreState = this.state.storeState;
+ if (pure && prevStoreState === storeState) {
+ return;
+ }
+
+ if (pure && !this.doStatePropsDependOnOwnProps) {
+ var haveStatePropsChanged = tryCatch(this.updateStatePropsIfNeeded, this);
+ if (!haveStatePropsChanged) {
+ return;
+ }
+ if (haveStatePropsChanged === errorObject) {
+ this.statePropsPrecalculationError = errorObject.value;
+ }
+ this.haveStatePropsBeenPrecalculated = true;
+ }
+
+ this.hasStoreStateChanged = true;
+ this.setState({ storeState: storeState });
+ };
+
+ Connect.prototype.getWrappedInstance = function getWrappedInstance() {
+ (0, _invariant2["default"])(withRef, 'To access the wrapped instance, you need to specify ' + '{ withRef: true } as the fourth argument of the connect() call.');
+
+ return this.refs.wrappedInstance;
+ };
+
+ Connect.prototype.render = function render() {
+ var haveOwnPropsChanged = this.haveOwnPropsChanged;
+ var hasStoreStateChanged = this.hasStoreStateChanged;
+ var haveStatePropsBeenPrecalculated = this.haveStatePropsBeenPrecalculated;
+ var statePropsPrecalculationError = this.statePropsPrecalculationError;
+ var renderedElement = this.renderedElement;
+
+ this.haveOwnPropsChanged = false;
+ this.hasStoreStateChanged = false;
+ this.haveStatePropsBeenPrecalculated = false;
+ this.statePropsPrecalculationError = null;
+
+ if (statePropsPrecalculationError) {
+ throw statePropsPrecalculationError;
+ }
+
+ var shouldUpdateStateProps = true;
+ var shouldUpdateDispatchProps = true;
+ if (pure && renderedElement) {
+ shouldUpdateStateProps = hasStoreStateChanged || haveOwnPropsChanged && this.doStatePropsDependOnOwnProps;
+ shouldUpdateDispatchProps = haveOwnPropsChanged && this.doDispatchPropsDependOnOwnProps;
+ }
+
+ var haveStatePropsChanged = false;
+ var haveDispatchPropsChanged = false;
+ if (haveStatePropsBeenPrecalculated) {
+ haveStatePropsChanged = true;
+ } else if (shouldUpdateStateProps) {
+ haveStatePropsChanged = this.updateStatePropsIfNeeded();
+ }
+ if (shouldUpdateDispatchProps) {
+ haveDispatchPropsChanged = this.updateDispatchPropsIfNeeded();
+ }
+
+ var haveMergedPropsChanged = true;
+ if (haveStatePropsChanged || haveDispatchPropsChanged || haveOwnPropsChanged) {
+ haveMergedPropsChanged = this.updateMergedPropsIfNeeded();
+ } else {
+ haveMergedPropsChanged = false;
+ }
+
+ if (!haveMergedPropsChanged && renderedElement) {
+ return renderedElement;
+ }
+
+ if (withRef) {
+ this.renderedElement = (0, _react.createElement)(WrappedComponent, _extends({}, this.mergedProps, {
+ ref: 'wrappedInstance'
+ }));
+ } else {
+ this.renderedElement = (0, _react.createElement)(WrappedComponent, this.mergedProps);
+ }
+
+ return this.renderedElement;
+ };
+
+ return Connect;
+ }(_react.Component);
+
+ Connect.displayName = connectDisplayName;
+ Connect.WrappedComponent = WrappedComponent;
+ Connect.contextTypes = {
+ store: _storeShape2["default"]
+ };
+ Connect.propTypes = {
+ store: _storeShape2["default"]
+ };
+
+ if (false) {
+ Connect.prototype.componentWillUpdate = function componentWillUpdate() {
+ if (this.version === version) {
+ return;
+ }
+
+ // We are hot reloading!
+ this.version = version;
+ this.trySubscribe();
+ this.clearCache();
+ };
+ }
+
+ return (0, _hoistNonReactStatics2["default"])(Connect, WrappedComponent);
+ };
+ }
+
+/***/ },
+/* 24 */
+/***/ function(module, exports) {
+
+ "use strict";
+
+ exports.__esModule = true;
+ exports["default"] = shallowEqual;
+ function shallowEqual(objA, objB) {
+ if (objA === objB) {
+ return true;
+ }
+
+ var keysA = Object.keys(objA);
+ var keysB = Object.keys(objB);
+
+ if (keysA.length !== keysB.length) {
+ return false;
+ }
+
+ // Test for A's keys different from B.
+ var hasOwn = Object.prototype.hasOwnProperty;
+ for (var i = 0; i < keysA.length; i++) {
+ if (!hasOwn.call(objB, keysA[i]) || objA[keysA[i]] !== objB[keysA[i]]) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+/***/ },
+/* 25 */
+/***/ function(module, exports, __webpack_require__) {
+
+ 'use strict';
+
+ exports.__esModule = true;
+ exports["default"] = wrapActionCreators;
+
+ var _redux = __webpack_require__(3);
+
+ function wrapActionCreators(actionCreators) {
+ return function (dispatch) {
+ return (0, _redux.bindActionCreators)(actionCreators, dispatch);
+ };
+ }
+
+/***/ },
+/* 26 */
+/***/ function(module, exports) {
+
+ /**
+ * Copyright 2015, Yahoo! Inc.
+ * Copyrights licensed under the New BSD License. See the accompanying LICENSE file for terms.
+ */
+ 'use strict';
+
+ var REACT_STATICS = {
+ childContextTypes: true,
+ contextTypes: true,
+ defaultProps: true,
+ displayName: true,
+ getDefaultProps: true,
+ mixins: true,
+ propTypes: true,
+ type: true
+ };
+
+ var KNOWN_STATICS = {
+ name: true,
+ length: true,
+ prototype: true,
+ caller: true,
+ arguments: true,
+ arity: true
+ };
+
+ var isGetOwnPropertySymbolsAvailable = typeof Object.getOwnPropertySymbols === 'function';
+
+ module.exports = function hoistNonReactStatics(targetComponent, sourceComponent, customStatics) {
+ if (typeof sourceComponent !== 'string') { // don't hoist over string (html) components
+ var keys = Object.getOwnPropertyNames(sourceComponent);
+
+ /* istanbul ignore else */
+ if (isGetOwnPropertySymbolsAvailable) {
+ keys = keys.concat(Object.getOwnPropertySymbols(sourceComponent));
+ }
+
+ for (var i = 0; i < keys.length; ++i) {
+ if (!REACT_STATICS[keys[i]] && !KNOWN_STATICS[keys[i]] && (!customStatics || !customStatics[keys[i]])) {
+ try {
+ targetComponent[keys[i]] = sourceComponent[keys[i]];
+ } catch (error) {
+
+ }
+ }
+ }
+ }
+
+ return targetComponent;
+ };
+
+
+/***/ },
+/* 27 */
+/***/ function(module, exports, __webpack_require__) {
+
+ /**
+ * Copyright 2013-2015, Facebook, Inc.
+ * All rights reserved.
+ *
+ * This source code is licensed under the BSD-style license found in the
+ * LICENSE file in the root directory of this source tree. An additional grant
+ * of patent rights can be found in the PATENTS file in the same directory.
+ */
+
+ 'use strict';
+
+ /**
+ * Use invariant() to assert state which your program assumes to be true.
+ *
+ * Provide sprintf-style format (only %s is supported) and arguments
+ * to provide information about what broke and what you were
+ * expecting.
+ *
+ * The invariant message will be stripped in production, but the invariant
+ * will remain to ensure logic does not differ in production.
+ */
+
+ var invariant = function(condition, format, a, b, c, d, e, f) {
+ if (false) {
+ if (format === undefined) {
+ throw new Error('invariant requires an error message argument');
+ }
+ }
+
+ if (!condition) {
+ var error;
+ if (format === undefined) {
+ error = new Error(
+ 'Minified exception occurred; use the non-minified dev environment ' +
+ 'for the full error message and additional helpful warnings.'
+ );
+ } else {
+ var args = [a, b, c, d, e, f];
+ var argIndex = 0;
+ error = new Error(
+ format.replace(/%s/g, function() { return args[argIndex++]; })
+ );
+ error.name = 'Invariant Violation';
+ }
+
+ error.framesToPop = 1; // we don't care about invariant's own frame
+ throw error;
+ }
+ };
+
+ module.exports = invariant;
+
+
+/***/ },
+/* 28 */
+/***/ function(module, exports, __webpack_require__) {
+
+ var _require = __webpack_require__(29);
+
+ var KeyShortcuts = _require.KeyShortcuts;
+
+ var _require2 = __webpack_require__(67);
+
+ var DebuggerTransport = _require2.DebuggerTransport;
+
+ var _require3 = __webpack_require__(79);
+
+ var DebuggerClient = _require3.DebuggerClient;
+
+ var PrefsHelper = __webpack_require__(83).PrefsHelper;
+
+ var _require4 = __webpack_require__(84);
+
+ var TargetFactory = _require4.TargetFactory;
+
+ var DevToolsUtils = __webpack_require__(68);
+ var AppConstants = __webpack_require__(70);
+ var EventEmitter = __webpack_require__(60);
+ var WebsocketTransport = __webpack_require__(85);
+ var Menu = __webpack_require__(86);
+ var MenuItem = __webpack_require__(87);
+
+ module.exports = {
+ KeyShortcuts,
+ PrefsHelper,
+ DebuggerClient,
+ DebuggerTransport,
+ TargetFactory,
+ DevToolsUtils,
+ AppConstants,
+ EventEmitter,
+ WebsocketTransport,
+ Menu,
+ MenuItem
+ };
+
+/***/ },
+/* 29 */
+/***/ function(module, exports, __webpack_require__) {
+
+ var _slicedToArray = function () { function sliceIterator(arr, i) { var _arr = []; var _n = true; var _d = false; var _e = undefined; try { for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i["return"]) _i["return"](); } finally { if (_d) throw _e; } } return _arr; } return function (arr, i) { if (Array.isArray(arr)) { return arr; } else if (Symbol.iterator in Object(arr)) { return sliceIterator(arr, i); } else { throw new TypeError("Invalid attempt to destructure non-iterable instance"); } }; }();
+
+ /* 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/. */
+
+ var _require = __webpack_require__(30);
+
+ var appinfo = _require.Services.appinfo;
+
+ var EventEmitter = __webpack_require__(60);
+ var isOSX = appinfo.OS === "Darwin";
+ "use strict";
+
+ // List of electron keys mapped to DOM API (DOM_VK_*) key code
+ var ElectronKeysMapping = {
+ "F1": "DOM_VK_F1",
+ "F2": "DOM_VK_F2",
+ "F3": "DOM_VK_F3",
+ "F4": "DOM_VK_F4",
+ "F5": "DOM_VK_F5",
+ "F6": "DOM_VK_F6",
+ "F7": "DOM_VK_F7",
+ "F8": "DOM_VK_F8",
+ "F9": "DOM_VK_F9",
+ "F10": "DOM_VK_F10",
+ "F11": "DOM_VK_F11",
+ "F12": "DOM_VK_F12",
+ "F13": "DOM_VK_F13",
+ "F14": "DOM_VK_F14",
+ "F15": "DOM_VK_F15",
+ "F16": "DOM_VK_F16",
+ "F17": "DOM_VK_F17",
+ "F18": "DOM_VK_F18",
+ "F19": "DOM_VK_F19",
+ "F20": "DOM_VK_F20",
+ "F21": "DOM_VK_F21",
+ "F22": "DOM_VK_F22",
+ "F23": "DOM_VK_F23",
+ "F24": "DOM_VK_F24",
+ "Space": "DOM_VK_SPACE",
+ "Backspace": "DOM_VK_BACK_SPACE",
+ "Delete": "DOM_VK_DELETE",
+ "Insert": "DOM_VK_INSERT",
+ "Return": "DOM_VK_RETURN",
+ "Enter": "DOM_VK_RETURN",
+ "Up": "DOM_VK_UP",
+ "Down": "DOM_VK_DOWN",
+ "Left": "DOM_VK_LEFT",
+ "Right": "DOM_VK_RIGHT",
+ "Home": "DOM_VK_HOME",
+ "End": "DOM_VK_END",
+ "PageUp": "DOM_VK_PAGE_UP",
+ "PageDown": "DOM_VK_PAGE_DOWN",
+ "Escape": "DOM_VK_ESCAPE",
+ "Esc": "DOM_VK_ESCAPE",
+ "Tab": "DOM_VK_TAB",
+ "VolumeUp": "DOM_VK_VOLUME_UP",
+ "VolumeDown": "DOM_VK_VOLUME_DOWN",
+ "VolumeMute": "DOM_VK_VOLUME_MUTE",
+ "PrintScreen": "DOM_VK_PRINTSCREEN"
+ };
+
+ /**
+ * Helper to listen for keyboard events decribed in .properties file.
+ *
+ * let shortcuts = new KeyShortcuts({
+ * window
+ * });
+ * shortcuts.on("Ctrl+F", event => {
+ * // `event` is the KeyboardEvent which relates to the key shortcuts
+ * });
+ *
+ * @param DOMWindow window
+ * The window object of the document to listen events from.
+ * @param DOMElement target
+ * Optional DOM Element on which we should listen events from.
+ * If omitted, we listen for all events fired on `window`.
+ */
+ function KeyShortcuts(_ref) {
+ var window = _ref.window;
+ var target = _ref.target;
+
+ this.window = window;
+ this.target = target || window;
+ this.keys = new Map();
+ this.eventEmitter = new EventEmitter();
+ this.target.addEventListener("keydown", this);
+ }
+
+ /*
+ * Parse an electron-like key string and return a normalized object which
+ * allow efficient match on DOM key event. The normalized object matches DOM
+ * API.
+ *
+ * @param DOMWindow window
+ * Any DOM Window object, just to fetch its `KeyboardEvent` object
+ * @param String str
+ * The shortcut string to parse, following this document:
+ * https://github.com/electron/electron/blob/master/docs/api/accelerator.md
+ */
+ KeyShortcuts.parseElectronKey = function (window, str) {
+ var modifiers = str.split("+");
+ var key = modifiers.pop();
+
+ var shortcut = {
+ ctrl: false,
+ meta: false,
+ alt: false,
+ shift: false,
+ // Set for character keys
+ key: undefined,
+ // Set for non-character keys
+ keyCode: undefined
+ };
+ for (var mod of modifiers) {
+ if (mod === "Alt") {
+ shortcut.alt = true;
+ } else if (["Command", "Cmd"].includes(mod)) {
+ shortcut.meta = true;
+ } else if (["CommandOrControl", "CmdOrCtrl"].includes(mod)) {
+ if (isOSX) {
+ shortcut.meta = true;
+ } else {
+ shortcut.ctrl = true;
+ }
+ } else if (["Control", "Ctrl"].includes(mod)) {
+ shortcut.ctrl = true;
+ } else if (mod === "Shift") {
+ shortcut.shift = true;
+ } else {
+ console.error("Unsupported modifier:", mod, "from key:", str);
+ return null;
+ }
+ }
+
+ // Plus is a special case. It's a character key and shouldn't be matched
+ // against a keycode as it is only accessible via Shift/Capslock
+ if (key === "Plus") {
+ key = "+";
+ }
+
+ if (typeof key === "string" && key.length === 1) {
+ // Match any single character
+ shortcut.key = key.toLowerCase();
+ } else if (key in ElectronKeysMapping) {
+ // Maps the others manually to DOM API DOM_VK_*
+ key = ElectronKeysMapping[key];
+ shortcut.keyCode = window.KeyboardEvent[key];
+ // Used only to stringify the shortcut
+ shortcut.keyCodeString = key;
+ shortcut.key = key;
+ } else {
+ console.error("Unsupported key:", key);
+ return null;
+ }
+
+ return shortcut;
+ };
+
+ KeyShortcuts.stringify = function (shortcut) {
+ var list = [];
+ if (shortcut.alt) {
+ list.push("Alt");
+ }
+ if (shortcut.ctrl) {
+ list.push("Ctrl");
+ }
+ if (shortcut.meta) {
+ list.push("Cmd");
+ }
+ if (shortcut.shift) {
+ list.push("Shift");
+ }
+ var key = void 0;
+ if (shortcut.key) {
+ key = shortcut.key.toUpperCase();
+ } else {
+ key = shortcut.keyCodeString;
+ }
+ list.push(key);
+ return list.join("+");
+ };
+
+ KeyShortcuts.prototype = {
+ destroy() {
+ this.target.removeEventListener("keydown", this);
+ this.keys.clear();
+ },
+
+ doesEventMatchShortcut(event, shortcut) {
+ if (shortcut.meta != event.metaKey) {
+ return false;
+ }
+ if (shortcut.ctrl != event.ctrlKey) {
+ return false;
+ }
+ if (shortcut.alt != event.altKey) {
+ return false;
+ }
+ // Shift is a special modifier, it may implicitely be required if the
+ // expected key is a special character accessible via shift.
+ if (shortcut.shift != event.shiftKey && event.key && event.key.match(/[a-zA-Z]/)) {
+ return false;
+ }
+ if (shortcut.keyCode) {
+ return event.keyCode == shortcut.keyCode;
+ } else if (event.key in ElectronKeysMapping) {
+ return ElectronKeysMapping[event.key] === shortcut.key;
+ }
+
+ // get the key from the keyCode if key is not provided.
+ var key = event.key || String.fromCharCode(event.keyCode);
+
+ // For character keys, we match if the final character is the expected one.
+ // But for digits we also accept indirect match to please azerty keyboard,
+ // which requires Shift to be pressed to get digits.
+ return key.toLowerCase() == shortcut.key || shortcut.key.match(/[0-9]/) && event.keyCode == shortcut.key.charCodeAt(0);
+ },
+
+ handleEvent(event) {
+ for (var _ref2 of this.keys) {
+ var _ref3 = _slicedToArray(_ref2, 2);
+
+ var key = _ref3[0];
+ var shortcut = _ref3[1];
+
+ if (this.doesEventMatchShortcut(event, shortcut)) {
+ this.eventEmitter.emit(key, event);
+ }
+ }
+ },
+
+ on(key, listener) {
+ if (typeof listener !== "function") {
+ throw new Error("KeyShortcuts.on() expects a function as " + "second argument");
+ }
+ if (!this.keys.has(key)) {
+ var shortcut = KeyShortcuts.parseElectronKey(this.window, key);
+ // The key string is wrong and we were unable to compute the key shortcut
+ if (!shortcut) {
+ return;
+ }
+ this.keys.set(key, shortcut);
+ }
+ this.eventEmitter.on(key, listener);
+ },
+
+ off(key, listener) {
+ this.eventEmitter.off(key, listener);
+ }
+ };
+ exports.KeyShortcuts = KeyShortcuts;
+
+/***/ },
+/* 30 */
+/***/ function(module, exports, __webpack_require__) {
+
+ var Services = __webpack_require__(31);
+ var SplitBox = __webpack_require__(32);
+ // const SplitBoxCSS = require("./client/shared/components/splitter/SplitBox.css")
+ var rep = __webpack_require__(34).Rep;
+ // const repCSS = require("./client/shared/components/reps/reps.css");
+ var Grip = __webpack_require__(44).Grip;
+ var sprintf = __webpack_require__(59).sprintf;
+
+ module.exports = {
+ Services,
+ SplitBox,
+ // SplitBoxCSS,
+ rep,
+ // repCSS,
+ Grip,
+ sprintf
+ };
+
+/***/ },
+/* 31 */
+/***/ function(module, exports) {
+
+ module.exports = devtoolsRequire("Services");
+
+/***/ },
+/* 32 */
+/***/ function(module, exports, __webpack_require__) {
+
+ var React = __webpack_require__(2);
+ var ReactDOM = __webpack_require__(16);
+ var Draggable = React.createFactory(__webpack_require__(33));
+ var dom = React.DOM;
+ var PropTypes = React.PropTypes;
+
+ /**
+ * This component represents a Splitter. The splitter supports vertical
+ * as well as horizontal mode.
+ */
+
+ var SplitBox = React.createClass({
+
+ propTypes: {
+ // Custom class name. You can use more names separated by a space.
+ className: PropTypes.string,
+ // Initial size of controlled panel.
+ initialSize: PropTypes.any,
+ // Optional initial width of controlled panel.
+ initialWidth: PropTypes.number,
+ // Optional initial height of controlled panel.
+ initialHeight: PropTypes.number,
+ // Left/top panel
+ startPanel: PropTypes.any,
+ // Min panel size.
+ minSize: PropTypes.any,
+ // Max panel size.
+ maxSize: PropTypes.any,
+ // Right/bottom panel
+ endPanel: PropTypes.any,
+ // True if the right/bottom panel should be controlled.
+ endPanelControl: PropTypes.bool,
+ // Size of the splitter handle bar.
+ splitterSize: PropTypes.number,
+ // True if the splitter bar is vertical (default is vertical).
+ vert: PropTypes.bool,
+ // Optional style properties passed into the splitbox
+ style: PropTypes.object
+ },
+
+ displayName: "SplitBox",
+
+ getDefaultProps() {
+ return {
+ splitterSize: 5,
+ vert: true,
+ endPanelControl: false
+ };
+ },
+
+ /**
+ * The state stores the current orientation (vertical or horizontal)
+ * and the current size (width/height). All these values can change
+ * during the component's life time.
+ */
+ getInitialState() {
+ return {
+ vert: this.props.vert,
+ width: this.props.initialWidth || this.props.initialSize,
+ height: this.props.initialHeight || this.props.initialSize
+ };
+ },
+
+ // Dragging Events
+
+ /**
+ * Set 'resizing' cursor on entire document during splitter dragging.
+ * This avoids cursor-flickering that happens when the mouse leaves
+ * the splitter bar area (happens frequently).
+ */
+ onStartMove() {
+ var splitBox = ReactDOM.findDOMNode(this);
+ var doc = splitBox.ownerDocument;
+ var defaultCursor = doc.documentElement.style.cursor;
+ doc.documentElement.style.cursor = this.state.vert ? "ew-resize" : "ns-resize";
+
+ splitBox.classList.add("dragging");
+
+ this.setState({
+ defaultCursor: defaultCursor
+ });
+ },
+
+ onStopMove() {
+ var splitBox = ReactDOM.findDOMNode(this);
+ var doc = splitBox.ownerDocument;
+ doc.documentElement.style.cursor = this.state.defaultCursor;
+
+ splitBox.classList.remove("dragging");
+ },
+
+ screenX() {
+ var borderWidth = (window.outerWidth - window.innerWidth) / 2;
+ return window.screenX + borderWidth;
+ },
+
+ screenY() {
+ var borderHeignt = window.outerHeight - window.innerHeight;
+ return window.screenY + borderHeignt;
+ },
+
+ /**
+ * Adjust size of the controlled panel. Depending on the current
+ * orientation we either remember the width or height of
+ * the splitter box.
+ */
+ onMove(x, y) {
+ var node = ReactDOM.findDOMNode(this);
+ var doc = node.ownerDocument;
+ var win = doc.defaultView;
+
+ var size = void 0;
+ var endPanelControl = this.props.endPanelControl;
+
+
+ if (this.state.vert) {
+ // Switch the control flag in case of RTL. Note that RTL
+ // has impact on vertical splitter only.
+ var dir = win.getComputedStyle(doc.documentElement).direction;
+ if (dir == "rtl") {
+ endPanelControl = !endPanelControl;
+ }
+
+ var innerOffset = x - this.screenX();
+ size = endPanelControl ? node.offsetLeft + node.offsetWidth - innerOffset : innerOffset - node.offsetLeft;
+
+ this.setState({
+ width: size
+ });
+ } else {
+ var _innerOffset = y - this.screenY();
+ size = endPanelControl ? node.offsetTop + node.offsetHeight - _innerOffset : _innerOffset - node.offsetTop;
+
+ this.setState({
+ height: size
+ });
+ }
+ },
+
+ // Rendering
+
+ render() {
+ var vert = this.state.vert;
+ var _props = this.props;
+ var startPanel = _props.startPanel;
+ var endPanel = _props.endPanel;
+ var endPanelControl = _props.endPanelControl;
+ var minSize = _props.minSize;
+ var maxSize = _props.maxSize;
+ var splitterSize = _props.splitterSize;
+
+
+ var style = Object.assign({}, this.props.style);
+
+ // Calculate class names list.
+ var classNames = ["split-box"];
+ classNames.push(vert ? "vert" : "horz");
+ if (this.props.className) {
+ classNames = classNames.concat(this.props.className.split(" "));
+ }
+
+ var leftPanelStyle = void 0;
+ var rightPanelStyle = void 0;
+
+ // Set proper size for panels depending on the current state.
+ if (vert) {
+ leftPanelStyle = {
+ maxWidth: endPanelControl ? null : maxSize,
+ minWidth: endPanelControl ? null : minSize,
+ width: endPanelControl ? null : this.state.width
+ };
+ rightPanelStyle = {
+ maxWidth: endPanelControl ? maxSize : null,
+ minWidth: endPanelControl ? minSize : null,
+ width: endPanelControl ? this.state.width : null
+ };
+ } else {
+ leftPanelStyle = {
+ maxHeight: endPanelControl ? null : maxSize,
+ minHeight: endPanelControl ? null : minSize,
+ height: endPanelControl ? null : this.state.height
+ };
+ rightPanelStyle = {
+ maxHeight: endPanelControl ? maxSize : null,
+ minHeight: endPanelControl ? minSize : null,
+ height: endPanelControl ? this.state.height : null
+ };
+ }
+
+ // Calculate splitter size
+ var splitterStyle = {
+ flex: "0 0 " + splitterSize + "px"
+ };
+
+ return dom.div({
+ className: classNames.join(" "),
+ style: style }, startPanel ? dom.div({
+ className: endPanelControl ? "uncontrolled" : "controlled",
+ style: leftPanelStyle }, startPanel) : null, Draggable({
+ className: "splitter",
+ style: splitterStyle,
+ onStart: this.onStartMove,
+ onStop: this.onStopMove,
+ onMove: this.onMove
+ }), endPanel ? dom.div({
+ className: endPanelControl ? "controlled" : "uncontrolled",
+ style: rightPanelStyle }, endPanel) : null);
+ }
+ });
+
+ module.exports = SplitBox;
+
+/***/ },
+/* 33 */
+/***/ function(module, exports, __webpack_require__) {
+
+ /* 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/. */
+
+ var React = __webpack_require__(2);
+ var ReactDOM = __webpack_require__(16);
+ var dom = React.DOM;
+ var PropTypes = React.PropTypes;
+
+
+ var Draggable = React.createClass({
+ displayName: "Draggable",
+
+ propTypes: {
+ onMove: PropTypes.func.isRequired,
+ onStart: PropTypes.func,
+ onStop: PropTypes.func,
+ style: PropTypes.object,
+ className: PropTypes.string
+ },
+
+ startDragging(ev) {
+ ev.preventDefault();
+ var doc = ReactDOM.findDOMNode(this).ownerDocument;
+ doc.addEventListener("mousemove", this.onMove);
+ doc.addEventListener("mouseup", this.onUp);
+ this.props.onStart && this.props.onStart();
+ },
+
+ onMove(ev) {
+ ev.preventDefault();
+ // Use screen coordinates so, moving mouse over iframes
+ // doesn't mangle (relative) coordinates.
+ this.props.onMove(ev.screenX, ev.screenY);
+ },
+
+ onUp(ev) {
+ ev.preventDefault();
+ var doc = ReactDOM.findDOMNode(this).ownerDocument;
+ doc.removeEventListener("mousemove", this.onMove);
+ doc.removeEventListener("mouseup", this.onUp);
+ this.props.onStop && this.props.onStop();
+ },
+
+ render() {
+ return dom.div({
+ style: this.props.style,
+ className: this.props.className,
+ onMouseDown: this.startDragging
+ });
+ }
+ });
+
+ module.exports = Draggable;
+
+/***/ },
+/* 34 */
+/***/ function(module, exports, __webpack_require__) {
+
+ var __WEBPACK_AMD_DEFINE_RESULT__;/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+ /* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+ /* 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";
+
+ // Make this available to both AMD and CJS environments
+
+ !(__WEBPACK_AMD_DEFINE_RESULT__ = function (require, exports, module) {
+ // Dependencies
+ var React = __webpack_require__(2);
+
+ var _require = __webpack_require__(35);
+
+ var isGrip = _require.isGrip;
+
+ // Load all existing rep templates
+
+ var _require2 = __webpack_require__(36);
+
+ var Undefined = _require2.Undefined;
+
+ var _require3 = __webpack_require__(37);
+
+ var Null = _require3.Null;
+
+ var _require4 = __webpack_require__(38);
+
+ var StringRep = _require4.StringRep;
+
+ var _require5 = __webpack_require__(39);
+
+ var Number = _require5.Number;
+
+ var _require6 = __webpack_require__(40);
+
+ var ArrayRep = _require6.ArrayRep;
+
+ var _require7 = __webpack_require__(42);
+
+ var Obj = _require7.Obj;
+
+ var _require8 = __webpack_require__(45);
+
+ var SymbolRep = _require8.SymbolRep;
+
+ // DOM types (grips)
+
+ var _require9 = __webpack_require__(46);
+
+ var Attribute = _require9.Attribute;
+
+ var _require10 = __webpack_require__(47);
+
+ var DateTime = _require10.DateTime;
+
+ var _require11 = __webpack_require__(48);
+
+ var Document = _require11.Document;
+
+ var _require12 = __webpack_require__(49);
+
+ var Event = _require12.Event;
+
+ var _require13 = __webpack_require__(50);
+
+ var Func = _require13.Func;
+
+ var _require14 = __webpack_require__(51);
+
+ var RegExp = _require14.RegExp;
+
+ var _require15 = __webpack_require__(52);
+
+ var StyleSheet = _require15.StyleSheet;
+
+ var _require16 = __webpack_require__(53);
+
+ var TextNode = _require16.TextNode;
+
+ var _require17 = __webpack_require__(54);
+
+ var Window = _require17.Window;
+
+ var _require18 = __webpack_require__(55);
+
+ var ObjectWithText = _require18.ObjectWithText;
+
+ var _require19 = __webpack_require__(56);
+
+ var ObjectWithURL = _require19.ObjectWithURL;
+
+ var _require20 = __webpack_require__(57);
+
+ var GripArray = _require20.GripArray;
+
+ var _require21 = __webpack_require__(58);
+
+ var GripMap = _require21.GripMap;
+
+ var _require22 = __webpack_require__(44);
+
+ var Grip = _require22.Grip;
+
+ // List of all registered template.
+ // XXX there should be a way for extensions to register a new
+ // or modify an existing rep.
+
+ var reps = [RegExp, StyleSheet, Event, DateTime, TextNode, Attribute, Func, ArrayRep, Document, Window, ObjectWithText, ObjectWithURL, GripArray, GripMap, Grip, Undefined, Null, StringRep, Number, SymbolRep];
+
+ /**
+ * Generic rep that is using for rendering native JS types or an object.
+ * The right template used for rendering is picked automatically according
+ * to the current value type. The value must be passed is as 'object'
+ * property.
+ */
+ var Rep = React.createClass({
+ displayName: "Rep",
+
+ propTypes: {
+ object: React.PropTypes.any,
+ defaultRep: React.PropTypes.object,
+ mode: React.PropTypes.string
+ },
+
+ render: function () {
+ var rep = getRep(this.props.object, this.props.defaultRep);
+ return rep(this.props);
+ }
+ });
+
+ // Helpers
+
+ /**
+ * Return a rep object that is responsible for rendering given
+ * object.
+ *
+ * @param object {Object} Object to be rendered in the UI. This
+ * can be generic JS object as well as a grip (handle to a remote
+ * debuggee object).
+ *
+ * @param defaultObject {React.Component} The default template
+ * that should be used to render given object if none is found.
+ */
+ function getRep(object) {
+ var defaultRep = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : Obj;
+
+ var type = typeof object;
+ if (type == "object" && object instanceof String) {
+ type = "string";
+ } else if (type == "object" && object.type === "symbol") {
+ type = "symbol";
+ }
+
+ if (isGrip(object)) {
+ type = object.class;
+ }
+
+ for (var i = 0; i < reps.length; i++) {
+ var rep = reps[i];
+ try {
+ // supportsObject could return weight (not only true/false
+ // but a number), which would allow to priorities templates and
+ // support better extensibility.
+ if (rep.supportsObject(object, type)) {
+ return React.createFactory(rep.rep);
+ }
+ } catch (err) {
+ console.error(err);
+ }
+ }
+
+ return React.createFactory(defaultRep.rep);
+ }
+
+ // Exports from this module
+ exports.Rep = Rep;
+ }.call(exports, __webpack_require__, exports, module), __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__));
+
+/***/ },
+/* 35 */
+/***/ function(module, exports, __webpack_require__) {
+
+ var __WEBPACK_AMD_DEFINE_RESULT__;/* globals URLSearchParams */
+ /* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+ /* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+ /* 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";
+
+ // Make this available to both AMD and CJS environments
+
+ function _toConsumableArray(arr) { if (Array.isArray(arr)) { for (var i = 0, arr2 = Array(arr.length); i < arr.length; i++) { arr2[i] = arr[i]; } return arr2; } else { return Array.from(arr); } }
+
+ !(__WEBPACK_AMD_DEFINE_RESULT__ = function (require, exports, module) {
+ // Dependencies
+ var React = __webpack_require__(2);
+
+ /**
+ * Create React factories for given arguments.
+ * Example:
+ * const { Rep } = createFactories(require("./rep"));
+ */
+ function createFactories(args) {
+ var result = {};
+ for (var p in args) {
+ result[p] = React.createFactory(args[p]);
+ }
+ return result;
+ }
+
+ /**
+ * Returns true if the given object is a grip (see RDP protocol)
+ */
+ function isGrip(object) {
+ return object && object.actor;
+ }
+
+ function escapeNewLines(value) {
+ return value.replace(/\r/gm, "\\r").replace(/\n/gm, "\\n");
+ }
+
+ function cropMultipleLines(text, limit) {
+ return escapeNewLines(cropString(text, limit));
+ }
+
+ function cropString(text, limit, alternativeText) {
+ if (!alternativeText) {
+ alternativeText = "\u2026";
+ }
+
+ // Make sure it's a string.
+ text = text + "";
+
+ // Crop the string only if a limit is actually specified.
+ if (!limit || limit <= 0) {
+ return text;
+ }
+
+ // Set the limit at least to the length of the alternative text
+ // plus one character of the original text.
+ if (limit <= alternativeText.length) {
+ limit = alternativeText.length + 1;
+ }
+
+ var halfLimit = (limit - alternativeText.length) / 2;
+
+ if (text.length > limit) {
+ return text.substr(0, Math.ceil(halfLimit)) + alternativeText + text.substr(text.length - Math.floor(halfLimit));
+ }
+
+ return text;
+ }
+
+ function parseURLParams(url) {
+ url = new URL(url);
+ return parseURLEncodedText(url.searchParams);
+ }
+
+ function parseURLEncodedText(text) {
+ var params = [];
+
+ // In case the text is empty just return the empty parameters
+ if (text == "") {
+ return params;
+ }
+
+ var searchParams = new URLSearchParams(text);
+ var entries = [].concat(_toConsumableArray(searchParams.entries()));
+ return entries.map(entry => {
+ return {
+ name: entry[0],
+ value: entry[1]
+ };
+ });
+ }
+
+ function getFileName(url) {
+ var split = splitURLBase(url);
+ return split.name;
+ }
+
+ function splitURLBase(url) {
+ if (!isDataURL(url)) {
+ return splitURLTrue(url);
+ }
+ return {};
+ }
+
+ function getURLDisplayString(url) {
+ return cropString(url);
+ }
+
+ function isDataURL(url) {
+ return url && url.substr(0, 5) == "data:";
+ }
+
+ function splitURLTrue(url) {
+ var reSplitFile = /(.*?):\/{2,3}([^\/]*)(.*?)([^\/]*?)($|\?.*)/;
+ var m = reSplitFile.exec(url);
+
+ if (!m) {
+ return {
+ name: url,
+ path: url
+ };
+ } else if (m[4] == "" && m[5] == "") {
+ return {
+ protocol: m[1],
+ domain: m[2],
+ path: m[3],
+ name: m[3] != "/" ? m[3] : m[2]
+ };
+ }
+
+ return {
+ protocol: m[1],
+ domain: m[2],
+ path: m[2] + m[3],
+ name: m[4] + m[5]
+ };
+ }
+
+ // Exports from this module
+ exports.createFactories = createFactories;
+ exports.isGrip = isGrip;
+ exports.cropString = cropString;
+ exports.cropMultipleLines = cropMultipleLines;
+ exports.parseURLParams = parseURLParams;
+ exports.parseURLEncodedText = parseURLEncodedText;
+ exports.getFileName = getFileName;
+ exports.getURLDisplayString = getURLDisplayString;
+ }.call(exports, __webpack_require__, exports, module), __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__));
+
+/***/ },
+/* 36 */
+/***/ function(module, exports, __webpack_require__) {
+
+ var __WEBPACK_AMD_DEFINE_RESULT__;/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+ /* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+ /* 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";
+
+ // Make this available to both AMD and CJS environments
+
+ !(__WEBPACK_AMD_DEFINE_RESULT__ = function (require, exports, module) {
+ // Dependencies
+ var React = __webpack_require__(2);
+
+ // Shortcuts
+ var span = React.DOM.span;
+
+ /**
+ * Renders undefined value
+ */
+
+ var Undefined = React.createClass({
+ displayName: "UndefinedRep",
+
+ render: function () {
+ return span({ className: "objectBox objectBox-undefined" }, "undefined");
+ }
+ });
+
+ function supportsObject(object, type) {
+ if (object && object.type && object.type == "undefined") {
+ return true;
+ }
+
+ return type == "undefined";
+ }
+
+ // Exports from this module
+
+ exports.Undefined = {
+ rep: Undefined,
+ supportsObject: supportsObject
+ };
+ }.call(exports, __webpack_require__, exports, module), __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__));
+
+/***/ },
+/* 37 */
+/***/ function(module, exports, __webpack_require__) {
+
+ var __WEBPACK_AMD_DEFINE_RESULT__;/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+ /* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+ /* 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";
+
+ // Make this available to both AMD and CJS environments
+
+ !(__WEBPACK_AMD_DEFINE_RESULT__ = function (require, exports, module) {
+ // Dependencies
+ var React = __webpack_require__(2);
+
+ // Shortcuts
+ var span = React.DOM.span;
+
+ /**
+ * Renders null value
+ */
+
+ var Null = React.createClass({
+ displayName: "NullRep",
+
+ render: function () {
+ return span({ className: "objectBox objectBox-null" }, "null");
+ }
+ });
+
+ function supportsObject(object, type) {
+ if (object && object.type && object.type == "null") {
+ return true;
+ }
+
+ return object == null;
+ }
+
+ // Exports from this module
+
+ exports.Null = {
+ rep: Null,
+ supportsObject: supportsObject
+ };
+ }.call(exports, __webpack_require__, exports, module), __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__));
+
+/***/ },
+/* 38 */
+/***/ function(module, exports, __webpack_require__) {
+
+ var __WEBPACK_AMD_DEFINE_RESULT__;/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+ /* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+ /* 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";
+
+ // Make this available to both AMD and CJS environments
+
+ !(__WEBPACK_AMD_DEFINE_RESULT__ = function (require, exports, module) {
+ // Dependencies
+ var React = __webpack_require__(2);
+
+ var _require = __webpack_require__(35);
+
+ var cropMultipleLines = _require.cropMultipleLines;
+
+ // Shortcuts
+
+ var span = React.DOM.span;
+
+ /**
+ * Renders a string. String value is enclosed within quotes.
+ */
+
+ var StringRep = React.createClass({
+ displayName: "StringRep",
+
+ propTypes: {
+ useQuotes: React.PropTypes.bool
+ },
+
+ getDefaultProps: function () {
+ return {
+ useQuotes: true
+ };
+ },
+
+ render: function () {
+ var text = this.props.object;
+ var member = this.props.member;
+ if (member && member.open) {
+ return span({ className: "objectBox objectBox-string" }, "\"" + text + "\"");
+ }
+
+ var croppedString = this.props.cropLimit ? cropMultipleLines(text, this.props.cropLimit) : cropMultipleLines(text);
+
+ var formattedString = this.props.useQuotes ? "\"" + croppedString + "\"" : croppedString;
+
+ return span({ className: "objectBox objectBox-string" }, formattedString);
+ }
+ });
+
+ function supportsObject(object, type) {
+ return type == "string";
+ }
+
+ // Exports from this module
+
+ exports.StringRep = {
+ rep: StringRep,
+ supportsObject: supportsObject
+ };
+ }.call(exports, __webpack_require__, exports, module), __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__));
+
+/***/ },
+/* 39 */
+/***/ function(module, exports, __webpack_require__) {
+
+ var __WEBPACK_AMD_DEFINE_RESULT__;/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+ /* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+ /* 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";
+
+ // Make this available to both AMD and CJS environments
+
+ !(__WEBPACK_AMD_DEFINE_RESULT__ = function (require, exports, module) {
+ // Dependencies
+ var React = __webpack_require__(2);
+
+ // Shortcuts
+ var span = React.DOM.span;
+
+ /**
+ * Renders a number
+ */
+
+ var Number = React.createClass({
+ displayName: "Number",
+
+ stringify: function (object) {
+ var isNegativeZero = Object.is(object, -0) || object.type && object.type == "-0";
+
+ return isNegativeZero ? "-0" : String(object);
+ },
+
+ render: function () {
+ var value = this.props.object;
+
+ return span({ className: "objectBox objectBox-number" }, this.stringify(value));
+ }
+ });
+
+ function supportsObject(object, type) {
+ return type == "boolean" || type == "number" || type == "object" && object.type == "-0";
+ }
+
+ // Exports from this module
+
+ exports.Number = {
+ rep: Number,
+ supportsObject: supportsObject
+ };
+ }.call(exports, __webpack_require__, exports, module), __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__));
+
+/***/ },
+/* 40 */
+/***/ function(module, exports, __webpack_require__) {
+
+ var __WEBPACK_AMD_DEFINE_RESULT__;/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+ /* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+ /* 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";
+
+ // Make this available to both AMD and CJS environments
+
+ !(__WEBPACK_AMD_DEFINE_RESULT__ = function (require, exports, module) {
+ // Dependencies
+ var React = __webpack_require__(2);
+
+ var _require = __webpack_require__(35);
+
+ var createFactories = _require.createFactories;
+
+ var _createFactories = createFactories(__webpack_require__(41));
+
+ var Caption = _createFactories.Caption;
+
+ // Shortcuts
+
+ var DOM = React.DOM;
+
+ /**
+ * Renders an array. The array is enclosed by left and right bracket
+ * and the max number of rendered items depends on the current mode.
+ */
+ var ArrayRep = React.createClass({
+ displayName: "ArrayRep",
+
+ getTitle: function (object, context) {
+ return "[" + object.length + "]";
+ },
+
+ arrayIterator: function (array, max) {
+ var items = [];
+ var delim = void 0;
+
+ for (var i = 0; i < array.length && i < max; i++) {
+ try {
+ var value = array[i];
+
+ delim = i == array.length - 1 ? "" : ", ";
+
+ items.push(ItemRep({
+ key: i,
+ object: value,
+ // Hardcode tiny mode to avoid recursive handling.
+ mode: "tiny",
+ delim: delim
+ }));
+ } catch (exc) {
+ items.push(ItemRep({
+ key: i,
+ object: exc,
+ mode: "tiny",
+ delim: delim
+ }));
+ }
+ }
+
+ if (array.length > max) {
+ var objectLink = this.props.objectLink || DOM.span;
+ items.push(Caption({
+ key: "more",
+ object: objectLink({
+ object: this.props.object
+ }, array.length - max + " more…")
+ }));
+ }
+
+ return items;
+ },
+
+ /**
+ * Returns true if the passed object is an array with additional (custom)
+ * properties, otherwise returns false. Custom properties should be
+ * displayed in extra expandable section.
+ *
+ * Example array with a custom property.
+ * let arr = [0, 1];
+ * arr.myProp = "Hello";
+ *
+ * @param {Array} array The array object.
+ */
+ hasSpecialProperties: function (array) {
+ function isInteger(x) {
+ var y = parseInt(x, 10);
+ if (isNaN(y)) {
+ return false;
+ }
+ return x === y.toString();
+ }
+
+ var props = Object.getOwnPropertyNames(array);
+ for (var i = 0; i < props.length; i++) {
+ var p = props[i];
+
+ // Valid indexes are skipped
+ if (isInteger(p)) {
+ continue;
+ }
+
+ // Ignore standard 'length' property, anything else is custom.
+ if (p != "length") {
+ return true;
+ }
+ }
+
+ return false;
+ },
+
+ // Event Handlers
+
+ onToggleProperties: function (event) {},
+
+ onClickBracket: function (event) {},
+
+ render: function () {
+ var mode = this.props.mode || "short";
+ var object = this.props.object;
+ var items = void 0;
+ var brackets = void 0;
+ var needSpace = function (space) {
+ return space ? { left: "[ ", right: " ]" } : { left: "[", right: "]" };
+ };
+
+ if (mode == "tiny") {
+ var isEmpty = object.length === 0;
+ items = DOM.span({ className: "length" }, isEmpty ? "" : object.length);
+ brackets = needSpace(false);
+ } else {
+ var max = mode == "short" ? 3 : 300;
+ items = this.arrayIterator(object, max);
+ brackets = needSpace(items.length > 0);
+ }
+
+ var objectLink = this.props.objectLink || DOM.span;
+
+ return DOM.span({
+ className: "objectBox objectBox-array" }, objectLink({
+ className: "arrayLeftBracket",
+ object: object
+ }, brackets.left), items, objectLink({
+ className: "arrayRightBracket",
+ object: object
+ }, brackets.right), DOM.span({
+ className: "arrayProperties",
+ role: "group" }));
+ }
+ });
+
+ /**
+ * Renders array item. Individual values are separated by a comma.
+ */
+ var ItemRep = React.createFactory(React.createClass({
+ displayName: "ItemRep",
+
+ render: function () {
+ var _createFactories2 = createFactories(__webpack_require__(34));
+
+ var Rep = _createFactories2.Rep;
+
+
+ var object = this.props.object;
+ var delim = this.props.delim;
+ var mode = this.props.mode;
+ return DOM.span({}, Rep({ object: object, mode: mode }), delim);
+ }
+ }));
+
+ function supportsObject(object, type) {
+ return Array.isArray(object) || Object.prototype.toString.call(object) === "[object Arguments]";
+ }
+
+ // Exports from this module
+ exports.ArrayRep = {
+ rep: ArrayRep,
+ supportsObject: supportsObject
+ };
+ }.call(exports, __webpack_require__, exports, module), __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__));
+
+/***/ },
+/* 41 */
+/***/ function(module, exports, __webpack_require__) {
+
+ var __WEBPACK_AMD_DEFINE_RESULT__;/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+ /* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+ /* 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";
+
+ // Make this available to both AMD and CJS environments
+
+ !(__WEBPACK_AMD_DEFINE_RESULT__ = function (require, exports, module) {
+ // Dependencies
+ var React = __webpack_require__(2);
+ var DOM = React.DOM;
+
+ /**
+ * Renders a caption. This template is used by other components
+ * that needs to distinguish between a simple text/value and a label.
+ */
+ var Caption = React.createClass({
+ displayName: "Caption",
+
+ render: function () {
+ return DOM.span({ "className": "caption" }, this.props.object);
+ }
+ });
+
+ // Exports from this module
+ exports.Caption = Caption;
+ }.call(exports, __webpack_require__, exports, module), __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__));
+
+/***/ },
+/* 42 */
+/***/ function(module, exports, __webpack_require__) {
+
+ var __WEBPACK_AMD_DEFINE_RESULT__;/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+ /* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+ /* 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";
+ // Make this available to both AMD and CJS environments
+
+ !(__WEBPACK_AMD_DEFINE_RESULT__ = function (require, exports, module) {
+ // Dependencies
+ var React = __webpack_require__(2);
+
+ var _require = __webpack_require__(35);
+
+ var createFactories = _require.createFactories;
+
+ var _createFactories = createFactories(__webpack_require__(41));
+
+ var Caption = _createFactories.Caption;
+
+ var _createFactories2 = createFactories(__webpack_require__(43));
+
+ var PropRep = _createFactories2.PropRep;
+ // Shortcuts
+
+ var span = React.DOM.span;
+ /**
+ * Renders an object. An object is represented by a list of its
+ * properties enclosed in curly brackets.
+ */
+
+ var Obj = React.createClass({
+ displayName: "Obj",
+
+ propTypes: {
+ object: React.PropTypes.object,
+ mode: React.PropTypes.string
+ },
+
+ getTitle: function (object) {
+ var className = object && object.class ? object.class : "Object";
+ if (this.props.objectLink) {
+ return this.props.objectLink({
+ object: object
+ }, className);
+ }
+ return className;
+ },
+
+ safePropIterator: function (object, max) {
+ max = typeof max === "undefined" ? 3 : max;
+ try {
+ return this.propIterator(object, max);
+ } catch (err) {
+ console.error(err);
+ }
+ return [];
+ },
+
+ propIterator: function (object, max) {
+ var isInterestingProp = (t, value) => {
+ // Do not pick objects, it could cause recursion.
+ return t == "boolean" || t == "number" || t == "string" && value;
+ };
+
+ // Work around https://bugzilla.mozilla.org/show_bug.cgi?id=945377
+ if (Object.prototype.toString.call(object) === "[object Generator]") {
+ object = Object.getPrototypeOf(object);
+ }
+
+ // Object members with non-empty values are preferred since it gives the
+ // user a better overview of the object.
+ var props = this.getProps(object, max, isInterestingProp);
+
+ if (props.length <= max) {
+ // There are not enough props yet (or at least, not enough props to
+ // be able to know whether we should print "more…" or not).
+ // Let's display also empty members and functions.
+ props = props.concat(this.getProps(object, max, (t, value) => {
+ return !isInterestingProp(t, value);
+ }));
+ }
+
+ if (props.length > max) {
+ props.pop();
+ var objectLink = this.props.objectLink || span;
+
+ props.push(Caption({
+ key: "more",
+ object: objectLink({
+ object: object
+ }, Object.keys(object).length - max + " more…")
+ }));
+ } else if (props.length > 0) {
+ // Remove the last comma.
+ props[props.length - 1] = React.cloneElement(props[props.length - 1], { delim: "" });
+ }
+
+ return props;
+ },
+
+ getProps: function (object, max, filter) {
+ var props = [];
+
+ max = max || 3;
+ if (!object) {
+ return props;
+ }
+
+ // Hardcode tiny mode to avoid recursive handling.
+ var mode = "tiny";
+
+ try {
+ for (var name in object) {
+ if (props.length > max) {
+ return props;
+ }
+
+ var value = void 0;
+ try {
+ value = object[name];
+ } catch (exc) {
+ continue;
+ }
+
+ var t = typeof value;
+ if (filter(t, value)) {
+ props.push(PropRep({
+ key: name,
+ mode: mode,
+ name: name,
+ object: value,
+ equal: ": ",
+ delim: ", "
+ }));
+ }
+ }
+ } catch (err) {
+ console.error(err);
+ }
+
+ return props;
+ },
+
+ render: function () {
+ var object = this.props.object;
+ var props = this.safePropIterator(object);
+ var objectLink = this.props.objectLink || span;
+
+ if (this.props.mode == "tiny" || !props.length) {
+ return span({ className: "objectBox objectBox-object" }, objectLink({ className: "objectTitle" }, this.getTitle(object)));
+ }
+
+ return span({ className: "objectBox objectBox-object" }, this.getTitle(object), objectLink({
+ className: "objectLeftBrace",
+ object: object
+ }, " { "), props, objectLink({
+ className: "objectRightBrace",
+ object: object
+ }, " }"));
+ }
+ });
+ function supportsObject(object, type) {
+ return true;
+ }
+
+ // Exports from this module
+ exports.Obj = {
+ rep: Obj,
+ supportsObject: supportsObject
+ };
+ }.call(exports, __webpack_require__, exports, module), __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__));
+
+/***/ },
+/* 43 */
+/***/ function(module, exports, __webpack_require__) {
+
+ var __WEBPACK_AMD_DEFINE_RESULT__;/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+ /* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+ /* 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";
+
+ // Make this available to both AMD and CJS environments
+
+ !(__WEBPACK_AMD_DEFINE_RESULT__ = function (require, exports, module) {
+ var React = __webpack_require__(2);
+
+ var _require = __webpack_require__(35);
+
+ var createFactories = _require.createFactories;
+ var span = React.DOM.span;
+
+ /**
+ * Property for Obj (local JS objects), Grip (remote JS objects)
+ * and GripMap (remote JS maps and weakmaps) reps.
+ * It's used to render object properties.
+ */
+
+ var PropRep = React.createFactory(React.createClass({
+ displayName: "PropRep",
+
+ propTypes: {
+ // Property name.
+ name: React.PropTypes.oneOfType([React.PropTypes.string, React.PropTypes.object]).isRequired,
+ // Equal character rendered between property name and value.
+ equal: React.PropTypes.string,
+ // Delimiter character used to separate individual properties.
+ delim: React.PropTypes.string,
+ mode: React.PropTypes.string
+ },
+
+ render: function () {
+ var _require2 = __webpack_require__(44);
+
+ var Grip = _require2.Grip;
+
+ var _createFactories = createFactories(__webpack_require__(34));
+
+ var Rep = _createFactories.Rep;
+
+
+ var key = void 0;
+ // The key can be a simple string, for plain objects,
+ // or another object for maps and weakmaps.
+ if (typeof this.props.name === "string") {
+ key = span({ "className": "nodeName" }, this.props.name);
+ } else {
+ key = Rep({
+ object: this.props.name,
+ mode: this.props.mode || "tiny",
+ defaultRep: Grip,
+ objectLink: this.props.objectLink
+ });
+ }
+
+ return span({}, key, span({
+ "className": "objectEqual"
+ }, this.props.equal), Rep(this.props), span({
+ "className": "objectComma"
+ }, this.props.delim));
+ }
+ }));
+
+ // Exports from this module
+ exports.PropRep = PropRep;
+ }.call(exports, __webpack_require__, exports, module), __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__));
+
+/***/ },
+/* 44 */
+/***/ function(module, exports, __webpack_require__) {
+
+ var __WEBPACK_AMD_DEFINE_RESULT__;/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+ /* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+ /* 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";
+
+ // Make this available to both AMD and CJS environments
+
+ !(__WEBPACK_AMD_DEFINE_RESULT__ = function (require, exports, module) {
+ // ReactJS
+ var React = __webpack_require__(2);
+ // Dependencies
+
+ var _require = __webpack_require__(35);
+
+ var createFactories = _require.createFactories;
+ var isGrip = _require.isGrip;
+
+ var _createFactories = createFactories(__webpack_require__(41));
+
+ var Caption = _createFactories.Caption;
+
+ var _createFactories2 = createFactories(__webpack_require__(43));
+
+ var PropRep = _createFactories2.PropRep;
+ // Shortcuts
+
+ var span = React.DOM.span;
+
+ /**
+ * Renders generic grip. Grip is client representation
+ * of remote JS object and is used as an input object
+ * for this rep component.
+ */
+
+ var GripRep = React.createClass({
+ displayName: "Grip",
+
+ propTypes: {
+ object: React.PropTypes.object.isRequired,
+ mode: React.PropTypes.string,
+ isInterestingProp: React.PropTypes.func
+ },
+
+ getTitle: function (object) {
+ if (this.props.objectLink) {
+ return this.props.objectLink({
+ object: object
+ }, object.class);
+ }
+ return object.class || "Object";
+ },
+
+ safePropIterator: function (object, max) {
+ max = typeof max === "undefined" ? 3 : max;
+ try {
+ return this.propIterator(object, max);
+ } catch (err) {
+ console.error(err);
+ }
+ return [];
+ },
+
+ propIterator: function (object, max) {
+ // Property filter. Show only interesting properties to the user.
+ var isInterestingProp = this.props.isInterestingProp || ((type, value) => {
+ return type == "boolean" || type == "number" || type == "string" && value.length != 0;
+ });
+
+ var ownProperties = object.preview ? object.preview.ownProperties : [];
+ var indexes = this.getPropIndexes(ownProperties, max, isInterestingProp);
+ if (indexes.length < max && indexes.length < object.ownPropertyLength) {
+ // There are not enough props yet. Then add uninteresting props to display them.
+ indexes = indexes.concat(this.getPropIndexes(ownProperties, max - indexes.length, (t, value, name) => {
+ return !isInterestingProp(t, value, name);
+ }));
+ }
+
+ var props = this.getProps(ownProperties, indexes);
+ if (props.length < object.ownPropertyLength) {
+ // There are some undisplayed props. Then display "more...".
+ var objectLink = this.props.objectLink || span;
+
+ props.push(Caption({
+ key: "more",
+ object: objectLink({
+ object: object
+ }, (object ? object.ownPropertyLength : 0) - max + " more…")
+ }));
+ } else if (props.length > 0) {
+ // Remove the last comma.
+ // NOTE: do not change comp._store.props directly to update a property,
+ // it should be re-rendered or cloned with changed props
+ var last = props.length - 1;
+ props[last] = React.cloneElement(props[last], {
+ delim: ""
+ });
+ }
+
+ return props;
+ },
+
+ /**
+ * Get props ordered by index.
+ *
+ * @param {Object} ownProperties Props object.
+ * @param {Array} indexes Indexes of props.
+ * @return {Array} Props.
+ */
+ getProps: function (ownProperties, indexes) {
+ var props = [];
+
+ // Make indexes ordered by ascending.
+ indexes.sort(function (a, b) {
+ return a - b;
+ });
+
+ indexes.forEach(i => {
+ var name = Object.keys(ownProperties)[i];
+ var prop = ownProperties[name];
+ var value = prop.value !== undefined ? prop.value : prop;
+ props.push(PropRep(Object.assign({}, this.props, {
+ key: name,
+ mode: "tiny",
+ name: name,
+ object: value,
+ equal: ": ",
+ delim: ", ",
+ defaultRep: Grip
+ })));
+ });
+
+ return props;
+ },
+
+ /**
+ * Get the indexes of props in the object.
+ *
+ * @param {Object} ownProperties Props object.
+ * @param {Number} max The maximum length of indexes array.
+ * @param {Function} filter Filter the props you want.
+ * @return {Array} Indexes of interesting props in the object.
+ */
+ getPropIndexes: function (ownProperties, max, filter) {
+ var indexes = [];
+
+ try {
+ var i = 0;
+ for (var name in ownProperties) {
+ if (indexes.length >= max) {
+ return indexes;
+ }
+
+ var prop = ownProperties[name];
+ var value = prop.value !== undefined ? prop.value : prop;
+
+ // Type is specified in grip's "class" field and for primitive
+ // values use typeof.
+ var type = value.class || typeof value;
+ type = type.toLowerCase();
+
+ if (filter(type, value, name)) {
+ indexes.push(i);
+ }
+ i++;
+ }
+ } catch (err) {
+ console.error(err);
+ }
+
+ return indexes;
+ },
+
+ render: function () {
+ var object = this.props.object;
+ var props = this.safePropIterator(object, this.props.mode == "long" ? 100 : 3);
+
+ var objectLink = this.props.objectLink || span;
+ if (this.props.mode == "tiny" || !props.length) {
+ return span({ className: "objectBox objectBox-object" }, this.getTitle(object), objectLink({
+ className: "objectLeftBrace",
+ object: object
+ }, ""));
+ }
+
+ return span({ className: "objectBox objectBox-object" }, this.getTitle(object), objectLink({
+ className: "objectLeftBrace",
+ object: object
+ }, " { "), props, objectLink({
+ className: "objectRightBrace",
+ object: object
+ }, " }"));
+ }
+ });
+
+ // Registration
+ function supportsObject(object, type) {
+ if (!isGrip(object)) {
+ return false;
+ }
+ return object.preview && object.preview.ownProperties;
+ }
+
+ var Grip = {
+ rep: GripRep,
+ supportsObject: supportsObject
+ };
+
+ // Exports from this module
+ exports.Grip = Grip;
+ }.call(exports, __webpack_require__, exports, module), __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__));
+
+/***/ },
+/* 45 */
+/***/ function(module, exports, __webpack_require__) {
+
+ var __WEBPACK_AMD_DEFINE_RESULT__;/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+ /* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+ /* 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";
+
+ // Make this available to both AMD and CJS environments
+
+ !(__WEBPACK_AMD_DEFINE_RESULT__ = function (require, exports, module) {
+ // Dependencies
+ var React = __webpack_require__(2);
+
+ // Shortcuts
+ var span = React.DOM.span;
+
+ /**
+ * Renders a symbol.
+ */
+
+ var SymbolRep = React.createClass({
+ displayName: "SymbolRep",
+
+ propTypes: {
+ object: React.PropTypes.object.isRequired
+ },
+
+ render: function () {
+ var object = this.props.object;
+ var name = object.name;
+
+
+ return span({ className: "objectBox objectBox-symbol" }, `Symbol(${ name || "" })`);
+ }
+ });
+
+ function supportsObject(object, type) {
+ return type == "symbol";
+ }
+
+ // Exports from this module
+ exports.SymbolRep = {
+ rep: SymbolRep,
+ supportsObject: supportsObject
+ };
+ }.call(exports, __webpack_require__, exports, module), __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__));
+
+/***/ },
+/* 46 */
+/***/ function(module, exports, __webpack_require__) {
+
+ var __WEBPACK_AMD_DEFINE_RESULT__;/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+ /* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+ /* 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";
+
+ // Make this available to both AMD and CJS environments
+
+ !(__WEBPACK_AMD_DEFINE_RESULT__ = function (require, exports, module) {
+ // ReactJS
+ var React = __webpack_require__(2);
+
+ // Reps
+
+ var _require = __webpack_require__(35);
+
+ var createFactories = _require.createFactories;
+ var isGrip = _require.isGrip;
+
+ var _require2 = __webpack_require__(38);
+
+ var StringRep = _require2.StringRep;
+
+ // Shortcuts
+
+ var span = React.DOM.span;
+
+ var _createFactories = createFactories(StringRep);
+
+ var StringRepFactory = _createFactories.rep;
+
+ /**
+ * Renders DOM attribute
+ */
+
+ var Attribute = React.createClass({
+ displayName: "Attr",
+
+ propTypes: {
+ object: React.PropTypes.object.isRequired
+ },
+
+ getTitle: function (grip) {
+ return grip.preview.nodeName;
+ },
+
+ render: function () {
+ var grip = this.props.object;
+ var value = grip.preview.value;
+ var objectLink = this.props.objectLink || span;
+
+ return objectLink({ className: "objectLink-Attr" }, span({}, span({ className: "attrTitle" }, this.getTitle(grip)), span({ className: "attrEqual" }, "="), StringRepFactory({ object: value })));
+ }
+ });
+
+ // Registration
+
+ function supportsObject(grip, type) {
+ if (!isGrip(grip)) {
+ return false;
+ }
+
+ return type == "Attr" && grip.preview;
+ }
+
+ exports.Attribute = {
+ rep: Attribute,
+ supportsObject: supportsObject
+ };
+ }.call(exports, __webpack_require__, exports, module), __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__));
+
+/***/ },
+/* 47 */
+/***/ function(module, exports, __webpack_require__) {
+
+ var __WEBPACK_AMD_DEFINE_RESULT__;/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+ /* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+ /* 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";
+
+ // Make this available to both AMD and CJS environments
+
+ !(__WEBPACK_AMD_DEFINE_RESULT__ = function (require, exports, module) {
+ // ReactJS
+ var React = __webpack_require__(2);
+
+ // Reps
+
+ var _require = __webpack_require__(35);
+
+ var isGrip = _require.isGrip;
+
+ // Shortcuts
+
+ var span = React.DOM.span;
+
+ /**
+ * Used to render JS built-in Date() object.
+ */
+
+ var DateTime = React.createClass({
+ displayName: "Date",
+
+ propTypes: {
+ object: React.PropTypes.object.isRequired
+ },
+
+ getTitle: function (grip) {
+ if (this.props.objectLink) {
+ return this.props.objectLink({
+ object: grip
+ }, grip.class + " ");
+ }
+ return "";
+ },
+
+ render: function () {
+ var grip = this.props.object;
+ return span({ className: "objectBox" }, this.getTitle(grip), span({ className: "Date" }, new Date(grip.preview.timestamp).toISOString()));
+ }
+ });
+
+ // Registration
+
+ function supportsObject(grip, type) {
+ if (!isGrip(grip)) {
+ return false;
+ }
+
+ return type == "Date" && grip.preview;
+ }
+
+ // Exports from this module
+ exports.DateTime = {
+ rep: DateTime,
+ supportsObject: supportsObject
+ };
+ }.call(exports, __webpack_require__, exports, module), __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__));
+
+/***/ },
+/* 48 */
+/***/ function(module, exports, __webpack_require__) {
+
+ var __WEBPACK_AMD_DEFINE_RESULT__;/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+ /* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+ /* 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";
+
+ // Make this available to both AMD and CJS environments
+
+ !(__WEBPACK_AMD_DEFINE_RESULT__ = function (require, exports, module) {
+ // ReactJS
+ var React = __webpack_require__(2);
+
+ // Reps
+
+ var _require = __webpack_require__(35);
+
+ var isGrip = _require.isGrip;
+ var getURLDisplayString = _require.getURLDisplayString;
+
+ // Shortcuts
+
+ var span = React.DOM.span;
+
+ /**
+ * Renders DOM document object.
+ */
+
+ var Document = React.createClass({
+ displayName: "Document",
+
+ propTypes: {
+ object: React.PropTypes.object.isRequired
+ },
+
+ getLocation: function (grip) {
+ var location = grip.preview.location;
+ return location ? getURLDisplayString(location) : "";
+ },
+
+ getTitle: function (grip) {
+ if (this.props.objectLink) {
+ return span({ className: "objectBox" }, this.props.objectLink({
+ object: grip
+ }, grip.class + " "));
+ }
+ return "";
+ },
+
+ getTooltip: function (doc) {
+ return doc.location.href;
+ },
+
+ render: function () {
+ var grip = this.props.object;
+
+ return span({ className: "objectBox objectBox-object" }, this.getTitle(grip), span({ className: "objectPropValue" }, this.getLocation(grip)));
+ }
+ });
+
+ // Registration
+
+ function supportsObject(object, type) {
+ if (!isGrip(object)) {
+ return false;
+ }
+
+ return object.preview && type == "HTMLDocument";
+ }
+
+ // Exports from this module
+ exports.Document = {
+ rep: Document,
+ supportsObject: supportsObject
+ };
+ }.call(exports, __webpack_require__, exports, module), __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__));
+
+/***/ },
+/* 49 */
+/***/ function(module, exports, __webpack_require__) {
+
+ var __WEBPACK_AMD_DEFINE_RESULT__;/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+ /* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+ /* 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";
+
+ // Make this available to both AMD and CJS environments
+
+ !(__WEBPACK_AMD_DEFINE_RESULT__ = function (require, exports, module) {
+ // ReactJS
+ var React = __webpack_require__(2);
+
+ // Reps
+
+ var _require = __webpack_require__(35);
+
+ var createFactories = _require.createFactories;
+ var isGrip = _require.isGrip;
+
+ var _createFactories = createFactories(__webpack_require__(44).Grip);
+
+ var rep = _createFactories.rep;
+
+ /**
+ * Renders DOM event objects.
+ */
+
+ var Event = React.createClass({
+ displayName: "event",
+
+ propTypes: {
+ object: React.PropTypes.object.isRequired
+ },
+
+ render: function () {
+ // Use `Object.assign` to keep `this.props` without changes because:
+ // 1. JSON.stringify/JSON.parse is slow.
+ // 2. Immutable.js is planned for the future.
+ var props = Object.assign({}, this.props);
+ props.object = Object.assign({}, this.props.object);
+ props.object.preview = Object.assign({}, this.props.object.preview);
+ props.object.preview.ownProperties = props.object.preview.properties;
+ delete props.object.preview.properties;
+ props.object.ownPropertyLength = Object.keys(props.object.preview.ownProperties).length;
+
+ switch (props.object.class) {
+ case "MouseEvent":
+ props.isInterestingProp = (type, value, name) => {
+ return name == "clientX" || name == "clientY" || name == "layerX" || name == "layerY";
+ };
+ break;
+ case "KeyboardEvent":
+ props.isInterestingProp = (type, value, name) => {
+ return name == "key" || name == "charCode" || name == "keyCode";
+ };
+ break;
+ case "MessageEvent":
+ props.isInterestingProp = (type, value, name) => {
+ return name == "isTrusted" || name == "data";
+ };
+ break;
+ }
+ return rep(props);
+ }
+ });
+
+ // Registration
+
+ function supportsObject(grip, type) {
+ if (!isGrip(grip)) {
+ return false;
+ }
+
+ return grip.preview && grip.preview.kind == "DOMEvent";
+ }
+
+ // Exports from this module
+ exports.Event = {
+ rep: Event,
+ supportsObject: supportsObject
+ };
+ }.call(exports, __webpack_require__, exports, module), __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__));
+
+/***/ },
+/* 50 */
+/***/ function(module, exports, __webpack_require__) {
+
+ var __WEBPACK_AMD_DEFINE_RESULT__;/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+ /* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+ /* 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";
+
+ // Make this available to both AMD and CJS environments
+
+ !(__WEBPACK_AMD_DEFINE_RESULT__ = function (require, exports, module) {
+ // ReactJS
+ var React = __webpack_require__(2);
+
+ // Reps
+
+ var _require = __webpack_require__(35);
+
+ var isGrip = _require.isGrip;
+ var cropString = _require.cropString;
+
+ // Shortcuts
+
+ var span = React.DOM.span;
+
+ /**
+ * This component represents a template for Function objects.
+ */
+
+ var Func = React.createClass({
+ displayName: "Func",
+
+ propTypes: {
+ object: React.PropTypes.object.isRequired
+ },
+
+ getTitle: function (grip) {
+ if (this.props.objectLink) {
+ return this.props.objectLink({
+ object: grip
+ }, "function ");
+ }
+ return "";
+ },
+
+ summarizeFunction: function (grip) {
+ var name = grip.userDisplayName || grip.displayName || grip.name || "function";
+ return cropString(name + "()", 100);
+ },
+
+ render: function () {
+ var grip = this.props.object;
+
+ return span({ className: "objectBox objectBox-function" }, this.getTitle(grip), this.summarizeFunction(grip));
+ }
+ });
+
+ // Registration
+
+ function supportsObject(grip, type) {
+ if (!isGrip(grip)) {
+ return type == "function";
+ }
+
+ return type == "Function";
+ }
+
+ // Exports from this module
+
+ exports.Func = {
+ rep: Func,
+ supportsObject: supportsObject
+ };
+ }.call(exports, __webpack_require__, exports, module), __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__));
+
+/***/ },
+/* 51 */
+/***/ function(module, exports, __webpack_require__) {
+
+ var __WEBPACK_AMD_DEFINE_RESULT__;/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+ /* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+ /* 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";
+
+ // Make this available to both AMD and CJS environments
+
+ !(__WEBPACK_AMD_DEFINE_RESULT__ = function (require, exports, module) {
+ // ReactJS
+ var React = __webpack_require__(2);
+
+ // Reps
+
+ var _require = __webpack_require__(35);
+
+ var isGrip = _require.isGrip;
+
+ // Shortcuts
+
+ var span = React.DOM.span;
+
+ /**
+ * Renders a grip object with regular expression.
+ */
+
+ var RegExp = React.createClass({
+ displayName: "regexp",
+
+ propTypes: {
+ object: React.PropTypes.object.isRequired
+ },
+
+ getSource: function (grip) {
+ return grip.displayString;
+ },
+
+ render: function () {
+ var grip = this.props.object;
+ var objectLink = this.props.objectLink || span;
+
+ return span({ className: "objectBox objectBox-regexp" }, objectLink({
+ object: grip,
+ className: "regexpSource"
+ }, this.getSource(grip)));
+ }
+ });
+
+ // Registration
+
+ function supportsObject(object, type) {
+ if (!isGrip(object)) {
+ return false;
+ }
+
+ return type == "RegExp";
+ }
+
+ // Exports from this module
+ exports.RegExp = {
+ rep: RegExp,
+ supportsObject: supportsObject
+ };
+ }.call(exports, __webpack_require__, exports, module), __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__));
+
+/***/ },
+/* 52 */
+/***/ function(module, exports, __webpack_require__) {
+
+ var __WEBPACK_AMD_DEFINE_RESULT__;/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+ /* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+ /* 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";
+
+ // Make this available to both AMD and CJS environments
+
+ !(__WEBPACK_AMD_DEFINE_RESULT__ = function (require, exports, module) {
+ // ReactJS
+ var React = __webpack_require__(2);
+
+ // Reps
+
+ var _require = __webpack_require__(35);
+
+ var isGrip = _require.isGrip;
+ var getURLDisplayString = _require.getURLDisplayString;
+
+ // Shortcuts
+
+ var DOM = React.DOM;
+
+ /**
+ * Renders a grip representing CSSStyleSheet
+ */
+ var StyleSheet = React.createClass({
+ displayName: "object",
+
+ propTypes: {
+ object: React.PropTypes.object.isRequired
+ },
+
+ getTitle: function (grip) {
+ var title = "StyleSheet ";
+ if (this.props.objectLink) {
+ return DOM.span({ className: "objectBox" }, this.props.objectLink({
+ object: grip
+ }, title));
+ }
+ return title;
+ },
+
+ getLocation: function (grip) {
+ // Embedded stylesheets don't have URL and so, no preview.
+ var url = grip.preview ? grip.preview.url : "";
+ return url ? getURLDisplayString(url) : "";
+ },
+
+ render: function () {
+ var grip = this.props.object;
+
+ return DOM.span({ className: "objectBox objectBox-object" }, this.getTitle(grip), DOM.span({ className: "objectPropValue" }, this.getLocation(grip)));
+ }
+ });
+
+ // Registration
+
+ function supportsObject(object, type) {
+ if (!isGrip(object)) {
+ return false;
+ }
+
+ return type == "CSSStyleSheet";
+ }
+
+ // Exports from this module
+
+ exports.StyleSheet = {
+ rep: StyleSheet,
+ supportsObject: supportsObject
+ };
+ }.call(exports, __webpack_require__, exports, module), __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__));
+
+/***/ },
+/* 53 */
+/***/ function(module, exports, __webpack_require__) {
+
+ var __WEBPACK_AMD_DEFINE_RESULT__;/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+ /* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+ /* 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";
+
+ // Make this available to both AMD and CJS environments
+
+ !(__WEBPACK_AMD_DEFINE_RESULT__ = function (require, exports, module) {
+ // ReactJS
+ var React = __webpack_require__(2);
+
+ // Reps
+
+ var _require = __webpack_require__(35);
+
+ var isGrip = _require.isGrip;
+ var cropMultipleLines = _require.cropMultipleLines;
+
+ // Shortcuts
+
+ var DOM = React.DOM;
+
+ /**
+ * Renders DOM #text node.
+ */
+ var TextNode = React.createClass({
+ displayName: "TextNode",
+
+ propTypes: {
+ object: React.PropTypes.object.isRequired,
+ mode: React.PropTypes.string
+ },
+
+ getTextContent: function (grip) {
+ return cropMultipleLines(grip.preview.textContent);
+ },
+
+ getTitle: function (grip) {
+ if (this.props.objectLink) {
+ return this.props.objectLink({
+ object: grip
+ }, "#text");
+ }
+ return "";
+ },
+
+ render: function () {
+ var grip = this.props.object;
+ var mode = this.props.mode || "short";
+
+ if (mode == "short" || mode == "tiny") {
+ return DOM.span({ className: "objectBox objectBox-textNode" }, this.getTitle(grip), "\"" + this.getTextContent(grip) + "\"");
+ }
+
+ var objectLink = this.props.objectLink || DOM.span;
+ return DOM.span({ className: "objectBox objectBox-textNode" }, this.getTitle(grip), objectLink({
+ object: grip
+ }, "<"), DOM.span({ className: "nodeTag" }, "TextNode"), " textContent=\"", DOM.span({ className: "nodeValue" }, this.getTextContent(grip)), "\"", objectLink({
+ object: grip
+ }, ">;"));
+ }
+ });
+
+ // Registration
+
+ function supportsObject(grip, type) {
+ if (!isGrip(grip)) {
+ return false;
+ }
+
+ return grip.preview && grip.class == "Text";
+ }
+
+ // Exports from this module
+ exports.TextNode = {
+ rep: TextNode,
+ supportsObject: supportsObject
+ };
+ }.call(exports, __webpack_require__, exports, module), __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__));
+
+/***/ },
+/* 54 */
+/***/ function(module, exports, __webpack_require__) {
+
+ var __WEBPACK_AMD_DEFINE_RESULT__;/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+ /* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+ /* 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";
+
+ // Make this available to both AMD and CJS environments
+
+ !(__WEBPACK_AMD_DEFINE_RESULT__ = function (require, exports, module) {
+ // ReactJS
+ var React = __webpack_require__(2);
+
+ // Reps
+
+ var _require = __webpack_require__(35);
+
+ var isGrip = _require.isGrip;
+ var getURLDisplayString = _require.getURLDisplayString;
+
+ // Shortcuts
+
+ var DOM = React.DOM;
+
+ /**
+ * Renders a grip representing a window.
+ */
+ var Window = React.createClass({
+ displayName: "Window",
+
+ propTypes: {
+ object: React.PropTypes.object.isRequired,
+ mode: React.PropTypes.string
+ },
+
+ getTitle: function (grip) {
+ if (this.props.objectLink) {
+ return DOM.span({ className: "objectBox" }, this.props.objectLink({
+ object: grip
+ }, grip.class + " "));
+ }
+ return "";
+ },
+
+ getLocation: function (grip) {
+ return getURLDisplayString(grip.preview.url);
+ },
+
+ getDisplayValue: function (grip) {
+ if (this.props.mode === "tiny") {
+ return grip.isGlobal ? "Global" : "Window";
+ } else {
+ return this.getLocation(grip);
+ }
+ },
+
+ render: function () {
+ var grip = this.props.object;
+
+ return DOM.span({ className: "objectBox objectBox-Window" }, this.getTitle(grip), DOM.span({ className: "objectPropValue" }, this.getDisplayValue(grip)));
+ }
+ });
+
+ // Registration
+
+ function supportsObject(object, type) {
+ if (!isGrip(object)) {
+ return false;
+ }
+
+ return object.preview && type == "Window";
+ }
+
+ // Exports from this module
+ exports.Window = {
+ rep: Window,
+ supportsObject: supportsObject
+ };
+ }.call(exports, __webpack_require__, exports, module), __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__));
+
+/***/ },
+/* 55 */
+/***/ function(module, exports, __webpack_require__) {
+
+ var __WEBPACK_AMD_DEFINE_RESULT__;/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+ /* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+ /* 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";
+
+ // Make this available to both AMD and CJS environments
+
+ !(__WEBPACK_AMD_DEFINE_RESULT__ = function (require, exports, module) {
+ // ReactJS
+ var React = __webpack_require__(2);
+
+ // Reps
+
+ var _require = __webpack_require__(35);
+
+ var isGrip = _require.isGrip;
+
+ // Shortcuts
+
+ var span = React.DOM.span;
+
+ /**
+ * Renders a grip object with textual data.
+ */
+
+ var ObjectWithText = React.createClass({
+ displayName: "ObjectWithText",
+
+ propTypes: {
+ object: React.PropTypes.object.isRequired
+ },
+
+ getTitle: function (grip) {
+ if (this.props.objectLink) {
+ return span({ className: "objectBox" }, this.props.objectLink({
+ object: grip
+ }, this.getType(grip) + " "));
+ }
+ return "";
+ },
+
+ getType: function (grip) {
+ return grip.class;
+ },
+
+ getDescription: function (grip) {
+ return "\"" + grip.preview.text + "\"";
+ },
+
+ render: function () {
+ var grip = this.props.object;
+ return span({ className: "objectBox objectBox-" + this.getType(grip) }, this.getTitle(grip), span({ className: "objectPropValue" }, this.getDescription(grip)));
+ }
+ });
+
+ // Registration
+
+ function supportsObject(grip, type) {
+ if (!isGrip(grip)) {
+ return false;
+ }
+
+ return grip.preview && grip.preview.kind == "ObjectWithText";
+ }
+
+ // Exports from this module
+ exports.ObjectWithText = {
+ rep: ObjectWithText,
+ supportsObject: supportsObject
+ };
+ }.call(exports, __webpack_require__, exports, module), __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__));
+
+/***/ },
+/* 56 */
+/***/ function(module, exports, __webpack_require__) {
+
+ var __WEBPACK_AMD_DEFINE_RESULT__;/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+ /* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+ /* 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";
+
+ // Make this available to both AMD and CJS environments
+
+ !(__WEBPACK_AMD_DEFINE_RESULT__ = function (require, exports, module) {
+ // ReactJS
+ var React = __webpack_require__(2);
+
+ // Reps
+
+ var _require = __webpack_require__(35);
+
+ var isGrip = _require.isGrip;
+ var getURLDisplayString = _require.getURLDisplayString;
+
+ // Shortcuts
+
+ var span = React.DOM.span;
+
+ /**
+ * Renders a grip object with URL data.
+ */
+
+ var ObjectWithURL = React.createClass({
+ displayName: "ObjectWithURL",
+
+ propTypes: {
+ object: React.PropTypes.object.isRequired
+ },
+
+ getTitle: function (grip) {
+ if (this.props.objectLink) {
+ return span({ className: "objectBox" }, this.props.objectLink({
+ object: grip
+ }, this.getType(grip) + " "));
+ }
+ return "";
+ },
+
+ getType: function (grip) {
+ return grip.class;
+ },
+
+ getDescription: function (grip) {
+ return getURLDisplayString(grip.preview.url);
+ },
+
+ render: function () {
+ var grip = this.props.object;
+ return span({ className: "objectBox objectBox-" + this.getType(grip) }, this.getTitle(grip), span({ className: "objectPropValue" }, this.getDescription(grip)));
+ }
+ });
+
+ // Registration
+
+ function supportsObject(grip, type) {
+ if (!isGrip(grip)) {
+ return false;
+ }
+
+ return grip.preview && grip.preview.kind == "ObjectWithURL";
+ }
+
+ // Exports from this module
+ exports.ObjectWithURL = {
+ rep: ObjectWithURL,
+ supportsObject: supportsObject
+ };
+ }.call(exports, __webpack_require__, exports, module), __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__));
+
+/***/ },
+/* 57 */
+/***/ function(module, exports, __webpack_require__) {
+
+ var __WEBPACK_AMD_DEFINE_RESULT__;/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+ /* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+ /* 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";
+
+ // Make this available to both AMD and CJS environments
+
+ !(__WEBPACK_AMD_DEFINE_RESULT__ = function (require, exports, module) {
+ // Dependencies
+ var React = __webpack_require__(2);
+
+ var _require = __webpack_require__(35);
+
+ var createFactories = _require.createFactories;
+ var isGrip = _require.isGrip;
+
+ var _createFactories = createFactories(__webpack_require__(41));
+
+ var Caption = _createFactories.Caption;
+
+ // Shortcuts
+
+ var span = React.DOM.span;
+
+ /**
+ * Renders an array. The array is enclosed by left and right bracket
+ * and the max number of rendered items depends on the current mode.
+ */
+
+ var GripArray = React.createClass({
+ displayName: "GripArray",
+
+ propTypes: {
+ object: React.PropTypes.object.isRequired,
+ mode: React.PropTypes.string,
+ provider: React.PropTypes.object
+ },
+
+ getLength: function (grip) {
+ return grip.preview ? grip.preview.length : 0;
+ },
+
+ getTitle: function (object, context) {
+ var objectLink = this.props.objectLink || span;
+ if (this.props.mode != "tiny") {
+ return objectLink({
+ object: object
+ }, object.class + " ");
+ }
+ return "";
+ },
+
+ arrayIterator: function (grip, max) {
+ var items = [];
+
+ if (!grip.preview || !grip.preview.length) {
+ return items;
+ }
+
+ var array = grip.preview.items;
+ if (!array) {
+ return items;
+ }
+
+ var delim = void 0;
+ // number of grip.preview.items is limited to 10, but we may have more
+ // items in grip-array
+ var delimMax = grip.preview.length > array.length ? array.length : array.length - 1;
+ var provider = this.props.provider;
+
+ for (var i = 0; i < array.length && i < max; i++) {
+ try {
+ var itemGrip = array[i];
+ var value = provider ? provider.getValue(itemGrip) : itemGrip;
+
+ delim = i == delimMax ? "" : ", ";
+
+ items.push(GripArrayItem(Object.assign({}, this.props, {
+ key: i,
+ object: value,
+ delim: delim })));
+ } catch (exc) {
+ items.push(GripArrayItem(Object.assign({}, this.props, {
+ object: exc,
+ delim: delim,
+ key: i })));
+ }
+ }
+ if (array.length > max || grip.preview.length > array.length) {
+ var objectLink = this.props.objectLink || span;
+ var leftItemNum = grip.preview.length - max > 0 ? grip.preview.length - max : grip.preview.length - array.length;
+ items.push(Caption({
+ key: "more",
+ object: objectLink({
+ object: this.props.object
+ }, leftItemNum + " more…")
+ }));
+ }
+
+ return items;
+ },
+
+ render: function () {
+ var mode = this.props.mode || "short";
+ var object = this.props.object;
+
+ var items = void 0;
+ var brackets = void 0;
+ var needSpace = function (space) {
+ return space ? { left: "[ ", right: " ]" } : { left: "[", right: "]" };
+ };
+
+ if (mode == "tiny") {
+ var objectLength = this.getLength(object);
+ var isEmpty = objectLength === 0;
+ items = span({ className: "length" }, isEmpty ? "" : objectLength);
+ brackets = needSpace(false);
+ } else {
+ var max = mode == "short" ? 3 : 300;
+ items = this.arrayIterator(object, max);
+ brackets = needSpace(items.length > 0);
+ }
+
+ var objectLink = this.props.objectLink || span;
+ var title = this.getTitle(object);
+
+ return span({
+ className: "objectBox objectBox-array" }, title, objectLink({
+ className: "arrayLeftBracket",
+ object: object
+ }, brackets.left), items, objectLink({
+ className: "arrayRightBracket",
+ object: object
+ }, brackets.right), span({
+ className: "arrayProperties",
+ role: "group" }));
+ }
+ });
+
+ /**
+ * Renders array item. Individual values are separated by
+ * a delimiter (a comma by default).
+ */
+ var GripArrayItem = React.createFactory(React.createClass({
+ displayName: "GripArrayItem",
+
+ propTypes: {
+ delim: React.PropTypes.string
+ },
+
+ render: function () {
+ var _createFactories2 = createFactories(__webpack_require__(34));
+
+ var Rep = _createFactories2.Rep;
+
+
+ return span({}, Rep(Object.assign({}, this.props, {
+ mode: "tiny"
+ })), this.props.delim);
+ }
+ }));
+
+ function supportsObject(grip, type) {
+ if (!isGrip(grip)) {
+ return false;
+ }
+
+ return grip.preview && grip.preview.kind == "ArrayLike";
+ }
+
+ // Exports from this module
+ exports.GripArray = {
+ rep: GripArray,
+ supportsObject: supportsObject
+ };
+ }.call(exports, __webpack_require__, exports, module), __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__));
+
+/***/ },
+/* 58 */
+/***/ function(module, exports, __webpack_require__) {
+
+ var __WEBPACK_AMD_DEFINE_RESULT__;/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+ /* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+ /* 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";
+ // Make this available to both AMD and CJS environments
+
+ var _slicedToArray = function () { function sliceIterator(arr, i) { var _arr = []; var _n = true; var _d = false; var _e = undefined; try { for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i["return"]) _i["return"](); } finally { if (_d) throw _e; } } return _arr; } return function (arr, i) { if (Array.isArray(arr)) { return arr; } else if (Symbol.iterator in Object(arr)) { return sliceIterator(arr, i); } else { throw new TypeError("Invalid attempt to destructure non-iterable instance"); } }; }();
+
+ !(__WEBPACK_AMD_DEFINE_RESULT__ = function (require, exports, module) {
+ // Dependencies
+ var React = __webpack_require__(2);
+
+ var _require = __webpack_require__(35);
+
+ var createFactories = _require.createFactories;
+ var isGrip = _require.isGrip;
+
+ var _createFactories = createFactories(__webpack_require__(41));
+
+ var Caption = _createFactories.Caption;
+
+ var _createFactories2 = createFactories(__webpack_require__(43));
+
+ var PropRep = _createFactories2.PropRep;
+
+ // Shortcuts
+
+ var span = React.DOM.span;
+ /**
+ * Renders an map. A map is represented by a list of its
+ * entries enclosed in curly brackets.
+ */
+
+ var GripMap = React.createClass({
+ displayName: "GripMap",
+
+ propTypes: {
+ object: React.PropTypes.object,
+ mode: React.PropTypes.string
+ },
+
+ getTitle: function (object) {
+ var title = object && object.class ? object.class : "Map";
+ if (this.props.objectLink) {
+ return this.props.objectLink({
+ object: object
+ }, title);
+ }
+ return title;
+ },
+
+ safeEntriesIterator: function (object, max) {
+ max = typeof max === "undefined" ? 3 : max;
+ try {
+ return this.entriesIterator(object, max);
+ } catch (err) {
+ console.error(err);
+ }
+ return [];
+ },
+
+ entriesIterator: function (object, max) {
+ // Entry filter. Show only interesting entries to the user.
+ var isInterestingEntry = this.props.isInterestingEntry || ((type, value) => {
+ return type == "boolean" || type == "number" || type == "string" && value.length != 0;
+ });
+
+ var mapEntries = object.preview && object.preview.entries ? object.preview.entries : [];
+
+ var indexes = this.getEntriesIndexes(mapEntries, max, isInterestingEntry);
+ if (indexes.length < max && indexes.length < mapEntries.length) {
+ // There are not enough entries yet, so we add uninteresting entries.
+ indexes = indexes.concat(this.getEntriesIndexes(mapEntries, max - indexes.length, (t, value, name) => {
+ return !isInterestingEntry(t, value, name);
+ }));
+ }
+
+ var entries = this.getEntries(mapEntries, indexes);
+ if (entries.length < mapEntries.length) {
+ // There are some undisplayed entries. Then display "more…".
+ var objectLink = this.props.objectLink || span;
+
+ entries.push(Caption({
+ key: "more",
+ object: objectLink({
+ object: object
+ }, `${ mapEntries.length - max } more…`)
+ }));
+ }
+
+ return entries;
+ },
+
+ /**
+ * Get entries ordered by index.
+ *
+ * @param {Array} entries Entries array.
+ * @param {Array} indexes Indexes of entries.
+ * @return {Array} Array of PropRep.
+ */
+ getEntries: function (entries, indexes) {
+ // Make indexes ordered by ascending.
+ indexes.sort(function (a, b) {
+ return a - b;
+ });
+
+ return indexes.map((index, i) => {
+ var _entries$index = _slicedToArray(entries[index], 2);
+
+ var key = _entries$index[0];
+ var entryValue = _entries$index[1];
+
+ var value = entryValue.value !== undefined ? entryValue.value : entryValue;
+
+ return PropRep({
+ // key,
+ name: key,
+ equal: ": ",
+ object: value,
+ // Do not add a trailing comma on the last entry
+ // if there won't be a "more..." item.
+ delim: i < indexes.length - 1 || indexes.length < entries.length ? ", " : "",
+ mode: "tiny",
+ objectLink: this.props.objectLink
+ });
+ });
+ },
+
+ /**
+ * Get the indexes of entries in the map.
+ *
+ * @param {Array} entries Entries array.
+ * @param {Number} max The maximum length of indexes array.
+ * @param {Function} filter Filter the entry you want.
+ * @return {Array} Indexes of filtered entries in the map.
+ */
+ getEntriesIndexes: function (entries, max, filter) {
+ return entries.reduce((indexes, _ref, i) => {
+ var _ref2 = _slicedToArray(_ref, 2);
+
+ var key = _ref2[0];
+ var entry = _ref2[1];
+
+ if (indexes.length < max) {
+ var value = entry && entry.value !== undefined ? entry.value : entry;
+ // Type is specified in grip's "class" field and for primitive
+ // values use typeof.
+ var type = (value && value.class ? value.class : typeof value).toLowerCase();
+
+ if (filter(type, value, key)) {
+ indexes.push(i);
+ }
+ }
+
+ return indexes;
+ }, []);
+ },
+
+ render: function () {
+ var object = this.props.object;
+ var props = this.safeEntriesIterator(object, this.props.mode == "long" ? 100 : 3);
+
+ var objectLink = this.props.objectLink || span;
+ if (this.props.mode == "tiny") {
+ return span({ className: "objectBox objectBox-object" }, this.getTitle(object), objectLink({
+ className: "objectLeftBrace",
+ object: object
+ }, ""));
+ }
+
+ return span({ className: "objectBox objectBox-object" }, this.getTitle(object), objectLink({
+ className: "objectLeftBrace",
+ object: object
+ }, " { "), props, objectLink({
+ className: "objectRightBrace",
+ object: object
+ }, " }"));
+ }
+ });
+
+ function supportsObject(grip, type) {
+ if (!isGrip(grip)) {
+ return false;
+ }
+ return grip.preview && grip.preview.kind == "MapLike";
+ }
+
+ // Exports from this module
+ exports.GripMap = {
+ rep: GripMap,
+ supportsObject: supportsObject
+ };
+ }.call(exports, __webpack_require__, exports, module), __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__));
+
+/***/ },
+/* 59 */
+/***/ function(module, exports, __webpack_require__) {
+
+ /**
+ * Copyright (c) 2007-2016, Alexandru Marasteanu <hello [at) alexei (dot] ro>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * * Neither the name of this software nor the names of its contributors may be
+ * used to endorse or promote products derived from this software without
+ * specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR
+ * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ */
+
+ /* globals window, exports, define */
+
+ (function (window) {
+ 'use strict';
+
+ var re = {
+ not_string: /[^s]/,
+ not_bool: /[^t]/,
+ not_type: /[^T]/,
+ not_primitive: /[^v]/,
+ number: /[diefg]/,
+ numeric_arg: /bcdiefguxX/,
+ json: /[j]/,
+ not_json: /[^j]/,
+ text: /^[^\x25]+/,
+ modulo: /^\x25{2}/,
+ placeholder: /^\x25(?:([1-9]\d*)\$|\(([^\)]+)\))?(\+)?(0|'[^$])?(-)?(\d+)?(?:\.(\d+))?([b-gijosStTuvxX])/,
+ key: /^([a-z_][a-z_\d]*)/i,
+ key_access: /^\.([a-z_][a-z_\d]*)/i,
+ index_access: /^\[(\d+)\]/,
+ sign: /^[\+\-]/
+ };
+
+ function sprintf() {
+ var key = arguments[0],
+ cache = sprintf.cache;
+ if (!(cache[key] && cache.hasOwnProperty(key))) {
+ cache[key] = sprintf.parse(key);
+ }
+ return sprintf.format.call(null, cache[key], arguments);
+ }
+
+ sprintf.format = function (parse_tree, argv) {
+ var cursor = 1,
+ tree_length = parse_tree.length,
+ node_type = '',
+ arg,
+ output = [],
+ i,
+ k,
+ match,
+ pad,
+ pad_character,
+ pad_length,
+ is_positive = true,
+ sign = '';
+ for (i = 0; i < tree_length; i++) {
+ node_type = get_type(parse_tree[i]);
+ if (node_type === 'string') {
+ output[output.length] = parse_tree[i];
+ } else if (node_type === 'array') {
+ match = parse_tree[i]; // convenience purposes only
+ if (match[2]) {
+ // keyword argument
+ arg = argv[cursor];
+ for (k = 0; k < match[2].length; k++) {
+ if (!arg.hasOwnProperty(match[2][k])) {
+ throw new Error(sprintf('[sprintf] property "%s" does not exist', match[2][k]));
+ }
+ arg = arg[match[2][k]];
+ }
+ } else if (match[1]) {
+ // positional argument (explicit)
+ arg = argv[match[1]];
+ } else {
+ // positional argument (implicit)
+ arg = argv[cursor++];
+ }
+
+ if (re.not_type.test(match[8]) && re.not_primitive.test(match[8]) && get_type(arg) == 'function') {
+ arg = arg();
+ }
+
+ if (re.numeric_arg.test(match[8]) && get_type(arg) != 'number' && isNaN(arg)) {
+ throw new TypeError(sprintf("[sprintf] expecting number but found %s", get_type(arg)));
+ }
+
+ if (re.number.test(match[8])) {
+ is_positive = arg >= 0;
+ }
+
+ switch (match[8]) {
+ case 'b':
+ arg = parseInt(arg, 10).toString(2);
+ break;
+ case 'c':
+ arg = String.fromCharCode(parseInt(arg, 10));
+ break;
+ case 'd':
+ case 'i':
+ arg = parseInt(arg, 10);
+ break;
+ case 'j':
+ arg = JSON.stringify(arg, null, match[6] ? parseInt(match[6]) : 0);
+ break;
+ case 'e':
+ arg = match[7] ? parseFloat(arg).toExponential(match[7]) : parseFloat(arg).toExponential();
+ break;
+ case 'f':
+ arg = match[7] ? parseFloat(arg).toFixed(match[7]) : parseFloat(arg);
+ break;
+ case 'g':
+ arg = match[7] ? parseFloat(arg).toPrecision(match[7]) : parseFloat(arg);
+ break;
+ case 'o':
+ arg = arg.toString(8);
+ break;
+ case 's':
+ case 'S':
+ arg = String(arg);
+ arg = match[7] ? arg.substring(0, match[7]) : arg;
+ break;
+ case 't':
+ arg = String(!!arg);
+ arg = match[7] ? arg.substring(0, match[7]) : arg;
+ break;
+ case 'T':
+ arg = get_type(arg);
+ arg = match[7] ? arg.substring(0, match[7]) : arg;
+ break;
+ case 'u':
+ arg = parseInt(arg, 10) >>> 0;
+ break;
+ case 'v':
+ arg = arg.valueOf();
+ arg = match[7] ? arg.substring(0, match[7]) : arg;
+ break;
+ case 'x':
+ arg = parseInt(arg, 10).toString(16);
+ break;
+ case 'X':
+ arg = parseInt(arg, 10).toString(16).toUpperCase();
+ break;
+ }
+ if (re.json.test(match[8])) {
+ output[output.length] = arg;
+ } else {
+ if (re.number.test(match[8]) && (!is_positive || match[3])) {
+ sign = is_positive ? '+' : '-';
+ arg = arg.toString().replace(re.sign, '');
+ } else {
+ sign = '';
+ }
+ pad_character = match[4] ? match[4] === '0' ? '0' : match[4].charAt(1) : ' ';
+ pad_length = match[6] - (sign + arg).length;
+ pad = match[6] ? pad_length > 0 ? str_repeat(pad_character, pad_length) : '' : '';
+ output[output.length] = match[5] ? sign + arg + pad : pad_character === '0' ? sign + pad + arg : pad + sign + arg;
+ }
+ }
+ }
+ return output.join('');
+ };
+
+ sprintf.cache = {};
+
+ sprintf.parse = function (fmt) {
+ var _fmt = fmt,
+ match = [],
+ parse_tree = [],
+ arg_names = 0;
+ while (_fmt) {
+ if ((match = re.text.exec(_fmt)) !== null) {
+ parse_tree[parse_tree.length] = match[0];
+ } else if ((match = re.modulo.exec(_fmt)) !== null) {
+ parse_tree[parse_tree.length] = '%';
+ } else if ((match = re.placeholder.exec(_fmt)) !== null) {
+ if (match[2]) {
+ arg_names |= 1;
+ var field_list = [],
+ replacement_field = match[2],
+ field_match = [];
+ if ((field_match = re.key.exec(replacement_field)) !== null) {
+ field_list[field_list.length] = field_match[1];
+ while ((replacement_field = replacement_field.substring(field_match[0].length)) !== '') {
+ if ((field_match = re.key_access.exec(replacement_field)) !== null) {
+ field_list[field_list.length] = field_match[1];
+ } else if ((field_match = re.index_access.exec(replacement_field)) !== null) {
+ field_list[field_list.length] = field_match[1];
+ } else {
+ throw new SyntaxError("[sprintf] failed to parse named argument key");
+ }
+ }
+ } else {
+ throw new SyntaxError("[sprintf] failed to parse named argument key");
+ }
+ match[2] = field_list;
+ } else {
+ arg_names |= 2;
+ }
+ if (arg_names === 3) {
+ throw new Error("[sprintf] mixing positional and named placeholders is not (yet) supported");
+ }
+ parse_tree[parse_tree.length] = match;
+ } else {
+ throw new SyntaxError("[sprintf] unexpected placeholder");
+ }
+ _fmt = _fmt.substring(match[0].length);
+ }
+ return parse_tree;
+ };
+
+ var vsprintf = function (fmt, argv, _argv) {
+ _argv = (argv || []).slice(0);
+ _argv.splice(0, 0, fmt);
+ return sprintf.apply(null, _argv);
+ };
+
+ /**
+ * helpers
+ */
+ function get_type(variable) {
+ if (typeof variable === 'number') {
+ return 'number';
+ } else if (typeof variable === 'string') {
+ return 'string';
+ } else {
+ return Object.prototype.toString.call(variable).slice(8, -1).toLowerCase();
+ }
+ }
+
+ var preformattedPadding = {
+ '0': ['', '0', '00', '000', '0000', '00000', '000000', '0000000'],
+ ' ': ['', ' ', ' ', ' ', ' ', ' ', ' ', ' '],
+ '_': ['', '_', '__', '___', '____', '_____', '______', '_______']
+ };
+ function str_repeat(input, multiplier) {
+ if (multiplier >= 0 && multiplier <= 7 && preformattedPadding[input]) {
+ return preformattedPadding[input][multiplier];
+ }
+ return Array(multiplier + 1).join(input);
+ }
+
+ /**
+ * export to either browser or node.js
+ */
+ if (true) {
+ exports.sprintf = sprintf;
+ exports.vsprintf = vsprintf;
+ } else {
+ window.sprintf = sprintf;
+ window.vsprintf = vsprintf;
+
+ if (typeof define === 'function' && define.amd) {
+ define(function () {
+ return {
+ sprintf: sprintf,
+ vsprintf: vsprintf
+ };
+ });
+ }
+ }
+ })(typeof window === 'undefined' ? this : window);
+
+/***/ },
+/* 60 */
+/***/ function(module, exports, __webpack_require__) {
+
+ /* 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/. */
+
+ /**
+ * EventEmitter.
+ */
+
+ var EventEmitter = function EventEmitter() {};
+ module.exports = EventEmitter;
+
+ var _require = __webpack_require__(61);
+
+ var Cu = _require.Cu;
+
+ var promise = __webpack_require__(66);
+
+ /**
+ * Decorate an object with event emitter functionality.
+ *
+ * @param Object aObjectToDecorate
+ * Bind all public methods of EventEmitter to
+ * the aObjectToDecorate object.
+ */
+ EventEmitter.decorate = function EventEmitter_decorate(aObjectToDecorate) {
+ var emitter = new EventEmitter();
+ aObjectToDecorate.on = emitter.on.bind(emitter);
+ aObjectToDecorate.off = emitter.off.bind(emitter);
+ aObjectToDecorate.once = emitter.once.bind(emitter);
+ aObjectToDecorate.emit = emitter.emit.bind(emitter);
+ };
+
+ EventEmitter.prototype = {
+ /**
+ * Connect a listener.
+ *
+ * @param string aEvent
+ * The event name to which we're connecting.
+ * @param function aListener
+ * Called when the event is fired.
+ */
+ on: function EventEmitter_on(aEvent, aListener) {
+ if (!this._eventEmitterListeners) this._eventEmitterListeners = new Map();
+ if (!this._eventEmitterListeners.has(aEvent)) {
+ this._eventEmitterListeners.set(aEvent, []);
+ }
+ this._eventEmitterListeners.get(aEvent).push(aListener);
+ },
+
+ /**
+ * Listen for the next time an event is fired.
+ *
+ * @param string aEvent
+ * The event name to which we're connecting.
+ * @param function aListener
+ * (Optional) Called when the event is fired. Will be called at most
+ * one time.
+ * @return promise
+ * A promise which is resolved when the event next happens. The
+ * resolution value of the promise is the first event argument. If
+ * you need access to second or subsequent event arguments (it's rare
+ * that this is needed) then use aListener
+ */
+ once: function EventEmitter_once(aEvent, aListener) {
+ var _this = this;
+
+ var deferred = promise.defer();
+
+ var handler = function (aEvent, aFirstArg) {
+ for (var _len = arguments.length, aRest = Array(_len > 2 ? _len - 2 : 0), _key = 2; _key < _len; _key++) {
+ aRest[_key - 2] = arguments[_key];
+ }
+
+ _this.off(aEvent, handler);
+ if (aListener) {
+ aListener.apply(null, [aEvent, aFirstArg].concat(aRest));
+ }
+ deferred.resolve(aFirstArg);
+ };
+
+ handler._originalListener = aListener;
+ this.on(aEvent, handler);
+
+ return deferred.promise;
+ },
+
+ /**
+ * Remove a previously-registered event listener. Works for events
+ * registered with either on or once.
+ *
+ * @param string aEvent
+ * The event name whose listener we're disconnecting.
+ * @param function aListener
+ * The listener to remove.
+ */
+ off: function EventEmitter_off(aEvent, aListener) {
+ if (!this._eventEmitterListeners) return;
+ var listeners = this._eventEmitterListeners.get(aEvent);
+ if (listeners) {
+ this._eventEmitterListeners.set(aEvent, listeners.filter(l => {
+ return l !== aListener && l._originalListener !== aListener;
+ }));
+ }
+ },
+
+ /**
+ * Emit an event. All arguments to this method will
+ * be sent to listener functions.
+ */
+ emit: function EventEmitter_emit(aEvent) {
+ var _this2 = this,
+ _arguments = arguments;
+
+ if (!this._eventEmitterListeners || !this._eventEmitterListeners.has(aEvent)) {
+ return;
+ }
+
+ var originalListeners = this._eventEmitterListeners.get(aEvent);
+
+ var _loop = function (listener) {
+ // If the object was destroyed during event emission, stop
+ // emitting.
+ if (!_this2._eventEmitterListeners) {
+ return "break";
+ }
+
+ // If listeners were removed during emission, make sure the
+ // event handler we're going to fire wasn't removed.
+ if (originalListeners === _this2._eventEmitterListeners.get(aEvent) || _this2._eventEmitterListeners.get(aEvent).some(l => l === listener)) {
+ try {
+ listener.apply(null, _arguments);
+ } catch (ex) {
+ // Prevent a bad listener from interfering with the others.
+ var msg = ex + ": " + ex.stack;
+ //console.error(msg);
+ console.log(msg);
+ }
+ }
+ };
+
+ for (var listener of this._eventEmitterListeners.get(aEvent)) {
+ var _ret = _loop(listener);
+
+ if (_ret === "break") break;
+ }
+ }
+ };
+
+/***/ },
+/* 61 */
+/***/ function(module, exports, __webpack_require__) {
+
+ /*
+ * A sham for https://developer.mozilla.org/en-US/Add-ons/SDK/Low-Level_APIs/chrome
+ */
+
+ var _require = __webpack_require__(62);
+
+ var inDOMUtils = _require.inDOMUtils;
+
+
+ var ourServices = {
+ inIDOMUtils: inDOMUtils,
+ nsIClipboardHelper: {
+ copyString: () => {}
+ },
+ nsIXULChromeRegistry: {
+ isLocaleRTL: () => {
+ return false;
+ }
+ },
+ nsIDOMParser: {}
+ };
+
+ module.exports = {
+ Cc: name => {
+ if (typeof console !== "undefined") {
+ console.log('Cc sham for', name);
+ }
+ return {
+ getService: name => ourServices[name],
+ createInstance: iface => ourServices[iface]
+ };
+ },
+ CC: (name, iface, method) => {
+ if (typeof console !== "undefined") {
+ console.log('CC sham for', name, iface, method);
+ }
+ return {};
+ },
+ Ci: {
+ nsIThread: {
+ "DISPATCH_NORMAL": 0,
+ "DISPATCH_SYNC": 1
+ },
+ nsIDOMNode: typeof HTMLElement !== "undefined" ? HTMLElement : null,
+ nsIFocusManager: {
+ MOVEFOCUS_BACKWARD: 2,
+ MOVEFOCUS_FORWARD: 1
+ },
+ nsIDOMKeyEvent: {},
+ nsIDOMCSSRule: { "UNKNOWN_RULE": 0, "STYLE_RULE": 1, "CHARSET_RULE": 2, "IMPORT_RULE": 3, "MEDIA_RULE": 4, "FONT_FACE_RULE": 5, "PAGE_RULE": 6, "KEYFRAMES_RULE": 7, "KEYFRAME_RULE": 8, "MOZ_KEYFRAMES_RULE": 7, "MOZ_KEYFRAME_RULE": 8, "NAMESPACE_RULE": 10, "COUNTER_STYLE_RULE": 11, "SUPPORTS_RULE": 12, "FONT_FEATURE_VALUES_RULE": 14 },
+ inIDOMUtils: "inIDOMUtils",
+ nsIClipboardHelper: "nsIClipboardHelper",
+ nsIXULChromeRegistry: "nsIXULChromeRegistry"
+ },
+ Cu: {
+ reportError: msg => {
+ typeof console !== "undefined" ? console.error(msg) : dump(msg);
+ },
+ callFunctionWithAsyncStack: fn => fn()
+ },
+ Cr: {},
+ components: {
+ isSuccessCode: () => (returnCode & 0x80000000) === 0
+ }
+ };
+
+/***/ },
+/* 62 */
+/***/ function(module, exports, __webpack_require__) {
+
+ // A sham for inDOMUtils.
+
+ "use strict";
+
+ var _slicedToArray = function () { function sliceIterator(arr, i) { var _arr = []; var _n = true; var _d = false; var _e = undefined; try { for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i["return"]) _i["return"](); } finally { if (_d) throw _e; } } return _arr; } return function (arr, i) { if (Array.isArray(arr)) { return arr; } else if (Symbol.iterator in Object(arr)) { return sliceIterator(arr, i); } else { throw new TypeError("Invalid attempt to destructure non-iterable instance"); } }; }();
+
+ var _require = __webpack_require__(63);
+
+ var CSSLexer = _require.CSSLexer;
+
+ var _require2 = __webpack_require__(64);
+
+ var cssColors = _require2.cssColors;
+
+ var _require3 = __webpack_require__(65);
+
+ var cssProperties = _require3.cssProperties;
+
+
+ var cssRGBMap;
+
+ // From inIDOMUtils.idl.
+ var EXCLUDE_SHORTHANDS = 1 << 0;
+ var INCLUDE_ALIASES = 1 << 1;
+ var TYPE_LENGTH = 0;
+ var TYPE_PERCENTAGE = 1;
+ var TYPE_COLOR = 2;
+ var TYPE_URL = 3;
+ var TYPE_ANGLE = 4;
+ var TYPE_FREQUENCY = 5;
+ var TYPE_TIME = 6;
+ var TYPE_GRADIENT = 7;
+ var TYPE_TIMING_FUNCTION = 8;
+ var TYPE_IMAGE_RECT = 9;
+ var TYPE_NUMBER = 10;
+
+ function getCSSLexer(text) {
+ return new CSSLexer(text);
+ }
+
+ function rgbToColorName(r, g, b) {
+ if (!cssRGBMap) {
+ cssRGBMap = new Map();
+ for (var name in cssColors) {
+ cssRGBMap.set(JSON.stringify(cssColors[name]), name);
+ }
+ }
+ var value = cssRGBMap.get(JSON.stringify([r, g, b]));
+ if (!value) {
+ throw new Error("no such color");
+ }
+ return value;
+ }
+
+ // Taken from dom/tests/mochitest/ajax/mochikit/MochiKit/Color.js
+ function _hslValue(n1, n2, hue) {
+ if (hue > 6.0) {
+ hue -= 6.0;
+ } else if (hue < 0.0) {
+ hue += 6.0;
+ }
+ var val;
+ if (hue < 1.0) {
+ val = n1 + (n2 - n1) * hue;
+ } else if (hue < 3.0) {
+ val = n2;
+ } else if (hue < 4.0) {
+ val = n1 + (n2 - n1) * (4.0 - hue);
+ } else {
+ val = n1;
+ }
+ return val;
+ }
+
+ // Taken from dom/tests/mochitest/ajax/mochikit/MochiKit/Color.js
+ // and then modified.
+ function hslToRGB(_ref) {
+ var _ref2 = _slicedToArray(_ref, 3);
+
+ var hue = _ref2[0];
+ var saturation = _ref2[1];
+ var lightness = _ref2[2];
+
+ var red;
+ var green;
+ var blue;
+ if (saturation === 0) {
+ red = lightness;
+ green = lightness;
+ blue = lightness;
+ } else {
+ var m2;
+ if (lightness <= 0.5) {
+ m2 = lightness * (1.0 + saturation);
+ } else {
+ m2 = lightness + saturation - lightness * saturation;
+ }
+ var m1 = 2.0 * lightness - m2;
+ var f = _hslValue;
+ var h6 = hue * 6.0;
+ red = f(m1, m2, h6 + 2);
+ green = f(m1, m2, h6);
+ blue = f(m1, m2, h6 - 2);
+ }
+ return [red, green, blue];
+ }
+
+ function colorToRGBA(name) {
+ name = name.trim().toLowerCase();
+ if (name in cssColors) {
+ return cssColors[name];
+ }
+
+ if (name === "transparent") {
+ return [0, 0, 0, 0];
+ }
+
+ var lexer = getCSSLexer(name);
+
+ var getToken = function () {
+ while (true) {
+ var token = lexer.nextToken();
+ if (!token || token.tokenType !== "comment" || token.tokenType !== "whitespace") {
+ return token;
+ }
+ }
+ };
+
+ var requireComma = function (token) {
+ if (token.tokenType !== "symbol" || token.text !== ",") {
+ return null;
+ }
+ return getToken();
+ };
+
+ var func = getToken();
+ if (!func || func.tokenType !== "function") {
+ return null;
+ }
+ var alpha = false;
+ if (func.text === "rgb" || func.text === "hsl") {
+ // Nothing.
+ } else if (func.text === "rgba" || func.text === "hsla") {
+ alpha = true;
+ } else {
+ return null;
+ }
+
+ var vals = [];
+ for (var i = 0; i < 3; ++i) {
+ var token = getToken();
+ if (i > 0) {
+ token = requireComma(token);
+ }
+ if (token.tokenType !== "number" || !token.isInteger) {
+ return null;
+ }
+ var num = token.number;
+ if (num < 0) {
+ num = 0;
+ } else if (num > 255) {
+ num = 255;
+ }
+ vals.push(num);
+ }
+
+ if (func.text === "hsl" || func.text === "hsla") {
+ vals = hslToRGB(vals);
+ }
+
+ if (alpha) {
+ var _token = requireComma(getToken());
+ if (_token.tokenType !== "number") {
+ return null;
+ }
+ var _num = _token.number;
+ if (_num < 0) {
+ _num = 0;
+ } else if (_num > 1) {
+ _num = 1;
+ }
+ vals.push(_num);
+ } else {
+ vals.push(1);
+ }
+
+ var parenToken = getToken();
+ if (!parenToken || parenToken.tokenType !== "symbol" || parenToken.text !== ")") {
+ return null;
+ }
+ if (getToken() !== null) {
+ return null;
+ }
+
+ return vals;
+ }
+
+ function isValidCSSColor(name) {
+ return colorToRGBA(name) !== null;
+ }
+
+ function isVariable(name) {
+ return name.startsWith("--");
+ }
+
+ function cssPropertyIsShorthand(name) {
+ if (isVariable(name)) {
+ return false;
+ }
+ if (!(name in cssProperties)) {
+ throw Error("unknown property " + name);
+ }
+ return !!cssProperties[name].subproperties;
+ }
+
+ function getSubpropertiesForCSSProperty(name) {
+ if (isVariable(name)) {
+ return [name];
+ }
+ if (!(name in cssProperties)) {
+ throw Error("unknown property " + name);
+ }
+ if ("subproperties" in cssProperties[name]) {
+ return cssProperties[name].subproperties.slice();
+ }
+ return [name];
+ }
+
+ function getCSSValuesForProperty(name) {
+ if (isVariable(name)) {
+ return ["initial", "inherit", "unset"];
+ }
+ if (!(name in cssProperties)) {
+ throw Error("unknown property " + name);
+ }
+ return cssProperties[name].values.slice();
+ }
+
+ function getCSSPropertyNames(flags) {
+ var names = Object.keys(cssProperties);
+ if ((flags & EXCLUDE_SHORTHANDS) !== 0) {
+ names = names.filter(name => cssProperties[name].subproperties);
+ }
+ if ((flags & INCLUDE_ALIASES) === 0) {
+ names = names.filter(name => !cssProperties[name].alias);
+ }
+ return names;
+ }
+
+ function cssPropertySupportsType(name, type) {
+ if (isVariable(name)) {
+ return false;
+ }
+ if (!(name in cssProperties)) {
+ throw Error("unknown property " + name);
+ }
+ return (cssProperties[name].supports & 1 << type) !== 0;
+ }
+
+ function isInheritedProperty(name) {
+ if (isVariable(name)) {
+ return true;
+ }
+ if (!(name in cssProperties)) {
+ return false;
+ }
+ return cssProperties[name].inherited;
+ }
+
+ function cssPropertyIsValid(name, value) {
+ if (isVariable(name)) {
+ return true;
+ }
+ if (!(name in cssProperties)) {
+ return false;
+ }
+ var elt = document.createElement("div");
+ elt.style = name + ":" + value;
+ return elt.style.length > 0;
+ }
+
+ exports.inDOMUtils = {
+ getCSSLexer,
+ rgbToColorName,
+ colorToRGBA,
+ isValidCSSColor,
+ cssPropertyIsShorthand,
+ getSubpropertiesForCSSProperty,
+ getCSSValuesForProperty,
+ getCSSPropertyNames,
+ cssPropertySupportsType,
+ isInheritedProperty,
+ cssPropertyIsValid,
+
+ // Constants.
+ EXCLUDE_SHORTHANDS,
+ INCLUDE_ALIASES,
+ TYPE_LENGTH,
+ TYPE_PERCENTAGE,
+ TYPE_COLOR,
+ TYPE_URL,
+ TYPE_ANGLE,
+ TYPE_FREQUENCY,
+ TYPE_TIME,
+ TYPE_GRADIENT,
+ TYPE_TIMING_FUNCTION,
+ TYPE_IMAGE_RECT,
+ TYPE_NUMBER
+ };
+
+/***/ },
+/* 63 */
+/***/ function(module, exports, __webpack_require__) {
+
+ var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;"use strict";
+
+ (function (root, factory) {
+ // Universal Module Definition (UMD) to support AMD, CommonJS/Node.js,
+ // Rhino, and plain browser loading.
+ if (true) {
+ !(__WEBPACK_AMD_DEFINE_ARRAY__ = [exports], __WEBPACK_AMD_DEFINE_FACTORY__ = (factory), __WEBPACK_AMD_DEFINE_RESULT__ = (typeof __WEBPACK_AMD_DEFINE_FACTORY__ === 'function' ? (__WEBPACK_AMD_DEFINE_FACTORY__.apply(exports, __WEBPACK_AMD_DEFINE_ARRAY__)) : __WEBPACK_AMD_DEFINE_FACTORY__), __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__));
+ } else if (typeof exports !== 'undefined') {
+ factory(exports);
+ } else {
+ factory(root);
+ }
+ })(this, function (exports) {
+
+ function between(num, first, last) {
+ return num >= first && num <= last;
+ }
+ function digit(code) {
+ return between(code, 0x30, 0x39);
+ }
+ function hexdigit(code) {
+ return digit(code) || between(code, 0x41, 0x46) || between(code, 0x61, 0x66);
+ }
+ function uppercaseletter(code) {
+ return between(code, 0x41, 0x5a);
+ }
+ function lowercaseletter(code) {
+ return between(code, 0x61, 0x7a);
+ }
+ function letter(code) {
+ return uppercaseletter(code) || lowercaseletter(code);
+ }
+ function nonascii(code) {
+ return code >= 0x80;
+ }
+ function namestartchar(code) {
+ return letter(code) || nonascii(code) || code == 0x5f;
+ }
+ function namechar(code) {
+ return namestartchar(code) || digit(code) || code == 0x2d;
+ }
+ function nonprintable(code) {
+ return between(code, 0, 8) || code == 0xb || between(code, 0xe, 0x1f) || code == 0x7f;
+ }
+ function newline(code) {
+ return code == 0xa;
+ }
+ function whitespace(code) {
+ return newline(code) || code == 9 || code == 0x20;
+ }
+
+ var maximumallowedcodepoint = 0x10ffff;
+
+ var InvalidCharacterError = function (message) {
+ this.message = message;
+ };
+ InvalidCharacterError.prototype = new Error();
+ InvalidCharacterError.prototype.name = 'InvalidCharacterError';
+
+ function stringFromCode(code) {
+ if (code <= 0xffff) return String.fromCharCode(code);
+ // Otherwise, encode astral char as surrogate pair.
+ code -= Math.pow(2, 20);
+ var lead = Math.floor(code / Math.pow(2, 10)) + 0xd800;
+ var trail = code % Math.pow(2, 10) + 0xdc00;
+ return String.fromCharCode(lead) + String.fromCharCode(trail);
+ }
+
+ function* tokenize(str, options) {
+ if (options === undefined) {
+ options = {};
+ }
+ if (options.loc === undefined) {
+ options.loc = false;
+ }
+ if (options.offsets === undefined) {
+ options.offsets = false;
+ }
+ if (options.keepComments === undefined) {
+ options.keepComments = false;
+ }
+ if (options.startOffset === undefined) {
+ options.startOffset = 0;
+ }
+
+ var i = options.startOffset - 1;
+ var code;
+
+ // Line number information.
+ var line = 0;
+ var column = 0;
+ // The only use of lastLineLength is in reconsume().
+ var lastLineLength = 0;
+ var incrLineno = function () {
+ line += 1;
+ lastLineLength = column;
+ column = 0;
+ };
+ var locStart = { line: line, column: column };
+ var offsetStart = i;
+
+ var codepoint = function (i) {
+ if (i >= str.length) {
+ return -1;
+ }
+ return str.charCodeAt(i);
+ };
+ var next = function (num) {
+ if (num === undefined) num = 1;
+ if (num > 3) throw "Spec Error: no more than three codepoints of lookahead.";
+
+ var rcode;
+ for (var offset = i + 1; num-- > 0; ++offset) {
+ rcode = codepoint(offset);
+ if (rcode === 0xd && codepoint(offset + 1) === 0xa) {
+ ++offset;
+ rcode = 0xa;
+ } else if (rcode === 0xd || rcode === 0xc) {
+ rcode = 0xa;
+ } else if (rcode === 0x0) {
+ rcode = 0xfffd;
+ }
+ }
+
+ return rcode;
+ };
+ var consume = function (num) {
+ if (num === undefined) num = 1;
+ while (num-- > 0) {
+ ++i;
+ code = codepoint(i);
+ if (code === 0xd && codepoint(i + 1) === 0xa) {
+ ++i;
+ code = 0xa;
+ } else if (code === 0xd || code === 0xc) {
+ code = 0xa;
+ } else if (code === 0x0) {
+ code = 0xfffd;
+ }
+ if (newline(code)) incrLineno();else column++;
+ }
+ return true;
+ };
+ var reconsume = function () {
+ i -= 1; // This is ok even in the \r\n case.
+ if (newline(code)) {
+ line -= 1;
+ column = lastLineLength;
+ } else {
+ column -= 1;
+ }
+ return true;
+ };
+ var eof = function (codepoint) {
+ if (codepoint === undefined) codepoint = code;
+ return codepoint == -1;
+ };
+ var donothing = function () {};
+ var parseerror = function () {
+ console.log("Parse error at index " + i + ", processing codepoint 0x" + code.toString(16) + ".");return true;
+ };
+
+ var consumeAToken = function () {
+ consume();
+ if (!options.keepComments) {
+ while (code == 0x2f && next() == 0x2a) {
+ consumeAComment();
+ consume();
+ }
+ }
+ locStart.line = line;
+ locStart.column = column;
+ offsetStart = i;
+ if (whitespace(code)) {
+ while (whitespace(next())) {
+ consume();
+ }return new WhitespaceToken();
+ } else if (code == 0x2f && next() == 0x2a) return consumeAComment();else if (code == 0x22) return consumeAStringToken();else if (code == 0x23) {
+ if (namechar(next()) || areAValidEscape(next(1), next(2))) {
+ var token = new HashToken();
+ if (wouldStartAnIdentifier(next(1), next(2), next(3))) {
+ token.type = "id";
+ token.tokenType = "id";
+ }
+ token.value = consumeAName();
+ token.text = token.value;
+ return token;
+ } else {
+ return new DelimToken(code);
+ }
+ } else if (code == 0x24) {
+ if (next() == 0x3d) {
+ consume();
+ return new SuffixMatchToken();
+ } else {
+ return new DelimToken(code);
+ }
+ } else if (code == 0x27) return consumeAStringToken();else if (code == 0x28) return new OpenParenToken();else if (code == 0x29) return new CloseParenToken();else if (code == 0x2a) {
+ if (next() == 0x3d) {
+ consume();
+ return new SubstringMatchToken();
+ } else {
+ return new DelimToken(code);
+ }
+ } else if (code == 0x2b) {
+ if (startsWithANumber()) {
+ reconsume();
+ return consumeANumericToken();
+ } else {
+ return new DelimToken(code);
+ }
+ } else if (code == 0x2c) return new CommaToken();else if (code == 0x2d) {
+ if (startsWithANumber()) {
+ reconsume();
+ return consumeANumericToken();
+ } else if (next(1) == 0x2d && next(2) == 0x3e) {
+ consume(2);
+ return new CDCToken();
+ } else if (startsWithAnIdentifier()) {
+ reconsume();
+ return consumeAnIdentlikeToken();
+ } else {
+ return new DelimToken(code);
+ }
+ } else if (code == 0x2e) {
+ if (startsWithANumber()) {
+ reconsume();
+ return consumeANumericToken();
+ } else {
+ return new DelimToken(code);
+ }
+ } else if (code == 0x3a) return new ColonToken();else if (code == 0x3b) return new SemicolonToken();else if (code == 0x3c) {
+ if (next(1) == 0x21 && next(2) == 0x2d && next(3) == 0x2d) {
+ consume(3);
+ return new CDOToken();
+ } else {
+ return new DelimToken(code);
+ }
+ } else if (code == 0x40) {
+ if (wouldStartAnIdentifier(next(1), next(2), next(3))) {
+ return new AtKeywordToken(consumeAName());
+ } else {
+ return new DelimToken(code);
+ }
+ } else if (code == 0x5b) return new OpenSquareToken();else if (code == 0x5c) {
+ if (startsWithAValidEscape()) {
+ reconsume();
+ return consumeAnIdentlikeToken();
+ } else {
+ parseerror();
+ return new DelimToken(code);
+ }
+ } else if (code == 0x5d) return new CloseSquareToken();else if (code == 0x5e) {
+ if (next() == 0x3d) {
+ consume();
+ return new PrefixMatchToken();
+ } else {
+ return new DelimToken(code);
+ }
+ } else if (code == 0x7b) return new OpenCurlyToken();else if (code == 0x7c) {
+ if (next() == 0x3d) {
+ consume();
+ return new DashMatchToken();
+ // } else if(next() == 0x7c) {
+ // consume();
+ // return new ColumnToken();
+ } else {
+ return new DelimToken(code);
+ }
+ } else if (code == 0x7d) return new CloseCurlyToken();else if (code == 0x7e) {
+ if (next() == 0x3d) {
+ consume();
+ return new IncludeMatchToken();
+ } else {
+ return new DelimToken(code);
+ }
+ } else if (digit(code)) {
+ reconsume();
+ return consumeANumericToken();
+ } else if (namestartchar(code)) {
+ reconsume();
+ return consumeAnIdentlikeToken();
+ } else if (eof()) return new EOFToken();else return new DelimToken(code);
+ };
+
+ var consumeAComment = function () {
+ consume();
+ var comment = "";
+ while (true) {
+ consume();
+ if (code == 0x2a && next() == 0x2f) {
+ consume();
+ break;
+ } else if (eof()) {
+ break;
+ }
+ comment += stringFromCode(code);
+ }
+ return new CommentToken(comment);
+ };
+
+ var consumeANumericToken = function () {
+ var num = consumeANumber();
+ var token;
+ if (wouldStartAnIdentifier(next(1), next(2), next(3))) {
+ token = new DimensionToken();
+ token.value = num.value;
+ token.repr = num.repr;
+ token.type = num.type;
+ token.unit = consumeAName();
+ token.text = token.unit;
+ } else if (next() == 0x25) {
+ consume();
+ token = new PercentageToken();
+ token.value = num.value;
+ token.repr = num.repr;
+ } else {
+ var token = new NumberToken();
+ token.value = num.value;
+ token.repr = num.repr;
+ token.type = num.type;
+ }
+ token.number = token.value;
+ token.isInteger = token.type === "integer";
+ // FIXME hasSign
+ return token;
+ };
+
+ var consumeAnIdentlikeToken = function () {
+ var str = consumeAName();
+ if (str.toLowerCase() == "url" && next() == 0x28) {
+ consume();
+ while (whitespace(next(1)) && whitespace(next(2))) {
+ consume();
+ }if (next() == 0x22 || next() == 0x27 || whitespace(next()) && (next(2) == 0x22 || next(2) == 0x27)) {
+ while (whitespace(next())) {
+ consume();
+ }consume();
+ var _str = consumeAStringToken();
+ while (whitespace(next())) {
+ consume();
+ } // The closing paren.
+ consume();
+ return new URLToken(_str.text);
+ } else {
+ return consumeAURLToken();
+ }
+ } else if (next() == 0x28) {
+ consume();
+ return new FunctionToken(str);
+ } else {
+ return new IdentToken(str);
+ }
+ };
+
+ var consumeAStringToken = function (endingCodePoint) {
+ if (endingCodePoint === undefined) endingCodePoint = code;
+ var string = "";
+ while (consume()) {
+ if (code == endingCodePoint || eof()) {
+ return new StringToken(string);
+ } else if (newline(code)) {
+ reconsume();
+ return new BadStringToken(string);
+ } else if (code == 0x5c) {
+ if (eof(next())) {
+ donothing();
+ } else if (newline(next())) {
+ consume();
+ } else {
+ string += stringFromCode(consumeEscape());
+ }
+ } else {
+ string += stringFromCode(code);
+ }
+ }
+ };
+
+ var consumeAURLToken = function () {
+ var token = new URLToken("");
+ while (whitespace(next())) {
+ consume();
+ }if (eof(next())) return token;
+ while (consume()) {
+ if (code == 0x29 || eof()) {
+ break;
+ } else if (whitespace(code)) {
+ while (whitespace(next())) {
+ consume();
+ }if (next() == 0x29 || eof(next())) {
+ consume();
+ break;
+ } else {
+ consumeTheRemnantsOfABadURL();
+ return new BadURLToken();
+ }
+ } else if (code == 0x22 || code == 0x27 || code == 0x28 || nonprintable(code)) {
+ parseerror();
+ consumeTheRemnantsOfABadURL();
+ return new BadURLToken();
+ } else if (code == 0x5c) {
+ if (startsWithAValidEscape()) {
+ token.value += stringFromCode(consumeEscape());
+ } else {
+ parseerror();
+ consumeTheRemnantsOfABadURL();
+ return new BadURLToken();
+ }
+ } else {
+ token.value += stringFromCode(code);
+ }
+ }
+ token.text = token.value;
+ return token;
+ };
+
+ var consumeEscape = function () {
+ // Assume the the current character is the \
+ // and the next code point is not a newline.
+ consume();
+ if (hexdigit(code)) {
+ // Consume 1-6 hex digits
+ var digits = [code];
+ for (var total = 0; total < 5; total++) {
+ if (hexdigit(next())) {
+ consume();
+ digits.push(code);
+ } else {
+ break;
+ }
+ }
+ if (whitespace(next())) consume();
+ var value = parseInt(digits.map(function (x) {
+ return String.fromCharCode(x);
+ }).join(''), 16);
+ if (value > maximumallowedcodepoint) value = 0xfffd;
+ return value;
+ } else if (eof()) {
+ return 0xfffd;
+ } else {
+ return code;
+ }
+ };
+
+ var areAValidEscape = function (c1, c2) {
+ if (c1 != 0x5c) return false;
+ if (newline(c2)) return false;
+ return true;
+ };
+ var startsWithAValidEscape = function () {
+ return areAValidEscape(code, next());
+ };
+
+ var wouldStartAnIdentifier = function (c1, c2, c3) {
+ if (c1 == 0x2d) {
+ return namestartchar(c2) || c2 == 0x2d || areAValidEscape(c2, c3);
+ } else if (namestartchar(c1)) {
+ return true;
+ } else if (c1 == 0x5c) {
+ return areAValidEscape(c1, c2);
+ } else {
+ return false;
+ }
+ };
+ var startsWithAnIdentifier = function () {
+ return wouldStartAnIdentifier(code, next(1), next(2));
+ };
+
+ var wouldStartANumber = function (c1, c2, c3) {
+ if (c1 == 0x2b || c1 == 0x2d) {
+ if (digit(c2)) return true;
+ if (c2 == 0x2e && digit(c3)) return true;
+ return false;
+ } else if (c1 == 0x2e) {
+ if (digit(c2)) return true;
+ return false;
+ } else if (digit(c1)) {
+ return true;
+ } else {
+ return false;
+ }
+ };
+ var startsWithANumber = function () {
+ return wouldStartANumber(code, next(1), next(2));
+ };
+
+ var consumeAName = function () {
+ var result = "";
+ while (consume()) {
+ if (namechar(code)) {
+ result += stringFromCode(code);
+ } else if (startsWithAValidEscape()) {
+ result += stringFromCode(consumeEscape());
+ } else {
+ reconsume();
+ return result;
+ }
+ }
+ };
+
+ var consumeANumber = function () {
+ var repr = [];
+ var type = "integer";
+ if (next() == 0x2b || next() == 0x2d) {
+ consume();
+ repr += stringFromCode(code);
+ }
+ while (digit(next())) {
+ consume();
+ repr += stringFromCode(code);
+ }
+ if (next(1) == 0x2e && digit(next(2))) {
+ consume();
+ repr += stringFromCode(code);
+ consume();
+ repr += stringFromCode(code);
+ type = "number";
+ while (digit(next())) {
+ consume();
+ repr += stringFromCode(code);
+ }
+ }
+ var c1 = next(1),
+ c2 = next(2),
+ c3 = next(3);
+ if ((c1 == 0x45 || c1 == 0x65) && digit(c2)) {
+ consume();
+ repr += stringFromCode(code);
+ consume();
+ repr += stringFromCode(code);
+ type = "number";
+ while (digit(next())) {
+ consume();
+ repr += stringFromCode(code);
+ }
+ } else if ((c1 == 0x45 || c1 == 0x65) && (c2 == 0x2b || c2 == 0x2d) && digit(c3)) {
+ consume();
+ repr += stringFromCode(code);
+ consume();
+ repr += stringFromCode(code);
+ consume();
+ repr += stringFromCode(code);
+ type = "number";
+ while (digit(next())) {
+ consume();
+ repr += stringFromCode(code);
+ }
+ }
+ var value = convertAStringToANumber(repr);
+ return { type: type, value: value, repr: repr };
+ };
+
+ var convertAStringToANumber = function (string) {
+ // CSS's number rules are identical to JS, afaik.
+ return +string;
+ };
+
+ var consumeTheRemnantsOfABadURL = function () {
+ while (consume()) {
+ if (code == 0x2d || eof()) {
+ return;
+ } else if (startsWithAValidEscape()) {
+ consumeEscape();
+ donothing();
+ } else {
+ donothing();
+ }
+ }
+ };
+
+ var iterationCount = 0;
+ while (!eof(next())) {
+ var token = consumeAToken();
+ if (options.loc) {
+ token.loc = {};
+ token.loc.start = { line: locStart.line, column: locStart.column };
+ token.loc.end = { line: line, column: column };
+ }
+ if (options.offsets) {
+ token.startOffset = offsetStart;
+ token.endOffset = i + 1;
+ }
+ yield token;
+ iterationCount++;
+ if (iterationCount > str.length * 2) return "I'm infinite-looping!";
+ }
+ }
+
+ function CSSParserToken() {
+ throw "Abstract Base Class";
+ }
+ CSSParserToken.prototype.toJSON = function () {
+ return { token: this.tokenType };
+ };
+ CSSParserToken.prototype.toString = function () {
+ return this.tokenType;
+ };
+ CSSParserToken.prototype.toSource = function () {
+ return '' + this;
+ };
+
+ function BadStringToken(text) {
+ this.text = text;
+ return this;
+ }
+ BadStringToken.prototype = Object.create(CSSParserToken.prototype);
+ BadStringToken.prototype.tokenType = "bad_string";
+
+ function BadURLToken() {
+ return this;
+ }
+ BadURLToken.prototype = Object.create(CSSParserToken.prototype);
+ BadURLToken.prototype.tokenType = "bad_url";
+
+ function WhitespaceToken() {
+ return this;
+ }
+ WhitespaceToken.prototype = Object.create(CSSParserToken.prototype);
+ WhitespaceToken.prototype.tokenType = "whitespace";
+ WhitespaceToken.prototype.toString = function () {
+ return "WS";
+ };
+ WhitespaceToken.prototype.toSource = function () {
+ return " ";
+ };
+
+ function CDOToken() {
+ return this;
+ }
+ CDOToken.prototype = Object.create(CSSParserToken.prototype);
+ CDOToken.prototype.tokenType = "htmlcomment";
+ CDOToken.prototype.toSource = function () {
+ return "<!--";
+ };
+
+ function CDCToken() {
+ return this;
+ }
+ CDCToken.prototype = Object.create(CSSParserToken.prototype);
+ CDCToken.prototype.tokenType = "htmlcomment";
+ CDCToken.prototype.toSource = function () {
+ return "-->";
+ };
+
+ function ColonToken() {
+ return this;
+ }
+ ColonToken.prototype = Object.create(CSSParserToken.prototype);
+ ColonToken.prototype.tokenType = "symbol";
+ ColonToken.prototype.text = ":";
+
+ function SemicolonToken() {
+ return this;
+ }
+ SemicolonToken.prototype = Object.create(CSSParserToken.prototype);
+ SemicolonToken.prototype.tokenType = "symbol";
+ SemicolonToken.prototype.text = ";";
+
+ function CommaToken() {
+ return this;
+ }
+ CommaToken.prototype = Object.create(CSSParserToken.prototype);
+ CommaToken.prototype.tokenType = "symbol";
+ CommaToken.prototype.text = ",";
+
+ function GroupingToken() {
+ throw "Abstract Base Class";
+ }
+ GroupingToken.prototype = Object.create(CSSParserToken.prototype);
+
+ function OpenCurlyToken() {
+ this.value = "{";this.mirror = "}";return this;
+ }
+ OpenCurlyToken.prototype = Object.create(GroupingToken.prototype);
+ OpenCurlyToken.prototype.tokenType = "symbol";
+ OpenCurlyToken.prototype.text = "{";
+
+ function CloseCurlyToken() {
+ this.value = "}";this.mirror = "{";return this;
+ }
+ CloseCurlyToken.prototype = Object.create(GroupingToken.prototype);
+ CloseCurlyToken.prototype.tokenType = "symbol";
+ CloseCurlyToken.prototype.text = "}";
+
+ function OpenSquareToken() {
+ this.value = "[";this.mirror = "]";return this;
+ }
+ OpenSquareToken.prototype = Object.create(GroupingToken.prototype);
+ OpenSquareToken.prototype.tokenType = "symbol";
+ OpenSquareToken.prototype.text = "[";
+
+ function CloseSquareToken() {
+ this.value = "]";this.mirror = "[";return this;
+ }
+ CloseSquareToken.prototype = Object.create(GroupingToken.prototype);
+ CloseSquareToken.prototype.tokenType = "symbol";
+ CloseSquareToken.prototype.text = "]";
+
+ function OpenParenToken() {
+ this.value = "(";this.mirror = ")";return this;
+ }
+ OpenParenToken.prototype = Object.create(GroupingToken.prototype);
+ OpenParenToken.prototype.tokenType = "symbol";
+ OpenParenToken.prototype.text = "(";
+
+ function CloseParenToken() {
+ this.value = ")";this.mirror = "(";return this;
+ }
+ CloseParenToken.prototype = Object.create(GroupingToken.prototype);
+ CloseParenToken.prototype.tokenType = "symbol";
+ CloseParenToken.prototype.text = ")";
+
+ function IncludeMatchToken() {
+ return this;
+ }
+ IncludeMatchToken.prototype = Object.create(CSSParserToken.prototype);
+ IncludeMatchToken.prototype.tokenType = "includes";
+
+ function DashMatchToken() {
+ return this;
+ }
+ DashMatchToken.prototype = Object.create(CSSParserToken.prototype);
+ DashMatchToken.prototype.tokenType = "dashmatch";
+
+ function PrefixMatchToken() {
+ return this;
+ }
+ PrefixMatchToken.prototype = Object.create(CSSParserToken.prototype);
+ PrefixMatchToken.prototype.tokenType = "beginsmatch";
+
+ function SuffixMatchToken() {
+ return this;
+ }
+ SuffixMatchToken.prototype = Object.create(CSSParserToken.prototype);
+ SuffixMatchToken.prototype.tokenType = "endsmatch";
+
+ function SubstringMatchToken() {
+ return this;
+ }
+ SubstringMatchToken.prototype = Object.create(CSSParserToken.prototype);
+ SubstringMatchToken.prototype.tokenType = "containsmatch";
+
+ function ColumnToken() {
+ return this;
+ }
+ ColumnToken.prototype = Object.create(CSSParserToken.prototype);
+ ColumnToken.prototype.tokenType = "||";
+
+ function EOFToken() {
+ return this;
+ }
+ EOFToken.prototype = Object.create(CSSParserToken.prototype);
+ EOFToken.prototype.tokenType = "EOF";
+ EOFToken.prototype.toSource = function () {
+ return "";
+ };
+
+ function DelimToken(code) {
+ this.value = stringFromCode(code);
+ this.text = this.value;
+ return this;
+ }
+ DelimToken.prototype = Object.create(CSSParserToken.prototype);
+ DelimToken.prototype.tokenType = "symbol";
+ DelimToken.prototype.toString = function () {
+ return "DELIM(" + this.value + ")";
+ };
+ DelimToken.prototype.toJSON = function () {
+ var json = this.constructor.prototype.constructor.prototype.toJSON.call(this);
+ json.value = this.value;
+ return json;
+ };
+ DelimToken.prototype.toSource = function () {
+ if (this.value == "\\") return "\\\n";else return this.value;
+ };
+
+ function StringValuedToken() {
+ throw "Abstract Base Class";
+ }
+ StringValuedToken.prototype = Object.create(CSSParserToken.prototype);
+ StringValuedToken.prototype.ASCIIMatch = function (str) {
+ return this.value.toLowerCase() == str.toLowerCase();
+ };
+ StringValuedToken.prototype.toJSON = function () {
+ var json = this.constructor.prototype.constructor.prototype.toJSON.call(this);
+ json.value = this.value;
+ return json;
+ };
+
+ function IdentToken(val) {
+ this.value = val;
+ this.text = val;
+ }
+ IdentToken.prototype = Object.create(StringValuedToken.prototype);
+ IdentToken.prototype.tokenType = "ident";
+ IdentToken.prototype.toString = function () {
+ return "IDENT(" + this.value + ")";
+ };
+ IdentToken.prototype.toSource = function () {
+ return escapeIdent(this.value);
+ };
+
+ function FunctionToken(val) {
+ this.value = val;
+ this.text = val;
+ this.mirror = ")";
+ }
+ FunctionToken.prototype = Object.create(StringValuedToken.prototype);
+ FunctionToken.prototype.tokenType = "function";
+ FunctionToken.prototype.toString = function () {
+ return "FUNCTION(" + this.value + ")";
+ };
+ FunctionToken.prototype.toSource = function () {
+ return escapeIdent(this.value) + "(";
+ };
+
+ function AtKeywordToken(val) {
+ this.value = val;
+ this.text = val;
+ }
+ AtKeywordToken.prototype = Object.create(StringValuedToken.prototype);
+ AtKeywordToken.prototype.tokenType = "at";
+ AtKeywordToken.prototype.toString = function () {
+ return "AT(" + this.value + ")";
+ };
+ AtKeywordToken.prototype.toSource = function () {
+ return "@" + escapeIdent(this.value);
+ };
+
+ function HashToken(val) {
+ this.value = val;
+ this.text = val;
+ this.type = "unrestricted";
+ }
+ HashToken.prototype = Object.create(StringValuedToken.prototype);
+ HashToken.prototype.tokenType = "hash";
+ HashToken.prototype.toString = function () {
+ return "HASH(" + this.value + ")";
+ };
+ HashToken.prototype.toJSON = function () {
+ var json = this.constructor.prototype.constructor.prototype.toJSON.call(this);
+ json.value = this.value;
+ json.type = this.type;
+ return json;
+ };
+ HashToken.prototype.toSource = function () {
+ if (this.type == "id") {
+ return "#" + escapeIdent(this.value);
+ } else {
+ return "#" + escapeHash(this.value);
+ }
+ };
+
+ function StringToken(val) {
+ this.value = val;
+ this.text = val;
+ }
+ StringToken.prototype = Object.create(StringValuedToken.prototype);
+ StringToken.prototype.tokenType = "string";
+ StringToken.prototype.toString = function () {
+ return '"' + escapeString(this.value) + '"';
+ };
+
+ function CommentToken(val) {
+ this.value = val;
+ }
+ CommentToken.prototype = Object.create(StringValuedToken.prototype);
+ CommentToken.prototype.tokenType = "comment";
+ CommentToken.prototype.toString = function () {
+ return '/*' + this.value + '*/';
+ };
+ CommentToken.prototype.toSource = CommentToken.prototype.toString;
+
+ function URLToken(val) {
+ this.value = val;
+ this.text = val;
+ }
+ URLToken.prototype = Object.create(StringValuedToken.prototype);
+ URLToken.prototype.tokenType = "url";
+ URLToken.prototype.toString = function () {
+ return "URL(" + this.value + ")";
+ };
+ URLToken.prototype.toSource = function () {
+ return 'url("' + escapeString(this.value) + '")';
+ };
+
+ function NumberToken() {
+ this.value = null;
+ this.type = "integer";
+ this.repr = "";
+ }
+ NumberToken.prototype = Object.create(CSSParserToken.prototype);
+ NumberToken.prototype.tokenType = "number";
+ NumberToken.prototype.toString = function () {
+ if (this.type == "integer") return "INT(" + this.value + ")";
+ return "NUMBER(" + this.value + ")";
+ };
+ NumberToken.prototype.toJSON = function () {
+ var json = this.constructor.prototype.constructor.prototype.toJSON.call(this);
+ json.value = this.value;
+ json.type = this.type;
+ json.repr = this.repr;
+ return json;
+ };
+ NumberToken.prototype.toSource = function () {
+ return this.repr;
+ };
+
+ function PercentageToken() {
+ this.value = null;
+ this.repr = "";
+ }
+ PercentageToken.prototype = Object.create(CSSParserToken.prototype);
+ PercentageToken.prototype.tokenType = "percentage";
+ PercentageToken.prototype.toString = function () {
+ return "PERCENTAGE(" + this.value + ")";
+ };
+ PercentageToken.prototype.toJSON = function () {
+ var json = this.constructor.prototype.constructor.prototype.toJSON.call(this);
+ json.value = this.value;
+ json.repr = this.repr;
+ return json;
+ };
+ PercentageToken.prototype.toSource = function () {
+ return this.repr + "%";
+ };
+
+ function DimensionToken() {
+ this.value = null;
+ this.type = "integer";
+ this.repr = "";
+ this.unit = "";
+ }
+ DimensionToken.prototype = Object.create(CSSParserToken.prototype);
+ DimensionToken.prototype.tokenType = "dimension";
+ DimensionToken.prototype.toString = function () {
+ return "DIM(" + this.value + "," + this.unit + ")";
+ };
+ DimensionToken.prototype.toJSON = function () {
+ var json = this.constructor.prototype.constructor.prototype.toJSON.call(this);
+ json.value = this.value;
+ json.type = this.type;
+ json.repr = this.repr;
+ json.unit = this.unit;
+ return json;
+ };
+ DimensionToken.prototype.toSource = function () {
+ var source = this.repr;
+ var unit = escapeIdent(this.unit);
+ if (unit[0].toLowerCase() == "e" && (unit[1] == "-" || between(unit.charCodeAt(1), 0x30, 0x39))) {
+ // Unit is ambiguous with scinot
+ // Remove the leading "e", replace with escape.
+ unit = "\\65 " + unit.slice(1, unit.length);
+ }
+ return source + unit;
+ };
+
+ function escapeIdent(string) {
+ string = '' + string;
+ var result = '';
+ var firstcode = string.charCodeAt(0);
+ for (var i = 0; i < string.length; i++) {
+ var code = string.charCodeAt(i);
+ if (code === 0x0) {
+ throw new InvalidCharacterError('Invalid character: the input contains U+0000.');
+ }
+
+ if (between(code, 0x1, 0x1f) || code == 0x7f || i === 0 && between(code, 0x30, 0x39) || i == 1 && between(code, 0x30, 0x39) && firstcode == 0x2d) {
+ result += '\\' + code.toString(16) + ' ';
+ } else if (code >= 0x80 || code == 0x2d || code == 0x5f || between(code, 0x30, 0x39) || between(code, 0x41, 0x5a) || between(code, 0x61, 0x7a)) {
+ result += string[i];
+ } else {
+ result += '\\' + string[i];
+ }
+ }
+ return result;
+ }
+
+ function escapeHash(string) {
+ // Escapes the contents of "unrestricted"-type hash tokens.
+ // Won't preserve the ID-ness of "id"-type hash tokens;
+ // use escapeIdent() for that.
+ string = '' + string;
+ var result = '';
+ for (var i = 0; i < string.length; i++) {
+ var code = string.charCodeAt(i);
+ if (code === 0x0) {
+ throw new InvalidCharacterError('Invalid character: the input contains U+0000.');
+ }
+
+ if (code >= 0x80 || code == 0x2d || code == 0x5f || between(code, 0x30, 0x39) || between(code, 0x41, 0x5a) || between(code, 0x61, 0x7a)) {
+ result += string[i];
+ } else {
+ result += '\\' + code.toString(16) + ' ';
+ }
+ }
+ return result;
+ }
+
+ function escapeString(string) {
+ string = '' + string;
+ var result = '';
+ for (var i = 0; i < string.length; i++) {
+ var code = string.charCodeAt(i);
+
+ if (code === 0x0) {
+ throw new InvalidCharacterError('Invalid character: the input contains U+0000.');
+ }
+
+ if (between(code, 0x1, 0x1f) || code == 0x7f) {
+ result += '\\' + code.toString(16) + ' ';
+ } else if (code == 0x22 || code == 0x5c) {
+ result += '\\' + string[i];
+ } else {
+ result += string[i];
+ }
+ }
+ return result;
+ }
+
+ // Exportation.
+ exports.tokenize = tokenize;
+ exports.IdentToken = IdentToken;
+ exports.FunctionToken = FunctionToken;
+ exports.AtKeywordToken = AtKeywordToken;
+ exports.HashToken = HashToken;
+ exports.StringToken = StringToken;
+ exports.BadStringToken = BadStringToken;
+ exports.URLToken = URLToken;
+ exports.BadURLToken = BadURLToken;
+ exports.DelimToken = DelimToken;
+ exports.NumberToken = NumberToken;
+ exports.PercentageToken = PercentageToken;
+ exports.DimensionToken = DimensionToken;
+ exports.IncludeMatchToken = IncludeMatchToken;
+ exports.DashMatchToken = DashMatchToken;
+ exports.PrefixMatchToken = PrefixMatchToken;
+ exports.SuffixMatchToken = SuffixMatchToken;
+ exports.SubstringMatchToken = SubstringMatchToken;
+ exports.ColumnToken = ColumnToken;
+ exports.WhitespaceToken = WhitespaceToken;
+ exports.CDOToken = CDOToken;
+ exports.CDCToken = CDCToken;
+ exports.ColonToken = ColonToken;
+ exports.SemicolonToken = SemicolonToken;
+ exports.CommaToken = CommaToken;
+ exports.OpenParenToken = OpenParenToken;
+ exports.CloseParenToken = CloseParenToken;
+ exports.OpenSquareToken = OpenSquareToken;
+ exports.CloseSquareToken = CloseSquareToken;
+ exports.OpenCurlyToken = OpenCurlyToken;
+ exports.CloseCurlyToken = CloseCurlyToken;
+ exports.EOFToken = EOFToken;
+ exports.CSSParserToken = CSSParserToken;
+ exports.GroupingToken = GroupingToken;
+
+ function TokenStream(tokens) {
+ // Assume that tokens is a iterator.
+ this.tokens = tokens;
+ this.token = undefined;
+ this.stored = [];
+ }
+ TokenStream.prototype.consume = function (num) {
+ if (num === undefined) num = 1;
+ while (num-- > 0) {
+ if (this.stored.length > 0) {
+ this.token = this.stored.shift();
+ } else {
+ var n = this.tokens.next();
+ while (!n.done && n.value instanceof CommentToken) {
+ n = this.tokens.next();
+ }
+ if (n.done) {
+ this.token = new EOFToken();
+ break;
+ }
+ this.token = n.value;
+ }
+ }
+ //console.log(this.i, this.token);
+ return true;
+ };
+ TokenStream.prototype.next = function () {
+ if (this.stored.length === 0) {
+ var n = this.tokens.next();
+ while (!n.done && n.value instanceof CommentToken) {
+ n = this.tokens.next();
+ }
+ if (n.done) return new EOFToken();
+ this.stored.push(n.value);
+ }
+ return this.stored[0];
+ };
+ TokenStream.prototype.reconsume = function () {
+ this.stored.unshift(this.token);
+ };
+
+ function parseerror(s, msg) {
+ console.log("Parse error at token " + s.i + ": " + s.token + ".\n" + msg);
+ return true;
+ }
+ function donothing() {
+ return true;
+ }
+
+ function consumeAListOfRules(s, topLevel) {
+ var rules = [];
+ var rule;
+ while (s.consume()) {
+ if (s.token instanceof WhitespaceToken) {
+ continue;
+ } else if (s.token instanceof EOFToken) {
+ return rules;
+ } else if (s.token instanceof CDOToken || s.token instanceof CDCToken) {
+ if (topLevel == "top-level") continue;
+ s.reconsume();
+ if (rule = consumeAQualifiedRule(s)) rules.push(rule);
+ } else if (s.token instanceof AtKeywordToken) {
+ s.reconsume();
+ if (rule = consumeAnAtRule(s)) rules.push(rule);
+ } else {
+ s.reconsume();
+ if (rule = consumeAQualifiedRule(s)) rules.push(rule);
+ }
+ }
+ }
+
+ function consumeAnAtRule(s) {
+ s.consume();
+ var rule = new AtRule(s.token.value);
+ while (s.consume()) {
+ if (s.token instanceof SemicolonToken || s.token instanceof EOFToken) {
+ return rule;
+ } else if (s.token instanceof OpenCurlyToken) {
+ rule.value = consumeASimpleBlock(s);
+ return rule;
+ } else {
+ s.reconsume();
+ rule.prelude.push(consumeAComponentValue(s));
+ }
+ }
+ }
+
+ function consumeAQualifiedRule(s) {
+ var rule = new QualifiedRule();
+ while (s.consume()) {
+ if (s.token instanceof EOFToken) {
+ parseerror(s, "Hit EOF when trying to parse the prelude of a qualified rule.");
+ return;
+ } else if (s.token instanceof OpenCurlyToken) {
+ rule.value = consumeASimpleBlock(s);
+ return rule;
+ } else {
+ s.reconsume();
+ rule.prelude.push(consumeAComponentValue(s));
+ }
+ }
+ }
+
+ function consumeAListOfDeclarations(s) {
+ var decls = [];
+ while (s.consume()) {
+ if (s.token instanceof WhitespaceToken || s.token instanceof SemicolonToken) {
+ donothing();
+ } else if (s.token instanceof EOFToken) {
+ return decls;
+ } else if (s.token instanceof AtKeywordToken) {
+ s.reconsume();
+ decls.push(consumeAnAtRule(s));
+ } else if (s.token instanceof IdentToken) {
+ var temp = [s.token];
+ while (!(s.next() instanceof SemicolonToken || s.next() instanceof EOFToken)) {
+ temp.push(consumeAComponentValue(s));
+ }var decl;
+ if (decl = consumeADeclaration(new TokenStream(temp))) decls.push(decl);
+ } else {
+ parseerror(s);
+ s.reconsume();
+ while (!(s.next() instanceof SemicolonToken || s.next() instanceof EOFToken)) {
+ consumeAComponentValue(s);
+ }
+ }
+ }
+ }
+
+ function consumeADeclaration(s) {
+ // Assumes that the next input token will be an ident token.
+ s.consume();
+ var decl = new Declaration(s.token.value);
+ while (s.next() instanceof WhitespaceToken) {
+ s.consume();
+ }if (!(s.next() instanceof ColonToken)) {
+ parseerror(s);
+ return;
+ } else {
+ s.consume();
+ }
+ while (!(s.next() instanceof EOFToken)) {
+ decl.value.push(consumeAComponentValue(s));
+ }
+ var foundImportant = false;
+ for (var i = decl.value.length - 1; i >= 0; i--) {
+ if (decl.value[i] instanceof WhitespaceToken) {
+ continue;
+ } else if (decl.value[i] instanceof IdentToken && decl.value[i].ASCIIMatch("important")) {
+ foundImportant = true;
+ } else if (foundImportant && decl.value[i] instanceof DelimToken && decl.value[i].value == "!") {
+ decl.value.splice(i, decl.value.length);
+ decl.important = true;
+ break;
+ } else {
+ break;
+ }
+ }
+ return decl;
+ }
+
+ function consumeAComponentValue(s) {
+ s.consume();
+ if (s.token instanceof OpenCurlyToken || s.token instanceof OpenSquareToken || s.token instanceof OpenParenToken) return consumeASimpleBlock(s);
+ if (s.token instanceof FunctionToken) return consumeAFunction(s);
+ return s.token;
+ }
+
+ function consumeASimpleBlock(s) {
+ var mirror = s.token.mirror;
+ var block = new SimpleBlock(s.token.value);
+ block.startToken = s.token;
+ while (s.consume()) {
+ if (s.token instanceof EOFToken || s.token instanceof GroupingToken && s.token.value == mirror) return block;else {
+ s.reconsume();
+ block.value.push(consumeAComponentValue(s));
+ }
+ }
+ }
+
+ function consumeAFunction(s) {
+ var func = new Func(s.token.value);
+ while (s.consume()) {
+ if (s.token instanceof EOFToken || s.token instanceof CloseParenToken) return func;else {
+ s.reconsume();
+ func.value.push(consumeAComponentValue(s));
+ }
+ }
+ }
+
+ function normalizeInput(input) {
+ if (typeof input == "string") return new TokenStream(tokenize(input));
+ if (input instanceof TokenStream) return input;
+ if (typeof input.next == "function") return new TokenStream(input);
+ if (input.length !== undefined) return new TokenStream(input[Symbol.iterator]());else throw SyntaxError(input);
+ }
+
+ function parseAStylesheet(s) {
+ s = normalizeInput(s);
+ var sheet = new Stylesheet();
+ sheet.value = consumeAListOfRules(s, "top-level");
+ return sheet;
+ }
+
+ function parseAListOfRules(s) {
+ s = normalizeInput(s);
+ return consumeAListOfRules(s);
+ }
+
+ function parseARule(s) {
+ s = normalizeInput(s);
+ while (s.next() instanceof WhitespaceToken) {
+ s.consume();
+ }if (s.next() instanceof EOFToken) throw SyntaxError();
+ var rule;
+ var startToken = s.next();
+ if (startToken instanceof AtKeywordToken) {
+ rule = consumeAnAtRule(s);
+ } else {
+ rule = consumeAQualifiedRule(s);
+ if (!rule) throw SyntaxError();
+ }
+ rule.startToken = startToken;
+ rule.endToken = s.token;
+ return rule;
+ }
+
+ function parseADeclaration(s) {
+ s = normalizeInput(s);
+ while (s.next() instanceof WhitespaceToken) {
+ s.consume();
+ }if (!(s.next() instanceof IdentToken)) throw SyntaxError();
+ var decl = consumeADeclaration(s);
+ if (decl) return decl;else throw SyntaxError();
+ }
+
+ function parseAListOfDeclarations(s) {
+ s = normalizeInput(s);
+ return consumeAListOfDeclarations(s);
+ }
+
+ function parseAComponentValue(s) {
+ s = normalizeInput(s);
+ while (s.next() instanceof WhitespaceToken) {
+ s.consume();
+ }if (s.next() instanceof EOFToken) throw SyntaxError();
+ var val = consumeAComponentValue(s);
+ if (!val) throw SyntaxError();
+ while (s.next() instanceof WhitespaceToken) {
+ s.consume();
+ }if (s.next() instanceof EOFToken) return val;
+ throw SyntaxError();
+ }
+
+ function parseAListOfComponentValues(s) {
+ s = normalizeInput(s);
+ var vals = [];
+ while (true) {
+ var val = consumeAComponentValue(s);
+ if (val instanceof EOFToken) return vals;else vals.push(val);
+ }
+ }
+
+ function parseACommaSeparatedListOfComponentValues(s) {
+ s = normalizeInput(s);
+ var listOfCVLs = [];
+ while (true) {
+ var vals = [];
+ while (true) {
+ var val = consumeAComponentValue(s);
+ if (val instanceof EOFToken) {
+ listOfCVLs.push(vals);
+ return listOfCVLs;
+ } else if (val instanceof CommaToken) {
+ listOfCVLs.push(vals);
+ break;
+ } else {
+ vals.push(val);
+ }
+ }
+ }
+ }
+
+ function CSSParserRule() {
+ throw "Abstract Base Class";
+ }
+ CSSParserRule.prototype.toString = function (indent) {
+ return JSON.stringify(this, null, indent);
+ };
+ CSSParserRule.prototype.toJSON = function () {
+ return { type: this.type, value: this.value };
+ };
+
+ function Stylesheet() {
+ this.value = [];
+ return this;
+ }
+ Stylesheet.prototype = Object.create(CSSParserRule.prototype);
+ Stylesheet.prototype.type = "STYLESHEET";
+
+ function AtRule(name) {
+ this.name = name;
+ this.prelude = [];
+ this.value = null;
+ return this;
+ }
+ AtRule.prototype = Object.create(CSSParserRule.prototype);
+ AtRule.prototype.type = "AT-RULE";
+ AtRule.prototype.toJSON = function () {
+ var json = this.constructor.prototype.constructor.prototype.toJSON.call(this);
+ json.name = this.name;
+ json.prelude = this.prelude;
+ return json;
+ };
+
+ function QualifiedRule() {
+ this.prelude = [];
+ this.value = [];
+ return this;
+ }
+ QualifiedRule.prototype = Object.create(CSSParserRule.prototype);
+ QualifiedRule.prototype.type = "QUALIFIED-RULE";
+ QualifiedRule.prototype.toJSON = function () {
+ var json = this.constructor.prototype.constructor.prototype.toJSON.call(this);
+ json.prelude = this.prelude;
+ return json;
+ };
+
+ function Declaration(name) {
+ this.name = name;
+ this.value = [];
+ this.important = false;
+ return this;
+ }
+ Declaration.prototype = Object.create(CSSParserRule.prototype);
+ Declaration.prototype.type = "DECLARATION";
+ Declaration.prototype.toJSON = function () {
+ var json = this.constructor.prototype.constructor.prototype.toJSON.call(this);
+ json.name = this.name;
+ json.important = this.important;
+ return json;
+ };
+
+ function SimpleBlock(type) {
+ this.name = type;
+ this.value = [];
+ return this;
+ }
+ SimpleBlock.prototype = Object.create(CSSParserRule.prototype);
+ SimpleBlock.prototype.type = "BLOCK";
+ SimpleBlock.prototype.toJSON = function () {
+ var json = this.constructor.prototype.constructor.prototype.toJSON.call(this);
+ json.name = this.name;
+ return json;
+ };
+
+ function Func(name) {
+ this.name = name;
+ this.value = [];
+ return this;
+ }
+ Func.prototype = Object.create(CSSParserRule.prototype);
+ Func.prototype.type = "FUNCTION";
+ Func.prototype.toJSON = function () {
+ var json = this.constructor.prototype.constructor.prototype.toJSON.call(this);
+ json.name = this.name;
+ return json;
+ };
+
+ function CSSLexer(text) {
+ this.stream = tokenize(text, {
+ loc: true,
+ offsets: true,
+ keepComments: true
+ });
+ this.lineNumber = 0;
+ this.columnNumber = 0;
+ return this;
+ }
+
+ CSSLexer.prototype.performEOFFixup = function (input, preserveBackslash) {
+ // Just lie for now.
+ return "";
+ };
+
+ CSSLexer.prototype.nextToken = function () {
+ if (!this.stream) {
+ return null;
+ }
+ var v = this.stream.next();
+ if (v.done || v.value.tokenType === "EOF") {
+ this.stream = null;
+ return null;
+ }
+ this.lineNumber = v.value.loc.start.line;
+ this.columnNumber = v.value.loc.start.column;
+ return v.value;
+ };
+
+ // Exportation.
+ exports.CSSParserRule = CSSParserRule;
+ exports.Stylesheet = Stylesheet;
+ exports.AtRule = AtRule;
+ exports.QualifiedRule = QualifiedRule;
+ exports.Declaration = Declaration;
+ exports.SimpleBlock = SimpleBlock;
+ exports.Func = Func;
+ exports.parseAStylesheet = parseAStylesheet;
+ exports.parseAListOfRules = parseAListOfRules;
+ exports.parseARule = parseARule;
+ exports.parseADeclaration = parseADeclaration;
+ exports.parseAListOfDeclarations = parseAListOfDeclarations;
+ exports.parseAComponentValue = parseAComponentValue;
+ exports.parseAListOfComponentValues = parseAListOfComponentValues;
+ exports.parseACommaSeparatedListOfComponentValues = parseACommaSeparatedListOfComponentValues;
+ exports.CSSLexer = CSSLexer;
+ });
+
+/***/ },
+/* 64 */
+/***/ function(module, exports) {
+
+ // auto-generated from nsColorNameList.h
+ var cssColors = {
+ aliceblue: [240, 248, 255],
+ antiquewhite: [250, 235, 215],
+ aqua: [0, 255, 255],
+ aquamarine: [127, 255, 212],
+ azure: [240, 255, 255],
+ beige: [245, 245, 220],
+ bisque: [255, 228, 196],
+ black: [0, 0, 0],
+ blanchedalmond: [255, 235, 205],
+ blue: [0, 0, 255],
+ blueviolet: [138, 43, 226],
+ brown: [165, 42, 42],
+ burlywood: [222, 184, 135],
+ cadetblue: [95, 158, 160],
+ chartreuse: [127, 255, 0],
+ chocolate: [210, 105, 30],
+ coral: [255, 127, 80],
+ cornflowerblue: [100, 149, 237],
+ cornsilk: [255, 248, 220],
+ crimson: [220, 20, 60],
+ cyan: [0, 255, 255],
+ darkblue: [0, 0, 139],
+ darkcyan: [0, 139, 139],
+ darkgoldenrod: [184, 134, 11],
+ darkgray: [169, 169, 169],
+ darkgreen: [0, 100, 0],
+ darkgrey: [169, 169, 169],
+ darkkhaki: [189, 183, 107],
+ darkmagenta: [139, 0, 139],
+ darkolivegreen: [85, 107, 47],
+ darkorange: [255, 140, 0],
+ darkorchid: [153, 50, 204],
+ darkred: [139, 0, 0],
+ darksalmon: [233, 150, 122],
+ darkseagreen: [143, 188, 143],
+ darkslateblue: [72, 61, 139],
+ darkslategray: [47, 79, 79],
+ darkslategrey: [47, 79, 79],
+ darkturquoise: [0, 206, 209],
+ darkviolet: [148, 0, 211],
+ deeppink: [255, 20, 147],
+ deepskyblue: [0, 191, 255],
+ dimgray: [105, 105, 105],
+ dimgrey: [105, 105, 105],
+ dodgerblue: [30, 144, 255],
+ firebrick: [178, 34, 34],
+ floralwhite: [255, 250, 240],
+ forestgreen: [34, 139, 34],
+ fuchsia: [255, 0, 255],
+ gainsboro: [220, 220, 220],
+ ghostwhite: [248, 248, 255],
+ gold: [255, 215, 0],
+ goldenrod: [218, 165, 32],
+ gray: [128, 128, 128],
+ grey: [128, 128, 128],
+ green: [0, 128, 0],
+ greenyellow: [173, 255, 47],
+ honeydew: [240, 255, 240],
+ hotpink: [255, 105, 180],
+ indianred: [205, 92, 92],
+ indigo: [75, 0, 130],
+ ivory: [255, 255, 240],
+ khaki: [240, 230, 140],
+ lavender: [230, 230, 250],
+ lavenderblush: [255, 240, 245],
+ lawngreen: [124, 252, 0],
+ lemonchiffon: [255, 250, 205],
+ lightblue: [173, 216, 230],
+ lightcoral: [240, 128, 128],
+ lightcyan: [224, 255, 255],
+ lightgoldenrodyellow: [250, 250, 210],
+ lightgray: [211, 211, 211],
+ lightgreen: [144, 238, 144],
+ lightgrey: [211, 211, 211],
+ lightpink: [255, 182, 193],
+ lightsalmon: [255, 160, 122],
+ lightseagreen: [32, 178, 170],
+ lightskyblue: [135, 206, 250],
+ lightslategray: [119, 136, 153],
+ lightslategrey: [119, 136, 153],
+ lightsteelblue: [176, 196, 222],
+ lightyellow: [255, 255, 224],
+ lime: [0, 255, 0],
+ limegreen: [50, 205, 50],
+ linen: [250, 240, 230],
+ magenta: [255, 0, 255],
+ maroon: [128, 0, 0],
+ mediumaquamarine: [102, 205, 170],
+ mediumblue: [0, 0, 205],
+ mediumorchid: [186, 85, 211],
+ mediumpurple: [147, 112, 219],
+ mediumseagreen: [60, 179, 113],
+ mediumslateblue: [123, 104, 238],
+ mediumspringgreen: [0, 250, 154],
+ mediumturquoise: [72, 209, 204],
+ mediumvioletred: [199, 21, 133],
+ midnightblue: [25, 25, 112],
+ mintcream: [245, 255, 250],
+ mistyrose: [255, 228, 225],
+ moccasin: [255, 228, 181],
+ navajowhite: [255, 222, 173],
+ navy: [0, 0, 128],
+ oldlace: [253, 245, 230],
+ olive: [128, 128, 0],
+ olivedrab: [107, 142, 35],
+ orange: [255, 165, 0],
+ orangered: [255, 69, 0],
+ orchid: [218, 112, 214],
+ palegoldenrod: [238, 232, 170],
+ palegreen: [152, 251, 152],
+ paleturquoise: [175, 238, 238],
+ palevioletred: [219, 112, 147],
+ papayawhip: [255, 239, 213],
+ peachpuff: [255, 218, 185],
+ peru: [205, 133, 63],
+ pink: [255, 192, 203],
+ plum: [221, 160, 221],
+ powderblue: [176, 224, 230],
+ purple: [128, 0, 128],
+ rebeccapurple: [102, 51, 153],
+ red: [255, 0, 0],
+ rosybrown: [188, 143, 143],
+ royalblue: [65, 105, 225],
+ saddlebrown: [139, 69, 19],
+ salmon: [250, 128, 114],
+ sandybrown: [244, 164, 96],
+ seagreen: [46, 139, 87],
+ seashell: [255, 245, 238],
+ sienna: [160, 82, 45],
+ silver: [192, 192, 192],
+ skyblue: [135, 206, 235],
+ slateblue: [106, 90, 205],
+ slategray: [112, 128, 144],
+ slategrey: [112, 128, 144],
+ snow: [255, 250, 250],
+ springgreen: [0, 255, 127],
+ steelblue: [70, 130, 180],
+ tan: [210, 180, 140],
+ teal: [0, 128, 128],
+ thistle: [216, 191, 216],
+ tomato: [255, 99, 71],
+ turquoise: [64, 224, 208],
+ violet: [238, 130, 238],
+ wheat: [245, 222, 179],
+ white: [255, 255, 255],
+ whitesmoke: [245, 245, 245],
+ yellow: [255, 255, 0],
+ yellowgreen: [154, 205, 50]
+ };
+ module.exports = { cssColors };
+
+/***/ },
+/* 65 */
+/***/ function(module, exports) {
+
+ // auto-generated by means you would rather not know
+ var cssProperties={"-moz-appearance":{inherited:false,supports:0,values:["-moz-gtk-info-bar","-moz-mac-disclosure-button-closed","-moz-mac-disclosure-button-open","-moz-mac-fullscreen-button","-moz-mac-help-button","-moz-mac-vibrancy-dark","-moz-mac-vibrancy-light","-moz-win-borderless-glass","-moz-win-browsertabbar-toolbox","-moz-win-communications-toolbox","-moz-win-exclude-glass","-moz-win-glass","-moz-win-media-toolbox","-moz-window-button-box","-moz-window-button-box-maximized","-moz-window-button-close","-moz-window-button-maximize","-moz-window-button-minimize","-moz-window-button-restore","-moz-window-frame-bottom","-moz-window-frame-left","-moz-window-frame-right","-moz-window-titlebar","-moz-window-titlebar-maximized","button","button-arrow-down","button-arrow-next","button-arrow-previous","button-arrow-up","button-bevel","button-focus","caret","checkbox","checkbox-container","checkbox-label","checkmenuitem","dialog","dualbutton","groupbox","inherit","initial","listbox","listitem","menuarrow","menubar","menucheckbox","menuimage","menuitem","menuitemtext","menulist","menulist-button","menulist-text","menulist-textfield","menupopup","menuradio","menuseparator","meterbar","meterchunk","none","number-input","progressbar","progressbar-vertical","progresschunk","progresschunk-vertical","radio","radio-container","radio-label","radiomenuitem","range","range-thumb","resizer","resizerpanel","scale-horizontal","scale-vertical","scalethumb-horizontal","scalethumb-vertical","scalethumbend","scalethumbstart","scalethumbtick","scrollbar","scrollbar-small","scrollbarbutton-down","scrollbarbutton-left","scrollbarbutton-right","scrollbarbutton-up","scrollbarthumb-horizontal","scrollbarthumb-vertical","scrollbartrack-horizontal","scrollbartrack-vertical","searchfield","separator","spinner","spinner-downbutton","spinner-textfield","spinner-upbutton","splitter","statusbar","statusbarpanel","tab","tab-scroll-arrow-back","tab-scroll-arrow-forward","tabpanel","tabpanels","textfield","textfield-multiline","toolbar","toolbarbutton","toolbarbutton-dropdown","toolbargripper","toolbox","tooltip","treeheader","treeheadercell","treeheadersortarrow","treeitem","treeline","treetwisty","treetwistyopen","treeview","unset","window"]},"-moz-outline-radius-topleft":{inherited:false,supports:3,values:["inherit","initial","unset"]},"-moz-outline-radius-topright":{inherited:false,supports:3,values:["inherit","initial","unset"]},"-moz-outline-radius-bottomright":{inherited:false,supports:3,values:["inherit","initial","unset"]},"-moz-outline-radius-bottomleft":{inherited:false,supports:3,values:["inherit","initial","unset"]},"-moz-tab-size":{inherited:true,supports:1024,values:["inherit","initial","unset"]},"animation-delay":{inherited:false,supports:64,values:["inherit","initial","unset"]},"animation-direction":{inherited:false,supports:0,values:["alternate","alternate-reverse","inherit","initial","normal","reverse","unset"]},"animation-duration":{inherited:false,supports:64,values:["inherit","initial","unset"]},"animation-fill-mode":{inherited:false,supports:0,values:["backwards","both","forwards","inherit","initial","none","unset"]},"animation-iteration-count":{inherited:false,supports:1024,values:["infinite","inherit","initial","unset"]},"animation-name":{inherited:false,supports:0,values:["inherit","initial","none","unset"]},"animation-play-state":{inherited:false,supports:0,values:["inherit","initial","paused","running","unset"]},"animation-timing-function":{inherited:false,supports:256,values:["cubic-bezier","ease","ease-in","ease-in-out","ease-out","inherit","initial","linear","step-end","step-start","steps","unset"]},"background-attachment":{inherited:false,supports:0,values:["fixed","inherit","initial","local","scroll","unset"]},"background-clip":{inherited:false,supports:0,values:["border-box","content-box","inherit","initial","padding-box","unset"]},"background-color":{inherited:false,supports:4,values:["aliceblue","antiquewhite","aqua","aquamarine","azure","beige","bisque","black","blanchedalmond","blue","blueviolet","brown","burlywood","cadetblue","chartreuse","chocolate","coral","cornflowerblue","cornsilk","crimson","currentColor","cyan","darkblue","darkcyan","darkgoldenrod","darkgray","darkgreen","darkgrey","darkkhaki","darkmagenta","darkolivegreen","darkorange","darkorchid","darkred","darksalmon","darkseagreen","darkslateblue","darkslategray","darkslategrey","darkturquoise","darkviolet","deeppink","deepskyblue","dimgray","dimgrey","dodgerblue","firebrick","floralwhite","forestgreen","fuchsia","gainsboro","ghostwhite","gold","goldenrod","gray","grey","green","greenyellow","honeydew","hotpink","hsl","hsla","indianred","indigo","inherit","initial","ivory","khaki","lavender","lavenderblush","lawngreen","lemonchiffon","lightblue","lightcoral","lightcyan","lightgoldenrodyellow","lightgray","lightgreen","lightgrey","lightpink","lightsalmon","lightseagreen","lightskyblue","lightslategray","lightslategrey","lightsteelblue","lightyellow","lime","limegreen","linen","magenta","maroon","mediumaquamarine","mediumblue","mediumorchid","mediumpurple","mediumseagreen","mediumslateblue","mediumspringgreen","mediumturquoise","mediumvioletred","midnightblue","mintcream","mistyrose","moccasin","navajowhite","navy","oldlace","olive","olivedrab","orange","orangered","orchid","palegoldenrod","palegreen","paleturquoise","palevioletred","papayawhip","peachpuff","peru","pink","plum","powderblue","purple","rebeccapurple","red","rgb","rgba","rosybrown","royalblue","saddlebrown","salmon","sandybrown","seagreen","seashell","sienna","silver","skyblue","slateblue","slategray","slategrey","snow","springgreen","steelblue","tan","teal","thistle","tomato","transparent","turquoise","unset","violet","wheat","white","whitesmoke","yellow","yellowgreen"]},"background-image":{inherited:false,supports:648,values:["-moz-element","-moz-image-rect","-moz-linear-gradient","-moz-radial-gradient","-moz-repeating-linear-gradient","-moz-repeating-radial-gradient","inherit","initial","linear-gradient","none","radial-gradient","repeating-linear-gradient","repeating-radial-gradient","unset","url"]},"background-blend-mode":{inherited:false,supports:0,values:["color","color-burn","color-dodge","darken","difference","exclusion","hard-light","hue","inherit","initial","lighten","luminosity","multiply","normal","overlay","saturation","screen","soft-light","unset"]},"background-origin":{inherited:false,supports:0,values:["border-box","content-box","inherit","initial","padding-box","unset"]},"background-position":{inherited:false,supports:3,values:["inherit","initial","unset"]},"background-repeat":{inherited:false,supports:0,values:["inherit","initial","no-repeat","repeat","repeat-x","repeat-y","unset"]},"background-size":{inherited:false,supports:3,values:["inherit","initial","unset"]},"-moz-binding":{inherited:false,supports:8,values:["inherit","initial","none","unset","url"]},"block-size":{inherited:false,supports:3,values:["-moz-calc","auto","calc","inherit","initial","unset"]},"border-block-end-color":{inherited:false,supports:4,values:["-moz-use-text-color","aliceblue","antiquewhite","aqua","aquamarine","azure","beige","bisque","black","blanchedalmond","blue","blueviolet","brown","burlywood","cadetblue","chartreuse","chocolate","coral","cornflowerblue","cornsilk","crimson","currentColor","cyan","darkblue","darkcyan","darkgoldenrod","darkgray","darkgreen","darkgrey","darkkhaki","darkmagenta","darkolivegreen","darkorange","darkorchid","darkred","darksalmon","darkseagreen","darkslateblue","darkslategray","darkslategrey","darkturquoise","darkviolet","deeppink","deepskyblue","dimgray","dimgrey","dodgerblue","firebrick","floralwhite","forestgreen","fuchsia","gainsboro","ghostwhite","gold","goldenrod","gray","grey","green","greenyellow","honeydew","hotpink","hsl","hsla","indianred","indigo","inherit","initial","ivory","khaki","lavender","lavenderblush","lawngreen","lemonchiffon","lightblue","lightcoral","lightcyan","lightgoldenrodyellow","lightgray","lightgreen","lightgrey","lightpink","lightsalmon","lightseagreen","lightskyblue","lightslategray","lightslategrey","lightsteelblue","lightyellow","lime","limegreen","linen","magenta","maroon","mediumaquamarine","mediumblue","mediumorchid","mediumpurple","mediumseagreen","mediumslateblue","mediumspringgreen","mediumturquoise","mediumvioletred","midnightblue","mintcream","mistyrose","moccasin","navajowhite","navy","oldlace","olive","olivedrab","orange","orangered","orchid","palegoldenrod","palegreen","paleturquoise","palevioletred","papayawhip","peachpuff","peru","pink","plum","powderblue","purple","rebeccapurple","red","rgb","rgba","rosybrown","royalblue","saddlebrown","salmon","sandybrown","seagreen","seashell","sienna","silver","skyblue","slateblue","slategray","slategrey","snow","springgreen","steelblue","tan","teal","thistle","tomato","transparent","turquoise","unset","violet","wheat","white","whitesmoke","yellow","yellowgreen"]},"border-block-end-style":{inherited:false,supports:0,values:["dashed","dotted","double","groove","hidden","inherit","initial","inset","none","outset","ridge","solid","unset"]},"border-block-end-width":{inherited:false,supports:1,values:["-moz-calc","calc","inherit","initial","medium","thick","thin","unset"]},"border-block-start-color":{inherited:false,supports:4,values:["-moz-use-text-color","aliceblue","antiquewhite","aqua","aquamarine","azure","beige","bisque","black","blanchedalmond","blue","blueviolet","brown","burlywood","cadetblue","chartreuse","chocolate","coral","cornflowerblue","cornsilk","crimson","currentColor","cyan","darkblue","darkcyan","darkgoldenrod","darkgray","darkgreen","darkgrey","darkkhaki","darkmagenta","darkolivegreen","darkorange","darkorchid","darkred","darksalmon","darkseagreen","darkslateblue","darkslategray","darkslategrey","darkturquoise","darkviolet","deeppink","deepskyblue","dimgray","dimgrey","dodgerblue","firebrick","floralwhite","forestgreen","fuchsia","gainsboro","ghostwhite","gold","goldenrod","gray","grey","green","greenyellow","honeydew","hotpink","hsl","hsla","indianred","indigo","inherit","initial","ivory","khaki","lavender","lavenderblush","lawngreen","lemonchiffon","lightblue","lightcoral","lightcyan","lightgoldenrodyellow","lightgray","lightgreen","lightgrey","lightpink","lightsalmon","lightseagreen","lightskyblue","lightslategray","lightslategrey","lightsteelblue","lightyellow","lime","limegreen","linen","magenta","maroon","mediumaquamarine","mediumblue","mediumorchid","mediumpurple","mediumseagreen","mediumslateblue","mediumspringgreen","mediumturquoise","mediumvioletred","midnightblue","mintcream","mistyrose","moccasin","navajowhite","navy","oldlace","olive","olivedrab","orange","orangered","orchid","palegoldenrod","palegreen","paleturquoise","palevioletred","papayawhip","peachpuff","peru","pink","plum","powderblue","purple","rebeccapurple","red","rgb","rgba","rosybrown","royalblue","saddlebrown","salmon","sandybrown","seagreen","seashell","sienna","silver","skyblue","slateblue","slategray","slategrey","snow","springgreen","steelblue","tan","teal","thistle","tomato","transparent","turquoise","unset","violet","wheat","white","whitesmoke","yellow","yellowgreen"]},"border-block-start-style":{inherited:false,supports:0,values:["dashed","dotted","double","groove","hidden","inherit","initial","inset","none","outset","ridge","solid","unset"]},"border-block-start-width":{inherited:false,supports:1,values:["-moz-calc","calc","inherit","initial","medium","thick","thin","unset"]},"border-bottom-color":{inherited:false,supports:4,values:["-moz-use-text-color","aliceblue","antiquewhite","aqua","aquamarine","azure","beige","bisque","black","blanchedalmond","blue","blueviolet","brown","burlywood","cadetblue","chartreuse","chocolate","coral","cornflowerblue","cornsilk","crimson","currentColor","cyan","darkblue","darkcyan","darkgoldenrod","darkgray","darkgreen","darkgrey","darkkhaki","darkmagenta","darkolivegreen","darkorange","darkorchid","darkred","darksalmon","darkseagreen","darkslateblue","darkslategray","darkslategrey","darkturquoise","darkviolet","deeppink","deepskyblue","dimgray","dimgrey","dodgerblue","firebrick","floralwhite","forestgreen","fuchsia","gainsboro","ghostwhite","gold","goldenrod","gray","grey","green","greenyellow","honeydew","hotpink","hsl","hsla","indianred","indigo","inherit","initial","ivory","khaki","lavender","lavenderblush","lawngreen","lemonchiffon","lightblue","lightcoral","lightcyan","lightgoldenrodyellow","lightgray","lightgreen","lightgrey","lightpink","lightsalmon","lightseagreen","lightskyblue","lightslategray","lightslategrey","lightsteelblue","lightyellow","lime","limegreen","linen","magenta","maroon","mediumaquamarine","mediumblue","mediumorchid","mediumpurple","mediumseagreen","mediumslateblue","mediumspringgreen","mediumturquoise","mediumvioletred","midnightblue","mintcream","mistyrose","moccasin","navajowhite","navy","oldlace","olive","olivedrab","orange","orangered","orchid","palegoldenrod","palegreen","paleturquoise","palevioletred","papayawhip","peachpuff","peru","pink","plum","powderblue","purple","rebeccapurple","red","rgb","rgba","rosybrown","royalblue","saddlebrown","salmon","sandybrown","seagreen","seashell","sienna","silver","skyblue","slateblue","slategray","slategrey","snow","springgreen","steelblue","tan","teal","thistle","tomato","transparent","turquoise","unset","violet","wheat","white","whitesmoke","yellow","yellowgreen"]},"-moz-border-bottom-colors":{inherited:false,supports:4,values:["inherit","initial","unset"]},"border-bottom-style":{inherited:false,supports:0,values:["dashed","dotted","double","groove","hidden","inherit","initial","inset","none","outset","ridge","solid","unset"]},"border-bottom-width":{inherited:false,supports:1,values:["-moz-calc","calc","inherit","initial","medium","thick","thin","unset"]},"border-collapse":{inherited:true,supports:0,values:["collapse","inherit","initial","separate","unset"]},"border-image-source":{inherited:false,supports:648,values:["-moz-element","-moz-image-rect","-moz-linear-gradient","-moz-radial-gradient","-moz-repeating-linear-gradient","-moz-repeating-radial-gradient","inherit","initial","linear-gradient","none","radial-gradient","repeating-linear-gradient","repeating-radial-gradient","unset","url"]},"border-image-slice":{inherited:false,supports:1026,values:["inherit","initial","unset"]},"border-image-width":{inherited:false,supports:1027,values:["inherit","initial","unset"]},"border-image-outset":{inherited:false,supports:1025,values:["inherit","initial","unset"]},"border-image-repeat":{inherited:false,supports:0,values:["inherit","initial","unset"]},"border-inline-end-color":{inherited:false,supports:4,values:["-moz-use-text-color","aliceblue","antiquewhite","aqua","aquamarine","azure","beige","bisque","black","blanchedalmond","blue","blueviolet","brown","burlywood","cadetblue","chartreuse","chocolate","coral","cornflowerblue","cornsilk","crimson","currentColor","cyan","darkblue","darkcyan","darkgoldenrod","darkgray","darkgreen","darkgrey","darkkhaki","darkmagenta","darkolivegreen","darkorange","darkorchid","darkred","darksalmon","darkseagreen","darkslateblue","darkslategray","darkslategrey","darkturquoise","darkviolet","deeppink","deepskyblue","dimgray","dimgrey","dodgerblue","firebrick","floralwhite","forestgreen","fuchsia","gainsboro","ghostwhite","gold","goldenrod","gray","grey","green","greenyellow","honeydew","hotpink","hsl","hsla","indianred","indigo","inherit","initial","ivory","khaki","lavender","lavenderblush","lawngreen","lemonchiffon","lightblue","lightcoral","lightcyan","lightgoldenrodyellow","lightgray","lightgreen","lightgrey","lightpink","lightsalmon","lightseagreen","lightskyblue","lightslategray","lightslategrey","lightsteelblue","lightyellow","lime","limegreen","linen","magenta","maroon","mediumaquamarine","mediumblue","mediumorchid","mediumpurple","mediumseagreen","mediumslateblue","mediumspringgreen","mediumturquoise","mediumvioletred","midnightblue","mintcream","mistyrose","moccasin","navajowhite","navy","oldlace","olive","olivedrab","orange","orangered","orchid","palegoldenrod","palegreen","paleturquoise","palevioletred","papayawhip","peachpuff","peru","pink","plum","powderblue","purple","rebeccapurple","red","rgb","rgba","rosybrown","royalblue","saddlebrown","salmon","sandybrown","seagreen","seashell","sienna","silver","skyblue","slateblue","slategray","slategrey","snow","springgreen","steelblue","tan","teal","thistle","tomato","transparent","turquoise","unset","violet","wheat","white","whitesmoke","yellow","yellowgreen"]},"border-inline-end-style":{inherited:false,supports:0,values:["dashed","dotted","double","groove","hidden","inherit","initial","inset","none","outset","ridge","solid","unset"]},"border-inline-end-width":{inherited:false,supports:1,values:["-moz-calc","calc","inherit","initial","medium","thick","thin","unset"]},"border-inline-start-color":{inherited:false,supports:4,values:["-moz-use-text-color","aliceblue","antiquewhite","aqua","aquamarine","azure","beige","bisque","black","blanchedalmond","blue","blueviolet","brown","burlywood","cadetblue","chartreuse","chocolate","coral","cornflowerblue","cornsilk","crimson","currentColor","cyan","darkblue","darkcyan","darkgoldenrod","darkgray","darkgreen","darkgrey","darkkhaki","darkmagenta","darkolivegreen","darkorange","darkorchid","darkred","darksalmon","darkseagreen","darkslateblue","darkslategray","darkslategrey","darkturquoise","darkviolet","deeppink","deepskyblue","dimgray","dimgrey","dodgerblue","firebrick","floralwhite","forestgreen","fuchsia","gainsboro","ghostwhite","gold","goldenrod","gray","grey","green","greenyellow","honeydew","hotpink","hsl","hsla","indianred","indigo","inherit","initial","ivory","khaki","lavender","lavenderblush","lawngreen","lemonchiffon","lightblue","lightcoral","lightcyan","lightgoldenrodyellow","lightgray","lightgreen","lightgrey","lightpink","lightsalmon","lightseagreen","lightskyblue","lightslategray","lightslategrey","lightsteelblue","lightyellow","lime","limegreen","linen","magenta","maroon","mediumaquamarine","mediumblue","mediumorchid","mediumpurple","mediumseagreen","mediumslateblue","mediumspringgreen","mediumturquoise","mediumvioletred","midnightblue","mintcream","mistyrose","moccasin","navajowhite","navy","oldlace","olive","olivedrab","orange","orangered","orchid","palegoldenrod","palegreen","paleturquoise","palevioletred","papayawhip","peachpuff","peru","pink","plum","powderblue","purple","rebeccapurple","red","rgb","rgba","rosybrown","royalblue","saddlebrown","salmon","sandybrown","seagreen","seashell","sienna","silver","skyblue","slateblue","slategray","slategrey","snow","springgreen","steelblue","tan","teal","thistle","tomato","transparent","turquoise","unset","violet","wheat","white","whitesmoke","yellow","yellowgreen"]},"border-inline-start-style":{inherited:false,supports:0,values:["dashed","dotted","double","groove","hidden","inherit","initial","inset","none","outset","ridge","solid","unset"]},"border-inline-start-width":{inherited:false,supports:1,values:["-moz-calc","calc","inherit","initial","medium","thick","thin","unset"]},"border-left-color":{inherited:false,supports:4,values:["-moz-use-text-color","aliceblue","antiquewhite","aqua","aquamarine","azure","beige","bisque","black","blanchedalmond","blue","blueviolet","brown","burlywood","cadetblue","chartreuse","chocolate","coral","cornflowerblue","cornsilk","crimson","currentColor","cyan","darkblue","darkcyan","darkgoldenrod","darkgray","darkgreen","darkgrey","darkkhaki","darkmagenta","darkolivegreen","darkorange","darkorchid","darkred","darksalmon","darkseagreen","darkslateblue","darkslategray","darkslategrey","darkturquoise","darkviolet","deeppink","deepskyblue","dimgray","dimgrey","dodgerblue","firebrick","floralwhite","forestgreen","fuchsia","gainsboro","ghostwhite","gold","goldenrod","gray","grey","green","greenyellow","honeydew","hotpink","hsl","hsla","indianred","indigo","inherit","initial","ivory","khaki","lavender","lavenderblush","lawngreen","lemonchiffon","lightblue","lightcoral","lightcyan","lightgoldenrodyellow","lightgray","lightgreen","lightgrey","lightpink","lightsalmon","lightseagreen","lightskyblue","lightslategray","lightslategrey","lightsteelblue","lightyellow","lime","limegreen","linen","magenta","maroon","mediumaquamarine","mediumblue","mediumorchid","mediumpurple","mediumseagreen","mediumslateblue","mediumspringgreen","mediumturquoise","mediumvioletred","midnightblue","mintcream","mistyrose","moccasin","navajowhite","navy","oldlace","olive","olivedrab","orange","orangered","orchid","palegoldenrod","palegreen","paleturquoise","palevioletred","papayawhip","peachpuff","peru","pink","plum","powderblue","purple","rebeccapurple","red","rgb","rgba","rosybrown","royalblue","saddlebrown","salmon","sandybrown","seagreen","seashell","sienna","silver","skyblue","slateblue","slategray","slategrey","snow","springgreen","steelblue","tan","teal","thistle","tomato","transparent","turquoise","unset","violet","wheat","white","whitesmoke","yellow","yellowgreen"]},"-moz-border-left-colors":{inherited:false,supports:4,values:["inherit","initial","unset"]},"border-left-style":{inherited:false,supports:0,values:["dashed","dotted","double","groove","hidden","inherit","initial","inset","none","outset","ridge","solid","unset"]},"border-left-width":{inherited:false,supports:1,values:["-moz-calc","calc","inherit","initial","medium","thick","thin","unset"]},"border-right-color":{inherited:false,supports:4,values:["-moz-use-text-color","aliceblue","antiquewhite","aqua","aquamarine","azure","beige","bisque","black","blanchedalmond","blue","blueviolet","brown","burlywood","cadetblue","chartreuse","chocolate","coral","cornflowerblue","cornsilk","crimson","currentColor","cyan","darkblue","darkcyan","darkgoldenrod","darkgray","darkgreen","darkgrey","darkkhaki","darkmagenta","darkolivegreen","darkorange","darkorchid","darkred","darksalmon","darkseagreen","darkslateblue","darkslategray","darkslategrey","darkturquoise","darkviolet","deeppink","deepskyblue","dimgray","dimgrey","dodgerblue","firebrick","floralwhite","forestgreen","fuchsia","gainsboro","ghostwhite","gold","goldenrod","gray","grey","green","greenyellow","honeydew","hotpink","hsl","hsla","indianred","indigo","inherit","initial","ivory","khaki","lavender","lavenderblush","lawngreen","lemonchiffon","lightblue","lightcoral","lightcyan","lightgoldenrodyellow","lightgray","lightgreen","lightgrey","lightpink","lightsalmon","lightseagreen","lightskyblue","lightslategray","lightslategrey","lightsteelblue","lightyellow","lime","limegreen","linen","magenta","maroon","mediumaquamarine","mediumblue","mediumorchid","mediumpurple","mediumseagreen","mediumslateblue","mediumspringgreen","mediumturquoise","mediumvioletred","midnightblue","mintcream","mistyrose","moccasin","navajowhite","navy","oldlace","olive","olivedrab","orange","orangered","orchid","palegoldenrod","palegreen","paleturquoise","palevioletred","papayawhip","peachpuff","peru","pink","plum","powderblue","purple","rebeccapurple","red","rgb","rgba","rosybrown","royalblue","saddlebrown","salmon","sandybrown","seagreen","seashell","sienna","silver","skyblue","slateblue","slategray","slategrey","snow","springgreen","steelblue","tan","teal","thistle","tomato","transparent","turquoise","unset","violet","wheat","white","whitesmoke","yellow","yellowgreen"]},"-moz-border-right-colors":{inherited:false,supports:4,values:["inherit","initial","unset"]},"border-right-style":{inherited:false,supports:0,values:["dashed","dotted","double","groove","hidden","inherit","initial","inset","none","outset","ridge","solid","unset"]},"border-right-width":{inherited:false,supports:1,values:["-moz-calc","calc","inherit","initial","medium","thick","thin","unset"]},"border-spacing":{inherited:true,supports:1,values:["inherit","initial","unset"]},"border-top-color":{inherited:false,supports:4,values:["-moz-use-text-color","aliceblue","antiquewhite","aqua","aquamarine","azure","beige","bisque","black","blanchedalmond","blue","blueviolet","brown","burlywood","cadetblue","chartreuse","chocolate","coral","cornflowerblue","cornsilk","crimson","currentColor","cyan","darkblue","darkcyan","darkgoldenrod","darkgray","darkgreen","darkgrey","darkkhaki","darkmagenta","darkolivegreen","darkorange","darkorchid","darkred","darksalmon","darkseagreen","darkslateblue","darkslategray","darkslategrey","darkturquoise","darkviolet","deeppink","deepskyblue","dimgray","dimgrey","dodgerblue","firebrick","floralwhite","forestgreen","fuchsia","gainsboro","ghostwhite","gold","goldenrod","gray","grey","green","greenyellow","honeydew","hotpink","hsl","hsla","indianred","indigo","inherit","initial","ivory","khaki","lavender","lavenderblush","lawngreen","lemonchiffon","lightblue","lightcoral","lightcyan","lightgoldenrodyellow","lightgray","lightgreen","lightgrey","lightpink","lightsalmon","lightseagreen","lightskyblue","lightslategray","lightslategrey","lightsteelblue","lightyellow","lime","limegreen","linen","magenta","maroon","mediumaquamarine","mediumblue","mediumorchid","mediumpurple","mediumseagreen","mediumslateblue","mediumspringgreen","mediumturquoise","mediumvioletred","midnightblue","mintcream","mistyrose","moccasin","navajowhite","navy","oldlace","olive","olivedrab","orange","orangered","orchid","palegoldenrod","palegreen","paleturquoise","palevioletred","papayawhip","peachpuff","peru","pink","plum","powderblue","purple","rebeccapurple","red","rgb","rgba","rosybrown","royalblue","saddlebrown","salmon","sandybrown","seagreen","seashell","sienna","silver","skyblue","slateblue","slategray","slategrey","snow","springgreen","steelblue","tan","teal","thistle","tomato","transparent","turquoise","unset","violet","wheat","white","whitesmoke","yellow","yellowgreen"]},"-moz-border-top-colors":{inherited:false,supports:4,values:["inherit","initial","unset"]},"border-top-style":{inherited:false,supports:0,values:["dashed","dotted","double","groove","hidden","inherit","initial","inset","none","outset","ridge","solid","unset"]},"border-top-width":{inherited:false,supports:1,values:["-moz-calc","calc","inherit","initial","medium","thick","thin","unset"]},"border-top-left-radius":{inherited:false,supports:3,values:["inherit","initial","unset"]},"border-top-right-radius":{inherited:false,supports:3,values:["inherit","initial","unset"]},"border-bottom-right-radius":{inherited:false,supports:3,values:["inherit","initial","unset"]},"border-bottom-left-radius":{inherited:false,supports:3,values:["inherit","initial","unset"]},"bottom":{inherited:false,supports:3,values:["-moz-calc","auto","calc","inherit","initial","unset"]},"box-decoration-break":{inherited:false,supports:0,values:["clone","inherit","initial","slice","unset"]},"box-shadow":{inherited:false,supports:5,values:["inherit","initial","unset"]},"box-sizing":{inherited:false,supports:0,values:["border-box","content-box","inherit","initial","padding-box","unset"]},"caption-side":{inherited:true,supports:0,values:["bottom","bottom-outside","inherit","initial","left","right","top","top-outside","unset"]},"clear":{inherited:false,supports:0,values:["both","inherit","initial","inline-end","inline-start","left","none","right","unset"]},"clip":{inherited:false,supports:0,values:["inherit","initial","unset"]},"color":{inherited:true,supports:4,values:["aliceblue","antiquewhite","aqua","aquamarine","azure","beige","bisque","black","blanchedalmond","blue","blueviolet","brown","burlywood","cadetblue","chartreuse","chocolate","coral","cornflowerblue","cornsilk","crimson","currentColor","cyan","darkblue","darkcyan","darkgoldenrod","darkgray","darkgreen","darkgrey","darkkhaki","darkmagenta","darkolivegreen","darkorange","darkorchid","darkred","darksalmon","darkseagreen","darkslateblue","darkslategray","darkslategrey","darkturquoise","darkviolet","deeppink","deepskyblue","dimgray","dimgrey","dodgerblue","firebrick","floralwhite","forestgreen","fuchsia","gainsboro","ghostwhite","gold","goldenrod","gray","grey","green","greenyellow","honeydew","hotpink","hsl","hsla","indianred","indigo","inherit","initial","ivory","khaki","lavender","lavenderblush","lawngreen","lemonchiffon","lightblue","lightcoral","lightcyan","lightgoldenrodyellow","lightgray","lightgreen","lightgrey","lightpink","lightsalmon","lightseagreen","lightskyblue","lightslategray","lightslategrey","lightsteelblue","lightyellow","lime","limegreen","linen","magenta","maroon","mediumaquamarine","mediumblue","mediumorchid","mediumpurple","mediumseagreen","mediumslateblue","mediumspringgreen","mediumturquoise","mediumvioletred","midnightblue","mintcream","mistyrose","moccasin","navajowhite","navy","oldlace","olive","olivedrab","orange","orangered","orchid","palegoldenrod","palegreen","paleturquoise","palevioletred","papayawhip","peachpuff","peru","pink","plum","powderblue","purple","rebeccapurple","red","rgb","rgba","rosybrown","royalblue","saddlebrown","salmon","sandybrown","seagreen","seashell","sienna","silver","skyblue","slateblue","slategray","slategrey","snow","springgreen","steelblue","tan","teal","thistle","tomato","transparent","turquoise","unset","violet","wheat","white","whitesmoke","yellow","yellowgreen"]},"-moz-column-count":{inherited:false,supports:1024,values:["auto","inherit","initial","unset"]},"-moz-column-fill":{inherited:false,supports:0,values:["auto","balance","inherit","initial","unset"]},"-moz-column-width":{inherited:false,supports:1,values:["-moz-calc","auto","calc","inherit","initial","unset"]},"-moz-column-gap":{inherited:false,supports:1,values:["-moz-calc","calc","inherit","initial","normal","unset"]},"-moz-column-rule-color":{inherited:false,supports:4,values:["-moz-use-text-color","aliceblue","antiquewhite","aqua","aquamarine","azure","beige","bisque","black","blanchedalmond","blue","blueviolet","brown","burlywood","cadetblue","chartreuse","chocolate","coral","cornflowerblue","cornsilk","crimson","currentColor","cyan","darkblue","darkcyan","darkgoldenrod","darkgray","darkgreen","darkgrey","darkkhaki","darkmagenta","darkolivegreen","darkorange","darkorchid","darkred","darksalmon","darkseagreen","darkslateblue","darkslategray","darkslategrey","darkturquoise","darkviolet","deeppink","deepskyblue","dimgray","dimgrey","dodgerblue","firebrick","floralwhite","forestgreen","fuchsia","gainsboro","ghostwhite","gold","goldenrod","gray","grey","green","greenyellow","honeydew","hotpink","hsl","hsla","indianred","indigo","inherit","initial","ivory","khaki","lavender","lavenderblush","lawngreen","lemonchiffon","lightblue","lightcoral","lightcyan","lightgoldenrodyellow","lightgray","lightgreen","lightgrey","lightpink","lightsalmon","lightseagreen","lightskyblue","lightslategray","lightslategrey","lightsteelblue","lightyellow","lime","limegreen","linen","magenta","maroon","mediumaquamarine","mediumblue","mediumorchid","mediumpurple","mediumseagreen","mediumslateblue","mediumspringgreen","mediumturquoise","mediumvioletred","midnightblue","mintcream","mistyrose","moccasin","navajowhite","navy","oldlace","olive","olivedrab","orange","orangered","orchid","palegoldenrod","palegreen","paleturquoise","palevioletred","papayawhip","peachpuff","peru","pink","plum","powderblue","purple","rebeccapurple","red","rgb","rgba","rosybrown","royalblue","saddlebrown","salmon","sandybrown","seagreen","seashell","sienna","silver","skyblue","slateblue","slategray","slategrey","snow","springgreen","steelblue","tan","teal","thistle","tomato","transparent","turquoise","unset","violet","wheat","white","whitesmoke","yellow","yellowgreen"]},"-moz-column-rule-style":{inherited:false,supports:0,values:["dashed","dotted","double","groove","hidden","inherit","initial","inset","none","outset","ridge","solid","unset"]},"-moz-column-rule-width":{inherited:false,supports:1,values:["-moz-calc","calc","inherit","initial","medium","thick","thin","unset"]},"contain":{inherited:false,supports:0,values:["inherit","initial","layout","none","paint","strict","style","unset"]},"content":{inherited:false,supports:8,values:["inherit","initial","unset"]},"-moz-control-character-visibility":{inherited:true,supports:0,values:["hidden","inherit","initial","unset","visible"]},"counter-increment":{inherited:false,supports:0,values:["inherit","initial","unset"]},"counter-reset":{inherited:false,supports:0,values:["inherit","initial","unset"]},"cursor":{inherited:true,supports:8,values:["inherit","initial","unset"]},"direction":{inherited:true,supports:0,values:["inherit","initial","ltr","rtl","unset"]},"display":{inherited:false,supports:0,values:["-moz-box","-moz-deck","-moz-grid","-moz-grid-group","-moz-grid-line","-moz-groupbox","-moz-inline-box","-moz-inline-grid","-moz-inline-stack","-moz-popup","-moz-stack","block","contents","flex","grid","inherit","initial","inline","inline-block","inline-flex","inline-grid","inline-table","list-item","none","ruby","ruby-base","ruby-base-container","ruby-text","ruby-text-container","table","table-caption","table-cell","table-column","table-column-group","table-footer-group","table-header-group","table-row","table-row-group","unset"]},"empty-cells":{inherited:true,supports:0,values:["hide","inherit","initial","show","unset"]},"align-content":{inherited:false,supports:0,values:["inherit","initial","unset"]},"align-items":{inherited:false,supports:0,values:["inherit","initial","unset"]},"align-self":{inherited:false,supports:0,values:["inherit","initial","unset"]},"flex-basis":{inherited:false,supports:3,values:["-moz-available","-moz-calc","-moz-fit-content","-moz-max-content","-moz-min-content","auto","calc","inherit","initial","unset"]},"flex-direction":{inherited:false,supports:0,values:["column","column-reverse","inherit","initial","row","row-reverse","unset"]},"flex-grow":{inherited:false,supports:1024,values:["inherit","initial","unset"]},"flex-shrink":{inherited:false,supports:1024,values:["inherit","initial","unset"]},"flex-wrap":{inherited:false,supports:0,values:["inherit","initial","nowrap","unset","wrap","wrap-reverse"]},"order":{inherited:false,supports:1024,values:["inherit","initial","unset"]},"justify-content":{inherited:false,supports:0,values:["inherit","initial","unset"]},"justify-items":{inherited:false,supports:0,values:["inherit","initial","unset"]},"justify-self":{inherited:false,supports:0,values:["inherit","initial","unset"]},"float":{inherited:false,supports:0,values:["inherit","initial","inline-end","inline-start","left","none","right","unset"]},"-moz-float-edge":{inherited:false,supports:0,values:["content-box","inherit","initial","margin-box","unset"]},"font-family":{inherited:true,supports:0,values:["inherit","initial","unset"]},"font-feature-settings":{inherited:true,supports:0,values:["inherit","initial","unset"]},"font-kerning":{inherited:true,supports:0,values:["auto","inherit","initial","none","normal","unset"]},"font-language-override":{inherited:true,supports:0,values:["inherit","initial","normal","unset"]},"font-size":{inherited:true,supports:3,values:["-moz-calc","calc","inherit","initial","large","larger","medium","small","smaller","unset","x-large","x-small","xx-large","xx-small"]},"font-size-adjust":{inherited:true,supports:1024,values:["inherit","initial","none","unset"]},"font-stretch":{inherited:true,supports:0,values:["condensed","expanded","extra-condensed","extra-expanded","inherit","initial","normal","semi-condensed","semi-expanded","ultra-condensed","ultra-expanded","unset"]},"font-style":{inherited:true,supports:0,values:["inherit","initial","italic","normal","oblique","unset"]},"font-synthesis":{inherited:true,supports:0,values:["inherit","initial","unset"]},"font-variant-alternates":{inherited:true,supports:0,values:["inherit","initial","unset"]},"font-variant-caps":{inherited:true,supports:0,values:["all-petite-caps","all-small-caps","inherit","initial","normal","petite-caps","small-caps","titling-caps","unicase","unset"]},"font-variant-east-asian":{inherited:true,supports:0,values:["inherit","initial","unset"]},"font-variant-ligatures":{inherited:true,supports:0,values:["inherit","initial","unset"]},"font-variant-numeric":{inherited:true,supports:0,values:["inherit","initial","unset"]},"font-variant-position":{inherited:true,supports:0,values:["inherit","initial","normal","sub","super","unset"]},"font-weight":{inherited:true,supports:1024,values:["inherit","initial","unset"]},"-moz-force-broken-image-icon":{inherited:false,supports:1024,values:["inherit","initial","unset"]},"grid-auto-flow":{inherited:false,supports:0,values:["inherit","initial","unset"]},"grid-auto-columns":{inherited:false,supports:3,values:["inherit","initial","unset"]},"grid-auto-rows":{inherited:false,supports:3,values:["inherit","initial","unset"]},"grid-template-areas":{inherited:false,supports:0,values:["inherit","initial","unset"]},"grid-template-columns":{inherited:false,supports:3,values:["inherit","initial","unset"]},"grid-template-rows":{inherited:false,supports:3,values:["inherit","initial","unset"]},"grid-column-start":{inherited:false,supports:1024,values:["inherit","initial","unset"]},"grid-column-end":{inherited:false,supports:1024,values:["inherit","initial","unset"]},"grid-row-start":{inherited:false,supports:1024,values:["inherit","initial","unset"]},"grid-row-end":{inherited:false,supports:1024,values:["inherit","initial","unset"]},"grid-column-gap":{inherited:false,supports:1,values:["-moz-calc","calc","inherit","initial","unset"]},"grid-row-gap":{inherited:false,supports:1,values:["-moz-calc","calc","inherit","initial","unset"]},"height":{inherited:false,supports:3,values:["-moz-available","-moz-calc","-moz-fit-content","-moz-max-content","-moz-min-content","auto","calc","inherit","initial","unset"]},"image-orientation":{inherited:true,supports:16,values:["inherit","initial","unset"]},"-moz-image-region":{inherited:true,supports:0,values:["inherit","initial","unset"]},"ime-mode":{inherited:false,supports:0,values:["active","auto","disabled","inactive","inherit","initial","normal","unset"]},"inline-size":{inherited:false,supports:3,values:["-moz-available","-moz-calc","-moz-fit-content","-moz-max-content","-moz-min-content","auto","calc","inherit","initial","unset"]},"left":{inherited:false,supports:3,values:["-moz-calc","auto","calc","inherit","initial","unset"]},"letter-spacing":{inherited:true,supports:1,values:["-moz-calc","calc","inherit","initial","normal","unset"]},"line-height":{inherited:true,supports:1027,values:["-moz-block-height","inherit","initial","normal","unset"]},"list-style-image":{inherited:true,supports:8,values:["inherit","initial","none","unset","url"]},"list-style-position":{inherited:true,supports:0,values:["inherit","initial","inside","outside","unset"]},"list-style-type":{inherited:true,supports:0,values:["inherit","initial","unset"]},"margin-block-end":{inherited:false,supports:3,values:["-moz-calc","auto","calc","inherit","initial","unset"]},"margin-block-start":{inherited:false,supports:3,values:["-moz-calc","auto","calc","inherit","initial","unset"]},"margin-bottom":{inherited:false,supports:3,values:["-moz-calc","auto","calc","inherit","initial","unset"]},"margin-inline-end":{inherited:false,supports:3,values:["-moz-calc","auto","calc","inherit","initial","unset"]},"margin-inline-start":{inherited:false,supports:3,values:["-moz-calc","auto","calc","inherit","initial","unset"]},"margin-left":{inherited:false,supports:3,values:["-moz-calc","auto","calc","inherit","initial","unset"]},"margin-right":{inherited:false,supports:3,values:["-moz-calc","auto","calc","inherit","initial","unset"]},"margin-top":{inherited:false,supports:3,values:["-moz-calc","auto","calc","inherit","initial","unset"]},"marker-offset":{inherited:false,supports:1,values:["-moz-calc","auto","calc","inherit","initial","unset"]},"max-block-size":{inherited:false,supports:3,values:["-moz-calc","calc","inherit","initial","none","unset"]},"max-height":{inherited:false,supports:3,values:["-moz-available","-moz-calc","-moz-fit-content","-moz-max-content","-moz-min-content","calc","inherit","initial","none","unset"]},"max-inline-size":{inherited:false,supports:3,values:["-moz-available","-moz-calc","-moz-fit-content","-moz-max-content","-moz-min-content","calc","inherit","initial","none","unset"]},"max-width":{inherited:false,supports:3,values:["-moz-available","-moz-calc","-moz-fit-content","-moz-max-content","-moz-min-content","calc","inherit","initial","none","unset"]},"min-height":{inherited:false,supports:3,values:["-moz-available","-moz-calc","-moz-fit-content","-moz-max-content","-moz-min-content","auto","calc","inherit","initial","unset"]},"min-block-size":{inherited:false,supports:3,values:["-moz-calc","auto","calc","inherit","initial","unset"]},"min-inline-size":{inherited:false,supports:3,values:["-moz-available","-moz-calc","-moz-fit-content","-moz-max-content","-moz-min-content","auto","calc","inherit","initial","unset"]},"min-width":{inherited:false,supports:3,values:["-moz-available","-moz-calc","-moz-fit-content","-moz-max-content","-moz-min-content","auto","calc","inherit","initial","unset"]},"mix-blend-mode":{inherited:false,supports:0,values:["color","color-burn","color-dodge","darken","difference","exclusion","hard-light","hue","inherit","initial","lighten","luminosity","multiply","normal","overlay","saturation","screen","soft-light","unset"]},"isolation":{inherited:false,supports:0,values:["auto","inherit","initial","isolate","unset"]},"object-fit":{inherited:false,supports:0,values:["contain","cover","fill","inherit","initial","none","scale-down","unset"]},"object-position":{inherited:false,supports:3,values:["inherit","initial","unset"]},"offset-block-end":{inherited:false,supports:3,values:["-moz-calc","auto","calc","inherit","initial","unset"]},"offset-block-start":{inherited:false,supports:3,values:["-moz-calc","auto","calc","inherit","initial","unset"]},"offset-inline-end":{inherited:false,supports:3,values:["-moz-calc","auto","calc","inherit","initial","unset"]},"offset-inline-start":{inherited:false,supports:3,values:["-moz-calc","auto","calc","inherit","initial","unset"]},"opacity":{inherited:false,supports:1024,values:["inherit","initial","unset"]},"-moz-orient":{inherited:false,supports:0,values:["block","horizontal","inherit","initial","inline","unset","vertical"]},"outline-color":{inherited:false,supports:4,values:["-moz-use-text-color","aliceblue","antiquewhite","aqua","aquamarine","azure","beige","bisque","black","blanchedalmond","blue","blueviolet","brown","burlywood","cadetblue","chartreuse","chocolate","coral","cornflowerblue","cornsilk","crimson","currentColor","cyan","darkblue","darkcyan","darkgoldenrod","darkgray","darkgreen","darkgrey","darkkhaki","darkmagenta","darkolivegreen","darkorange","darkorchid","darkred","darksalmon","darkseagreen","darkslateblue","darkslategray","darkslategrey","darkturquoise","darkviolet","deeppink","deepskyblue","dimgray","dimgrey","dodgerblue","firebrick","floralwhite","forestgreen","fuchsia","gainsboro","ghostwhite","gold","goldenrod","gray","grey","green","greenyellow","honeydew","hotpink","hsl","hsla","indianred","indigo","inherit","initial","ivory","khaki","lavender","lavenderblush","lawngreen","lemonchiffon","lightblue","lightcoral","lightcyan","lightgoldenrodyellow","lightgray","lightgreen","lightgrey","lightpink","lightsalmon","lightseagreen","lightskyblue","lightslategray","lightslategrey","lightsteelblue","lightyellow","lime","limegreen","linen","magenta","maroon","mediumaquamarine","mediumblue","mediumorchid","mediumpurple","mediumseagreen","mediumslateblue","mediumspringgreen","mediumturquoise","mediumvioletred","midnightblue","mintcream","mistyrose","moccasin","navajowhite","navy","oldlace","olive","olivedrab","orange","orangered","orchid","palegoldenrod","palegreen","paleturquoise","palevioletred","papayawhip","peachpuff","peru","pink","plum","powderblue","purple","rebeccapurple","red","rgb","rgba","rosybrown","royalblue","saddlebrown","salmon","sandybrown","seagreen","seashell","sienna","silver","skyblue","slateblue","slategray","slategrey","snow","springgreen","steelblue","tan","teal","thistle","tomato","transparent","turquoise","unset","violet","wheat","white","whitesmoke","yellow","yellowgreen"]},"outline-style":{inherited:false,supports:0,values:["auto","dashed","dotted","double","groove","inherit","initial","inset","none","outset","ridge","solid","unset"]},"outline-width":{inherited:false,supports:1,values:["-moz-calc","calc","inherit","initial","medium","thick","thin","unset"]},"outline-offset":{inherited:false,supports:1,values:["-moz-calc","calc","inherit","initial","unset"]},"overflow-x":{inherited:false,supports:0,values:["-moz-hidden-unscrollable","auto","hidden","inherit","initial","scroll","unset","visible"]},"overflow-y":{inherited:false,supports:0,values:["-moz-hidden-unscrollable","auto","hidden","inherit","initial","scroll","unset","visible"]},"padding-block-end":{inherited:false,supports:3,values:["-moz-calc","calc","inherit","initial","unset"]},"padding-block-start":{inherited:false,supports:3,values:["-moz-calc","calc","inherit","initial","unset"]},"padding-bottom":{inherited:false,supports:3,values:["-moz-calc","calc","inherit","initial","unset"]},"padding-inline-end":{inherited:false,supports:3,values:["-moz-calc","calc","inherit","initial","unset"]},"padding-inline-start":{inherited:false,supports:3,values:["-moz-calc","calc","inherit","initial","unset"]},"padding-left":{inherited:false,supports:3,values:["-moz-calc","calc","inherit","initial","unset"]},"padding-right":{inherited:false,supports:3,values:["-moz-calc","calc","inherit","initial","unset"]},"padding-top":{inherited:false,supports:3,values:["-moz-calc","calc","inherit","initial","unset"]},"page-break-after":{inherited:false,supports:0,values:["always","auto","avoid","inherit","initial","left","right","unset"]},"page-break-before":{inherited:false,supports:0,values:["always","auto","avoid","inherit","initial","left","right","unset"]},"page-break-inside":{inherited:false,supports:0,values:["auto","avoid","inherit","initial","unset"]},"paint-order":{inherited:true,supports:0,values:["inherit","initial","unset"]},"pointer-events":{inherited:true,supports:0,values:["all","auto","fill","inherit","initial","none","painted","stroke","unset","visible","visiblefill","visiblepainted","visiblestroke"]},"position":{inherited:false,supports:0,values:["absolute","fixed","inherit","initial","relative","static","sticky","unset"]},"quotes":{inherited:true,supports:0,values:["inherit","initial","unset"]},"resize":{inherited:false,supports:0,values:["both","horizontal","inherit","initial","none","unset","vertical"]},"right":{inherited:false,supports:3,values:["-moz-calc","auto","calc","inherit","initial","unset"]},"ruby-align":{inherited:true,supports:0,values:["center","inherit","initial","space-around","space-between","start","unset"]},"ruby-position":{inherited:true,supports:0,values:["inherit","initial","over","under","unset"]},"scroll-behavior":{inherited:false,supports:0,values:["auto","inherit","initial","smooth","unset"]},"scroll-snap-coordinate":{inherited:false,supports:3,values:["inherit","initial","unset"]},"scroll-snap-destination":{inherited:false,supports:3,values:["inherit","initial","unset"]},"scroll-snap-points-x":{inherited:false,supports:0,values:["inherit","initial","unset"]},"scroll-snap-points-y":{inherited:false,supports:0,values:["inherit","initial","unset"]},"scroll-snap-type-x":{inherited:false,supports:0,values:["inherit","initial","mandatory","none","proximity","unset"]},"scroll-snap-type-y":{inherited:false,supports:0,values:["inherit","initial","mandatory","none","proximity","unset"]},"table-layout":{inherited:false,supports:0,values:["auto","fixed","inherit","initial","unset"]},"text-align":{inherited:true,supports:0,values:["-moz-center","-moz-left","-moz-right","center","end","inherit","initial","justify","left","right","start","unset"]},"-moz-text-align-last":{inherited:true,supports:0,values:["auto","center","end","inherit","initial","justify","left","right","start","unset"]},"text-decoration-color":{inherited:false,supports:4,values:["-moz-use-text-color","aliceblue","antiquewhite","aqua","aquamarine","azure","beige","bisque","black","blanchedalmond","blue","blueviolet","brown","burlywood","cadetblue","chartreuse","chocolate","coral","cornflowerblue","cornsilk","crimson","currentColor","cyan","darkblue","darkcyan","darkgoldenrod","darkgray","darkgreen","darkgrey","darkkhaki","darkmagenta","darkolivegreen","darkorange","darkorchid","darkred","darksalmon","darkseagreen","darkslateblue","darkslategray","darkslategrey","darkturquoise","darkviolet","deeppink","deepskyblue","dimgray","dimgrey","dodgerblue","firebrick","floralwhite","forestgreen","fuchsia","gainsboro","ghostwhite","gold","goldenrod","gray","grey","green","greenyellow","honeydew","hotpink","hsl","hsla","indianred","indigo","inherit","initial","ivory","khaki","lavender","lavenderblush","lawngreen","lemonchiffon","lightblue","lightcoral","lightcyan","lightgoldenrodyellow","lightgray","lightgreen","lightgrey","lightpink","lightsalmon","lightseagreen","lightskyblue","lightslategray","lightslategrey","lightsteelblue","lightyellow","lime","limegreen","linen","magenta","maroon","mediumaquamarine","mediumblue","mediumorchid","mediumpurple","mediumseagreen","mediumslateblue","mediumspringgreen","mediumturquoise","mediumvioletred","midnightblue","mintcream","mistyrose","moccasin","navajowhite","navy","oldlace","olive","olivedrab","orange","orangered","orchid","palegoldenrod","palegreen","paleturquoise","palevioletred","papayawhip","peachpuff","peru","pink","plum","powderblue","purple","rebeccapurple","red","rgb","rgba","rosybrown","royalblue","saddlebrown","salmon","sandybrown","seagreen","seashell","sienna","silver","skyblue","slateblue","slategray","slategrey","snow","springgreen","steelblue","tan","teal","thistle","tomato","transparent","turquoise","unset","violet","wheat","white","whitesmoke","yellow","yellowgreen"]},"text-decoration-line":{inherited:false,supports:0,values:["inherit","initial","unset"]},"text-decoration-style":{inherited:false,supports:0,values:["-moz-none","dashed","dotted","double","inherit","initial","solid","unset","wavy"]},"text-indent":{inherited:true,supports:3,values:["-moz-calc","calc","inherit","initial","unset"]},"text-orientation":{inherited:true,supports:0,values:["inherit","initial","mixed","sideways","sideways-right","unset","upright"]},"text-overflow":{inherited:false,supports:0,values:["inherit","initial","unset"]},"text-shadow":{inherited:true,supports:5,values:["inherit","initial","unset"]},"-moz-text-size-adjust":{inherited:true,supports:0,values:["auto","inherit","initial","none","unset"]},"text-transform":{inherited:true,supports:0,values:["capitalize","full-width","inherit","initial","lowercase","none","unset","uppercase"]},"transform":{inherited:false,supports:0,values:["inherit","initial","unset"]},"transform-box":{inherited:false,supports:0,values:["border-box","fill-box","inherit","initial","unset","view-box"]},"transform-origin":{inherited:false,supports:3,values:["inherit","initial","unset"]},"perspective-origin":{inherited:false,supports:3,values:["inherit","initial","unset"]},"perspective":{inherited:false,supports:1,values:["inherit","initial","none","unset"]},"transform-style":{inherited:false,supports:0,values:["flat","inherit","initial","preserve-3d","unset"]},"backface-visibility":{inherited:false,supports:0,values:["hidden","inherit","initial","unset","visible"]},"top":{inherited:false,supports:3,values:["-moz-calc","auto","calc","inherit","initial","unset"]},"transition-delay":{inherited:false,supports:64,values:["inherit","initial","unset"]},"transition-duration":{inherited:false,supports:64,values:["inherit","initial","unset"]},"transition-property":{inherited:false,supports:0,values:["all","inherit","initial","none","unset"]},"transition-timing-function":{inherited:false,supports:256,values:["cubic-bezier","ease","ease-in","ease-in-out","ease-out","inherit","initial","linear","step-end","step-start","steps","unset"]},"unicode-bidi":{inherited:false,supports:0,values:["-moz-isolate","-moz-isolate-override","-moz-plaintext","bidi-override","embed","inherit","initial","normal","unset"]},"-moz-user-focus":{inherited:true,supports:0,values:["ignore","inherit","initial","none","normal","select-after","select-all","select-before","select-menu","select-same","unset"]},"-moz-user-input":{inherited:true,supports:0,values:["auto","disabled","enabled","inherit","initial","none","unset"]},"-moz-user-modify":{inherited:true,supports:0,values:["inherit","initial","read-only","read-write","unset","write-only"]},"-moz-user-select":{inherited:false,supports:0,values:["-moz-all","-moz-none","-moz-text","all","auto","element","elements","inherit","initial","none","text","toggle","tri-state","unset"]},"vertical-align":{inherited:false,supports:3,values:["-moz-calc","-moz-middle-with-baseline","baseline","bottom","calc","inherit","initial","middle","sub","super","text-bottom","text-top","top","unset"]},"visibility":{inherited:true,supports:0,values:["collapse","hidden","inherit","initial","unset","visible"]},"white-space":{inherited:true,supports:0,values:["-moz-pre-space","inherit","initial","normal","nowrap","pre","pre-line","pre-wrap","unset"]},"width":{inherited:false,supports:3,values:["-moz-available","-moz-calc","-moz-fit-content","-moz-max-content","-moz-min-content","auto","calc","inherit","initial","unset"]},"-moz-window-dragging":{inherited:true,supports:0,values:["drag","inherit","initial","no-drag","unset"]},"word-break":{inherited:true,supports:0,values:["break-all","inherit","initial","keep-all","normal","unset"]},"word-spacing":{inherited:true,supports:3,values:["-moz-calc","calc","inherit","initial","normal","unset"]},"word-wrap":{inherited:true,supports:0,values:["break-word","inherit","initial","normal","unset"]},"hyphens":{inherited:true,supports:0,values:["auto","inherit","initial","manual","none","unset"]},"writing-mode":{inherited:true,supports:0,values:["horizontal-tb","inherit","initial","lr","lr-tb","rl","rl-tb","sideways-lr","sideways-rl","tb","tb-rl","unset","vertical-lr","vertical-rl"]},"z-index":{inherited:false,supports:1024,values:["auto","inherit","initial","unset"]},"-moz-box-align":{inherited:false,supports:0,values:["baseline","center","end","inherit","initial","start","stretch","unset"]},"-moz-box-direction":{inherited:false,supports:0,values:["inherit","initial","normal","reverse","unset"]},"-moz-box-flex":{inherited:false,supports:1024,values:["inherit","initial","unset"]},"-moz-box-orient":{inherited:false,supports:0,values:["block-axis","horizontal","inherit","initial","inline-axis","unset","vertical"]},"-moz-box-pack":{inherited:false,supports:0,values:["center","end","inherit","initial","justify","start","unset"]},"-moz-box-ordinal-group":{inherited:false,supports:1024,values:["inherit","initial","unset"]},"-moz-stack-sizing":{inherited:false,supports:0,values:["ignore","inherit","initial","stretch-to-fit","unset"]},"clip-path":{inherited:false,supports:8,values:["inherit","initial","unset"]},"clip-rule":{inherited:true,supports:0,values:["evenodd","inherit","initial","nonzero","unset"]},"color-interpolation":{inherited:true,supports:0,values:["auto","inherit","initial","linearrgb","srgb","unset"]},"color-interpolation-filters":{inherited:true,supports:0,values:["auto","inherit","initial","linearrgb","srgb","unset"]},"dominant-baseline":{inherited:false,supports:0,values:["alphabetic","auto","central","hanging","ideographic","inherit","initial","mathematical","middle","no-change","reset-size","text-after-edge","text-before-edge","unset","use-script"]},"fill":{inherited:true,supports:12,values:["inherit","initial","unset"]},"fill-opacity":{inherited:true,supports:1024,values:["inherit","initial","unset"]},"fill-rule":{inherited:true,supports:0,values:["evenodd","inherit","initial","nonzero","unset"]},"filter":{inherited:false,supports:8,values:["inherit","initial","unset"]},"flood-color":{inherited:false,supports:4,values:["aliceblue","antiquewhite","aqua","aquamarine","azure","beige","bisque","black","blanchedalmond","blue","blueviolet","brown","burlywood","cadetblue","chartreuse","chocolate","coral","cornflowerblue","cornsilk","crimson","currentColor","cyan","darkblue","darkcyan","darkgoldenrod","darkgray","darkgreen","darkgrey","darkkhaki","darkmagenta","darkolivegreen","darkorange","darkorchid","darkred","darksalmon","darkseagreen","darkslateblue","darkslategray","darkslategrey","darkturquoise","darkviolet","deeppink","deepskyblue","dimgray","dimgrey","dodgerblue","firebrick","floralwhite","forestgreen","fuchsia","gainsboro","ghostwhite","gold","goldenrod","gray","grey","green","greenyellow","honeydew","hotpink","hsl","hsla","indianred","indigo","inherit","initial","ivory","khaki","lavender","lavenderblush","lawngreen","lemonchiffon","lightblue","lightcoral","lightcyan","lightgoldenrodyellow","lightgray","lightgreen","lightgrey","lightpink","lightsalmon","lightseagreen","lightskyblue","lightslategray","lightslategrey","lightsteelblue","lightyellow","lime","limegreen","linen","magenta","maroon","mediumaquamarine","mediumblue","mediumorchid","mediumpurple","mediumseagreen","mediumslateblue","mediumspringgreen","mediumturquoise","mediumvioletred","midnightblue","mintcream","mistyrose","moccasin","navajowhite","navy","oldlace","olive","olivedrab","orange","orangered","orchid","palegoldenrod","palegreen","paleturquoise","palevioletred","papayawhip","peachpuff","peru","pink","plum","powderblue","purple","rebeccapurple","red","rgb","rgba","rosybrown","royalblue","saddlebrown","salmon","sandybrown","seagreen","seashell","sienna","silver","skyblue","slateblue","slategray","slategrey","snow","springgreen","steelblue","tan","teal","thistle","tomato","transparent","turquoise","unset","violet","wheat","white","whitesmoke","yellow","yellowgreen"]},"flood-opacity":{inherited:false,supports:1024,values:["inherit","initial","unset"]},"image-rendering":{inherited:true,supports:0,values:["-moz-crisp-edges","auto","inherit","initial","optimizequality","optimizespeed","unset"]},"lighting-color":{inherited:false,supports:4,values:["aliceblue","antiquewhite","aqua","aquamarine","azure","beige","bisque","black","blanchedalmond","blue","blueviolet","brown","burlywood","cadetblue","chartreuse","chocolate","coral","cornflowerblue","cornsilk","crimson","currentColor","cyan","darkblue","darkcyan","darkgoldenrod","darkgray","darkgreen","darkgrey","darkkhaki","darkmagenta","darkolivegreen","darkorange","darkorchid","darkred","darksalmon","darkseagreen","darkslateblue","darkslategray","darkslategrey","darkturquoise","darkviolet","deeppink","deepskyblue","dimgray","dimgrey","dodgerblue","firebrick","floralwhite","forestgreen","fuchsia","gainsboro","ghostwhite","gold","goldenrod","gray","grey","green","greenyellow","honeydew","hotpink","hsl","hsla","indianred","indigo","inherit","initial","ivory","khaki","lavender","lavenderblush","lawngreen","lemonchiffon","lightblue","lightcoral","lightcyan","lightgoldenrodyellow","lightgray","lightgreen","lightgrey","lightpink","lightsalmon","lightseagreen","lightskyblue","lightslategray","lightslategrey","lightsteelblue","lightyellow","lime","limegreen","linen","magenta","maroon","mediumaquamarine","mediumblue","mediumorchid","mediumpurple","mediumseagreen","mediumslateblue","mediumspringgreen","mediumturquoise","mediumvioletred","midnightblue","mintcream","mistyrose","moccasin","navajowhite","navy","oldlace","olive","olivedrab","orange","orangered","orchid","palegoldenrod","palegreen","paleturquoise","palevioletred","papayawhip","peachpuff","peru","pink","plum","powderblue","purple","rebeccapurple","red","rgb","rgba","rosybrown","royalblue","saddlebrown","salmon","sandybrown","seagreen","seashell","sienna","silver","skyblue","slateblue","slategray","slategrey","snow","springgreen","steelblue","tan","teal","thistle","tomato","transparent","turquoise","unset","violet","wheat","white","whitesmoke","yellow","yellowgreen"]},"marker-end":{inherited:true,supports:8,values:["inherit","initial","none","unset","url"]},"marker-mid":{inherited:true,supports:8,values:["inherit","initial","none","unset","url"]},"marker-start":{inherited:true,supports:8,values:["inherit","initial","none","unset","url"]},"mask":{inherited:false,supports:8,values:["inherit","initial","none","unset","url"]},"mask-type":{inherited:false,supports:0,values:["alpha","inherit","initial","luminance","unset"]},"shape-rendering":{inherited:true,supports:0,values:["auto","crispedges","geometricprecision","inherit","initial","optimizespeed","unset"]},"stop-color":{inherited:false,supports:4,values:["aliceblue","antiquewhite","aqua","aquamarine","azure","beige","bisque","black","blanchedalmond","blue","blueviolet","brown","burlywood","cadetblue","chartreuse","chocolate","coral","cornflowerblue","cornsilk","crimson","currentColor","cyan","darkblue","darkcyan","darkgoldenrod","darkgray","darkgreen","darkgrey","darkkhaki","darkmagenta","darkolivegreen","darkorange","darkorchid","darkred","darksalmon","darkseagreen","darkslateblue","darkslategray","darkslategrey","darkturquoise","darkviolet","deeppink","deepskyblue","dimgray","dimgrey","dodgerblue","firebrick","floralwhite","forestgreen","fuchsia","gainsboro","ghostwhite","gold","goldenrod","gray","grey","green","greenyellow","honeydew","hotpink","hsl","hsla","indianred","indigo","inherit","initial","ivory","khaki","lavender","lavenderblush","lawngreen","lemonchiffon","lightblue","lightcoral","lightcyan","lightgoldenrodyellow","lightgray","lightgreen","lightgrey","lightpink","lightsalmon","lightseagreen","lightskyblue","lightslategray","lightslategrey","lightsteelblue","lightyellow","lime","limegreen","linen","magenta","maroon","mediumaquamarine","mediumblue","mediumorchid","mediumpurple","mediumseagreen","mediumslateblue","mediumspringgreen","mediumturquoise","mediumvioletred","midnightblue","mintcream","mistyrose","moccasin","navajowhite","navy","oldlace","olive","olivedrab","orange","orangered","orchid","palegoldenrod","palegreen","paleturquoise","palevioletred","papayawhip","peachpuff","peru","pink","plum","powderblue","purple","rebeccapurple","red","rgb","rgba","rosybrown","royalblue","saddlebrown","salmon","sandybrown","seagreen","seashell","sienna","silver","skyblue","slateblue","slategray","slategrey","snow","springgreen","steelblue","tan","teal","thistle","tomato","transparent","turquoise","unset","violet","wheat","white","whitesmoke","yellow","yellowgreen"]},"stop-opacity":{inherited:false,supports:1024,values:["inherit","initial","unset"]},"stroke":{inherited:true,supports:12,values:["inherit","initial","unset"]},"stroke-dasharray":{inherited:true,supports:1027,values:["inherit","initial","unset"]},"stroke-dashoffset":{inherited:true,supports:1027,values:["inherit","initial","unset"]},"stroke-linecap":{inherited:true,supports:0,values:["butt","inherit","initial","round","square","unset"]},"stroke-linejoin":{inherited:true,supports:0,values:["bevel","inherit","initial","miter","round","unset"]},"stroke-miterlimit":{inherited:true,supports:1024,values:["inherit","initial","unset"]},"stroke-opacity":{inherited:true,supports:1024,values:["inherit","initial","unset"]},"stroke-width":{inherited:true,supports:1027,values:["inherit","initial","unset"]},"text-anchor":{inherited:true,supports:0,values:["end","inherit","initial","middle","start","unset"]},"text-rendering":{inherited:true,supports:0,values:["auto","geometricprecision","inherit","initial","optimizelegibility","optimizespeed","unset"]},"vector-effect":{inherited:false,supports:0,values:["inherit","initial","non-scaling-stroke","none","unset"]},"will-change":{inherited:false,supports:0,values:["inherit","initial","unset"]},"-moz-outline-radius":{subproperties:["-moz-outline-radius-topleft","-moz-outline-radius-topright","-moz-outline-radius-bottomright","-moz-outline-radius-bottomleft"],inherited:false,supports:3,values:["inherit","initial","unset"]},"all":{subproperties:["-moz-appearance","-moz-outline-radius-topleft","-moz-outline-radius-topright","-moz-outline-radius-bottomright","-moz-outline-radius-bottomleft","-moz-tab-size","-x-system-font","animation-delay","animation-direction","animation-duration","animation-fill-mode","animation-iteration-count","animation-name","animation-play-state","animation-timing-function","background-attachment","background-clip","background-color","background-image","background-blend-mode","background-origin","background-position","background-repeat","background-size","-moz-binding","block-size","border-block-end-color","border-block-end-style","border-block-end-width","border-block-start-color","border-block-start-style","border-block-start-width","border-bottom-color","-moz-border-bottom-colors","border-bottom-style","border-bottom-width","border-collapse","border-image-source","border-image-slice","border-image-width","border-image-outset","border-image-repeat","border-inline-end-color","border-inline-end-style","border-inline-end-width","border-inline-start-color","border-inline-start-style","border-inline-start-width","border-left-color","-moz-border-left-colors","border-left-style","border-left-width","border-right-color","-moz-border-right-colors","border-right-style","border-right-width","border-spacing","border-top-color","-moz-border-top-colors","border-top-style","border-top-width","border-top-left-radius","border-top-right-radius","border-bottom-right-radius","border-bottom-left-radius","bottom","box-decoration-break","box-shadow","box-sizing","caption-side","clear","clip","color","-moz-column-count","-moz-column-fill","-moz-column-width","-moz-column-gap","-moz-column-rule-color","-moz-column-rule-style","-moz-column-rule-width","contain","content","-moz-control-character-visibility","counter-increment","counter-reset","cursor","display","empty-cells","align-content","align-items","align-self","flex-basis","flex-direction","flex-grow","flex-shrink","flex-wrap","order","justify-content","justify-items","justify-self","float","-moz-float-edge","font-family","font-feature-settings","font-kerning","font-language-override","font-size","font-size-adjust","-moz-osx-font-smoothing","font-stretch","font-style","font-synthesis","font-variant-alternates","font-variant-caps","font-variant-east-asian","font-variant-ligatures","font-variant-numeric","font-variant-position","font-weight","-moz-force-broken-image-icon","grid-auto-flow","grid-auto-columns","grid-auto-rows","grid-template-areas","grid-template-columns","grid-template-rows","grid-column-start","grid-column-end","grid-row-start","grid-row-end","grid-column-gap","grid-row-gap","height","image-orientation","-moz-image-region","ime-mode","inline-size","left","letter-spacing","line-height","list-style-image","list-style-position","list-style-type","margin-block-end","margin-block-start","margin-bottom","margin-inline-end","margin-inline-start","margin-left","margin-right","margin-top","marker-offset","max-block-size","max-height","max-inline-size","max-width","-moz-min-font-size-ratio","min-height","min-block-size","min-inline-size","min-width","mix-blend-mode","isolation","object-fit","object-position","offset-block-end","offset-block-start","offset-inline-end","offset-inline-start","opacity","-moz-orient","outline-color","outline-style","outline-width","outline-offset","overflow-clip-box","overflow-x","overflow-y","padding-block-end","padding-block-start","padding-bottom","padding-inline-end","padding-inline-start","padding-left","padding-right","padding-top","page-break-after","page-break-before","page-break-inside","paint-order","pointer-events","position","quotes","resize","right","ruby-align","ruby-position","scroll-behavior","scroll-snap-coordinate","scroll-snap-destination","scroll-snap-points-x","scroll-snap-points-y","scroll-snap-type-x","scroll-snap-type-y","table-layout","text-align","-moz-text-align-last","text-combine-upright","text-decoration-color","text-decoration-line","text-decoration-style","text-indent","text-orientation","text-overflow","text-shadow","-moz-text-size-adjust","text-transform","transform","transform-box","transform-origin","perspective-origin","perspective","transform-style","backface-visibility","top","-moz-top-layer","touch-action","transition-delay","transition-duration","transition-property","transition-timing-function","-moz-user-focus","-moz-user-input","-moz-user-modify","-moz-user-select","vertical-align","visibility","white-space","width","-moz-window-dragging","-moz-window-shadow","word-break","word-spacing","word-wrap","hyphens","writing-mode","z-index","-moz-box-align","-moz-box-direction","-moz-box-flex","-moz-box-orient","-moz-box-pack","-moz-box-ordinal-group","-moz-stack-sizing","clip-path","clip-rule","color-interpolation","color-interpolation-filters","dominant-baseline","fill","fill-opacity","fill-rule","filter","flood-color","flood-opacity","image-rendering","lighting-color","marker-end","marker-mid","marker-start","mask","mask-type","shape-rendering","stop-color","stop-opacity","stroke","stroke-dasharray","stroke-dashoffset","stroke-linecap","stroke-linejoin","stroke-miterlimit","stroke-opacity","stroke-width","text-anchor","text-rendering","vector-effect","will-change"],inherited:false,supports:2015,values:["-moz-all","-moz-available","-moz-block-height","-moz-box","-moz-calc","-moz-center","-moz-crisp-edges","-moz-deck","-moz-element","-moz-fit-content","-moz-grid","-moz-grid-group","-moz-grid-line","-moz-groupbox","-moz-gtk-info-bar","-moz-hidden-unscrollable","-moz-image-rect","-moz-inline-box","-moz-inline-grid","-moz-inline-stack","-moz-left","-moz-linear-gradient","-moz-mac-disclosure-button-closed","-moz-mac-disclosure-button-open","-moz-mac-fullscreen-button","-moz-mac-help-button","-moz-mac-vibrancy-dark","-moz-mac-vibrancy-light","-moz-max-content","-moz-middle-with-baseline","-moz-min-content","-moz-none","-moz-popup","-moz-pre-space","-moz-radial-gradient","-moz-repeating-linear-gradient","-moz-repeating-radial-gradient","-moz-right","-moz-stack","-moz-text","-moz-use-text-color","-moz-win-borderless-glass","-moz-win-browsertabbar-toolbox","-moz-win-communications-toolbox","-moz-win-exclude-glass","-moz-win-glass","-moz-win-media-toolbox","-moz-window-button-box","-moz-window-button-box-maximized","-moz-window-button-close","-moz-window-button-maximize","-moz-window-button-minimize","-moz-window-button-restore","-moz-window-frame-bottom","-moz-window-frame-left","-moz-window-frame-right","-moz-window-titlebar","-moz-window-titlebar-maximized","absolute","active","aliceblue","all","all-petite-caps","all-small-caps","alpha","alphabetic","alternate","alternate-reverse","always","antiquewhite","aqua","aquamarine","auto","avoid","azure","backwards","balance","baseline","beige","bevel","bisque","black","blanchedalmond","block","block-axis","blue","blueviolet","border-box","both","bottom","bottom-outside","break-all","break-word","brown","burlywood","butt","button","button-arrow-down","button-arrow-next","button-arrow-previous","button-arrow-up","button-bevel","button-focus","cadetblue","calc","capitalize","caret","center","central","chartreuse","checkbox","checkbox-container","checkbox-label","checkmenuitem","chocolate","clone","collapse","color","color-burn","color-dodge","column","column-reverse","condensed","contain","content-box","contents","coral","cornflowerblue","cornsilk","cover","crimson","crispedges","cubic-bezier","currentColor","cyan","darkblue","darkcyan","darken","darkgoldenrod","darkgray","darkgreen","darkgrey","darkkhaki","darkmagenta","darkolivegreen","darkorange","darkorchid","darkred","darksalmon","darkseagreen","darkslateblue","darkslategray","darkslategrey","darkturquoise","darkviolet","dashed","deeppink","deepskyblue","dialog","difference","dimgray","dimgrey","disabled","dodgerblue","dotted","double","drag","dualbutton","ease","ease-in","ease-in-out","ease-out","element","elements","enabled","end","evenodd","exclusion","expanded","extra-condensed","extra-expanded","fill","fill-box","firebrick","fixed","flat","flex","floralwhite","forestgreen","forwards","fuchsia","full-width","gainsboro","geometricprecision","ghostwhite","gold","goldenrod","gray","grey","green","greenyellow","grid","groove","groupbox","hanging","hard-light","hidden","hide","honeydew","horizontal","horizontal-tb","hotpink","hsl","hsla","hue","ideographic","ignore","inactive","indianred","indigo","infinite","inherit","initial","inline","inline-axis","inline-block","inline-end","inline-flex","inline-grid","inline-start","inline-table","inset","inside","isolate","italic","ivory","justify","keep-all","khaki","large","larger","lavender","lavenderblush","lawngreen","layout","left","lemonchiffon","lightblue","lightcoral","lightcyan","lighten","lightgoldenrodyellow","lightgray","lightgreen","lightgrey","lightpink","lightsalmon","lightseagreen","lightskyblue","lightslategray","lightslategrey","lightsteelblue","lightyellow","lime","limegreen","linear","linear-gradient","linearrgb","linen","list-item","listbox","listitem","local","lowercase","lr","lr-tb","luminance","luminosity","magenta","mandatory","manual","margin-box","maroon","mathematical","medium","mediumaquamarine","mediumblue","mediumorchid","mediumpurple","mediumseagreen","mediumslateblue","mediumspringgreen","mediumturquoise","mediumvioletred","menuarrow","menubar","menucheckbox","menuimage","menuitem","menuitemtext","menulist","menulist-button","menulist-text","menulist-textfield","menupopup","menuradio","menuseparator","meterbar","meterchunk","middle","midnightblue","mintcream","mistyrose","miter","mixed","moccasin","multiply","navajowhite","navy","no-change","no-drag","no-repeat","non-scaling-stroke","none","nonzero","normal","nowrap","number-input","oblique","oldlace","olive","olivedrab","optimizelegibility","optimizequality","optimizespeed","orange","orangered","orchid","outset","outside","over","overlay","padding-box","paint","painted","palegoldenrod","palegreen","paleturquoise","palevioletred","papayawhip","paused","peachpuff","peru","petite-caps","pink","plum","powderblue","pre","pre-line","pre-wrap","preserve-3d","progressbar","progressbar-vertical","progresschunk","progresschunk-vertical","proximity","purple","radial-gradient","radio","radio-container","radio-label","radiomenuitem","range","range-thumb","read-only","read-write","rebeccapurple","red","relative","repeat","repeat-x","repeat-y","repeating-linear-gradient","repeating-radial-gradient","reset-size","resizer","resizerpanel","reverse","rgb","rgba","ridge","right","rl","rl-tb","rosybrown","round","row","row-reverse","royalblue","ruby","ruby-base","ruby-base-container","ruby-text","ruby-text-container","running","saddlebrown","salmon","sandybrown","saturation","scale-down","scale-horizontal","scale-vertical","scalethumb-horizontal","scalethumb-vertical","scalethumbend","scalethumbstart","scalethumbtick","screen","scroll","scrollbar","scrollbar-small","scrollbarbutton-down","scrollbarbutton-left","scrollbarbutton-right","scrollbarbutton-up","scrollbarthumb-horizontal","scrollbarthumb-vertical","scrollbartrack-horizontal","scrollbartrack-vertical","seagreen","searchfield","seashell","select-after","select-all","select-before","select-menu","select-same","semi-condensed","semi-expanded","separate","separator","show","sideways","sideways-lr","sideways-right","sideways-rl","sienna","silver","skyblue","slateblue","slategray","slategrey","slice","small","small-caps","smaller","smooth","snow","soft-light","solid","space-around","space-between","spinner","spinner-downbutton","spinner-textfield","spinner-upbutton","splitter","springgreen","square","srgb","start","static","statusbar","statusbarpanel","steelblue","step-end","step-start","steps","sticky","stretch","stretch-to-fit","strict","stroke","style","sub","super","tab","tab-scroll-arrow-back","tab-scroll-arrow-forward","table","table-caption","table-cell","table-column","table-column-group","table-footer-group","table-header-group","table-row","table-row-group","tabpanel","tabpanels","tan","tb","tb-rl","teal","text","text-after-edge","text-before-edge","text-bottom","text-top","textfield","textfield-multiline","thick","thin","thistle","titling-caps","toggle","tomato","toolbar","toolbarbutton","toolbarbutton-dropdown","toolbargripper","toolbox","tooltip","top","top-outside","transparent","treeheader","treeheadercell","treeheadersortarrow","treeitem","treeline","treetwisty","treetwistyopen","treeview","tri-state","turquoise","ultra-condensed","ultra-expanded","under","unicase","unset","uppercase","upright","url","use-script","vertical","vertical-lr","vertical-rl","view-box","violet","visible","visiblefill","visiblepainted","visiblestroke","wavy","wheat","white","whitesmoke","window","wrap","wrap-reverse","write-only","x-large","x-small","xx-large","xx-small","yellow","yellowgreen"]},"animation":{subproperties:["animation-duration","animation-timing-function","animation-delay","animation-direction","animation-fill-mode","animation-iteration-count","animation-play-state","animation-name"],inherited:false,supports:1344,values:["alternate","alternate-reverse","backwards","both","cubic-bezier","ease","ease-in","ease-in-out","ease-out","forwards","infinite","inherit","initial","linear","none","normal","paused","reverse","running","step-end","step-start","steps","unset"]},"background":{subproperties:["background-color","background-image","background-repeat","background-attachment","background-position","background-clip","background-origin","background-size"],inherited:false,supports:655,values:["-moz-element","-moz-image-rect","-moz-linear-gradient","-moz-radial-gradient","-moz-repeating-linear-gradient","-moz-repeating-radial-gradient","aliceblue","antiquewhite","aqua","aquamarine","azure","beige","bisque","black","blanchedalmond","blue","blueviolet","border-box","brown","burlywood","cadetblue","chartreuse","chocolate","content-box","coral","cornflowerblue","cornsilk","crimson","currentColor","cyan","darkblue","darkcyan","darkgoldenrod","darkgray","darkgreen","darkgrey","darkkhaki","darkmagenta","darkolivegreen","darkorange","darkorchid","darkred","darksalmon","darkseagreen","darkslateblue","darkslategray","darkslategrey","darkturquoise","darkviolet","deeppink","deepskyblue","dimgray","dimgrey","dodgerblue","firebrick","fixed","floralwhite","forestgreen","fuchsia","gainsboro","ghostwhite","gold","goldenrod","gray","grey","green","greenyellow","honeydew","hotpink","hsl","hsla","indianred","indigo","inherit","initial","ivory","khaki","lavender","lavenderblush","lawngreen","lemonchiffon","lightblue","lightcoral","lightcyan","lightgoldenrodyellow","lightgray","lightgreen","lightgrey","lightpink","lightsalmon","lightseagreen","lightskyblue","lightslategray","lightslategrey","lightsteelblue","lightyellow","lime","limegreen","linear-gradient","linen","local","magenta","maroon","mediumaquamarine","mediumblue","mediumorchid","mediumpurple","mediumseagreen","mediumslateblue","mediumspringgreen","mediumturquoise","mediumvioletred","midnightblue","mintcream","mistyrose","moccasin","navajowhite","navy","no-repeat","none","oldlace","olive","olivedrab","orange","orangered","orchid","padding-box","palegoldenrod","palegreen","paleturquoise","palevioletred","papayawhip","peachpuff","peru","pink","plum","powderblue","purple","radial-gradient","rebeccapurple","red","repeat","repeat-x","repeat-y","repeating-linear-gradient","repeating-radial-gradient","rgb","rgba","rosybrown","royalblue","saddlebrown","salmon","sandybrown","scroll","seagreen","seashell","sienna","silver","skyblue","slateblue","slategray","slategrey","snow","springgreen","steelblue","tan","teal","thistle","tomato","transparent","turquoise","unset","url","violet","wheat","white","whitesmoke","yellow","yellowgreen"]},"border":{subproperties:["border-top-width","border-right-width","border-bottom-width","border-left-width","border-top-style","border-right-style","border-bottom-style","border-left-style","border-top-color","border-right-color","border-bottom-color","border-left-color","-moz-border-top-colors","-moz-border-right-colors","-moz-border-bottom-colors","-moz-border-left-colors","border-image-source","border-image-slice","border-image-width","border-image-outset","border-image-repeat"],inherited:false,supports:5,values:["-moz-calc","-moz-element","-moz-image-rect","-moz-linear-gradient","-moz-radial-gradient","-moz-repeating-linear-gradient","-moz-repeating-radial-gradient","-moz-use-text-color","aliceblue","antiquewhite","aqua","aquamarine","azure","beige","bisque","black","blanchedalmond","blue","blueviolet","brown","burlywood","cadetblue","calc","chartreuse","chocolate","coral","cornflowerblue","cornsilk","crimson","currentColor","cyan","darkblue","darkcyan","darkgoldenrod","darkgray","darkgreen","darkgrey","darkkhaki","darkmagenta","darkolivegreen","darkorange","darkorchid","darkred","darksalmon","darkseagreen","darkslateblue","darkslategray","darkslategrey","darkturquoise","darkviolet","dashed","deeppink","deepskyblue","dimgray","dimgrey","dodgerblue","dotted","double","firebrick","floralwhite","forestgreen","fuchsia","gainsboro","ghostwhite","gold","goldenrod","gray","grey","green","greenyellow","groove","hidden","honeydew","hotpink","hsl","hsla","indianred","indigo","inherit","initial","inset","ivory","khaki","lavender","lavenderblush","lawngreen","lemonchiffon","lightblue","lightcoral","lightcyan","lightgoldenrodyellow","lightgray","lightgreen","lightgrey","lightpink","lightsalmon","lightseagreen","lightskyblue","lightslategray","lightslategrey","lightsteelblue","lightyellow","lime","limegreen","linear-gradient","linen","magenta","maroon","medium","mediumaquamarine","mediumblue","mediumorchid","mediumpurple","mediumseagreen","mediumslateblue","mediumspringgreen","mediumturquoise","mediumvioletred","midnightblue","mintcream","mistyrose","moccasin","navajowhite","navy","none","oldlace","olive","olivedrab","orange","orangered","orchid","outset","palegoldenrod","palegreen","paleturquoise","palevioletred","papayawhip","peachpuff","peru","pink","plum","powderblue","purple","radial-gradient","rebeccapurple","red","repeating-linear-gradient","repeating-radial-gradient","rgb","rgba","ridge","rosybrown","royalblue","saddlebrown","salmon","sandybrown","seagreen","seashell","sienna","silver","skyblue","slateblue","slategray","slategrey","snow","solid","springgreen","steelblue","tan","teal","thick","thin","thistle","tomato","transparent","turquoise","unset","url","violet","wheat","white","whitesmoke","yellow","yellowgreen"]},"border-block-end":{subproperties:["border-block-end-width","border-block-end-style","border-block-end-color"],inherited:false,supports:5,values:["-moz-calc","-moz-use-text-color","aliceblue","antiquewhite","aqua","aquamarine","azure","beige","bisque","black","blanchedalmond","blue","blueviolet","brown","burlywood","cadetblue","calc","chartreuse","chocolate","coral","cornflowerblue","cornsilk","crimson","currentColor","cyan","darkblue","darkcyan","darkgoldenrod","darkgray","darkgreen","darkgrey","darkkhaki","darkmagenta","darkolivegreen","darkorange","darkorchid","darkred","darksalmon","darkseagreen","darkslateblue","darkslategray","darkslategrey","darkturquoise","darkviolet","dashed","deeppink","deepskyblue","dimgray","dimgrey","dodgerblue","dotted","double","firebrick","floralwhite","forestgreen","fuchsia","gainsboro","ghostwhite","gold","goldenrod","gray","grey","green","greenyellow","groove","hidden","honeydew","hotpink","hsl","hsla","indianred","indigo","inherit","initial","inset","ivory","khaki","lavender","lavenderblush","lawngreen","lemonchiffon","lightblue","lightcoral","lightcyan","lightgoldenrodyellow","lightgray","lightgreen","lightgrey","lightpink","lightsalmon","lightseagreen","lightskyblue","lightslategray","lightslategrey","lightsteelblue","lightyellow","lime","limegreen","linen","magenta","maroon","medium","mediumaquamarine","mediumblue","mediumorchid","mediumpurple","mediumseagreen","mediumslateblue","mediumspringgreen","mediumturquoise","mediumvioletred","midnightblue","mintcream","mistyrose","moccasin","navajowhite","navy","none","oldlace","olive","olivedrab","orange","orangered","orchid","outset","palegoldenrod","palegreen","paleturquoise","palevioletred","papayawhip","peachpuff","peru","pink","plum","powderblue","purple","rebeccapurple","red","rgb","rgba","ridge","rosybrown","royalblue","saddlebrown","salmon","sandybrown","seagreen","seashell","sienna","silver","skyblue","slateblue","slategray","slategrey","snow","solid","springgreen","steelblue","tan","teal","thick","thin","thistle","tomato","transparent","turquoise","unset","violet","wheat","white","whitesmoke","yellow","yellowgreen"]},"border-block-start":{subproperties:["border-block-start-width","border-block-start-style","border-block-start-color"],inherited:false,supports:5,values:["-moz-calc","-moz-use-text-color","aliceblue","antiquewhite","aqua","aquamarine","azure","beige","bisque","black","blanchedalmond","blue","blueviolet","brown","burlywood","cadetblue","calc","chartreuse","chocolate","coral","cornflowerblue","cornsilk","crimson","currentColor","cyan","darkblue","darkcyan","darkgoldenrod","darkgray","darkgreen","darkgrey","darkkhaki","darkmagenta","darkolivegreen","darkorange","darkorchid","darkred","darksalmon","darkseagreen","darkslateblue","darkslategray","darkslategrey","darkturquoise","darkviolet","dashed","deeppink","deepskyblue","dimgray","dimgrey","dodgerblue","dotted","double","firebrick","floralwhite","forestgreen","fuchsia","gainsboro","ghostwhite","gold","goldenrod","gray","grey","green","greenyellow","groove","hidden","honeydew","hotpink","hsl","hsla","indianred","indigo","inherit","initial","inset","ivory","khaki","lavender","lavenderblush","lawngreen","lemonchiffon","lightblue","lightcoral","lightcyan","lightgoldenrodyellow","lightgray","lightgreen","lightgrey","lightpink","lightsalmon","lightseagreen","lightskyblue","lightslategray","lightslategrey","lightsteelblue","lightyellow","lime","limegreen","linen","magenta","maroon","medium","mediumaquamarine","mediumblue","mediumorchid","mediumpurple","mediumseagreen","mediumslateblue","mediumspringgreen","mediumturquoise","mediumvioletred","midnightblue","mintcream","mistyrose","moccasin","navajowhite","navy","none","oldlace","olive","olivedrab","orange","orangered","orchid","outset","palegoldenrod","palegreen","paleturquoise","palevioletred","papayawhip","peachpuff","peru","pink","plum","powderblue","purple","rebeccapurple","red","rgb","rgba","ridge","rosybrown","royalblue","saddlebrown","salmon","sandybrown","seagreen","seashell","sienna","silver","skyblue","slateblue","slategray","slategrey","snow","solid","springgreen","steelblue","tan","teal","thick","thin","thistle","tomato","transparent","turquoise","unset","violet","wheat","white","whitesmoke","yellow","yellowgreen"]},"border-bottom":{subproperties:["border-bottom-width","border-bottom-style","border-bottom-color"],inherited:false,supports:5,values:["-moz-calc","-moz-use-text-color","aliceblue","antiquewhite","aqua","aquamarine","azure","beige","bisque","black","blanchedalmond","blue","blueviolet","brown","burlywood","cadetblue","calc","chartreuse","chocolate","coral","cornflowerblue","cornsilk","crimson","currentColor","cyan","darkblue","darkcyan","darkgoldenrod","darkgray","darkgreen","darkgrey","darkkhaki","darkmagenta","darkolivegreen","darkorange","darkorchid","darkred","darksalmon","darkseagreen","darkslateblue","darkslategray","darkslategrey","darkturquoise","darkviolet","dashed","deeppink","deepskyblue","dimgray","dimgrey","dodgerblue","dotted","double","firebrick","floralwhite","forestgreen","fuchsia","gainsboro","ghostwhite","gold","goldenrod","gray","grey","green","greenyellow","groove","hidden","honeydew","hotpink","hsl","hsla","indianred","indigo","inherit","initial","inset","ivory","khaki","lavender","lavenderblush","lawngreen","lemonchiffon","lightblue","lightcoral","lightcyan","lightgoldenrodyellow","lightgray","lightgreen","lightgrey","lightpink","lightsalmon","lightseagreen","lightskyblue","lightslategray","lightslategrey","lightsteelblue","lightyellow","lime","limegreen","linen","magenta","maroon","medium","mediumaquamarine","mediumblue","mediumorchid","mediumpurple","mediumseagreen","mediumslateblue","mediumspringgreen","mediumturquoise","mediumvioletred","midnightblue","mintcream","mistyrose","moccasin","navajowhite","navy","none","oldlace","olive","olivedrab","orange","orangered","orchid","outset","palegoldenrod","palegreen","paleturquoise","palevioletred","papayawhip","peachpuff","peru","pink","plum","powderblue","purple","rebeccapurple","red","rgb","rgba","ridge","rosybrown","royalblue","saddlebrown","salmon","sandybrown","seagreen","seashell","sienna","silver","skyblue","slateblue","slategray","slategrey","snow","solid","springgreen","steelblue","tan","teal","thick","thin","thistle","tomato","transparent","turquoise","unset","violet","wheat","white","whitesmoke","yellow","yellowgreen"]},"border-color":{subproperties:["border-top-color","border-right-color","border-bottom-color","border-left-color"],inherited:false,supports:4,values:["-moz-use-text-color","aliceblue","antiquewhite","aqua","aquamarine","azure","beige","bisque","black","blanchedalmond","blue","blueviolet","brown","burlywood","cadetblue","chartreuse","chocolate","coral","cornflowerblue","cornsilk","crimson","currentColor","cyan","darkblue","darkcyan","darkgoldenrod","darkgray","darkgreen","darkgrey","darkkhaki","darkmagenta","darkolivegreen","darkorange","darkorchid","darkred","darksalmon","darkseagreen","darkslateblue","darkslategray","darkslategrey","darkturquoise","darkviolet","deeppink","deepskyblue","dimgray","dimgrey","dodgerblue","firebrick","floralwhite","forestgreen","fuchsia","gainsboro","ghostwhite","gold","goldenrod","gray","grey","green","greenyellow","honeydew","hotpink","hsl","hsla","indianred","indigo","inherit","initial","ivory","khaki","lavender","lavenderblush","lawngreen","lemonchiffon","lightblue","lightcoral","lightcyan","lightgoldenrodyellow","lightgray","lightgreen","lightgrey","lightpink","lightsalmon","lightseagreen","lightskyblue","lightslategray","lightslategrey","lightsteelblue","lightyellow","lime","limegreen","linen","magenta","maroon","mediumaquamarine","mediumblue","mediumorchid","mediumpurple","mediumseagreen","mediumslateblue","mediumspringgreen","mediumturquoise","mediumvioletred","midnightblue","mintcream","mistyrose","moccasin","navajowhite","navy","oldlace","olive","olivedrab","orange","orangered","orchid","palegoldenrod","palegreen","paleturquoise","palevioletred","papayawhip","peachpuff","peru","pink","plum","powderblue","purple","rebeccapurple","red","rgb","rgba","rosybrown","royalblue","saddlebrown","salmon","sandybrown","seagreen","seashell","sienna","silver","skyblue","slateblue","slategray","slategrey","snow","springgreen","steelblue","tan","teal","thistle","tomato","transparent","turquoise","unset","violet","wheat","white","whitesmoke","yellow","yellowgreen"]},"border-image":{subproperties:["border-image-source","border-image-slice","border-image-width","border-image-outset","border-image-repeat"],inherited:false,supports:1675,values:["-moz-element","-moz-image-rect","-moz-linear-gradient","-moz-radial-gradient","-moz-repeating-linear-gradient","-moz-repeating-radial-gradient","inherit","initial","linear-gradient","none","radial-gradient","repeating-linear-gradient","repeating-radial-gradient","unset","url"]},"border-inline-end":{subproperties:["border-inline-end-width","border-inline-end-style","border-inline-end-color"],inherited:false,supports:5,values:["-moz-calc","-moz-use-text-color","aliceblue","antiquewhite","aqua","aquamarine","azure","beige","bisque","black","blanchedalmond","blue","blueviolet","brown","burlywood","cadetblue","calc","chartreuse","chocolate","coral","cornflowerblue","cornsilk","crimson","currentColor","cyan","darkblue","darkcyan","darkgoldenrod","darkgray","darkgreen","darkgrey","darkkhaki","darkmagenta","darkolivegreen","darkorange","darkorchid","darkred","darksalmon","darkseagreen","darkslateblue","darkslategray","darkslategrey","darkturquoise","darkviolet","dashed","deeppink","deepskyblue","dimgray","dimgrey","dodgerblue","dotted","double","firebrick","floralwhite","forestgreen","fuchsia","gainsboro","ghostwhite","gold","goldenrod","gray","grey","green","greenyellow","groove","hidden","honeydew","hotpink","hsl","hsla","indianred","indigo","inherit","initial","inset","ivory","khaki","lavender","lavenderblush","lawngreen","lemonchiffon","lightblue","lightcoral","lightcyan","lightgoldenrodyellow","lightgray","lightgreen","lightgrey","lightpink","lightsalmon","lightseagreen","lightskyblue","lightslategray","lightslategrey","lightsteelblue","lightyellow","lime","limegreen","linen","magenta","maroon","medium","mediumaquamarine","mediumblue","mediumorchid","mediumpurple","mediumseagreen","mediumslateblue","mediumspringgreen","mediumturquoise","mediumvioletred","midnightblue","mintcream","mistyrose","moccasin","navajowhite","navy","none","oldlace","olive","olivedrab","orange","orangered","orchid","outset","palegoldenrod","palegreen","paleturquoise","palevioletred","papayawhip","peachpuff","peru","pink","plum","powderblue","purple","rebeccapurple","red","rgb","rgba","ridge","rosybrown","royalblue","saddlebrown","salmon","sandybrown","seagreen","seashell","sienna","silver","skyblue","slateblue","slategray","slategrey","snow","solid","springgreen","steelblue","tan","teal","thick","thin","thistle","tomato","transparent","turquoise","unset","violet","wheat","white","whitesmoke","yellow","yellowgreen"]},"border-inline-start":{subproperties:["border-inline-start-width","border-inline-start-style","border-inline-start-color"],inherited:false,supports:5,values:["-moz-calc","-moz-use-text-color","aliceblue","antiquewhite","aqua","aquamarine","azure","beige","bisque","black","blanchedalmond","blue","blueviolet","brown","burlywood","cadetblue","calc","chartreuse","chocolate","coral","cornflowerblue","cornsilk","crimson","currentColor","cyan","darkblue","darkcyan","darkgoldenrod","darkgray","darkgreen","darkgrey","darkkhaki","darkmagenta","darkolivegreen","darkorange","darkorchid","darkred","darksalmon","darkseagreen","darkslateblue","darkslategray","darkslategrey","darkturquoise","darkviolet","dashed","deeppink","deepskyblue","dimgray","dimgrey","dodgerblue","dotted","double","firebrick","floralwhite","forestgreen","fuchsia","gainsboro","ghostwhite","gold","goldenrod","gray","grey","green","greenyellow","groove","hidden","honeydew","hotpink","hsl","hsla","indianred","indigo","inherit","initial","inset","ivory","khaki","lavender","lavenderblush","lawngreen","lemonchiffon","lightblue","lightcoral","lightcyan","lightgoldenrodyellow","lightgray","lightgreen","lightgrey","lightpink","lightsalmon","lightseagreen","lightskyblue","lightslategray","lightslategrey","lightsteelblue","lightyellow","lime","limegreen","linen","magenta","maroon","medium","mediumaquamarine","mediumblue","mediumorchid","mediumpurple","mediumseagreen","mediumslateblue","mediumspringgreen","mediumturquoise","mediumvioletred","midnightblue","mintcream","mistyrose","moccasin","navajowhite","navy","none","oldlace","olive","olivedrab","orange","orangered","orchid","outset","palegoldenrod","palegreen","paleturquoise","palevioletred","papayawhip","peachpuff","peru","pink","plum","powderblue","purple","rebeccapurple","red","rgb","rgba","ridge","rosybrown","royalblue","saddlebrown","salmon","sandybrown","seagreen","seashell","sienna","silver","skyblue","slateblue","slategray","slategrey","snow","solid","springgreen","steelblue","tan","teal","thick","thin","thistle","tomato","transparent","turquoise","unset","violet","wheat","white","whitesmoke","yellow","yellowgreen"]},"border-left":{subproperties:["border-left-width","border-left-style","border-left-color"],inherited:false,supports:5,values:["-moz-calc","-moz-use-text-color","aliceblue","antiquewhite","aqua","aquamarine","azure","beige","bisque","black","blanchedalmond","blue","blueviolet","brown","burlywood","cadetblue","calc","chartreuse","chocolate","coral","cornflowerblue","cornsilk","crimson","currentColor","cyan","darkblue","darkcyan","darkgoldenrod","darkgray","darkgreen","darkgrey","darkkhaki","darkmagenta","darkolivegreen","darkorange","darkorchid","darkred","darksalmon","darkseagreen","darkslateblue","darkslategray","darkslategrey","darkturquoise","darkviolet","dashed","deeppink","deepskyblue","dimgray","dimgrey","dodgerblue","dotted","double","firebrick","floralwhite","forestgreen","fuchsia","gainsboro","ghostwhite","gold","goldenrod","gray","grey","green","greenyellow","groove","hidden","honeydew","hotpink","hsl","hsla","indianred","indigo","inherit","initial","inset","ivory","khaki","lavender","lavenderblush","lawngreen","lemonchiffon","lightblue","lightcoral","lightcyan","lightgoldenrodyellow","lightgray","lightgreen","lightgrey","lightpink","lightsalmon","lightseagreen","lightskyblue","lightslategray","lightslategrey","lightsteelblue","lightyellow","lime","limegreen","linen","magenta","maroon","medium","mediumaquamarine","mediumblue","mediumorchid","mediumpurple","mediumseagreen","mediumslateblue","mediumspringgreen","mediumturquoise","mediumvioletred","midnightblue","mintcream","mistyrose","moccasin","navajowhite","navy","none","oldlace","olive","olivedrab","orange","orangered","orchid","outset","palegoldenrod","palegreen","paleturquoise","palevioletred","papayawhip","peachpuff","peru","pink","plum","powderblue","purple","rebeccapurple","red","rgb","rgba","ridge","rosybrown","royalblue","saddlebrown","salmon","sandybrown","seagreen","seashell","sienna","silver","skyblue","slateblue","slategray","slategrey","snow","solid","springgreen","steelblue","tan","teal","thick","thin","thistle","tomato","transparent","turquoise","unset","violet","wheat","white","whitesmoke","yellow","yellowgreen"]},"border-right":{subproperties:["border-right-width","border-right-style","border-right-color"],inherited:false,supports:5,values:["-moz-calc","-moz-use-text-color","aliceblue","antiquewhite","aqua","aquamarine","azure","beige","bisque","black","blanchedalmond","blue","blueviolet","brown","burlywood","cadetblue","calc","chartreuse","chocolate","coral","cornflowerblue","cornsilk","crimson","currentColor","cyan","darkblue","darkcyan","darkgoldenrod","darkgray","darkgreen","darkgrey","darkkhaki","darkmagenta","darkolivegreen","darkorange","darkorchid","darkred","darksalmon","darkseagreen","darkslateblue","darkslategray","darkslategrey","darkturquoise","darkviolet","dashed","deeppink","deepskyblue","dimgray","dimgrey","dodgerblue","dotted","double","firebrick","floralwhite","forestgreen","fuchsia","gainsboro","ghostwhite","gold","goldenrod","gray","grey","green","greenyellow","groove","hidden","honeydew","hotpink","hsl","hsla","indianred","indigo","inherit","initial","inset","ivory","khaki","lavender","lavenderblush","lawngreen","lemonchiffon","lightblue","lightcoral","lightcyan","lightgoldenrodyellow","lightgray","lightgreen","lightgrey","lightpink","lightsalmon","lightseagreen","lightskyblue","lightslategray","lightslategrey","lightsteelblue","lightyellow","lime","limegreen","linen","magenta","maroon","medium","mediumaquamarine","mediumblue","mediumorchid","mediumpurple","mediumseagreen","mediumslateblue","mediumspringgreen","mediumturquoise","mediumvioletred","midnightblue","mintcream","mistyrose","moccasin","navajowhite","navy","none","oldlace","olive","olivedrab","orange","orangered","orchid","outset","palegoldenrod","palegreen","paleturquoise","palevioletred","papayawhip","peachpuff","peru","pink","plum","powderblue","purple","rebeccapurple","red","rgb","rgba","ridge","rosybrown","royalblue","saddlebrown","salmon","sandybrown","seagreen","seashell","sienna","silver","skyblue","slateblue","slategray","slategrey","snow","solid","springgreen","steelblue","tan","teal","thick","thin","thistle","tomato","transparent","turquoise","unset","violet","wheat","white","whitesmoke","yellow","yellowgreen"]},"border-style":{subproperties:["border-top-style","border-right-style","border-bottom-style","border-left-style"],inherited:false,supports:0,values:["dashed","dotted","double","groove","hidden","inherit","initial","inset","none","outset","ridge","solid","unset"]},"border-top":{subproperties:["border-top-width","border-top-style","border-top-color"],inherited:false,supports:5,values:["-moz-calc","-moz-use-text-color","aliceblue","antiquewhite","aqua","aquamarine","azure","beige","bisque","black","blanchedalmond","blue","blueviolet","brown","burlywood","cadetblue","calc","chartreuse","chocolate","coral","cornflowerblue","cornsilk","crimson","currentColor","cyan","darkblue","darkcyan","darkgoldenrod","darkgray","darkgreen","darkgrey","darkkhaki","darkmagenta","darkolivegreen","darkorange","darkorchid","darkred","darksalmon","darkseagreen","darkslateblue","darkslategray","darkslategrey","darkturquoise","darkviolet","dashed","deeppink","deepskyblue","dimgray","dimgrey","dodgerblue","dotted","double","firebrick","floralwhite","forestgreen","fuchsia","gainsboro","ghostwhite","gold","goldenrod","gray","grey","green","greenyellow","groove","hidden","honeydew","hotpink","hsl","hsla","indianred","indigo","inherit","initial","inset","ivory","khaki","lavender","lavenderblush","lawngreen","lemonchiffon","lightblue","lightcoral","lightcyan","lightgoldenrodyellow","lightgray","lightgreen","lightgrey","lightpink","lightsalmon","lightseagreen","lightskyblue","lightslategray","lightslategrey","lightsteelblue","lightyellow","lime","limegreen","linen","magenta","maroon","medium","mediumaquamarine","mediumblue","mediumorchid","mediumpurple","mediumseagreen","mediumslateblue","mediumspringgreen","mediumturquoise","mediumvioletred","midnightblue","mintcream","mistyrose","moccasin","navajowhite","navy","none","oldlace","olive","olivedrab","orange","orangered","orchid","outset","palegoldenrod","palegreen","paleturquoise","palevioletred","papayawhip","peachpuff","peru","pink","plum","powderblue","purple","rebeccapurple","red","rgb","rgba","ridge","rosybrown","royalblue","saddlebrown","salmon","sandybrown","seagreen","seashell","sienna","silver","skyblue","slateblue","slategray","slategrey","snow","solid","springgreen","steelblue","tan","teal","thick","thin","thistle","tomato","transparent","turquoise","unset","violet","wheat","white","whitesmoke","yellow","yellowgreen"]},"border-width":{subproperties:["border-top-width","border-right-width","border-bottom-width","border-left-width"],inherited:false,supports:1,values:["-moz-calc","calc","inherit","initial","medium","thick","thin","unset"]},"border-radius":{subproperties:["border-top-left-radius","border-top-right-radius","border-bottom-right-radius","border-bottom-left-radius"],inherited:false,supports:3,values:["inherit","initial","unset"]},"-moz-columns":{subproperties:["-moz-column-count","-moz-column-width"],inherited:false,supports:1025,values:["-moz-calc","auto","calc","inherit","initial","unset"]},"-moz-column-rule":{subproperties:["-moz-column-rule-width","-moz-column-rule-style","-moz-column-rule-color"],inherited:false,supports:5,values:["-moz-calc","-moz-use-text-color","aliceblue","antiquewhite","aqua","aquamarine","azure","beige","bisque","black","blanchedalmond","blue","blueviolet","brown","burlywood","cadetblue","calc","chartreuse","chocolate","coral","cornflowerblue","cornsilk","crimson","currentColor","cyan","darkblue","darkcyan","darkgoldenrod","darkgray","darkgreen","darkgrey","darkkhaki","darkmagenta","darkolivegreen","darkorange","darkorchid","darkred","darksalmon","darkseagreen","darkslateblue","darkslategray","darkslategrey","darkturquoise","darkviolet","dashed","deeppink","deepskyblue","dimgray","dimgrey","dodgerblue","dotted","double","firebrick","floralwhite","forestgreen","fuchsia","gainsboro","ghostwhite","gold","goldenrod","gray","grey","green","greenyellow","groove","hidden","honeydew","hotpink","hsl","hsla","indianred","indigo","inherit","initial","inset","ivory","khaki","lavender","lavenderblush","lawngreen","lemonchiffon","lightblue","lightcoral","lightcyan","lightgoldenrodyellow","lightgray","lightgreen","lightgrey","lightpink","lightsalmon","lightseagreen","lightskyblue","lightslategray","lightslategrey","lightsteelblue","lightyellow","lime","limegreen","linen","magenta","maroon","medium","mediumaquamarine","mediumblue","mediumorchid","mediumpurple","mediumseagreen","mediumslateblue","mediumspringgreen","mediumturquoise","mediumvioletred","midnightblue","mintcream","mistyrose","moccasin","navajowhite","navy","none","oldlace","olive","olivedrab","orange","orangered","orchid","outset","palegoldenrod","palegreen","paleturquoise","palevioletred","papayawhip","peachpuff","peru","pink","plum","powderblue","purple","rebeccapurple","red","rgb","rgba","ridge","rosybrown","royalblue","saddlebrown","salmon","sandybrown","seagreen","seashell","sienna","silver","skyblue","slateblue","slategray","slategrey","snow","solid","springgreen","steelblue","tan","teal","thick","thin","thistle","tomato","transparent","turquoise","unset","violet","wheat","white","whitesmoke","yellow","yellowgreen"]},"flex":{subproperties:["flex-grow","flex-shrink","flex-basis"],inherited:false,supports:1027,values:["-moz-available","-moz-calc","-moz-fit-content","-moz-max-content","-moz-min-content","auto","calc","inherit","initial","unset"]},"flex-flow":{subproperties:["flex-direction","flex-wrap"],inherited:false,supports:0,values:["column","column-reverse","inherit","initial","nowrap","row","row-reverse","unset","wrap","wrap-reverse"]},"font":{subproperties:["font-family","font-style","font-weight","font-size","line-height","font-size-adjust","font-stretch","-x-system-font","font-feature-settings","font-language-override","font-kerning","font-synthesis","font-variant-alternates","font-variant-caps","font-variant-east-asian","font-variant-ligatures","font-variant-numeric","font-variant-position"],inherited:true,supports:1027,values:["-moz-block-height","-moz-calc","all-petite-caps","all-small-caps","auto","calc","condensed","expanded","extra-condensed","extra-expanded","inherit","initial","italic","large","larger","medium","none","normal","oblique","petite-caps","semi-condensed","semi-expanded","small","small-caps","smaller","sub","super","titling-caps","ultra-condensed","ultra-expanded","unicase","unset","x-large","x-small","xx-large","xx-small"]},"font-variant":{subproperties:["font-variant-alternates","font-variant-caps","font-variant-east-asian","font-variant-ligatures","font-variant-numeric","font-variant-position"],inherited:true,supports:0,values:["all-petite-caps","all-small-caps","inherit","initial","normal","petite-caps","small-caps","sub","super","titling-caps","unicase","unset"]},"grid-template":{subproperties:["grid-template-areas","grid-template-columns","grid-template-rows"],inherited:false,supports:3,values:["inherit","initial","unset"]},"grid":{subproperties:["grid-template-areas","grid-template-columns","grid-template-rows","grid-auto-flow","grid-auto-columns","grid-auto-rows","grid-column-gap","grid-row-gap"],inherited:false,supports:3,values:["-moz-calc","calc","inherit","initial","unset"]},"grid-column":{subproperties:["grid-column-start","grid-column-end"],inherited:false,supports:1024,values:["inherit","initial","unset"]},"grid-row":{subproperties:["grid-row-start","grid-row-end"],inherited:false,supports:1024,values:["inherit","initial","unset"]},"grid-area":{subproperties:["grid-row-start","grid-column-start","grid-row-end","grid-column-end"],inherited:false,supports:1024,values:["inherit","initial","unset"]},"grid-gap":{subproperties:["grid-column-gap","grid-row-gap"],inherited:false,supports:1,values:["-moz-calc","calc","inherit","initial","unset"]},"list-style":{subproperties:["list-style-type","list-style-image","list-style-position"],inherited:true,supports:8,values:["inherit","initial","inside","none","outside","unset","url"]},"margin":{subproperties:["margin-top","margin-right","margin-bottom","margin-left"],inherited:false,supports:3,values:["-moz-calc","auto","calc","inherit","initial","unset"]},"outline":{subproperties:["outline-width","outline-style","outline-color"],inherited:false,supports:5,values:["-moz-calc","-moz-use-text-color","aliceblue","antiquewhite","aqua","aquamarine","auto","azure","beige","bisque","black","blanchedalmond","blue","blueviolet","brown","burlywood","cadetblue","calc","chartreuse","chocolate","coral","cornflowerblue","cornsilk","crimson","currentColor","cyan","darkblue","darkcyan","darkgoldenrod","darkgray","darkgreen","darkgrey","darkkhaki","darkmagenta","darkolivegreen","darkorange","darkorchid","darkred","darksalmon","darkseagreen","darkslateblue","darkslategray","darkslategrey","darkturquoise","darkviolet","dashed","deeppink","deepskyblue","dimgray","dimgrey","dodgerblue","dotted","double","firebrick","floralwhite","forestgreen","fuchsia","gainsboro","ghostwhite","gold","goldenrod","gray","grey","green","greenyellow","groove","honeydew","hotpink","hsl","hsla","indianred","indigo","inherit","initial","inset","ivory","khaki","lavender","lavenderblush","lawngreen","lemonchiffon","lightblue","lightcoral","lightcyan","lightgoldenrodyellow","lightgray","lightgreen","lightgrey","lightpink","lightsalmon","lightseagreen","lightskyblue","lightslategray","lightslategrey","lightsteelblue","lightyellow","lime","limegreen","linen","magenta","maroon","medium","mediumaquamarine","mediumblue","mediumorchid","mediumpurple","mediumseagreen","mediumslateblue","mediumspringgreen","mediumturquoise","mediumvioletred","midnightblue","mintcream","mistyrose","moccasin","navajowhite","navy","none","oldlace","olive","olivedrab","orange","orangered","orchid","outset","palegoldenrod","palegreen","paleturquoise","palevioletred","papayawhip","peachpuff","peru","pink","plum","powderblue","purple","rebeccapurple","red","rgb","rgba","ridge","rosybrown","royalblue","saddlebrown","salmon","sandybrown","seagreen","seashell","sienna","silver","skyblue","slateblue","slategray","slategrey","snow","solid","springgreen","steelblue","tan","teal","thick","thin","thistle","tomato","transparent","turquoise","unset","violet","wheat","white","whitesmoke","yellow","yellowgreen"]},"overflow":{subproperties:["overflow-x","overflow-y"],inherited:false,supports:0,values:["-moz-hidden-unscrollable","auto","hidden","inherit","initial","scroll","unset","visible"]},"padding":{subproperties:["padding-top","padding-right","padding-bottom","padding-left"],inherited:false,supports:3,values:["-moz-calc","calc","inherit","initial","unset"]},"scroll-snap-type":{subproperties:["scroll-snap-type-x","scroll-snap-type-y"],inherited:false,supports:0,values:["inherit","initial","mandatory","none","proximity","unset"]},"text-decoration":{subproperties:["text-decoration-color","text-decoration-line","text-decoration-style"],inherited:false,supports:4,values:["-moz-none","-moz-use-text-color","aliceblue","antiquewhite","aqua","aquamarine","azure","beige","bisque","black","blanchedalmond","blue","blueviolet","brown","burlywood","cadetblue","chartreuse","chocolate","coral","cornflowerblue","cornsilk","crimson","currentColor","cyan","darkblue","darkcyan","darkgoldenrod","darkgray","darkgreen","darkgrey","darkkhaki","darkmagenta","darkolivegreen","darkorange","darkorchid","darkred","darksalmon","darkseagreen","darkslateblue","darkslategray","darkslategrey","darkturquoise","darkviolet","dashed","deeppink","deepskyblue","dimgray","dimgrey","dodgerblue","dotted","double","firebrick","floralwhite","forestgreen","fuchsia","gainsboro","ghostwhite","gold","goldenrod","gray","grey","green","greenyellow","honeydew","hotpink","hsl","hsla","indianred","indigo","inherit","initial","ivory","khaki","lavender","lavenderblush","lawngreen","lemonchiffon","lightblue","lightcoral","lightcyan","lightgoldenrodyellow","lightgray","lightgreen","lightgrey","lightpink","lightsalmon","lightseagreen","lightskyblue","lightslategray","lightslategrey","lightsteelblue","lightyellow","lime","limegreen","linen","magenta","maroon","mediumaquamarine","mediumblue","mediumorchid","mediumpurple","mediumseagreen","mediumslateblue","mediumspringgreen","mediumturquoise","mediumvioletred","midnightblue","mintcream","mistyrose","moccasin","navajowhite","navy","oldlace","olive","olivedrab","orange","orangered","orchid","palegoldenrod","palegreen","paleturquoise","palevioletred","papayawhip","peachpuff","peru","pink","plum","powderblue","purple","rebeccapurple","red","rgb","rgba","rosybrown","royalblue","saddlebrown","salmon","sandybrown","seagreen","seashell","sienna","silver","skyblue","slateblue","slategray","slategrey","snow","solid","springgreen","steelblue","tan","teal","thistle","tomato","transparent","turquoise","unset","violet","wavy","wheat","white","whitesmoke","yellow","yellowgreen"]},"transition":{subproperties:["transition-property","transition-duration","transition-timing-function","transition-delay"],inherited:false,supports:320,values:["all","cubic-bezier","ease","ease-in","ease-in-out","ease-out","inherit","initial","linear","none","step-end","step-start","steps","unset"]},"marker":{subproperties:["marker-start","marker-mid","marker-end"],inherited:true,supports:8,values:["inherit","initial","none","unset","url"]},"-moz-transform":{alias:true,subproperties:["transform"],inherited:false,supports:0,values:["inherit","initial","unset"]},"-moz-transform-origin":{alias:true,inherited:false,supports:3,values:["inherit","initial","unset"]},"-moz-perspective-origin":{alias:true,inherited:false,supports:3,values:["inherit","initial","unset"]},"-moz-perspective":{alias:true,inherited:false,supports:1,values:["inherit","initial","none","unset"]},"-moz-transform-style":{alias:true,inherited:false,supports:0,values:["flat","inherit","initial","preserve-3d","unset"]},"-moz-backface-visibility":{alias:true,inherited:false,supports:0,values:["hidden","inherit","initial","unset","visible"]},"-moz-border-image":{alias:true,subproperties:["border-image-source","border-image-slice","border-image-width","border-image-outset","border-image-repeat"],inherited:false,supports:1675,values:["-moz-element","-moz-image-rect","-moz-linear-gradient","-moz-radial-gradient","-moz-repeating-linear-gradient","-moz-repeating-radial-gradient","inherit","initial","linear-gradient","none","radial-gradient","repeating-linear-gradient","repeating-radial-gradient","unset","url"]},"-moz-transition":{alias:true,subproperties:["transition-property","transition-duration","transition-timing-function","transition-delay"],inherited:false,supports:320,values:["all","cubic-bezier","ease","ease-in","ease-in-out","ease-out","inherit","initial","linear","none","step-end","step-start","steps","unset"]},"-moz-transition-delay":{alias:true,inherited:false,supports:64,values:["inherit","initial","unset"]},"-moz-transition-duration":{alias:true,inherited:false,supports:64,values:["inherit","initial","unset"]},"-moz-transition-property":{alias:true,inherited:false,supports:0,values:["all","inherit","initial","none","unset"]},"-moz-transition-timing-function":{alias:true,inherited:false,supports:256,values:["cubic-bezier","ease","ease-in","ease-in-out","ease-out","inherit","initial","linear","step-end","step-start","steps","unset"]},"-moz-animation":{alias:true,subproperties:["animation-duration","animation-timing-function","animation-delay","animation-direction","animation-fill-mode","animation-iteration-count","animation-play-state","animation-name"],inherited:false,supports:1344,values:["alternate","alternate-reverse","backwards","both","cubic-bezier","ease","ease-in","ease-in-out","ease-out","forwards","infinite","inherit","initial","linear","none","normal","paused","reverse","running","step-end","step-start","steps","unset"]},"-moz-animation-delay":{alias:true,inherited:false,supports:64,values:["inherit","initial","unset"]},"-moz-animation-direction":{alias:true,inherited:false,supports:0,values:["alternate","alternate-reverse","inherit","initial","normal","reverse","unset"]},"-moz-animation-duration":{alias:true,inherited:false,supports:64,values:["inherit","initial","unset"]},"-moz-animation-fill-mode":{alias:true,inherited:false,supports:0,values:["backwards","both","forwards","inherit","initial","none","unset"]},"-moz-animation-iteration-count":{alias:true,inherited:false,supports:1024,values:["infinite","inherit","initial","unset"]},"-moz-animation-name":{alias:true,inherited:false,supports:0,values:["inherit","initial","none","unset"]},"-moz-animation-play-state":{alias:true,inherited:false,supports:0,values:["inherit","initial","paused","running","unset"]},"-moz-animation-timing-function":{alias:true,inherited:false,supports:256,values:["cubic-bezier","ease","ease-in","ease-in-out","ease-out","inherit","initial","linear","step-end","step-start","steps","unset"]},"-moz-box-sizing":{alias:true,inherited:false,supports:0,values:["border-box","content-box","inherit","initial","padding-box","unset"]},"-moz-font-feature-settings":{alias:true,inherited:true,supports:0,values:["inherit","initial","unset"]},"-moz-font-language-override":{alias:true,inherited:true,supports:0,values:["inherit","initial","normal","unset"]},"-moz-padding-end":{alias:true,inherited:false,supports:3,values:["-moz-calc","calc","inherit","initial","unset"]},"-moz-padding-start":{alias:true,inherited:false,supports:3,values:["-moz-calc","calc","inherit","initial","unset"]},"-moz-margin-end":{alias:true,inherited:false,supports:3,values:["-moz-calc","auto","calc","inherit","initial","unset"]},"-moz-margin-start":{alias:true,inherited:false,supports:3,values:["-moz-calc","auto","calc","inherit","initial","unset"]},"-moz-border-end":{alias:true,subproperties:["border-inline-end-width","border-inline-end-style","border-inline-end-color"],inherited:false,supports:5,values:["-moz-calc","-moz-use-text-color","aliceblue","antiquewhite","aqua","aquamarine","azure","beige","bisque","black","blanchedalmond","blue","blueviolet","brown","burlywood","cadetblue","calc","chartreuse","chocolate","coral","cornflowerblue","cornsilk","crimson","currentColor","cyan","darkblue","darkcyan","darkgoldenrod","darkgray","darkgreen","darkgrey","darkkhaki","darkmagenta","darkolivegreen","darkorange","darkorchid","darkred","darksalmon","darkseagreen","darkslateblue","darkslategray","darkslategrey","darkturquoise","darkviolet","dashed","deeppink","deepskyblue","dimgray","dimgrey","dodgerblue","dotted","double","firebrick","floralwhite","forestgreen","fuchsia","gainsboro","ghostwhite","gold","goldenrod","gray","grey","green","greenyellow","groove","hidden","honeydew","hotpink","hsl","hsla","indianred","indigo","inherit","initial","inset","ivory","khaki","lavender","lavenderblush","lawngreen","lemonchiffon","lightblue","lightcoral","lightcyan","lightgoldenrodyellow","lightgray","lightgreen","lightgrey","lightpink","lightsalmon","lightseagreen","lightskyblue","lightslategray","lightslategrey","lightsteelblue","lightyellow","lime","limegreen","linen","magenta","maroon","medium","mediumaquamarine","mediumblue","mediumorchid","mediumpurple","mediumseagreen","mediumslateblue","mediumspringgreen","mediumturquoise","mediumvioletred","midnightblue","mintcream","mistyrose","moccasin","navajowhite","navy","none","oldlace","olive","olivedrab","orange","orangered","orchid","outset","palegoldenrod","palegreen","paleturquoise","palevioletred","papayawhip","peachpuff","peru","pink","plum","powderblue","purple","rebeccapurple","red","rgb","rgba","ridge","rosybrown","royalblue","saddlebrown","salmon","sandybrown","seagreen","seashell","sienna","silver","skyblue","slateblue","slategray","slategrey","snow","solid","springgreen","steelblue","tan","teal","thick","thin","thistle","tomato","transparent","turquoise","unset","violet","wheat","white","whitesmoke","yellow","yellowgreen"]},"-moz-border-end-color":{alias:true,inherited:false,supports:4,values:["-moz-use-text-color","aliceblue","antiquewhite","aqua","aquamarine","azure","beige","bisque","black","blanchedalmond","blue","blueviolet","brown","burlywood","cadetblue","chartreuse","chocolate","coral","cornflowerblue","cornsilk","crimson","currentColor","cyan","darkblue","darkcyan","darkgoldenrod","darkgray","darkgreen","darkgrey","darkkhaki","darkmagenta","darkolivegreen","darkorange","darkorchid","darkred","darksalmon","darkseagreen","darkslateblue","darkslategray","darkslategrey","darkturquoise","darkviolet","deeppink","deepskyblue","dimgray","dimgrey","dodgerblue","firebrick","floralwhite","forestgreen","fuchsia","gainsboro","ghostwhite","gold","goldenrod","gray","grey","green","greenyellow","honeydew","hotpink","hsl","hsla","indianred","indigo","inherit","initial","ivory","khaki","lavender","lavenderblush","lawngreen","lemonchiffon","lightblue","lightcoral","lightcyan","lightgoldenrodyellow","lightgray","lightgreen","lightgrey","lightpink","lightsalmon","lightseagreen","lightskyblue","lightslategray","lightslategrey","lightsteelblue","lightyellow","lime","limegreen","linen","magenta","maroon","mediumaquamarine","mediumblue","mediumorchid","mediumpurple","mediumseagreen","mediumslateblue","mediumspringgreen","mediumturquoise","mediumvioletred","midnightblue","mintcream","mistyrose","moccasin","navajowhite","navy","oldlace","olive","olivedrab","orange","orangered","orchid","palegoldenrod","palegreen","paleturquoise","palevioletred","papayawhip","peachpuff","peru","pink","plum","powderblue","purple","rebeccapurple","red","rgb","rgba","rosybrown","royalblue","saddlebrown","salmon","sandybrown","seagreen","seashell","sienna","silver","skyblue","slateblue","slategray","slategrey","snow","springgreen","steelblue","tan","teal","thistle","tomato","transparent","turquoise","unset","violet","wheat","white","whitesmoke","yellow","yellowgreen"]},"-moz-border-end-style":{alias:true,inherited:false,supports:0,values:["dashed","dotted","double","groove","hidden","inherit","initial","inset","none","outset","ridge","solid","unset"]},"-moz-border-end-width":{alias:true,inherited:false,supports:1,values:["-moz-calc","calc","inherit","initial","medium","thick","thin","unset"]},"-moz-border-start":{alias:true,subproperties:["border-inline-start-width","border-inline-start-style","border-inline-start-color"],inherited:false,supports:5,values:["-moz-calc","-moz-use-text-color","aliceblue","antiquewhite","aqua","aquamarine","azure","beige","bisque","black","blanchedalmond","blue","blueviolet","brown","burlywood","cadetblue","calc","chartreuse","chocolate","coral","cornflowerblue","cornsilk","crimson","currentColor","cyan","darkblue","darkcyan","darkgoldenrod","darkgray","darkgreen","darkgrey","darkkhaki","darkmagenta","darkolivegreen","darkorange","darkorchid","darkred","darksalmon","darkseagreen","darkslateblue","darkslategray","darkslategrey","darkturquoise","darkviolet","dashed","deeppink","deepskyblue","dimgray","dimgrey","dodgerblue","dotted","double","firebrick","floralwhite","forestgreen","fuchsia","gainsboro","ghostwhite","gold","goldenrod","gray","grey","green","greenyellow","groove","hidden","honeydew","hotpink","hsl","hsla","indianred","indigo","inherit","initial","inset","ivory","khaki","lavender","lavenderblush","lawngreen","lemonchiffon","lightblue","lightcoral","lightcyan","lightgoldenrodyellow","lightgray","lightgreen","lightgrey","lightpink","lightsalmon","lightseagreen","lightskyblue","lightslategray","lightslategrey","lightsteelblue","lightyellow","lime","limegreen","linen","magenta","maroon","medium","mediumaquamarine","mediumblue","mediumorchid","mediumpurple","mediumseagreen","mediumslateblue","mediumspringgreen","mediumturquoise","mediumvioletred","midnightblue","mintcream","mistyrose","moccasin","navajowhite","navy","none","oldlace","olive","olivedrab","orange","orangered","orchid","outset","palegoldenrod","palegreen","paleturquoise","palevioletred","papayawhip","peachpuff","peru","pink","plum","powderblue","purple","rebeccapurple","red","rgb","rgba","ridge","rosybrown","royalblue","saddlebrown","salmon","sandybrown","seagreen","seashell","sienna","silver","skyblue","slateblue","slategray","slategrey","snow","solid","springgreen","steelblue","tan","teal","thick","thin","thistle","tomato","transparent","turquoise","unset","violet","wheat","white","whitesmoke","yellow","yellowgreen"]},"-moz-border-start-color":{alias:true,inherited:false,supports:4,values:["-moz-use-text-color","aliceblue","antiquewhite","aqua","aquamarine","azure","beige","bisque","black","blanchedalmond","blue","blueviolet","brown","burlywood","cadetblue","chartreuse","chocolate","coral","cornflowerblue","cornsilk","crimson","currentColor","cyan","darkblue","darkcyan","darkgoldenrod","darkgray","darkgreen","darkgrey","darkkhaki","darkmagenta","darkolivegreen","darkorange","darkorchid","darkred","darksalmon","darkseagreen","darkslateblue","darkslategray","darkslategrey","darkturquoise","darkviolet","deeppink","deepskyblue","dimgray","dimgrey","dodgerblue","firebrick","floralwhite","forestgreen","fuchsia","gainsboro","ghostwhite","gold","goldenrod","gray","grey","green","greenyellow","honeydew","hotpink","hsl","hsla","indianred","indigo","inherit","initial","ivory","khaki","lavender","lavenderblush","lawngreen","lemonchiffon","lightblue","lightcoral","lightcyan","lightgoldenrodyellow","lightgray","lightgreen","lightgrey","lightpink","lightsalmon","lightseagreen","lightskyblue","lightslategray","lightslategrey","lightsteelblue","lightyellow","lime","limegreen","linen","magenta","maroon","mediumaquamarine","mediumblue","mediumorchid","mediumpurple","mediumseagreen","mediumslateblue","mediumspringgreen","mediumturquoise","mediumvioletred","midnightblue","mintcream","mistyrose","moccasin","navajowhite","navy","oldlace","olive","olivedrab","orange","orangered","orchid","palegoldenrod","palegreen","paleturquoise","palevioletred","papayawhip","peachpuff","peru","pink","plum","powderblue","purple","rebeccapurple","red","rgb","rgba","rosybrown","royalblue","saddlebrown","salmon","sandybrown","seagreen","seashell","sienna","silver","skyblue","slateblue","slategray","slategrey","snow","springgreen","steelblue","tan","teal","thistle","tomato","transparent","turquoise","unset","violet","wheat","white","whitesmoke","yellow","yellowgreen"]},"-moz-border-start-style":{alias:true,inherited:false,supports:0,values:["dashed","dotted","double","groove","hidden","inherit","initial","inset","none","outset","ridge","solid","unset"]},"-moz-border-start-width":{alias:true,inherited:false,supports:1,values:["-moz-calc","calc","inherit","initial","medium","thick","thin","unset"]},"-moz-hyphens":{alias:true,inherited:true,supports:0,values:["auto","inherit","initial","manual","none","unset"]},"-webkit-animation":{alias:true,subproperties:["animation-duration","animation-timing-function","animation-delay","animation-direction","animation-fill-mode","animation-iteration-count","animation-play-state","animation-name"],inherited:false,supports:1344,values:["alternate","alternate-reverse","backwards","both","cubic-bezier","ease","ease-in","ease-in-out","ease-out","forwards","infinite","inherit","initial","linear","none","normal","paused","reverse","running","step-end","step-start","steps","unset"]},"-webkit-animation-delay":{alias:true,inherited:false,supports:64,values:["inherit","initial","unset"]},"-webkit-animation-direction":{alias:true,inherited:false,supports:0,values:["alternate","alternate-reverse","inherit","initial","normal","reverse","unset"]},"-webkit-animation-duration":{alias:true,inherited:false,supports:64,values:["inherit","initial","unset"]},"-webkit-animation-fill-mode":{alias:true,inherited:false,supports:0,values:["backwards","both","forwards","inherit","initial","none","unset"]},"-webkit-animation-iteration-count":{alias:true,inherited:false,supports:1024,values:["infinite","inherit","initial","unset"]},"-webkit-animation-name":{alias:true,inherited:false,supports:0,values:["inherit","initial","none","unset"]},"-webkit-animation-play-state":{alias:true,inherited:false,supports:0,values:["inherit","initial","paused","running","unset"]},"-webkit-animation-timing-function":{alias:true,inherited:false,supports:256,values:["cubic-bezier","ease","ease-in","ease-in-out","ease-out","inherit","initial","linear","step-end","step-start","steps","unset"]},"-webkit-text-size-adjust":{alias:true,inherited:true,supports:0,values:["auto","inherit","initial","none","unset"]},"-webkit-transform":{alias:true,inherited:false,supports:0,values:["inherit","initial","unset"]},"-webkit-transform-origin":{alias:true,inherited:false,supports:3,values:["inherit","initial","unset"]},"-webkit-transform-style":{alias:true,inherited:false,supports:0,values:["flat","inherit","initial","preserve-3d","unset"]},"-webkit-backface-visibility":{alias:true,inherited:false,supports:0,values:["hidden","inherit","initial","unset","visible"]},"-webkit-perspective":{alias:true,inherited:false,supports:1,values:["inherit","initial","none","unset"]},"-webkit-perspective-origin":{alias:true,inherited:false,supports:3,values:["inherit","initial","unset"]},"-webkit-transition":{alias:true,subproperties:["transition-property","transition-duration","transition-timing-function","transition-delay"],inherited:false,supports:320,values:["all","cubic-bezier","ease","ease-in","ease-in-out","ease-out","inherit","initial","linear","none","step-end","step-start","steps","unset"]},"-webkit-transition-delay":{alias:true,inherited:false,supports:64,values:["inherit","initial","unset"]},"-webkit-transition-duration":{alias:true,inherited:false,supports:64,values:["inherit","initial","unset"]},"-webkit-transition-property":{alias:true,inherited:false,supports:0,values:["all","inherit","initial","none","unset"]},"-webkit-transition-timing-function":{alias:true,inherited:false,supports:256,values:["cubic-bezier","ease","ease-in","ease-in-out","ease-out","inherit","initial","linear","step-end","step-start","steps","unset"]},"-webkit-border-radius":{alias:true,subproperties:["border-top-left-radius","border-top-right-radius","border-bottom-right-radius","border-bottom-left-radius"],inherited:false,supports:3,values:["inherit","initial","unset"]},"-webkit-border-top-left-radius":{alias:true,inherited:false,supports:3,values:["inherit","initial","unset"]},"-webkit-border-top-right-radius":{alias:true,inherited:false,supports:3,values:["inherit","initial","unset"]},"-webkit-border-bottom-left-radius":{alias:true,inherited:false,supports:3,values:["inherit","initial","unset"]},"-webkit-border-bottom-right-radius":{alias:true,inherited:false,supports:3,values:["inherit","initial","unset"]},"-webkit-appearance":{alias:true,inherited:false,supports:0,values:["-moz-gtk-info-bar","-moz-mac-disclosure-button-closed","-moz-mac-disclosure-button-open","-moz-mac-fullscreen-button","-moz-mac-help-button","-moz-mac-vibrancy-dark","-moz-mac-vibrancy-light","-moz-win-borderless-glass","-moz-win-browsertabbar-toolbox","-moz-win-communications-toolbox","-moz-win-exclude-glass","-moz-win-glass","-moz-win-media-toolbox","-moz-window-button-box","-moz-window-button-box-maximized","-moz-window-button-close","-moz-window-button-maximize","-moz-window-button-minimize","-moz-window-button-restore","-moz-window-frame-bottom","-moz-window-frame-left","-moz-window-frame-right","-moz-window-titlebar","-moz-window-titlebar-maximized","button","button-arrow-down","button-arrow-next","button-arrow-previous","button-arrow-up","button-bevel","button-focus","caret","checkbox","checkbox-container","checkbox-label","checkmenuitem","dialog","dualbutton","groupbox","inherit","initial","listbox","listitem","menuarrow","menubar","menucheckbox","menuimage","menuitem","menuitemtext","menulist","menulist-button","menulist-text","menulist-textfield","menupopup","menuradio","menuseparator","meterbar","meterchunk","none","number-input","progressbar","progressbar-vertical","progresschunk","progresschunk-vertical","radio","radio-container","radio-label","radiomenuitem","range","range-thumb","resizer","resizerpanel","scale-horizontal","scale-vertical","scalethumb-horizontal","scalethumb-vertical","scalethumbend","scalethumbstart","scalethumbtick","scrollbar","scrollbar-small","scrollbarbutton-down","scrollbarbutton-left","scrollbarbutton-right","scrollbarbutton-up","scrollbarthumb-horizontal","scrollbarthumb-vertical","scrollbartrack-horizontal","scrollbartrack-vertical","searchfield","separator","spinner","spinner-downbutton","spinner-textfield","spinner-upbutton","splitter","statusbar","statusbarpanel","tab","tab-scroll-arrow-back","tab-scroll-arrow-forward","tabpanel","tabpanels","textfield","textfield-multiline","toolbar","toolbarbutton","toolbarbutton-dropdown","toolbargripper","toolbox","tooltip","treeheader","treeheadercell","treeheadersortarrow","treeitem","treeline","treetwisty","treetwistyopen","treeview","unset","window"]},"-webkit-background-clip":{alias:true,inherited:false,supports:0,values:["border-box","content-box","inherit","initial","padding-box","unset"]},"-webkit-background-origin":{alias:true,inherited:false,supports:0,values:["border-box","content-box","inherit","initial","padding-box","unset"]},"-webkit-background-size":{alias:true,inherited:false,supports:3,values:["inherit","initial","unset"]},"-webkit-border-image":{alias:true,subproperties:["border-image-source","border-image-slice","border-image-width","border-image-outset","border-image-repeat"],inherited:false,supports:1675,values:["-moz-element","-moz-image-rect","-moz-linear-gradient","-moz-radial-gradient","-moz-repeating-linear-gradient","-moz-repeating-radial-gradient","inherit","initial","linear-gradient","none","radial-gradient","repeating-linear-gradient","repeating-radial-gradient","unset","url"]},"-webkit-border-image-outset":{alias:true,inherited:false,supports:1025,values:["inherit","initial","unset"]},"-webkit-border-image-repeat":{alias:true,inherited:false,supports:0,values:["inherit","initial","unset"]},"-webkit-border-image-slice":{alias:true,inherited:false,supports:1026,values:["inherit","initial","unset"]},"-webkit-border-image-source":{alias:true,inherited:false,supports:648,values:["-moz-element","-moz-image-rect","-moz-linear-gradient","-moz-radial-gradient","-moz-repeating-linear-gradient","-moz-repeating-radial-gradient","inherit","initial","linear-gradient","none","radial-gradient","repeating-linear-gradient","repeating-radial-gradient","unset","url"]},"-webkit-border-image-width":{alias:true,inherited:false,supports:1027,values:["inherit","initial","unset"]},"-webkit-box-shadow":{alias:true,inherited:false,supports:5,values:["inherit","initial","unset"]},"-webkit-box-sizing":{alias:true,inherited:false,supports:0,values:["border-box","content-box","inherit","initial","padding-box","unset"]},"-webkit-box-flex":{alias:true,inherited:false,supports:1024,values:["inherit","initial","unset"]},"-webkit-box-ordinal-group":{alias:true,inherited:false,supports:1024,values:["inherit","initial","unset"]},"-webkit-box-align":{alias:true,inherited:false,supports:0,values:["inherit","initial","unset"]},"-webkit-box-pack":{alias:true,inherited:false,supports:0,values:["inherit","initial","unset"]},"-webkit-user-select":{alias:true,inherited:false,supports:0,values:["-moz-all","-moz-none","-moz-text","all","auto","element","elements","inherit","initial","none","text","toggle","tri-state","unset"]}};module.exports={cssProperties};
+
+/***/ },
+/* 66 */
+/***/ function(module, exports) {
+
+ /*
+ * A sham for https://dxr.mozilla.org/mozilla-central/source/toolkit/modules/Promise.jsm
+ */
+
+ /**
+ * Promise.jsm is mostly the Promise web API with a `defer` method. Just drop this in here,
+ * and use the native web API (although building with webpack/babel, it may replace this
+ * with it's own version if we want to target environments that do not have `Promise`.
+ */
+
+ var p = typeof window != "undefined" ? window.Promise : Promise;
+ p.defer = function defer() {
+ var resolve, reject;
+ var promise = new Promise(function () {
+ resolve = arguments[0];
+ reject = arguments[1];
+ });
+ return {
+ resolve: resolve,
+ reject: reject,
+ promise: promise
+ };
+ };
+
+ module.exports = p;
+
+/***/ },
+/* 67 */
+/***/ function(module, exports, __webpack_require__) {
+
+ /* eslint-env browser */
+ /* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+ /* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+ /* 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/. */
+
+ // TODO: Get rid of this code once the marionette server loads transport.js as
+ // an SDK module (see bug 1000814)
+
+ "use strict";
+
+ var DevToolsUtils = __webpack_require__(68);
+ var dumpn = DevToolsUtils.dumpn;
+ var dumpv = DevToolsUtils.dumpv;
+
+ var StreamUtils = __webpack_require__(74);
+
+ var _require = __webpack_require__(75);
+
+ var Packet = _require.Packet;
+ var JSONPacket = _require.JSONPacket;
+ var BulkPacket = _require.BulkPacket;
+
+ var promise = __webpack_require__(66);
+ var EventEmitter = __webpack_require__(60);
+ var utf8 = __webpack_require__(78);
+
+ var PACKET_HEADER_MAX = 200;
+
+ /**
+ * An adapter that handles data transfers between the debugger client and
+ * server. It can work with both nsIPipe and nsIServerSocket transports so
+ * long as the properly created input and output streams are specified.
+ * (However, for intra-process connections, LocalDebuggerTransport, below,
+ * is more efficient than using an nsIPipe pair with DebuggerTransport.)
+ *
+ * @param input nsIAsyncInputStream
+ * The input stream.
+ * @param output nsIAsyncOutputStream
+ * The output stream.
+ *
+ * Given a DebuggerTransport instance dt:
+ * 1) Set dt.hooks to a packet handler object (described below).
+ * 2) Call dt.ready() to begin watching for input packets.
+ * 3) Call dt.send() / dt.startBulkSend() to send packets.
+ * 4) Call dt.close() to close the connection, and disengage from the event
+ * loop.
+ *
+ * A packet handler is an object with the following methods:
+ *
+ * - onPacket(packet) - called when we have received a complete packet.
+ * |packet| is the parsed form of the packet --- a JavaScript value, not
+ * a JSON-syntax string.
+ *
+ * - onBulkPacket(packet) - called when we have switched to bulk packet
+ * receiving mode. |packet| is an object containing:
+ * * actor: Name of actor that will receive the packet
+ * * type: Name of actor's method that should be called on receipt
+ * * length: Size of the data to be read
+ * * stream: This input stream should only be used directly if you can ensure
+ * that you will read exactly |length| bytes and will not close the
+ * stream when reading is complete
+ * * done: If you use the stream directly (instead of |copyTo| below), you
+ * must signal completion by resolving / rejecting this deferred.
+ * If it's rejected, the transport will be closed. If an Error is
+ * supplied as a rejection value, it will be logged via |dumpn|.
+ * If you do use |copyTo|, resolving is taken care of for you when
+ * copying completes.
+ * * copyTo: A helper function for getting your data out of the stream that
+ * meets the stream handling requirements above, and has the
+ * following signature:
+ * @param output nsIAsyncOutputStream
+ * The stream to copy to.
+ * @return Promise
+ * The promise is resolved when copying completes or rejected if any
+ * (unexpected) errors occur.
+ * This object also emits "progress" events for each chunk that is
+ * copied. See stream-utils.js.
+ *
+ * - onClosed(reason) - called when the connection is closed. |reason| is
+ * an optional nsresult or object, typically passed when the transport is
+ * closed due to some error in a underlying stream.
+ *
+ * See ./packets.js and the Remote Debugging Protocol specification for more
+ * details on the format of these packets.
+ */
+ function DebuggerTransport(socket) {
+ EventEmitter.decorate(this);
+
+ this._socket = socket;
+
+ // The current incoming (possibly partial) header, which will determine which
+ // type of Packet |_incoming| below will become.
+ this._incomingHeader = "";
+ // The current incoming Packet object
+ this._incoming = null;
+ // A queue of outgoing Packet objects
+ this._outgoing = [];
+
+ this.hooks = null;
+ this.active = false;
+
+ this._incomingEnabled = true;
+ this._outgoingEnabled = true;
+
+ this.close = this.close.bind(this);
+ }
+
+ DebuggerTransport.prototype = {
+ /**
+ * Transmit an object as a JSON packet.
+ *
+ * This method returns immediately, without waiting for the entire
+ * packet to be transmitted, registering event handlers as needed to
+ * transmit the entire packet. Packets are transmitted in the order
+ * they are passed to this method.
+ */
+ send: function (object) {
+ this.emit("send", object);
+
+ var packet = new JSONPacket(this);
+ packet.object = object;
+ this._outgoing.push(packet);
+ this._flushOutgoing();
+ },
+
+ /**
+ * Transmit streaming data via a bulk packet.
+ *
+ * This method initiates the bulk send process by queuing up the header data.
+ * The caller receives eventual access to a stream for writing.
+ *
+ * N.B.: Do *not* attempt to close the stream handed to you, as it will
+ * continue to be used by this transport afterwards. Most users should
+ * instead use the provided |copyFrom| function instead.
+ *
+ * @param header Object
+ * This is modeled after the format of JSON packets above, but does not
+ * actually contain the data, but is instead just a routing header:
+ * * actor: Name of actor that will receive the packet
+ * * type: Name of actor's method that should be called on receipt
+ * * length: Size of the data to be sent
+ * @return Promise
+ * The promise will be resolved when you are allowed to write to the
+ * stream with an object containing:
+ * * stream: This output stream should only be used directly if
+ * you can ensure that you will write exactly |length|
+ * bytes and will not close the stream when writing is
+ * complete
+ * * done: If you use the stream directly (instead of |copyFrom|
+ * below), you must signal completion by resolving /
+ * rejecting this deferred. If it's rejected, the
+ * transport will be closed. If an Error is supplied as
+ * a rejection value, it will be logged via |dumpn|. If
+ * you do use |copyFrom|, resolving is taken care of for
+ * you when copying completes.
+ * * copyFrom: A helper function for getting your data onto the
+ * stream that meets the stream handling requirements
+ * above, and has the following signature:
+ * @param input nsIAsyncInputStream
+ * The stream to copy from.
+ * @return Promise
+ * The promise is resolved when copying completes or
+ * rejected if any (unexpected) errors occur.
+ * This object also emits "progress" events for each chunk
+ * that is copied. See stream-utils.js.
+ */
+ startBulkSend: function (header) {
+ this.emit("startBulkSend", header);
+
+ var packet = new BulkPacket(this);
+ packet.header = header;
+ this._outgoing.push(packet);
+ this._flushOutgoing();
+ return packet.streamReadyForWriting;
+ },
+
+ /**
+ * Close the transport.
+ * @param reason nsresult / object (optional)
+ * The status code or error message that corresponds to the reason for
+ * closing the transport (likely because a stream closed or failed).
+ */
+ close: function (reason) {
+ this.emit("onClosed", reason);
+
+ this.active = false;
+ this._socket.close();
+ this._destroyIncoming();
+ this._destroyAllOutgoing();
+ if (this.hooks) {
+ this.hooks.onClosed(reason);
+ this.hooks = null;
+ }
+ if (reason) {
+ dumpn("Transport closed: " + DevToolsUtils.safeErrorString(reason));
+ } else {
+ dumpn("Transport closed.");
+ }
+ },
+
+ /**
+ * The currently outgoing packet (at the top of the queue).
+ */
+ get _currentOutgoing() {
+ return this._outgoing[0];
+ },
+
+ /**
+ * Flush data to the outgoing stream. Waits until the output stream notifies
+ * us that it is ready to be written to (via onOutputStreamReady).
+ */
+ _flushOutgoing: function () {
+ if (!this._outgoingEnabled || this._outgoing.length === 0) {
+ return;
+ }
+
+ // If the top of the packet queue has nothing more to send, remove it.
+ if (this._currentOutgoing.done) {
+ this._finishCurrentOutgoing();
+ }
+
+ if (this._outgoing.length > 0) {
+ setTimeout(this.onOutputStreamReady.bind(this), 0);
+ }
+ },
+
+ /**
+ * Pause this transport's attempts to write to the output stream. This is
+ * used when we've temporarily handed off our output stream for writing bulk
+ * data.
+ */
+ pauseOutgoing: function () {
+ this._outgoingEnabled = false;
+ },
+
+ /**
+ * Resume this transport's attempts to write to the output stream.
+ */
+ resumeOutgoing: function () {
+ this._outgoingEnabled = true;
+ this._flushOutgoing();
+ },
+
+ // nsIOutputStreamCallback
+ /**
+ * This is called when the output stream is ready for more data to be written.
+ * The current outgoing packet will attempt to write some amount of data, but
+ * may not complete.
+ */
+ onOutputStreamReady: DevToolsUtils.makeInfallible(function () {
+ if (!this._outgoingEnabled || this._outgoing.length === 0) {
+ return;
+ }
+
+ try {
+ this._currentOutgoing.write({
+ write: data => {
+ var count = data.length;
+ this._socket.send(data);
+ return count;
+ }
+ });
+ } catch (e) {
+ if (e.result != Cr.NS_BASE_STREAM_WOULD_BLOCK) {
+ this.close(e.result);
+ return;
+ } else {
+ throw e;
+ }
+ }
+
+ this._flushOutgoing();
+ }, "DebuggerTransport.prototype.onOutputStreamReady"),
+
+ /**
+ * Remove the current outgoing packet from the queue upon completion.
+ */
+ _finishCurrentOutgoing: function () {
+ if (this._currentOutgoing) {
+ this._currentOutgoing.destroy();
+ this._outgoing.shift();
+ }
+ },
+
+ /**
+ * Clear the entire outgoing queue.
+ */
+ _destroyAllOutgoing: function () {
+ for (var packet of this._outgoing) {
+ packet.destroy();
+ }
+ this._outgoing = [];
+ },
+
+ /**
+ * Initialize the input stream for reading. Once this method has been called,
+ * we watch for packets on the input stream, and pass them to the appropriate
+ * handlers via this.hooks.
+ */
+ ready: function () {
+ this.active = true;
+ this._waitForIncoming();
+ },
+
+ /**
+ * Asks the input stream to notify us (via onInputStreamReady) when it is
+ * ready for reading.
+ */
+ _waitForIncoming: function () {
+ if (this._incomingEnabled && !this._socket.onmessage) {
+ this._socket.onmessage = this.onInputStreamReady.bind(this);
+ }
+ },
+
+ /**
+ * Pause this transport's attempts to read from the input stream. This is
+ * used when we've temporarily handed off our input stream for reading bulk
+ * data.
+ */
+ pauseIncoming: function () {
+ this._incomingEnabled = false;
+ },
+
+ /**
+ * Resume this transport's attempts to read from the input stream.
+ */
+ resumeIncoming: function () {
+ this._incomingEnabled = true;
+ this._flushIncoming();
+ this._waitForIncoming();
+ },
+
+ // nsIInputStreamCallback
+ /**
+ * Called when the stream is either readable or closed.
+ */
+ onInputStreamReady: DevToolsUtils.makeInfallible(function (event) {
+ var data = event.data;
+ // TODO: ws-tcp-proxy decodes utf-8, but the transport expects to see the
+ // encoded bytes. Simplest step is to re-encode for now.
+ data = utf8.encode(data);
+ var stream = {
+ available() {
+ return data.length;
+ },
+ readBytes(count) {
+ var result = data.slice(0, count);
+ data = data.slice(count);
+ return result;
+ }
+ };
+
+ try {
+ while (data && this._incomingEnabled && this._processIncoming(stream, stream.available())) {}
+ this._waitForIncoming();
+ } catch (e) {
+ if (e.result != Cr.NS_BASE_STREAM_WOULD_BLOCK) {
+ this.close(e.result);
+ } else {
+ throw e;
+ }
+ }
+ }, "DebuggerTransport.prototype.onInputStreamReady"),
+
+ /**
+ * Process the incoming data. Will create a new currently incoming Packet if
+ * needed. Tells the incoming Packet to read as much data as it can, but
+ * reading may not complete. The Packet signals that its data is ready for
+ * delivery by calling one of this transport's _on*Ready methods (see
+ * ./packets.js and the _on*Ready methods below).
+ * @return boolean
+ * Whether incoming stream processing should continue for any
+ * remaining data.
+ */
+ _processIncoming: function (stream, count) {
+ dumpv("Data available: " + count);
+
+ if (!count) {
+ dumpv("Nothing to read, skipping");
+ return false;
+ }
+
+ try {
+ if (!this._incoming) {
+ dumpv("Creating a new packet from incoming");
+
+ if (!this._readHeader(stream)) {
+ return false; // Not enough data to read packet type
+ }
+
+ // Attempt to create a new Packet by trying to parse each possible
+ // header pattern.
+ this._incoming = Packet.fromHeader(this._incomingHeader, this);
+ if (!this._incoming) {
+ throw new Error("No packet types for header: " + this._incomingHeader);
+ }
+ }
+
+ if (!this._incoming.done) {
+ // We have an incomplete packet, keep reading it.
+ dumpv("Existing packet incomplete, keep reading");
+ this._incoming.read(stream);
+ }
+ } catch (e) {
+ var msg = "Error reading incoming packet: (" + e + " - " + e.stack + ")";
+ dumpn(msg);
+
+ // Now in an invalid state, shut down the transport.
+ this.close();
+ return false;
+ }
+
+ if (!this._incoming.done) {
+ // Still not complete, we'll wait for more data.
+ dumpv("Packet not done, wait for more");
+ return true;
+ }
+
+ // Ready for next packet
+ this._flushIncoming();
+ return true;
+ },
+
+ /**
+ * Read as far as we can into the incoming data, attempting to build up a
+ * complete packet header (which terminates with ":"). We'll only read up to
+ * PACKET_HEADER_MAX characters.
+ * @return boolean
+ * True if we now have a complete header.
+ */
+ _readHeader: function (stream) {
+ var amountToRead = PACKET_HEADER_MAX - this._incomingHeader.length;
+ this._incomingHeader += StreamUtils.delimitedRead(stream, ":", amountToRead);
+ if (dumpv.wantVerbose) {
+ dumpv("Header read: " + this._incomingHeader);
+ }
+
+ if (this._incomingHeader.endsWith(":")) {
+ if (dumpv.wantVerbose) {
+ dumpv("Found packet header successfully: " + this._incomingHeader);
+ }
+ return true;
+ }
+
+ if (this._incomingHeader.length >= PACKET_HEADER_MAX) {
+ throw new Error("Failed to parse packet header!");
+ }
+
+ // Not enough data yet.
+ return false;
+ },
+
+ /**
+ * If the incoming packet is done, log it as needed and clear the buffer.
+ */
+ _flushIncoming: function () {
+ if (!this._incoming.done) {
+ return;
+ }
+ if (dumpn.wantLogging) {
+ dumpn("Got: " + this._incoming);
+ }
+ this._destroyIncoming();
+ },
+
+ /**
+ * Handler triggered by an incoming JSONPacket completing it's |read| method.
+ * Delivers the packet to this.hooks.onPacket.
+ */
+ _onJSONObjectReady: function (object) {
+ DevToolsUtils.executeSoon(DevToolsUtils.makeInfallible(() => {
+ // Ensure the transport is still alive by the time this runs.
+ if (this.active) {
+ this.emit("onPacket", object);
+ this.hooks.onPacket(object);
+ }
+ }, "DebuggerTransport instance's this.hooks.onPacket"));
+ },
+
+ /**
+ * Handler triggered by an incoming BulkPacket entering the |read| phase for
+ * the stream portion of the packet. Delivers info about the incoming
+ * streaming data to this.hooks.onBulkPacket. See the main comment on the
+ * transport at the top of this file for more details.
+ */
+ _onBulkReadReady: function () {
+ for (var _len = arguments.length, args = Array(_len), _key = 0; _key < _len; _key++) {
+ args[_key] = arguments[_key];
+ }
+
+ DevToolsUtils.executeSoon(DevToolsUtils.makeInfallible(() => {
+ // Ensure the transport is still alive by the time this runs.
+ if (this.active) {
+ var _hooks;
+
+ this.emit.apply(this, ["onBulkPacket"].concat(args));
+ (_hooks = this.hooks).onBulkPacket.apply(_hooks, args);
+ }
+ }, "DebuggerTransport instance's this.hooks.onBulkPacket"));
+ },
+
+ /**
+ * Remove all handlers and references related to the current incoming packet,
+ * either because it is now complete or because the transport is closing.
+ */
+ _destroyIncoming: function () {
+ if (this._incoming) {
+ this._incoming.destroy();
+ }
+ this._incomingHeader = "";
+ this._incoming = null;
+ }
+
+ };
+
+ exports.DebuggerTransport = DebuggerTransport;
+
+ /**
+ * An adapter that handles data transfers between the debugger client and
+ * server when they both run in the same process. It presents the same API as
+ * DebuggerTransport, but instead of transmitting serialized messages across a
+ * connection it merely calls the packet dispatcher of the other side.
+ *
+ * @param other LocalDebuggerTransport
+ * The other endpoint for this debugger connection.
+ *
+ * @see DebuggerTransport
+ */
+ function LocalDebuggerTransport(other) {
+ EventEmitter.decorate(this);
+
+ this.other = other;
+ this.hooks = null;
+
+ /*
+ * A packet number, shared between this and this.other. This isn't used
+ * by the protocol at all, but it makes the packet traces a lot easier to
+ * follow.
+ */
+ this._serial = this.other ? this.other._serial : { count: 0 };
+ this.close = this.close.bind(this);
+ }
+
+ LocalDebuggerTransport.prototype = {
+ /**
+ * Transmit a message by directly calling the onPacket handler of the other
+ * endpoint.
+ */
+ send: function (packet) {
+ this.emit("send", packet);
+
+ var serial = this._serial.count++;
+ if (dumpn.wantLogging) {
+ /* Check 'from' first, as 'echo' packets have both. */
+ if (packet.from) {
+ dumpn("Packet " + serial + " sent from " + uneval(packet.from));
+ } else if (packet.to) {
+ dumpn("Packet " + serial + " sent to " + uneval(packet.to));
+ }
+ }
+ this._deepFreeze(packet);
+ var other = this.other;
+ if (other) {
+ DevToolsUtils.executeSoon(DevToolsUtils.makeInfallible(() => {
+ // Avoid the cost of JSON.stringify() when logging is disabled.
+ if (dumpn.wantLogging) {
+ dumpn("Received packet " + serial + ": " + JSON.stringify(packet, null, 2));
+ }
+ if (other.hooks) {
+ other.emit("onPacket", packet);
+ other.hooks.onPacket(packet);
+ }
+ }, "LocalDebuggerTransport instance's this.other.hooks.onPacket"));
+ }
+ },
+
+ /**
+ * Send a streaming bulk packet directly to the onBulkPacket handler of the
+ * other endpoint.
+ *
+ * This case is much simpler than the full DebuggerTransport, since there is
+ * no primary stream we have to worry about managing while we hand it off to
+ * others temporarily. Instead, we can just make a single use pipe and be
+ * done with it.
+ */
+ startBulkSend: function (_ref) {
+ var actor = _ref.actor;
+ var type = _ref.type;
+ var length = _ref.length;
+
+ this.emit("startBulkSend", { actor, type, length });
+
+ var serial = this._serial.count++;
+
+ dumpn("Sent bulk packet " + serial + " for actor " + actor);
+ if (!this.other) {
+ return;
+ }
+
+ var pipe = new Pipe(true, true, 0, 0, null);
+
+ DevToolsUtils.executeSoon(DevToolsUtils.makeInfallible(() => {
+ dumpn("Received bulk packet " + serial);
+ if (!this.other.hooks) {
+ return;
+ }
+
+ // Receiver
+ var deferred = promise.defer();
+ var packet = {
+ actor: actor,
+ type: type,
+ length: length,
+ copyTo: output => {
+ var copying = StreamUtils.copyStream(pipe.inputStream, output, length);
+ deferred.resolve(copying);
+ return copying;
+ },
+ stream: pipe.inputStream,
+ done: deferred
+ };
+
+ this.other.emit("onBulkPacket", packet);
+ this.other.hooks.onBulkPacket(packet);
+
+ // Await the result of reading from the stream
+ deferred.promise.then(() => pipe.inputStream.close(), this.close);
+ }, "LocalDebuggerTransport instance's this.other.hooks.onBulkPacket"));
+
+ // Sender
+ var sendDeferred = promise.defer();
+
+ // The remote transport is not capable of resolving immediately here, so we
+ // shouldn't be able to either.
+ DevToolsUtils.executeSoon(() => {
+ var copyDeferred = promise.defer();
+
+ sendDeferred.resolve({
+ copyFrom: input => {
+ var copying = StreamUtils.copyStream(input, pipe.outputStream, length);
+ copyDeferred.resolve(copying);
+ return copying;
+ },
+ stream: pipe.outputStream,
+ done: copyDeferred
+ });
+
+ // Await the result of writing to the stream
+ copyDeferred.promise.then(() => pipe.outputStream.close(), this.close);
+ });
+
+ return sendDeferred.promise;
+ },
+
+ /**
+ * Close the transport.
+ */
+ close: function () {
+ this.emit("close");
+
+ if (this.other) {
+ // Remove the reference to the other endpoint before calling close(), to
+ // avoid infinite recursion.
+ var other = this.other;
+ this.other = null;
+ other.close();
+ }
+ if (this.hooks) {
+ try {
+ this.hooks.onClosed();
+ } catch (ex) {
+ console.error(ex);
+ }
+ this.hooks = null;
+ }
+ },
+
+ /**
+ * An empty method for emulating the DebuggerTransport API.
+ */
+ ready: function () {},
+
+ /**
+ * Helper function that makes an object fully immutable.
+ */
+ _deepFreeze: function (object) {
+ Object.freeze(object);
+ for (var prop in object) {
+ // Freeze the properties that are objects, not on the prototype, and not
+ // already frozen. Note that this might leave an unfrozen reference
+ // somewhere in the object if there is an already frozen object containing
+ // an unfrozen object.
+ if (object.hasOwnProperty(prop) && typeof object === "object" && !Object.isFrozen(object)) {
+ this._deepFreeze(o[prop]);
+ }
+ }
+ }
+ };
+
+ exports.LocalDebuggerTransport = LocalDebuggerTransport;
+
+ /**
+ * A transport for the debugging protocol that uses nsIMessageSenders to
+ * exchange packets with servers running in child processes.
+ *
+ * In the parent process, |sender| should be the nsIMessageSender for the
+ * child process. In a child process, |sender| should be the child process
+ * message manager, which sends packets to the parent.
+ *
+ * |prefix| is a string included in the message names, to distinguish
+ * multiple servers running in the same child process.
+ *
+ * This transport exchanges messages named 'debug:<prefix>:packet', where
+ * <prefix> is |prefix|, whose data is the protocol packet.
+ */
+ function ChildDebuggerTransport(sender, prefix) {
+ EventEmitter.decorate(this);
+
+ this._sender = sender.QueryInterface(Ci.nsIMessageSender);
+ this._messageName = "debug:" + prefix + ":packet";
+ }
+
+ /*
+ * To avoid confusion, we use 'message' to mean something that
+ * nsIMessageSender conveys, and 'packet' to mean a remote debugging
+ * protocol packet.
+ */
+ ChildDebuggerTransport.prototype = {
+ constructor: ChildDebuggerTransport,
+
+ hooks: null,
+
+ ready: function () {
+ this._sender.addMessageListener(this._messageName, this);
+ },
+
+ close: function () {
+ this._sender.removeMessageListener(this._messageName, this);
+ this.emit("onClosed");
+ this.hooks.onClosed();
+ },
+
+ receiveMessage: function (_ref2) {
+ var data = _ref2.data;
+
+ this.emit("onPacket", data);
+ this.hooks.onPacket(data);
+ },
+
+ send: function (packet) {
+ this.emit("send", packet);
+ this._sender.sendAsyncMessage(this._messageName, packet);
+ },
+
+ startBulkSend: function () {
+ throw new Error("Can't send bulk data to child processes.");
+ }
+ };
+
+ exports.ChildDebuggerTransport = ChildDebuggerTransport;
+
+ // WorkerDebuggerTransport is defined differently depending on whether we are
+ // on the main thread or a worker thread. In the former case, we are required
+ // by the devtools loader, and isWorker will be false. Otherwise, we are
+ // required by the worker loader, and isWorker will be true.
+ //
+ // Each worker debugger supports only a single connection to the main thread.
+ // However, its theoretically possible for multiple servers to connect to the
+ // same worker. Consequently, each transport has a connection id, to allow
+ // messages from multiple connections to be multiplexed on a single channel.
+
+ if (typeof WorkerGlobalScope === 'undefined') {
+ // i.e. not in a worker
+ (function () {
+ // Main thread
+ /**
+ * A transport that uses a WorkerDebugger to send packets from the main
+ * thread to a worker thread.
+ */
+ function WorkerDebuggerTransport(dbg, id) {
+ this._dbg = dbg;
+ this._id = id;
+ this.onMessage = this._onMessage.bind(this);
+ }
+
+ WorkerDebuggerTransport.prototype = {
+ constructor: WorkerDebuggerTransport,
+
+ ready: function () {
+ this._dbg.addListener(this);
+ },
+
+ close: function () {
+ this._dbg.removeListener(this);
+ if (this.hooks) {
+ this.hooks.onClosed();
+ }
+ },
+
+ send: function (packet) {
+ this._dbg.postMessage(JSON.stringify({
+ type: "message",
+ id: this._id,
+ message: packet
+ }));
+ },
+
+ startBulkSend: function () {
+ throw new Error("Can't send bulk data from worker threads!");
+ },
+
+ _onMessage: function (message) {
+ var packet = JSON.parse(message);
+ if (packet.type !== "message" || packet.id !== this._id) {
+ return;
+ }
+
+ if (this.hooks) {
+ this.hooks.onPacket(packet.message);
+ }
+ }
+ };
+
+ exports.WorkerDebuggerTransport = WorkerDebuggerTransport;
+ }).call(this);
+ } else {
+ (function () {
+ // Worker thread
+ /*
+ * A transport that uses a WorkerDebuggerGlobalScope to send packets from a
+ * worker thread to the main thread.
+ */
+ function WorkerDebuggerTransport(scope, id) {
+ this._scope = scope;
+ this._id = id;
+ this._onMessage = this._onMessage.bind(this);
+ }
+
+ WorkerDebuggerTransport.prototype = {
+ constructor: WorkerDebuggerTransport,
+
+ ready: function () {
+ this._scope.addEventListener("message", this._onMessage);
+ },
+
+ close: function () {
+ this._scope.removeEventListener("message", this._onMessage);
+ if (this.hooks) {
+ this.hooks.onClosed();
+ }
+ },
+
+ send: function (packet) {
+ this._scope.postMessage(JSON.stringify({
+ type: "message",
+ id: this._id,
+ message: packet
+ }));
+ },
+
+ startBulkSend: function () {
+ throw new Error("Can't send bulk data from worker threads!");
+ },
+
+ _onMessage: function (event) {
+ var packet = JSON.parse(event.data);
+ if (packet.type !== "message" || packet.id !== this._id) {
+ return;
+ }
+
+ if (this.hooks) {
+ this.hooks.onPacket(packet.message);
+ }
+ }
+ };
+
+ exports.WorkerDebuggerTransport = WorkerDebuggerTransport;
+ }).call(this);
+ }
+
+/***/ },
+/* 68 */
+/***/ function(module, exports, __webpack_require__) {
+
+ var _this = this;
+
+ function _toConsumableArray(arr) { if (Array.isArray(arr)) { for (var i = 0, arr2 = Array(arr.length); i < arr.length; i++) { arr2[i] = arr[i]; } return arr2; } else { return Array.from(arr); } }
+
+ /* 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/. */
+
+ /* General utilities used throughout devtools. */
+ var _require = __webpack_require__(61);
+
+ var Ci = _require.Ci;
+ var Cu = _require.Cu;
+ var Cc = _require.Cc;
+ var components = _require.components;
+
+ var _require2 = __webpack_require__(30);
+
+ var Services = _require2.Services;
+
+ var promise = __webpack_require__(66);
+
+ var _require3 = __webpack_require__(69);
+
+ var FileUtils = _require3.FileUtils;
+
+ /**
+ * Turn the error |aError| into a string, without fail.
+ */
+
+ exports.safeErrorString = function safeErrorString(aError) {
+ try {
+ var errorString = aError.toString();
+ if (typeof errorString == "string") {
+ // Attempt to attach a stack to |errorString|. If it throws an error, or
+ // isn't a string, don't use it.
+ try {
+ if (aError.stack) {
+ var stack = aError.stack.toString();
+ if (typeof stack == "string") {
+ errorString += "\nStack: " + stack;
+ }
+ }
+ } catch (ee) {}
+
+ // Append additional line and column number information to the output,
+ // since it might not be part of the stringified error.
+ if (typeof aError.lineNumber == "number" && typeof aError.columnNumber == "number") {
+ errorString += "Line: " + aError.lineNumber + ", column: " + aError.columnNumber;
+ }
+
+ return errorString;
+ }
+ } catch (ee) {}
+
+ // We failed to find a good error description, so do the next best thing.
+ return Object.prototype.toString.call(aError);
+ };
+
+ /**
+ * Report that |aWho| threw an exception, |aException|.
+ */
+ exports.reportException = function reportException(aWho, aException) {
+ var msg = aWho + " threw an exception: " + exports.safeErrorString(aException);
+
+ console.log(msg);
+
+ // if (Cu && console.error) {
+ // /*
+ // * Note that the xpcshell test harness registers an observer for
+ // * console messages, so when we're running tests, this will cause
+ // * the test to quit.
+ // */
+ // console.error(msg);
+ // }
+ };
+
+ /**
+ * Given a handler function that may throw, return an infallible handler
+ * function that calls the fallible handler, and logs any exceptions it
+ * throws.
+ *
+ * @param aHandler function
+ * A handler function, which may throw.
+ * @param aName string
+ * A name for aHandler, for use in error messages. If omitted, we use
+ * aHandler.name.
+ *
+ * (SpiderMonkey does generate good names for anonymous functions, but we
+ * don't have a way to get at them from JavaScript at the moment.)
+ */
+ exports.makeInfallible = function makeInfallible(aHandler, aName) {
+ if (!aName) aName = aHandler.name;
+
+ return function () /* arguments */{
+ // try {
+ return aHandler.apply(this, arguments);
+ // } catch (ex) {
+ // let who = "Handler function";
+ // if (aName) {
+ // who += " " + aName;
+ // }
+ // return exports.reportException(who, ex);
+ // }
+ };
+ };
+
+ /**
+ * Waits for the next tick in the event loop to execute a callback.
+ */
+ exports.executeSoon = function executeSoon(aFn) {
+ setTimeout(aFn, 0);
+ };
+
+ /**
+ * Waits for the next tick in the event loop.
+ *
+ * @return Promise
+ * A promise that is resolved after the next tick in the event loop.
+ */
+ exports.waitForTick = function waitForTick() {
+ var deferred = promise.defer();
+ exports.executeSoon(deferred.resolve);
+ return deferred.promise;
+ };
+
+ /**
+ * Waits for the specified amount of time to pass.
+ *
+ * @param number aDelay
+ * The amount of time to wait, in milliseconds.
+ * @return Promise
+ * A promise that is resolved after the specified amount of time passes.
+ */
+ exports.waitForTime = function waitForTime(aDelay) {
+ var deferred = promise.defer();
+ setTimeout(deferred.resolve, aDelay);
+ return deferred.promise;
+ };
+
+ /**
+ * Like Array.prototype.forEach, but doesn't cause jankiness when iterating over
+ * very large arrays by yielding to the browser and continuing execution on the
+ * next tick.
+ *
+ * @param Array aArray
+ * The array being iterated over.
+ * @param Function aFn
+ * The function called on each item in the array. If a promise is
+ * returned by this function, iterating over the array will be paused
+ * until the respective promise is resolved.
+ * @returns Promise
+ * A promise that is resolved once the whole array has been iterated
+ * over, and all promises returned by the aFn callback are resolved.
+ */
+ exports.yieldingEach = function yieldingEach(aArray, aFn) {
+ var deferred = promise.defer();
+
+ var i = 0;
+ var len = aArray.length;
+ var outstanding = [deferred.promise];
+
+ (function loop() {
+ var start = Date.now();
+
+ while (i < len) {
+ // Don't block the main thread for longer than 16 ms at a time. To
+ // maintain 60fps, you have to render every frame in at least 16ms; we
+ // aren't including time spent in non-JS here, but this is Good
+ // Enough(tm).
+ if (Date.now() - start > 16) {
+ exports.executeSoon(loop);
+ return;
+ }
+
+ try {
+ outstanding.push(aFn(aArray[i], i++));
+ } catch (e) {
+ deferred.reject(e);
+ return;
+ }
+ }
+
+ deferred.resolve();
+ })();
+
+ return promise.all(outstanding);
+ };
+
+ /**
+ * Like XPCOMUtils.defineLazyGetter, but with a |this| sensitive getter that
+ * allows the lazy getter to be defined on a prototype and work correctly with
+ * instances.
+ *
+ * @param Object aObject
+ * The prototype object to define the lazy getter on.
+ * @param String aKey
+ * The key to define the lazy getter on.
+ * @param Function aCallback
+ * The callback that will be called to determine the value. Will be
+ * called with the |this| value of the current instance.
+ */
+ exports.defineLazyPrototypeGetter = function defineLazyPrototypeGetter(aObject, aKey, aCallback) {
+ Object.defineProperty(aObject, aKey, {
+ configurable: true,
+ get: function () {
+ var value = aCallback.call(this);
+
+ Object.defineProperty(this, aKey, {
+ configurable: true,
+ writable: true,
+ value: value
+ });
+
+ return value;
+ }
+ });
+ };
+
+ /**
+ * Safely get the property value from a Debugger.Object for a given key. Walks
+ * the prototype chain until the property is found.
+ *
+ * @param Debugger.Object aObject
+ * The Debugger.Object to get the value from.
+ * @param String aKey
+ * The key to look for.
+ * @return Any
+ */
+ exports.getProperty = function getProperty(aObj, aKey) {
+ var root = aObj;
+ try {
+ do {
+ var desc = aObj.getOwnPropertyDescriptor(aKey);
+ if (desc) {
+ if ("value" in desc) {
+ return desc.value;
+ }
+ // Call the getter if it's safe.
+ return exports.hasSafeGetter(desc) ? desc.get.call(root).return : undefined;
+ }
+ aObj = aObj.proto;
+ } while (aObj);
+ } catch (e) {
+ // If anything goes wrong report the error and return undefined.
+ exports.reportException("getProperty", e);
+ }
+ return undefined;
+ };
+
+ /**
+ * Determines if a descriptor has a getter which doesn't call into JavaScript.
+ *
+ * @param Object aDesc
+ * The descriptor to check for a safe getter.
+ * @return Boolean
+ * Whether a safe getter was found.
+ */
+ exports.hasSafeGetter = function hasSafeGetter(aDesc) {
+ // Scripted functions that are CCWs will not appear scripted until after
+ // unwrapping.
+ try {
+ var fn = aDesc.get.unwrap();
+ return fn && fn.callable && fn.class == "Function" && fn.script === undefined;
+ } catch (e) {
+ // Avoid exception 'Object in compartment marked as invisible to Debugger'
+ return false;
+ }
+ };
+
+ /**
+ * Check if it is safe to read properties and execute methods from the given JS
+ * object. Safety is defined as being protected from unintended code execution
+ * from content scripts (or cross-compartment code).
+ *
+ * See bugs 945920 and 946752 for discussion.
+ *
+ * @type Object aObj
+ * The object to check.
+ * @return Boolean
+ * True if it is safe to read properties from aObj, or false otherwise.
+ */
+ exports.isSafeJSObject = function isSafeJSObject(aObj) {
+ // If we are running on a worker thread, Cu is not available. In this case,
+ // we always return false, just to be on the safe side.
+ if (isWorker) {
+ return false;
+ }
+
+ if (Cu.getGlobalForObject(aObj) == Cu.getGlobalForObject(exports.isSafeJSObject)) {
+ return true; // aObj is not a cross-compartment wrapper.
+ }
+
+ var principal = Cu.getObjectPrincipal(aObj);
+ if (Services.scriptSecurityManager.isSystemPrincipal(principal)) {
+ return true; // allow chrome objects
+ }
+
+ return Cu.isXrayWrapper(aObj);
+ };
+
+ exports.dumpn = function dumpn(str) {
+ if (exports.dumpn.wantLogging) {
+ console.log("DBG-SERVER: " + str + "\n");
+ }
+ };
+
+ // We want wantLogging to be writable. The exports object is frozen by the
+ // loader, so define it on dumpn instead.
+ exports.dumpn.wantLogging = false;
+
+ /**
+ * A verbose logger for low-level tracing.
+ */
+ exports.dumpv = function (msg) {
+ if (exports.dumpv.wantVerbose) {
+ exports.dumpn(msg);
+ }
+ };
+
+ // We want wantLogging to be writable. The exports object is frozen by the
+ // loader, so define it on dumpn instead.
+ exports.dumpv.wantVerbose = false;
+
+ /**
+ * Utility function for updating an object with the properties of
+ * other objects.
+ *
+ * @param aTarget Object
+ * The object being updated.
+ * @param aNewAttrs Object
+ * The rest params are objects to update aTarget with. You
+ * can pass as many as you like.
+ */
+ exports.update = function update(aTarget) {
+ for (var _len = arguments.length, aArgs = Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) {
+ aArgs[_key - 1] = arguments[_key];
+ }
+
+ for (var attrs of aArgs) {
+ for (var key in attrs) {
+ var desc = Object.getOwnPropertyDescriptor(attrs, key);
+
+ if (desc) {
+ Object.defineProperty(aTarget, key, desc);
+ }
+ }
+ }
+
+ return aTarget;
+ };
+
+ /**
+ * Utility function for getting the values from an object as an array
+ *
+ * @param aObject Object
+ * The object to iterate over
+ */
+ exports.values = function values(aObject) {
+ return Object.keys(aObject).map(k => aObject[k]);
+ };
+
+ /**
+ * Defines a getter on a specified object that will be created upon first use.
+ *
+ * @param aObject
+ * The object to define the lazy getter on.
+ * @param aName
+ * The name of the getter to define on aObject.
+ * @param aLambda
+ * A function that returns what the getter should return. This will
+ * only ever be called once.
+ */
+ exports.defineLazyGetter = function defineLazyGetter(aObject, aName, aLambda) {
+ Object.defineProperty(aObject, aName, {
+ get: function () {
+ delete aObject[aName];
+ return aObject[aName] = aLambda.apply(aObject);
+ },
+ configurable: true,
+ enumerable: true
+ });
+ };
+
+ // DEPRECATED: use DevToolsUtils.assert(condition, message) instead!
+ var haveLoggedDeprecationMessage = false;
+ exports.dbg_assert = function dbg_assert(cond, e) {
+ if (!haveLoggedDeprecationMessage) {
+ haveLoggedDeprecationMessage = true;
+ var deprecationMessage = "DevToolsUtils.dbg_assert is deprecated! Use DevToolsUtils.assert instead!" + Error().stack;
+ console.log(deprecationMessage);
+ if (typeof console === "object" && console && console.warn) {
+ console.warn(deprecationMessage);
+ }
+ }
+
+ if (!cond) {
+ return e;
+ }
+ };
+
+ var _require4 = __webpack_require__(70);
+
+ var AppConstants = _require4.AppConstants;
+
+ /**
+ * No operation. The empty function.
+ */
+
+ exports.noop = function () {};
+
+ function reallyAssert(condition, message) {
+ if (!condition) {
+ var err = new Error("Assertion failure: " + message);
+ exports.reportException("DevToolsUtils.assert", err);
+ throw err;
+ }
+ }
+
+ /**
+ * DevToolsUtils.assert(condition, message)
+ *
+ * @param Boolean condition
+ * @param String message
+ *
+ * Assertions are enabled when any of the following are true:
+ * - This is a DEBUG_JS_MODULES build
+ * - This is a DEBUG build
+ * - DevToolsUtils.testing is set to true
+ *
+ * If assertions are enabled, then `condition` is checked and if false-y, the
+ * assertion failure is logged and then an error is thrown.
+ *
+ * If assertions are not enabled, then this function is a no-op.
+ *
+ * This is an improvement over `dbg_assert`, which doesn't actually cause any
+ * fatal behavior, and is therefore much easier to accidentally ignore.
+ */
+ Object.defineProperty(exports, "assert", {
+ get: () => AppConstants.DEBUG || AppConstants.DEBUG_JS_MODULES || _this.testing ? reallyAssert : exports.noop
+ });
+
+ /**
+ * Defines a getter on a specified object for a module. The module will not
+ * be imported until first use.
+ *
+ * @param aObject
+ * The object to define the lazy getter on.
+ * @param aName
+ * The name of the getter to define on aObject for the module.
+ * @param aResource
+ * The URL used to obtain the module.
+ * @param aSymbol
+ * The name of the symbol exported by the module.
+ * This parameter is optional and defaults to aName.
+ */
+ exports.defineLazyModuleGetter = function defineLazyModuleGetter(aObject, aName, aResource, aSymbol) {
+ this.defineLazyGetter(aObject, aName, function XPCU_moduleLambda() {
+ var temp = {};
+ Cu.import(aResource, temp);
+ return temp[aSymbol || aName];
+ });
+ };
+
+ var _require5 = __webpack_require__(71);
+
+ var NetUtil = _require5.NetUtil;
+
+ var _require6 = __webpack_require__(72);
+
+ var TextDecoder = _require6.TextDecoder;
+ var OS = _require6.OS;
+
+
+ var NetworkHelper = __webpack_require__(73);
+
+ /**
+ * Performs a request to load the desired URL and returns a promise.
+ *
+ * @param aURL String
+ * The URL we will request.
+ * @param aOptions Object
+ * An object with the following optional properties:
+ * - loadFromCache: if false, will bypass the cache and
+ * always load fresh from the network (default: true)
+ * - policy: the nsIContentPolicy type to apply when fetching the URL
+ * - window: the window to get the loadGroup from
+ * - charset: the charset to use if the channel doesn't provide one
+ * @returns Promise that resolves with an object with the following members on
+ * success:
+ * - content: the document at that URL, as a string,
+ * - contentType: the content type of the document
+ *
+ * If an error occurs, the promise is rejected with that error.
+ *
+ * XXX: It may be better to use nsITraceableChannel to get to the sources
+ * without relying on caching when we can (not for eval, etc.):
+ * http://www.softwareishard.com/blog/firebug/nsitraceablechannel-intercept-http-traffic/
+ */
+ function mainThreadFetch(aURL) {
+ var aOptions = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : { loadFromCache: true,
+ policy: Ci.nsIContentPolicy.TYPE_OTHER,
+ window: null,
+ charset: null };
+
+ // Create a channel.
+ var url = aURL.split(" -> ").pop();
+ var channel = void 0;
+ try {
+ channel = newChannelForURL(url, aOptions);
+ } catch (ex) {
+ return promise.reject(ex);
+ }
+
+ // Set the channel options.
+ channel.loadFlags = aOptions.loadFromCache ? channel.LOAD_FROM_CACHE : channel.LOAD_BYPASS_CACHE;
+
+ if (aOptions.window) {
+ // Respect private browsing.
+ channel.loadGroup = aOptions.window.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIWebNavigation).QueryInterface(Ci.nsIDocumentLoader).loadGroup;
+ }
+
+ var deferred = promise.defer();
+ var onResponse = (stream, status, request) => {
+ if (!components.isSuccessCode(status)) {
+ deferred.reject(new Error(`Failed to fetch ${ url }. Code ${ status }.`));
+ return;
+ }
+
+ try {
+ // We cannot use NetUtil to do the charset conversion as if charset
+ // information is not available and our default guess is wrong the method
+ // might fail and we lose the stream data. This means we can't fall back
+ // to using the locale default encoding (bug 1181345).
+
+ // Read and decode the data according to the locale default encoding.
+ var available = stream.available();
+ var source = NetUtil.readInputStreamToString(stream, available);
+ stream.close();
+
+ // If the channel or the caller has correct charset information, the
+ // content will be decoded correctly. If we have to fall back to UTF-8 and
+ // the guess is wrong, the conversion fails and convertToUnicode returns
+ // the input unmodified. Essentially we try to decode the data as UTF-8
+ // and if that fails, we use the locale specific default encoding. This is
+ // the best we can do if the source does not provide charset info.
+ var charset = channel.contentCharset || aOptions.charset || "UTF-8";
+ var unicodeSource = NetworkHelper.convertToUnicode(source, charset);
+
+ deferred.resolve({
+ content: unicodeSource,
+ contentType: request.contentType
+ });
+ } catch (ex) {
+ var uri = request.originalURI;
+ if (ex.name === "NS_BASE_STREAM_CLOSED" && uri instanceof Ci.nsIFileURL) {
+ // Empty files cause NS_BASE_STREAM_CLOSED exception. Use OS.File to
+ // differentiate between empty files and other errors (bug 1170864).
+ // This can be removed when bug 982654 is fixed.
+
+ uri.QueryInterface(Ci.nsIFileURL);
+ var result = OS.File.read(uri.file.path).then(bytes => {
+ // Convert the bytearray to a String.
+ var decoder = new TextDecoder();
+ var content = decoder.decode(bytes);
+
+ // We can't detect the contentType without opening a channel
+ // and that failed already. This is the best we can do here.
+ return {
+ content,
+ contentType: "text/plain"
+ };
+ });
+
+ deferred.resolve(result);
+ } else {
+ deferred.reject(ex);
+ }
+ }
+ };
+
+ // Open the channel
+ try {
+ NetUtil.asyncFetch(channel, onResponse);
+ } catch (ex) {
+ return promise.reject(ex);
+ }
+
+ return deferred.promise;
+ }
+
+ /**
+ * Opens a channel for given URL. Tries a bit harder than NetUtil.newChannel.
+ *
+ * @param {String} url - The URL to open a channel for.
+ * @param {Object} options - The options object passed to @method fetch.
+ * @return {nsIChannel} - The newly created channel. Throws on failure.
+ */
+ function newChannelForURL(url, _ref) {
+ var policy = _ref.policy;
+
+ var channelOptions = {
+ contentPolicyType: policy,
+ loadUsingSystemPrincipal: true,
+ uri: url
+ };
+
+ try {
+ return NetUtil.newChannel(channelOptions);
+ } catch (e) {
+ // In the xpcshell tests, the script url is the absolute path of the test
+ // file, which will make a malformed URI error be thrown. Add the file
+ // scheme to see if it helps.
+ channelOptions.uri = "file://" + url;
+
+ return NetUtil.newChannel(channelOptions);
+ }
+ }
+
+ // Fetch is defined differently depending on whether we are on the main thread
+ // or a worker thread.
+ if (typeof WorkerGlobalScope === "undefined") {
+ // i.e. not in a worker
+ exports.fetch = mainThreadFetch;
+ } else {
+ // Services is not available in worker threads, nor is there any other way
+ // to fetch a URL. We need to enlist the help from the main thread here, by
+ // issuing an rpc request, to fetch the URL on our behalf.
+ exports.fetch = function (url, options) {
+ return rpc("fetch", url, options);
+ };
+ }
+
+ /**
+ * Returns a promise that is resolved or rejected when all promises have settled
+ * (resolved or rejected).
+ *
+ * This differs from Promise.all, which will reject immediately after the first
+ * rejection, instead of waiting for the remaining promises to settle.
+ *
+ * @param values
+ * Iterable of promises that may be pending, resolved, or rejected. When
+ * when all promises have settled (resolved or rejected), the returned
+ * promise will be resolved or rejected as well.
+ *
+ * @return A new promise that is fulfilled when all values have settled
+ * (resolved or rejected). Its resolution value will be an array of all
+ * resolved values in the given order, or undefined if values is an
+ * empty array. The reject reason will be forwarded from the first
+ * promise in the list of given promises to be rejected.
+ */
+ exports.settleAll = values => {
+ if (values === null || typeof values[Symbol.iterator] != "function") {
+ throw new Error("settleAll() expects an iterable.");
+ }
+
+ var deferred = promise.defer();
+
+ values = Array.isArray(values) ? values : [].concat(_toConsumableArray(values));
+ var countdown = values.length;
+ var resolutionValues = new Array(countdown);
+ var rejectionValue = void 0;
+ var rejectionOccurred = false;
+
+ if (!countdown) {
+ deferred.resolve(resolutionValues);
+ return deferred.promise;
+ }
+
+ function checkForCompletion() {
+ if (--countdown > 0) {
+ return;
+ }
+ if (!rejectionOccurred) {
+ deferred.resolve(resolutionValues);
+ } else {
+ deferred.reject(rejectionValue);
+ }
+ }
+
+ var _loop = function (i) {
+ var index = i;
+ var value = values[i];
+ var resolver = result => {
+ resolutionValues[index] = result;
+ checkForCompletion();
+ };
+ var rejecter = error => {
+ if (!rejectionOccurred) {
+ rejectionValue = error;
+ rejectionOccurred = true;
+ }
+ checkForCompletion();
+ };
+
+ if (value && typeof value.then == "function") {
+ value.then(resolver, rejecter);
+ } else {
+ // Given value is not a promise, forward it as a resolution value.
+ resolver(value);
+ }
+ };
+
+ for (var i = 0; i < values.length; i++) {
+ _loop(i);
+ }
+
+ return deferred.promise;
+ };
+
+ /**
+ * When the testing flag is set, various behaviors may be altered from
+ * production mode, typically to enable easier testing or enhanced debugging.
+ */
+ var testing = false;
+ Object.defineProperty(exports, "testing", {
+ get: function () {
+ return testing;
+ },
+ set: function (state) {
+ testing = state;
+ }
+ });
+
+ /**
+ * Open the file at the given path for reading.
+ *
+ * @param {String} filePath
+ *
+ * @returns Promise<nsIInputStream>
+ */
+ exports.openFileStream = function (filePath) {
+ return new Promise((resolve, reject) => {
+ var uri = NetUtil.newURI(new FileUtils.File(filePath));
+ NetUtil.asyncFetch({ uri, loadUsingSystemPrincipal: true }, (stream, result) => {
+ if (!components.isSuccessCode(result)) {
+ reject(new Error(`Could not open "${ filePath }": result = ${ result }`));
+ return;
+ }
+
+ resolve(stream);
+ });
+ });
+ };
+
+ exports.isGenerator = function (fn) {
+ if (typeof fn !== "function") {
+ return false;
+ }
+ var proto = Object.getPrototypeOf(fn);
+ if (!proto) {
+ return false;
+ }
+ var ctor = proto.constructor;
+ if (!ctor) {
+ return false;
+ }
+ return ctor.name == "GeneratorFunction";
+ };
+
+ exports.isPromise = function (p) {
+ return p && typeof p.then === "function";
+ };
+
+ /**
+ * Return true if `thing` is a SavedFrame, false otherwise.
+ */
+ exports.isSavedFrame = function (thing) {
+ return Object.prototype.toString.call(thing) === "[object SavedFrame]";
+ };
+
+/***/ },
+/* 69 */
+/***/ function(module, exports) {
+
+ /*
+ * A sham for https://dxr.mozilla.org/mozilla-central/source/toolkit/modules/FileUtils.jsm
+ */
+
+/***/ },
+/* 70 */
+/***/ function(module, exports) {
+
+ /*
+ * A sham for https://dxr.mozilla.org/mozilla-central/source/toolkit/modules/AppConstants.jsm
+ */
+
+ module.exports = { AppConstants: {} };
+
+/***/ },
+/* 71 */
+/***/ function(module, exports) {
+
+ /*
+ * A sham for https://dxr.mozilla.org/mozilla-central/source/netwerk/base/NetUtil.jsm
+ */
+
+/***/ },
+/* 72 */
+/***/ function(module, exports) {
+
+ /*
+ * A sham for https://dxr.mozilla.org/mozilla-central/source/toolkit/components/osfile/osfile.jsm
+ */
+
+/***/ },
+/* 73 */
+/***/ function(module, exports, __webpack_require__) {
+
+ /* vim:set ts=2 sw=2 sts=2 et: */
+ /*
+ * Software License Agreement (BSD License)
+ *
+ * Copyright (c) 2007, Parakey Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use of this software in source and binary forms, with or without modification,
+ * are permitted provided that the following conditions are met:
+ *
+ * * Redistributions of source code must retain the above
+ * copyright notice, this list of conditions and the
+ * following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the
+ * following disclaimer in the documentation and/or other
+ * materials provided with the distribution.
+ *
+ * * Neither the name of Parakey Inc. nor the names of its
+ * contributors may be used to endorse or promote products
+ * derived from this software without specific prior
+ * written permission of Parakey Inc.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
+ * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
+ * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+ * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+ /*
+ * Creator:
+ * Joe Hewitt
+ * Contributors
+ * John J. Barton (IBM Almaden)
+ * Jan Odvarko (Mozilla Corp.)
+ * Max Stepanov (Aptana Inc.)
+ * Rob Campbell (Mozilla Corp.)
+ * Hans Hillen (Paciello Group, Mozilla)
+ * Curtis Bartley (Mozilla Corp.)
+ * Mike Collins (IBM Almaden)
+ * Kevin Decker
+ * Mike Ratcliffe (Comartis AG)
+ * Hernan Rodríguez Colmeiro
+ * Austin Andrews
+ * Christoph Dorn
+ * Steven Roussey (AppCenter Inc, Network54)
+ * Mihai Sucan (Mozilla Corp.)
+ */
+
+ "use strict";
+
+ var _require = __webpack_require__(61);
+
+ var components = _require.components;
+ var Cc = _require.Cc;
+ var Ci = _require.Ci;
+ var Cu = _require.Cu;
+
+ var _require2 = __webpack_require__(71);
+
+ var NetUtil = _require2.NetUtil;
+
+ var DevToolsUtils = __webpack_require__(68);
+
+ // The cache used in the `nsIURL` function.
+ var gNSURLStore = new Map();
+
+ /**
+ * Helper object for networking stuff.
+ *
+ * Most of the following functions have been taken from the Firebug source. They
+ * have been modified to match the Firefox coding rules.
+ */
+ var NetworkHelper = {
+ /**
+ * Converts aText with a given aCharset to unicode.
+ *
+ * @param string aText
+ * Text to convert.
+ * @param string aCharset
+ * Charset to convert the text to.
+ * @returns string
+ * Converted text.
+ */
+ convertToUnicode: function NH_convertToUnicode(aText, aCharset) {
+ var conv = Cc("@mozilla.org/intl/scriptableunicodeconverter").createInstance(Ci.nsIScriptableUnicodeConverter);
+ try {
+ conv.charset = aCharset || "UTF-8";
+ return conv.ConvertToUnicode(aText);
+ } catch (ex) {
+ return aText;
+ }
+ },
+
+ /**
+ * Reads all available bytes from aStream and converts them to aCharset.
+ *
+ * @param nsIInputStream aStream
+ * @param string aCharset
+ * @returns string
+ * UTF-16 encoded string based on the content of aStream and aCharset.
+ */
+ readAndConvertFromStream: function NH_readAndConvertFromStream(aStream, aCharset) {
+ var text = null;
+ try {
+ text = NetUtil.readInputStreamToString(aStream, aStream.available());
+ return this.convertToUnicode(text, aCharset);
+ } catch (err) {
+ return text;
+ }
+ },
+
+ /**
+ * Reads the posted text from aRequest.
+ *
+ * @param nsIHttpChannel aRequest
+ * @param string aCharset
+ * The content document charset, used when reading the POSTed data.
+ * @returns string or null
+ * Returns the posted string if it was possible to read from aRequest
+ * otherwise null.
+ */
+ readPostTextFromRequest: function NH_readPostTextFromRequest(aRequest, aCharset) {
+ if (aRequest instanceof Ci.nsIUploadChannel) {
+ var iStream = aRequest.uploadStream;
+
+ var isSeekableStream = false;
+ if (iStream instanceof Ci.nsISeekableStream) {
+ isSeekableStream = true;
+ }
+
+ var prevOffset = void 0;
+ if (isSeekableStream) {
+ prevOffset = iStream.tell();
+ iStream.seek(Ci.nsISeekableStream.NS_SEEK_SET, 0);
+ }
+
+ // Read data from the stream.
+ var text = this.readAndConvertFromStream(iStream, aCharset);
+
+ // Seek locks the file, so seek to the beginning only if necko hasn't
+ // read it yet, since necko doesn't seek to 0 before reading (at lest
+ // not till 459384 is fixed).
+ if (isSeekableStream && prevOffset == 0) {
+ iStream.seek(Ci.nsISeekableStream.NS_SEEK_SET, 0);
+ }
+ return text;
+ }
+ return null;
+ },
+
+ /**
+ * Reads the posted text from the page's cache.
+ *
+ * @param nsIDocShell aDocShell
+ * @param string aCharset
+ * @returns string or null
+ * Returns the posted string if it was possible to read from
+ * aDocShell otherwise null.
+ */
+ readPostTextFromPage: function NH_readPostTextFromPage(aDocShell, aCharset) {
+ var webNav = aDocShell.QueryInterface(Ci.nsIWebNavigation);
+ return this.readPostTextFromPageViaWebNav(webNav, aCharset);
+ },
+
+ /**
+ * Reads the posted text from the page's cache, given an nsIWebNavigation
+ * object.
+ *
+ * @param nsIWebNavigation aWebNav
+ * @param string aCharset
+ * @returns string or null
+ * Returns the posted string if it was possible to read from
+ * aWebNav, otherwise null.
+ */
+ readPostTextFromPageViaWebNav: function NH_readPostTextFromPageViaWebNav(aWebNav, aCharset) {
+ if (aWebNav instanceof Ci.nsIWebPageDescriptor) {
+ var descriptor = aWebNav.currentDescriptor;
+
+ if (descriptor instanceof Ci.nsISHEntry && descriptor.postData && descriptor instanceof Ci.nsISeekableStream) {
+ descriptor.seek(NS_SEEK_SET, 0);
+
+ return this.readAndConvertFromStream(descriptor, aCharset);
+ }
+ }
+ return null;
+ },
+
+ /**
+ * Gets the web appId that is associated with aRequest.
+ *
+ * @param nsIHttpChannel aRequest
+ * @returns number|null
+ * The appId for the given request, if available.
+ */
+ getAppIdForRequest: function NH_getAppIdForRequest(aRequest) {
+ try {
+ return this.getRequestLoadContext(aRequest).appId;
+ } catch (ex) {
+ // request loadContent is not always available.
+ }
+ return null;
+ },
+
+ /**
+ * Gets the topFrameElement that is associated with aRequest. This
+ * works in single-process and multiprocess contexts. It may cross
+ * the content/chrome boundary.
+ *
+ * @param nsIHttpChannel aRequest
+ * @returns nsIDOMElement|null
+ * The top frame element for the given request.
+ */
+ getTopFrameForRequest: function NH_getTopFrameForRequest(aRequest) {
+ try {
+ return this.getRequestLoadContext(aRequest).topFrameElement;
+ } catch (ex) {
+ // request loadContent is not always available.
+ }
+ return null;
+ },
+
+ /**
+ * Gets the nsIDOMWindow that is associated with aRequest.
+ *
+ * @param nsIHttpChannel aRequest
+ * @returns nsIDOMWindow or null
+ */
+ getWindowForRequest: function NH_getWindowForRequest(aRequest) {
+ try {
+ return this.getRequestLoadContext(aRequest).associatedWindow;
+ } catch (ex) {
+ // TODO: bug 802246 - getWindowForRequest() throws on b2g: there is no
+ // associatedWindow property.
+ }
+ return null;
+ },
+
+ /**
+ * Gets the nsILoadContext that is associated with aRequest.
+ *
+ * @param nsIHttpChannel aRequest
+ * @returns nsILoadContext or null
+ */
+ getRequestLoadContext: function NH_getRequestLoadContext(aRequest) {
+ try {
+ return aRequest.notificationCallbacks.getInterface(Ci.nsILoadContext);
+ } catch (ex) {}
+
+ try {
+ return aRequest.loadGroup.notificationCallbacks.getInterface(Ci.nsILoadContext);
+ } catch (ex) {}
+
+ return null;
+ },
+
+ /**
+ * Determines whether the request has been made for the top level document.
+ *
+ * @param nsIHttpChannel aRequest
+ * @returns Boolean True if the request represents the top level document.
+ */
+ isTopLevelLoad: function (aRequest) {
+ if (aRequest instanceof Ci.nsIChannel) {
+ var loadInfo = aRequest.loadInfo;
+ if (loadInfo && loadInfo.parentOuterWindowID == loadInfo.outerWindowID) {
+ return aRequest.loadFlags & Ci.nsIChannel.LOAD_DOCUMENT_URI;
+ }
+ }
+
+ return false;
+ },
+
+ /**
+ * Loads the content of aUrl from the cache.
+ *
+ * @param string aUrl
+ * URL to load the cached content for.
+ * @param string aCharset
+ * Assumed charset of the cached content. Used if there is no charset
+ * on the channel directly.
+ * @param function aCallback
+ * Callback that is called with the loaded cached content if available
+ * or null if something failed while getting the cached content.
+ */
+ loadFromCache: function NH_loadFromCache(aUrl, aCharset, aCallback) {
+ var channel = NetUtil.newChannel({ uri: aUrl, loadUsingSystemPrincipal: true });
+
+ // Ensure that we only read from the cache and not the server.
+ channel.loadFlags = Ci.nsIRequest.LOAD_FROM_CACHE | Ci.nsICachingChannel.LOAD_ONLY_FROM_CACHE | Ci.nsICachingChannel.LOAD_BYPASS_LOCAL_CACHE_IF_BUSY;
+
+ NetUtil.asyncFetch(channel, (aInputStream, aStatusCode, aRequest) => {
+ if (!components.isSuccessCode(aStatusCode)) {
+ aCallback(null);
+ return;
+ }
+
+ // Try to get the encoding from the channel. If there is none, then use
+ // the passed assumed aCharset.
+ var aChannel = aRequest.QueryInterface(Ci.nsIChannel);
+ var contentCharset = aChannel.contentCharset || aCharset;
+
+ // Read the content of the stream using contentCharset as encoding.
+ aCallback(this.readAndConvertFromStream(aInputStream, contentCharset));
+ });
+ },
+
+ /**
+ * Parse a raw Cookie header value.
+ *
+ * @param string aHeader
+ * The raw Cookie header value.
+ * @return array
+ * Array holding an object for each cookie. Each object holds the
+ * following properties: name and value.
+ */
+ parseCookieHeader: function NH_parseCookieHeader(aHeader) {
+ var cookies = aHeader.split(";");
+ var result = [];
+
+ cookies.forEach(function (aCookie) {
+ var equal = aCookie.indexOf("=");
+ var name = aCookie.substr(0, equal);
+ var value = aCookie.substr(equal + 1);
+ result.push({ name: unescape(name.trim()),
+ value: unescape(value.trim()) });
+ });
+
+ return result;
+ },
+
+ /**
+ * Parse a raw Set-Cookie header value.
+ *
+ * @param string aHeader
+ * The raw Set-Cookie header value.
+ * @return array
+ * Array holding an object for each cookie. Each object holds the
+ * following properties: name, value, secure (boolean), httpOnly
+ * (boolean), path, domain and expires (ISO date string).
+ */
+ parseSetCookieHeader: function NH_parseSetCookieHeader(aHeader) {
+ var rawCookies = aHeader.split(/\r\n|\n|\r/);
+ var cookies = [];
+
+ rawCookies.forEach(function (aCookie) {
+ var equal = aCookie.indexOf("=");
+ var name = unescape(aCookie.substr(0, equal).trim());
+ var parts = aCookie.substr(equal + 1).split(";");
+ var value = unescape(parts.shift().trim());
+
+ var cookie = { name: name, value: value };
+
+ parts.forEach(function (aPart) {
+ var part = aPart.trim();
+ if (part.toLowerCase() == "secure") {
+ cookie.secure = true;
+ } else if (part.toLowerCase() == "httponly") {
+ cookie.httpOnly = true;
+ } else if (part.indexOf("=") > -1) {
+ var pair = part.split("=");
+ pair[0] = pair[0].toLowerCase();
+ if (pair[0] == "path" || pair[0] == "domain") {
+ cookie[pair[0]] = pair[1];
+ } else if (pair[0] == "expires") {
+ try {
+ pair[1] = pair[1].replace(/-/g, ' ');
+ cookie.expires = new Date(pair[1]).toISOString();
+ } catch (ex) {}
+ }
+ }
+ });
+
+ cookies.push(cookie);
+ });
+
+ return cookies;
+ },
+
+ // This is a list of all the mime category maps jviereck could find in the
+ // firebug code base.
+ mimeCategoryMap: {
+ "text/plain": "txt",
+ "text/html": "html",
+ "text/xml": "xml",
+ "text/xsl": "txt",
+ "text/xul": "txt",
+ "text/css": "css",
+ "text/sgml": "txt",
+ "text/rtf": "txt",
+ "text/x-setext": "txt",
+ "text/richtext": "txt",
+ "text/javascript": "js",
+ "text/jscript": "txt",
+ "text/tab-separated-values": "txt",
+ "text/rdf": "txt",
+ "text/xif": "txt",
+ "text/ecmascript": "js",
+ "text/vnd.curl": "txt",
+ "text/x-json": "json",
+ "text/x-js": "txt",
+ "text/js": "txt",
+ "text/vbscript": "txt",
+ "view-source": "txt",
+ "view-fragment": "txt",
+ "application/xml": "xml",
+ "application/xhtml+xml": "xml",
+ "application/atom+xml": "xml",
+ "application/rss+xml": "xml",
+ "application/vnd.mozilla.maybe.feed": "xml",
+ "application/vnd.mozilla.xul+xml": "xml",
+ "application/javascript": "js",
+ "application/x-javascript": "js",
+ "application/x-httpd-php": "txt",
+ "application/rdf+xml": "xml",
+ "application/ecmascript": "js",
+ "application/http-index-format": "txt",
+ "application/json": "json",
+ "application/x-js": "txt",
+ "multipart/mixed": "txt",
+ "multipart/x-mixed-replace": "txt",
+ "image/svg+xml": "svg",
+ "application/octet-stream": "bin",
+ "image/jpeg": "image",
+ "image/jpg": "image",
+ "image/gif": "image",
+ "image/png": "image",
+ "image/bmp": "image",
+ "application/x-shockwave-flash": "flash",
+ "video/x-flv": "flash",
+ "audio/mpeg3": "media",
+ "audio/x-mpeg-3": "media",
+ "video/mpeg": "media",
+ "video/x-mpeg": "media",
+ "audio/ogg": "media",
+ "application/ogg": "media",
+ "application/x-ogg": "media",
+ "application/x-midi": "media",
+ "audio/midi": "media",
+ "audio/x-mid": "media",
+ "audio/x-midi": "media",
+ "music/crescendo": "media",
+ "audio/wav": "media",
+ "audio/x-wav": "media",
+ "text/json": "json",
+ "application/x-json": "json",
+ "application/json-rpc": "json",
+ "application/x-web-app-manifest+json": "json",
+ "application/manifest+json": "json"
+ },
+
+ /**
+ * Check if the given MIME type is a text-only MIME type.
+ *
+ * @param string aMimeType
+ * @return boolean
+ */
+ isTextMimeType: function NH_isTextMimeType(aMimeType) {
+ if (aMimeType.indexOf("text/") == 0) {
+ return true;
+ }
+
+ // XML and JSON often come with custom MIME types, so in addition to the
+ // standard "application/xml" and "application/json", we also look for
+ // variants like "application/x-bigcorp+xml". For JSON we allow "+json" and
+ // "-json" as suffixes.
+ if (/^application\/\w+(?:[\.-]\w+)*(?:\+xml|[-+]json)$/.test(aMimeType)) {
+ return true;
+ }
+
+ var category = this.mimeCategoryMap[aMimeType] || null;
+ switch (category) {
+ case "txt":
+ case "js":
+ case "json":
+ case "css":
+ case "html":
+ case "svg":
+ case "xml":
+ return true;
+
+ default:
+ return false;
+ }
+ },
+
+ /**
+ * Takes a securityInfo object of nsIRequest, the nsIRequest itself and
+ * extracts security information from them.
+ *
+ * @param object securityInfo
+ * The securityInfo object of a request. If null channel is assumed
+ * to be insecure.
+ * @param object httpActivity
+ * The httpActivity object for the request with at least members
+ * { private, hostname }.
+ *
+ * @return object
+ * Returns an object containing following members:
+ * - state: The security of the connection used to fetch this
+ * request. Has one of following string values:
+ * * "insecure": the connection was not secure (only http)
+ * * "weak": the connection has minor security issues
+ * * "broken": secure connection failed (e.g. expired cert)
+ * * "secure": the connection was properly secured.
+ * If state == broken:
+ * - errorMessage: full error message from nsITransportSecurityInfo.
+ * If state == secure:
+ * - protocolVersion: one of TLSv1, TLSv1.1, TLSv1.2.
+ * - cipherSuite: the cipher suite used in this connection.
+ * - cert: information about certificate used in this connection.
+ * See parseCertificateInfo for the contents.
+ * - hsts: true if host uses Strict Transport Security, false otherwise
+ * - hpkp: true if host uses Public Key Pinning, false otherwise
+ * If state == weak: Same as state == secure and
+ * - weaknessReasons: list of reasons that cause the request to be
+ * considered weak. See getReasonsForWeakness.
+ */
+ parseSecurityInfo: function NH_parseSecurityInfo(securityInfo, httpActivity) {
+ var info = {
+ state: "insecure"
+ };
+
+ // The request did not contain any security info.
+ if (!securityInfo) {
+ return info;
+ }
+
+ /**
+ * Different scenarios to consider here and how they are handled:
+ * - request is HTTP, the connection is not secure
+ * => securityInfo is null
+ * => state === "insecure"
+ *
+ * - request is HTTPS, the connection is secure
+ * => .securityState has STATE_IS_SECURE flag
+ * => state === "secure"
+ *
+ * - request is HTTPS, the connection has security issues
+ * => .securityState has STATE_IS_INSECURE flag
+ * => .errorCode is an NSS error code.
+ * => state === "broken"
+ *
+ * - request is HTTPS, the connection was terminated before the security
+ * could be validated
+ * => .securityState has STATE_IS_INSECURE flag
+ * => .errorCode is NOT an NSS error code.
+ * => .errorMessage is not available.
+ * => state === "insecure"
+ *
+ * - request is HTTPS but it uses a weak cipher or old protocol, see
+ * http://hg.mozilla.org/mozilla-central/annotate/def6ed9d1c1a/
+ * security/manager/ssl/nsNSSCallbacks.cpp#l1233
+ * - request is mixed content (which makes no sense whatsoever)
+ * => .securityState has STATE_IS_BROKEN flag
+ * => .errorCode is NOT an NSS error code
+ * => .errorMessage is not available
+ * => state === "weak"
+ */
+
+ securityInfo.QueryInterface(Ci.nsITransportSecurityInfo);
+ securityInfo.QueryInterface(Ci.nsISSLStatusProvider);
+
+ var wpl = Ci.nsIWebProgressListener;
+ var NSSErrorsService = Cc['@mozilla.org/nss_errors_service;1'].getService(Ci.nsINSSErrorsService);
+ var SSLStatus = securityInfo.SSLStatus;
+ if (!NSSErrorsService.isNSSErrorCode(securityInfo.errorCode)) {
+ var state = securityInfo.securityState;
+
+ var uri = null;
+ if (httpActivity.channel && httpActivity.channel.URI) {
+ uri = httpActivity.channel.URI;
+ }
+ if (uri && !uri.schemeIs("https") && !uri.schemeIs("wss")) {
+ // it is not enough to look at the transport security info - schemes other than
+ // https and wss are subject to downgrade/etc at the scheme level and should
+ // always be considered insecure
+ info.state = "insecure";
+ } else if (state & wpl.STATE_IS_SECURE) {
+ // The connection is secure if the scheme is sufficient
+ info.state = "secure";
+ } else if (state & wpl.STATE_IS_BROKEN) {
+ // The connection is not secure, there was no error but there's some
+ // minor security issues.
+ info.state = "weak";
+ info.weaknessReasons = this.getReasonsForWeakness(state);
+ } else if (state & wpl.STATE_IS_INSECURE) {
+ // This was most likely an https request that was aborted before
+ // validation. Return info as info.state = insecure.
+ return info;
+ } else {
+ DevToolsUtils.reportException("NetworkHelper.parseSecurityInfo", "Security state " + state + " has no known STATE_IS_* flags.");
+ return info;
+ }
+
+ // Cipher suite.
+ info.cipherSuite = SSLStatus.cipherName;
+
+ // Protocol version.
+ info.protocolVersion = this.formatSecurityProtocol(SSLStatus.protocolVersion);
+
+ // Certificate.
+ info.cert = this.parseCertificateInfo(SSLStatus.serverCert);
+
+ // HSTS and HPKP if available.
+ if (httpActivity.hostname) {
+ var sss = Cc("@mozilla.org/ssservice;1").getService(Ci.nsISiteSecurityService);
+
+ // SiteSecurityService uses different storage if the channel is
+ // private. Thus we must give isSecureHost correct flags or we
+ // might get incorrect results.
+ var flags = httpActivity.private ? Ci.nsISocketProvider.NO_PERMANENT_STORAGE : 0;
+
+ var host = httpActivity.hostname;
+
+ info.hsts = sss.isSecureHost(sss.HEADER_HSTS, host, flags);
+ info.hpkp = sss.isSecureHost(sss.HEADER_HPKP, host, flags);
+ } else {
+ DevToolsUtils.reportException("NetworkHelper.parseSecurityInfo", "Could not get HSTS/HPKP status as hostname is not available.");
+ info.hsts = false;
+ info.hpkp = false;
+ }
+ } else {
+ // The connection failed.
+ info.state = "broken";
+ info.errorMessage = securityInfo.errorMessage;
+ }
+
+ return info;
+ },
+
+ /**
+ * Takes an nsIX509Cert and returns an object with certificate information.
+ *
+ * @param nsIX509Cert cert
+ * The certificate to extract the information from.
+ * @return object
+ * An object with following format:
+ * {
+ * subject: { commonName, organization, organizationalUnit },
+ * issuer: { commonName, organization, organizationUnit },
+ * validity: { start, end },
+ * fingerprint: { sha1, sha256 }
+ * }
+ */
+ parseCertificateInfo: function NH_parseCertifificateInfo(cert) {
+ var info = {};
+ if (cert) {
+ info.subject = {
+ commonName: cert.commonName,
+ organization: cert.organization,
+ organizationalUnit: cert.organizationalUnit
+ };
+
+ info.issuer = {
+ commonName: cert.issuerCommonName,
+ organization: cert.issuerOrganization,
+ organizationUnit: cert.issuerOrganizationUnit
+ };
+
+ info.validity = {
+ start: cert.validity.notBeforeLocalDay,
+ end: cert.validity.notAfterLocalDay
+ };
+
+ info.fingerprint = {
+ sha1: cert.sha1Fingerprint,
+ sha256: cert.sha256Fingerprint
+ };
+ } else {
+ DevToolsUtils.reportException("NetworkHelper.parseCertificateInfo", "Secure connection established without certificate.");
+ }
+
+ return info;
+ },
+
+ /**
+ * Takes protocolVersion of SSLStatus object and returns human readable
+ * description.
+ *
+ * @param Number version
+ * One of nsISSLStatus version constants.
+ * @return string
+ * One of TLSv1, TLSv1.1, TLSv1.2 if @param version is valid,
+ * Unknown otherwise.
+ */
+ formatSecurityProtocol: function NH_formatSecurityProtocol(version) {
+ switch (version) {
+ case Ci.nsISSLStatus.TLS_VERSION_1:
+ return "TLSv1";
+ case Ci.nsISSLStatus.TLS_VERSION_1_1:
+ return "TLSv1.1";
+ case Ci.nsISSLStatus.TLS_VERSION_1_2:
+ return "TLSv1.2";
+ default:
+ DevToolsUtils.reportException("NetworkHelper.formatSecurityProtocol", "protocolVersion " + version + " is unknown.");
+ return "Unknown";
+ }
+ },
+
+ /**
+ * Takes the securityState bitfield and returns reasons for weak connection
+ * as an array of strings.
+ *
+ * @param Number state
+ * nsITransportSecurityInfo.securityState.
+ *
+ * @return Array[String]
+ * List of weakness reasons. A subset of { cipher } where
+ * * cipher: The cipher suite is consireded to be weak (RC4).
+ */
+ getReasonsForWeakness: function NH_getReasonsForWeakness(state) {
+ var wpl = Ci.nsIWebProgressListener;
+
+ // If there's non-fatal security issues the request has STATE_IS_BROKEN
+ // flag set. See http://hg.mozilla.org/mozilla-central/file/44344099d119
+ // /security/manager/ssl/nsNSSCallbacks.cpp#l1233
+ var reasons = [];
+
+ if (state & wpl.STATE_IS_BROKEN) {
+ var isCipher = state & wpl.STATE_USES_WEAK_CRYPTO;
+
+ if (isCipher) {
+ reasons.push("cipher");
+ }
+
+ if (!isCipher) {
+ DevToolsUtils.reportException("NetworkHelper.getReasonsForWeakness", "STATE_IS_BROKEN without a known reason. Full state was: " + state);
+ }
+ }
+
+ return reasons;
+ },
+
+ /**
+ * Parse a url's query string into its components
+ *
+ * @param string aQueryString
+ * The query part of a url
+ * @return array
+ * Array of query params {name, value}
+ */
+ parseQueryString: function (aQueryString) {
+ // Make sure there's at least one param available.
+ // Be careful here, params don't necessarily need to have values, so
+ // no need to verify the existence of a "=".
+ if (!aQueryString) {
+ return;
+ }
+
+ // Turn the params string into an array containing { name: value } tuples.
+ var paramsArray = aQueryString.replace(/^[?&]/, "").split("&").map(e => {
+ var param = e.split("=");
+ return {
+ name: param[0] ? NetworkHelper.convertToUnicode(unescape(param[0])) : "",
+ value: param[1] ? NetworkHelper.convertToUnicode(unescape(param[1])) : ""
+ };
+ });
+
+ return paramsArray;
+ },
+
+ /**
+ * Helper for getting an nsIURL instance out of a string.
+ */
+ nsIURL: function (aUrl) {
+ var aStore = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : gNSURLStore;
+
+ if (aStore.has(aUrl)) {
+ return aStore.get(aUrl);
+ }
+
+ var uri = Services.io.newURI(aUrl, null, null).QueryInterface(Ci.nsIURL);
+ aStore.set(aUrl, uri);
+ return uri;
+ }
+ };
+
+ for (var prop of Object.getOwnPropertyNames(NetworkHelper)) {
+ exports[prop] = NetworkHelper[prop];
+ }
+
+/***/ },
+/* 74 */
+/***/ function(module, exports, __webpack_require__) {
+
+ /* 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/. */
+
+ var _require = __webpack_require__(61);
+
+ var Ci = _require.Ci;
+ var Cc = _require.Cc;
+ var Cr = _require.Cr;
+ var CC = _require.CC;
+
+ var _require2 = __webpack_require__(30);
+
+ var Services = _require2.Services;
+
+ var _require3 = __webpack_require__(68);
+
+ var dumpv = _require3.dumpv;
+
+ var EventEmitter = __webpack_require__(60);
+ var promise = __webpack_require__(66);
+
+ var IOUtil = Cc("@mozilla.org/io-util;1").getService(Ci.nsIIOUtil);
+
+ var ScriptableInputStream = CC("@mozilla.org/scriptableinputstream;1", "nsIScriptableInputStream", "init");
+
+ var BUFFER_SIZE = 0x8000;
+
+ /**
+ * This helper function (and its companion object) are used by bulk senders and
+ * receivers to read and write data in and out of other streams. Functions that
+ * make use of this tool are passed to callers when it is time to read or write
+ * bulk data. It is highly recommended to use these copier functions instead of
+ * the stream directly because the copier enforces the agreed upon length.
+ * Since bulk mode reuses an existing stream, the sender and receiver must write
+ * and read exactly the agreed upon amount of data, or else the entire transport
+ * will be left in a invalid state. Additionally, other methods of stream
+ * copying (such as NetUtil.asyncCopy) close the streams involved, which would
+ * terminate the debugging transport, and so it is avoided here.
+ *
+ * Overall, this *works*, but clearly the optimal solution would be able to just
+ * use the streams directly. If it were possible to fully implement
+ * nsIInputStream / nsIOutputStream in JS, wrapper streams could be created to
+ * enforce the length and avoid closing, and consumers could use familiar stream
+ * utilities like NetUtil.asyncCopy.
+ *
+ * The function takes two async streams and copies a precise number of bytes
+ * from one to the other. Copying begins immediately, but may complete at some
+ * future time depending on data size. Use the returned promise to know when
+ * it's complete.
+ *
+ * @param input nsIAsyncInputStream
+ * The stream to copy from.
+ * @param output nsIAsyncOutputStream
+ * The stream to copy to.
+ * @param length Integer
+ * The amount of data that needs to be copied.
+ * @return Promise
+ * The promise is resolved when copying completes or rejected if any
+ * (unexpected) errors occur.
+ */
+ function copyStream(input, output, length) {
+ var copier = new StreamCopier(input, output, length);
+ return copier.copy();
+ }
+
+ function StreamCopier(input, output, length) {
+ EventEmitter.decorate(this);
+ this._id = StreamCopier._nextId++;
+ this.input = input;
+ // Save off the base output stream, since we know it's async as we've required
+ this.baseAsyncOutput = output;
+ if (IOUtil.outputStreamIsBuffered(output)) {
+ this.output = output;
+ } else {
+ this.output = Cc("@mozilla.org/network/buffered-output-stream;1").createInstance(Ci.nsIBufferedOutputStream);
+ this.output.init(output, BUFFER_SIZE);
+ }
+ this._length = length;
+ this._amountLeft = length;
+ this._deferred = promise.defer();
+
+ this._copy = this._copy.bind(this);
+ this._flush = this._flush.bind(this);
+ this._destroy = this._destroy.bind(this);
+
+ // Copy promise's then method up to this object.
+ // Allows the copier to offer a promise interface for the simple succeed or
+ // fail scenarios, but also emit events (due to the EventEmitter) for other
+ // states, like progress.
+ this.then = this._deferred.promise.then.bind(this._deferred.promise);
+ this.then(this._destroy, this._destroy);
+
+ // Stream ready callback starts as |_copy|, but may switch to |_flush| at end
+ // if flushing would block the output stream.
+ this._streamReadyCallback = this._copy;
+ }
+ StreamCopier._nextId = 0;
+
+ StreamCopier.prototype = {
+
+ copy: function () {
+ // Dispatch to the next tick so that it's possible to attach a progress
+ // event listener, even for extremely fast copies (like when testing).
+ Services.tm.currentThread.dispatch(() => {
+ try {
+ this._copy();
+ } catch (e) {
+ this._deferred.reject(e);
+ }
+ }, 0);
+ return this;
+ },
+
+ _copy: function () {
+ var bytesAvailable = this.input.available();
+ var amountToCopy = Math.min(bytesAvailable, this._amountLeft);
+ this._debug("Trying to copy: " + amountToCopy);
+
+ var bytesCopied = void 0;
+ try {
+ bytesCopied = this.output.writeFrom(this.input, amountToCopy);
+ } catch (e) {
+ if (e.result == Cr.NS_BASE_STREAM_WOULD_BLOCK) {
+ this._debug("Base stream would block, will retry");
+ this._debug("Waiting for output stream");
+ this.baseAsyncOutput.asyncWait(this, 0, 0, Services.tm.currentThread);
+ return;
+ } else {
+ throw e;
+ }
+ }
+
+ this._amountLeft -= bytesCopied;
+ this._debug("Copied: " + bytesCopied + ", Left: " + this._amountLeft);
+ this._emitProgress();
+
+ if (this._amountLeft === 0) {
+ this._debug("Copy done!");
+ this._flush();
+ return;
+ }
+
+ this._debug("Waiting for input stream");
+ this.input.asyncWait(this, 0, 0, Services.tm.currentThread);
+ },
+
+ _emitProgress: function () {
+ this.emit("progress", {
+ bytesSent: this._length - this._amountLeft,
+ totalBytes: this._length
+ });
+ },
+
+ _flush: function () {
+ try {
+ this.output.flush();
+ } catch (e) {
+ if (e.result == Cr.NS_BASE_STREAM_WOULD_BLOCK || e.result == Cr.NS_ERROR_FAILURE) {
+ this._debug("Flush would block, will retry");
+ this._streamReadyCallback = this._flush;
+ this._debug("Waiting for output stream");
+ this.baseAsyncOutput.asyncWait(this, 0, 0, Services.tm.currentThread);
+ return;
+ } else {
+ throw e;
+ }
+ }
+ this._deferred.resolve();
+ },
+
+ _destroy: function () {
+ this._destroy = null;
+ this._copy = null;
+ this._flush = null;
+ this.input = null;
+ this.output = null;
+ },
+
+ // nsIInputStreamCallback
+ onInputStreamReady: function () {
+ this._streamReadyCallback();
+ },
+
+ // nsIOutputStreamCallback
+ onOutputStreamReady: function () {
+ this._streamReadyCallback();
+ },
+
+ _debug: function (msg) {
+ // Prefix logs with the copier ID, which makes logs much easier to
+ // understand when several copiers are running simultaneously
+ dumpv("Copier: " + this._id + " " + msg);
+ }
+
+ };
+
+ /**
+ * Read from a stream, one byte at a time, up to the next |delimiter|
+ * character, but stopping if we've read |count| without finding it. Reading
+ * also terminates early if there are less than |count| bytes available on the
+ * stream. In that case, we only read as many bytes as the stream currently has
+ * to offer.
+ * TODO: This implementation could be removed if bug 984651 is fixed, which
+ * provides a native version of the same idea.
+ * @param stream nsIInputStream
+ * The input stream to read from.
+ * @param delimiter string
+ * The character we're trying to find.
+ * @param count integer
+ * The max number of characters to read while searching.
+ * @return string
+ * The data collected. If the delimiter was found, this string will
+ * end with it.
+ */
+ function delimitedRead(stream, delimiter, count) {
+ dumpv("Starting delimited read for " + delimiter + " up to " + count + " bytes");
+
+ var scriptableStream = void 0;
+ if (stream.readBytes) {
+ scriptableStream = stream;
+ } else {
+ scriptableStream = new ScriptableInputStream(stream);
+ }
+
+ var data = "";
+
+ // Don't exceed what's available on the stream
+ count = Math.min(count, stream.available());
+
+ if (count <= 0) {
+ return data;
+ }
+
+ var char = void 0;
+ while (char !== delimiter && count > 0) {
+ char = scriptableStream.readBytes(1);
+ count--;
+ data += char;
+ }
+
+ return data;
+ }
+
+ module.exports = {
+ copyStream: copyStream,
+ delimitedRead: delimitedRead
+ };
+
+/***/ },
+/* 75 */
+/***/ function(module, exports, __webpack_require__) {
+
+ /* 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";
+
+ /**
+ * Packets contain read / write functionality for the different packet types
+ * supported by the debugging protocol, so that a transport can focus on
+ * delivery and queue management without worrying too much about the specific
+ * packet types.
+ *
+ * They are intended to be "one use only", so a new packet should be
+ * instantiated for each incoming or outgoing packet.
+ *
+ * A complete Packet type should expose at least the following:
+ * * read(stream, scriptableStream)
+ * Called when the input stream has data to read
+ * * write(stream)
+ * Called when the output stream is ready to write
+ * * get done()
+ * Returns true once the packet is done being read / written
+ * * destroy()
+ * Called to clean up at the end of use
+ */
+
+ var _require = __webpack_require__(61);
+
+ var Cc = _require.Cc;
+ var Ci = _require.Ci;
+ var Cu = _require.Cu;
+
+ var DevToolsUtils = __webpack_require__(68);
+ var dumpn = DevToolsUtils.dumpn;
+ var dumpv = DevToolsUtils.dumpv;
+
+ var StreamUtils = __webpack_require__(74);
+ var promise = __webpack_require__(66);
+
+ /*DevToolsUtils.defineLazyGetter(this, "unicodeConverter", () => {
+ const unicodeConverter = Cc("@mozilla.org/intl/scriptableunicodeconverter")
+ .createInstance(Ci.nsIScriptableUnicodeConverter);
+ unicodeConverter.charset = "UTF-8";
+ return unicodeConverter;
+ });*/
+ var utf8 = __webpack_require__(76);
+
+ // The transport's previous check ensured the header length did not exceed 20
+ // characters. Here, we opt for the somewhat smaller, but still large limit of
+ // 1 TiB.
+ var PACKET_LENGTH_MAX = Math.pow(2, 40);
+
+ /**
+ * A generic Packet processing object (extended by two subtypes below).
+ */
+ function Packet(transport) {
+ this._transport = transport;
+ this._length = 0;
+ }
+
+ /**
+ * Attempt to initialize a new Packet based on the incoming packet header we've
+ * received so far. We try each of the types in succession, trying JSON packets
+ * first since they are much more common.
+ * @param header string
+ * The packet header string to attempt parsing.
+ * @param transport DebuggerTransport
+ * The transport instance that will own the packet.
+ * @return Packet
+ * The parsed packet of the matching type, or null if no types matched.
+ */
+ Packet.fromHeader = function (header, transport) {
+ return JSONPacket.fromHeader(header, transport) || BulkPacket.fromHeader(header, transport);
+ };
+
+ Packet.prototype = {
+
+ get length() {
+ return this._length;
+ },
+
+ set length(length) {
+ if (length > PACKET_LENGTH_MAX) {
+ throw Error("Packet length " + length + " exceeds the max length of " + PACKET_LENGTH_MAX);
+ }
+ this._length = length;
+ },
+
+ destroy: function () {
+ this._transport = null;
+ }
+
+ };
+
+ exports.Packet = Packet;
+
+ /**
+ * With a JSON packet (the typical packet type sent via the transport), data is
+ * transferred as a JSON packet serialized into a string, with the string length
+ * prepended to the packet, followed by a colon ([length]:[packet]). The
+ * contents of the JSON packet are specified in the Remote Debugging Protocol
+ * specification.
+ * @param transport DebuggerTransport
+ * The transport instance that will own the packet.
+ */
+ function JSONPacket(transport) {
+ Packet.call(this, transport);
+ this._data = "";
+ this._done = false;
+ }
+
+ /**
+ * Attempt to initialize a new JSONPacket based on the incoming packet header
+ * we've received so far.
+ * @param header string
+ * The packet header string to attempt parsing.
+ * @param transport DebuggerTransport
+ * The transport instance that will own the packet.
+ * @return JSONPacket
+ * The parsed packet, or null if it's not a match.
+ */
+ JSONPacket.fromHeader = function (header, transport) {
+ var match = this.HEADER_PATTERN.exec(header);
+
+ if (!match) {
+ return null;
+ }
+
+ dumpv("Header matches JSON packet");
+ var packet = new JSONPacket(transport);
+ packet.length = +match[1];
+ return packet;
+ };
+
+ JSONPacket.HEADER_PATTERN = /^(\d+):$/;
+
+ JSONPacket.prototype = Object.create(Packet.prototype);
+
+ Object.defineProperty(JSONPacket.prototype, "object", {
+ /**
+ * Gets the object (not the serialized string) being read or written.
+ */
+ get: function () {
+ return this._object;
+ },
+
+ /**
+ * Sets the object to be sent when write() is called.
+ */
+ set: function (object) {
+ this._object = object;
+ var data = JSON.stringify(object);
+ this._data = data;
+ this.length = this._data.length;
+ }
+ });
+
+ JSONPacket.prototype.read = function (stream, scriptableStream) {
+ dumpv("Reading JSON packet");
+
+ // Read in more packet data.
+ this._readData(stream, scriptableStream);
+
+ if (!this.done) {
+ // Don't have a complete packet yet.
+ return;
+ }
+
+ var json = this._data;
+ try {
+ json = utf8.decode(json);
+ this._object = JSON.parse(json);
+ } catch (e) {
+ var msg = "Error parsing incoming packet: " + json + " (" + e + " - " + e.stack + ")";
+ if (console.error) {
+ console.error(msg);
+ }
+ dumpn(msg);
+ return;
+ }
+
+ this._transport._onJSONObjectReady(this._object);
+ };
+
+ JSONPacket.prototype._readData = function (stream, scriptableStream) {
+ if (!scriptableStream) {
+ scriptableStream = stream;
+ }
+ if (dumpv.wantVerbose) {
+ dumpv("Reading JSON data: _l: " + this.length + " dL: " + this._data.length + " sA: " + stream.available());
+ }
+ var bytesToRead = Math.min(this.length - this._data.length, stream.available());
+ this._data += scriptableStream.readBytes(bytesToRead);
+ this._done = this._data.length === this.length;
+ };
+
+ JSONPacket.prototype.write = function (stream) {
+ dumpv("Writing JSON packet");
+
+ if (this._outgoing === undefined) {
+ // Format the serialized packet to a buffer
+ this._outgoing = this.length + ":" + this._data;
+ }
+
+ var written = stream.write(this._outgoing, this._outgoing.length);
+ this._outgoing = this._outgoing.slice(written);
+ this._done = !this._outgoing.length;
+ };
+
+ Object.defineProperty(JSONPacket.prototype, "done", {
+ get: function () {
+ return this._done;
+ }
+ });
+
+ JSONPacket.prototype.toString = function () {
+ return JSON.stringify(this._object, null, 2);
+ };
+
+ exports.JSONPacket = JSONPacket;
+
+ /**
+ * With a bulk packet, data is transferred by temporarily handing over the
+ * transport's input or output stream to the application layer for writing data
+ * directly. This can be much faster for large data sets, and avoids various
+ * stages of copies and data duplication inherent in the JSON packet type. The
+ * bulk packet looks like:
+ *
+ * bulk [actor] [type] [length]:[data]
+ *
+ * The interpretation of the data portion depends on the kind of actor and the
+ * packet's type. See the Remote Debugging Protocol Stream Transport spec for
+ * more details.
+ * @param transport DebuggerTransport
+ * The transport instance that will own the packet.
+ */
+ function BulkPacket(transport) {
+ Packet.call(this, transport);
+ this._done = false;
+ this._readyForWriting = promise.defer();
+ }
+
+ /**
+ * Attempt to initialize a new BulkPacket based on the incoming packet header
+ * we've received so far.
+ * @param header string
+ * The packet header string to attempt parsing.
+ * @param transport DebuggerTransport
+ * The transport instance that will own the packet.
+ * @return BulkPacket
+ * The parsed packet, or null if it's not a match.
+ */
+ BulkPacket.fromHeader = function (header, transport) {
+ var match = this.HEADER_PATTERN.exec(header);
+
+ if (!match) {
+ return null;
+ }
+
+ dumpv("Header matches bulk packet");
+ var packet = new BulkPacket(transport);
+ packet.header = {
+ actor: match[1],
+ type: match[2],
+ length: +match[3]
+ };
+ return packet;
+ };
+
+ BulkPacket.HEADER_PATTERN = /^bulk ([^: ]+) ([^: ]+) (\d+):$/;
+
+ BulkPacket.prototype = Object.create(Packet.prototype);
+
+ BulkPacket.prototype.read = function (stream) {
+ dumpv("Reading bulk packet, handing off input stream");
+
+ // Temporarily pause monitoring of the input stream
+ this._transport.pauseIncoming();
+
+ var deferred = promise.defer();
+
+ this._transport._onBulkReadReady({
+ actor: this.actor,
+ type: this.type,
+ length: this.length,
+ copyTo: output => {
+ dumpv("CT length: " + this.length);
+ var copying = StreamUtils.copyStream(stream, output, this.length);
+ deferred.resolve(copying);
+ return copying;
+ },
+ stream: stream,
+ done: deferred
+ });
+
+ // Await the result of reading from the stream
+ deferred.promise.then(() => {
+ dumpv("onReadDone called, ending bulk mode");
+ this._done = true;
+ this._transport.resumeIncoming();
+ }, this._transport.close);
+
+ // Ensure this is only done once
+ this.read = () => {
+ throw new Error("Tried to read() a BulkPacket's stream multiple times.");
+ };
+ };
+
+ BulkPacket.prototype.write = function (stream) {
+ dumpv("Writing bulk packet");
+
+ if (this._outgoingHeader === undefined) {
+ dumpv("Serializing bulk packet header");
+ // Format the serialized packet header to a buffer
+ this._outgoingHeader = "bulk " + this.actor + " " + this.type + " " + this.length + ":";
+ }
+
+ // Write the header, or whatever's left of it to write.
+ if (this._outgoingHeader.length) {
+ dumpv("Writing bulk packet header");
+ var written = stream.write(this._outgoingHeader, this._outgoingHeader.length);
+ this._outgoingHeader = this._outgoingHeader.slice(written);
+ return;
+ }
+
+ dumpv("Handing off output stream");
+
+ // Temporarily pause the monitoring of the output stream
+ this._transport.pauseOutgoing();
+
+ var deferred = promise.defer();
+
+ this._readyForWriting.resolve({
+ copyFrom: input => {
+ dumpv("CF length: " + this.length);
+ var copying = StreamUtils.copyStream(input, stream, this.length);
+ deferred.resolve(copying);
+ return copying;
+ },
+ stream: stream,
+ done: deferred
+ });
+
+ // Await the result of writing to the stream
+ deferred.promise.then(() => {
+ dumpv("onWriteDone called, ending bulk mode");
+ this._done = true;
+ this._transport.resumeOutgoing();
+ }, this._transport.close);
+
+ // Ensure this is only done once
+ this.write = () => {
+ throw new Error("Tried to write() a BulkPacket's stream multiple times.");
+ };
+ };
+
+ Object.defineProperty(BulkPacket.prototype, "streamReadyForWriting", {
+ get: function () {
+ return this._readyForWriting.promise;
+ }
+ });
+
+ Object.defineProperty(BulkPacket.prototype, "header", {
+ get: function () {
+ return {
+ actor: this.actor,
+ type: this.type,
+ length: this.length
+ };
+ },
+
+ set: function (header) {
+ this.actor = header.actor;
+ this.type = header.type;
+ this.length = header.length;
+ }
+ });
+
+ Object.defineProperty(BulkPacket.prototype, "done", {
+ get: function () {
+ return this._done;
+ }
+ });
+
+ BulkPacket.prototype.toString = function () {
+ return "Bulk: " + JSON.stringify(this.header, null, 2);
+ };
+
+ exports.BulkPacket = BulkPacket;
+
+ /**
+ * RawPacket is used to test the transport's error handling of malformed
+ * packets, by writing data directly onto the stream.
+ * @param transport DebuggerTransport
+ * The transport instance that will own the packet.
+ * @param data string
+ * The raw string to send out onto the stream.
+ */
+ function RawPacket(transport, data) {
+ Packet.call(this, transport);
+ this._data = data;
+ this.length = data.length;
+ this._done = false;
+ }
+
+ RawPacket.prototype = Object.create(Packet.prototype);
+
+ RawPacket.prototype.read = function (stream) {
+ // This hasn't yet been needed for testing.
+ throw Error("Not implmented.");
+ };
+
+ RawPacket.prototype.write = function (stream) {
+ var written = stream.write(this._data, this._data.length);
+ this._data = this._data.slice(written);
+ this._done = !this._data.length;
+ };
+
+ Object.defineProperty(RawPacket.prototype, "done", {
+ get: function () {
+ return this._done;
+ }
+ });
+
+ exports.RawPacket = RawPacket;
+
+/***/ },
+/* 76 */
+/***/ function(module, exports, __webpack_require__) {
+
+ var __WEBPACK_AMD_DEFINE_RESULT__;/* WEBPACK VAR INJECTION */(function(module, global) {/*! https://mths.be/utf8js v2.0.0 by @mathias */
+ ;(function (root) {
+
+ // Detect free variables `exports`
+ var freeExports = typeof exports == 'object' && exports;
+
+ // Detect free variable `module`
+ var freeModule = typeof module == 'object' && module && module.exports == freeExports && module;
+
+ // Detect free variable `global`, from Node.js or Browserified code,
+ // and use it as `root`
+ var freeGlobal = typeof global == 'object' && global;
+ if (freeGlobal.global === freeGlobal || freeGlobal.window === freeGlobal) {
+ root = freeGlobal;
+ }
+
+ /*--------------------------------------------------------------------------*/
+
+ var stringFromCharCode = String.fromCharCode;
+
+ // Taken from https://mths.be/punycode
+ function ucs2decode(string) {
+ var output = [];
+ var counter = 0;
+ var length = string.length;
+ var value;
+ var extra;
+ while (counter < length) {
+ value = string.charCodeAt(counter++);
+ if (value >= 0xD800 && value <= 0xDBFF && counter < length) {
+ // high surrogate, and there is a next character
+ extra = string.charCodeAt(counter++);
+ if ((extra & 0xFC00) == 0xDC00) {
+ // low surrogate
+ output.push(((value & 0x3FF) << 10) + (extra & 0x3FF) + 0x10000);
+ } else {
+ // unmatched surrogate; only append this code unit, in case the next
+ // code unit is the high surrogate of a surrogate pair
+ output.push(value);
+ counter--;
+ }
+ } else {
+ output.push(value);
+ }
+ }
+ return output;
+ }
+
+ // Taken from https://mths.be/punycode
+ function ucs2encode(array) {
+ var length = array.length;
+ var index = -1;
+ var value;
+ var output = '';
+ while (++index < length) {
+ value = array[index];
+ if (value > 0xFFFF) {
+ value -= 0x10000;
+ output += stringFromCharCode(value >>> 10 & 0x3FF | 0xD800);
+ value = 0xDC00 | value & 0x3FF;
+ }
+ output += stringFromCharCode(value);
+ }
+ return output;
+ }
+
+ function checkScalarValue(codePoint) {
+ if (codePoint >= 0xD800 && codePoint <= 0xDFFF) {
+ throw Error('Lone surrogate U+' + codePoint.toString(16).toUpperCase() + ' is not a scalar value');
+ }
+ }
+ /*--------------------------------------------------------------------------*/
+
+ function createByte(codePoint, shift) {
+ return stringFromCharCode(codePoint >> shift & 0x3F | 0x80);
+ }
+
+ function encodeCodePoint(codePoint) {
+ if ((codePoint & 0xFFFFFF80) == 0) {
+ // 1-byte sequence
+ return stringFromCharCode(codePoint);
+ }
+ var symbol = '';
+ if ((codePoint & 0xFFFFF800) == 0) {
+ // 2-byte sequence
+ symbol = stringFromCharCode(codePoint >> 6 & 0x1F | 0xC0);
+ } else if ((codePoint & 0xFFFF0000) == 0) {
+ // 3-byte sequence
+ checkScalarValue(codePoint);
+ symbol = stringFromCharCode(codePoint >> 12 & 0x0F | 0xE0);
+ symbol += createByte(codePoint, 6);
+ } else if ((codePoint & 0xFFE00000) == 0) {
+ // 4-byte sequence
+ symbol = stringFromCharCode(codePoint >> 18 & 0x07 | 0xF0);
+ symbol += createByte(codePoint, 12);
+ symbol += createByte(codePoint, 6);
+ }
+ symbol += stringFromCharCode(codePoint & 0x3F | 0x80);
+ return symbol;
+ }
+
+ function utf8encode(string) {
+ var codePoints = ucs2decode(string);
+ var length = codePoints.length;
+ var index = -1;
+ var codePoint;
+ var byteString = '';
+ while (++index < length) {
+ codePoint = codePoints[index];
+ byteString += encodeCodePoint(codePoint);
+ }
+ return byteString;
+ }
+
+ /*--------------------------------------------------------------------------*/
+
+ function readContinuationByte() {
+ if (byteIndex >= byteCount) {
+ throw Error('Invalid byte index');
+ }
+
+ var continuationByte = byteArray[byteIndex] & 0xFF;
+ byteIndex++;
+
+ if ((continuationByte & 0xC0) == 0x80) {
+ return continuationByte & 0x3F;
+ }
+
+ // If we end up here, it’s not a continuation byte
+ throw Error('Invalid continuation byte');
+ }
+
+ function decodeSymbol() {
+ var byte1;
+ var byte2;
+ var byte3;
+ var byte4;
+ var codePoint;
+
+ if (byteIndex > byteCount) {
+ throw Error('Invalid byte index');
+ }
+
+ if (byteIndex == byteCount) {
+ return false;
+ }
+
+ // Read first byte
+ byte1 = byteArray[byteIndex] & 0xFF;
+ byteIndex++;
+
+ // 1-byte sequence (no continuation bytes)
+ if ((byte1 & 0x80) == 0) {
+ return byte1;
+ }
+
+ // 2-byte sequence
+ if ((byte1 & 0xE0) == 0xC0) {
+ var byte2 = readContinuationByte();
+ codePoint = (byte1 & 0x1F) << 6 | byte2;
+ if (codePoint >= 0x80) {
+ return codePoint;
+ } else {
+ throw Error('Invalid continuation byte');
+ }
+ }
+
+ // 3-byte sequence (may include unpaired surrogates)
+ if ((byte1 & 0xF0) == 0xE0) {
+ byte2 = readContinuationByte();
+ byte3 = readContinuationByte();
+ codePoint = (byte1 & 0x0F) << 12 | byte2 << 6 | byte3;
+ if (codePoint >= 0x0800) {
+ checkScalarValue(codePoint);
+ return codePoint;
+ } else {
+ throw Error('Invalid continuation byte');
+ }
+ }
+
+ // 4-byte sequence
+ if ((byte1 & 0xF8) == 0xF0) {
+ byte2 = readContinuationByte();
+ byte3 = readContinuationByte();
+ byte4 = readContinuationByte();
+ codePoint = (byte1 & 0x0F) << 0x12 | byte2 << 0x0C | byte3 << 0x06 | byte4;
+ if (codePoint >= 0x010000 && codePoint <= 0x10FFFF) {
+ return codePoint;
+ }
+ }
+
+ throw Error('Invalid UTF-8 detected');
+ }
+
+ var byteArray;
+ var byteCount;
+ var byteIndex;
+ function utf8decode(byteString) {
+ byteArray = ucs2decode(byteString);
+ byteCount = byteArray.length;
+ byteIndex = 0;
+ var codePoints = [];
+ var tmp;
+ while ((tmp = decodeSymbol()) !== false) {
+ codePoints.push(tmp);
+ }
+ return ucs2encode(codePoints);
+ }
+
+ /*--------------------------------------------------------------------------*/
+
+ var utf8 = {
+ 'version': '2.0.0',
+ 'encode': utf8encode,
+ 'decode': utf8decode
+ };
+
+ // Some AMD build optimizers, like r.js, check for specific condition patterns
+ // like the following:
+ if (true) {
+ !(__WEBPACK_AMD_DEFINE_RESULT__ = function () {
+ return utf8;
+ }.call(exports, __webpack_require__, exports, module), __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__));
+ } else if (freeExports && !freeExports.nodeType) {
+ if (freeModule) {
+ // in Node.js or RingoJS v0.8.0+
+ freeModule.exports = utf8;
+ } else {
+ // in Narwhal or RingoJS v0.7.0-
+ var object = {};
+ var hasOwnProperty = object.hasOwnProperty;
+ for (var key in utf8) {
+ hasOwnProperty.call(utf8, key) && (freeExports[key] = utf8[key]);
+ }
+ }
+ } else {
+ // in Rhino or a web browser
+ root.utf8 = utf8;
+ }
+ })(this);
+ /* WEBPACK VAR INJECTION */}.call(exports, __webpack_require__(77)(module), (function() { return this; }())))
+
+/***/ },
+/* 77 */
+/***/ function(module, exports) {
+
+ module.exports = function(module) {
+ if(!module.webpackPolyfill) {
+ module.deprecate = function() {};
+ module.paths = [];
+ // module.parent = undefined by default
+ module.children = [];
+ module.webpackPolyfill = 1;
+ }
+ return module;
+ }
+
+
+/***/ },
+/* 78 */
+/***/ function(module, exports, __webpack_require__) {
+
+ var __WEBPACK_AMD_DEFINE_RESULT__;/* WEBPACK VAR INJECTION */(function(module, global) {/*! https://mths.be/utf8js v2.0.0 by @mathias */
+ ;(function (root) {
+
+ // Detect free variables `exports`
+ var freeExports = typeof exports == 'object' && exports;
+
+ // Detect free variable `module`
+ var freeModule = typeof module == 'object' && module && module.exports == freeExports && module;
+
+ // Detect free variable `global`, from Node.js or Browserified code,
+ // and use it as `root`
+ var freeGlobal = typeof global == 'object' && global;
+ if (freeGlobal.global === freeGlobal || freeGlobal.window === freeGlobal) {
+ root = freeGlobal;
+ }
+
+ /*--------------------------------------------------------------------------*/
+
+ var stringFromCharCode = String.fromCharCode;
+
+ // Taken from https://mths.be/punycode
+ function ucs2decode(string) {
+ var output = [];
+ var counter = 0;
+ var length = string.length;
+ var value;
+ var extra;
+ while (counter < length) {
+ value = string.charCodeAt(counter++);
+ if (value >= 0xD800 && value <= 0xDBFF && counter < length) {
+ // high surrogate, and there is a next character
+ extra = string.charCodeAt(counter++);
+ if ((extra & 0xFC00) == 0xDC00) {
+ // low surrogate
+ output.push(((value & 0x3FF) << 10) + (extra & 0x3FF) + 0x10000);
+ } else {
+ // unmatched surrogate; only append this code unit, in case the next
+ // code unit is the high surrogate of a surrogate pair
+ output.push(value);
+ counter--;
+ }
+ } else {
+ output.push(value);
+ }
+ }
+ return output;
+ }
+
+ // Taken from https://mths.be/punycode
+ function ucs2encode(array) {
+ var length = array.length;
+ var index = -1;
+ var value;
+ var output = '';
+ while (++index < length) {
+ value = array[index];
+ if (value > 0xFFFF) {
+ value -= 0x10000;
+ output += stringFromCharCode(value >>> 10 & 0x3FF | 0xD800);
+ value = 0xDC00 | value & 0x3FF;
+ }
+ output += stringFromCharCode(value);
+ }
+ return output;
+ }
+
+ function checkScalarValue(codePoint) {
+ if (codePoint >= 0xD800 && codePoint <= 0xDFFF) {
+ throw Error('Lone surrogate U+' + codePoint.toString(16).toUpperCase() + ' is not a scalar value');
+ }
+ }
+ /*--------------------------------------------------------------------------*/
+
+ function createByte(codePoint, shift) {
+ return stringFromCharCode(codePoint >> shift & 0x3F | 0x80);
+ }
+
+ function encodeCodePoint(codePoint) {
+ if ((codePoint & 0xFFFFFF80) == 0) {
+ // 1-byte sequence
+ return stringFromCharCode(codePoint);
+ }
+ var symbol = '';
+ if ((codePoint & 0xFFFFF800) == 0) {
+ // 2-byte sequence
+ symbol = stringFromCharCode(codePoint >> 6 & 0x1F | 0xC0);
+ } else if ((codePoint & 0xFFFF0000) == 0) {
+ // 3-byte sequence
+ checkScalarValue(codePoint);
+ symbol = stringFromCharCode(codePoint >> 12 & 0x0F | 0xE0);
+ symbol += createByte(codePoint, 6);
+ } else if ((codePoint & 0xFFE00000) == 0) {
+ // 4-byte sequence
+ symbol = stringFromCharCode(codePoint >> 18 & 0x07 | 0xF0);
+ symbol += createByte(codePoint, 12);
+ symbol += createByte(codePoint, 6);
+ }
+ symbol += stringFromCharCode(codePoint & 0x3F | 0x80);
+ return symbol;
+ }
+
+ function utf8encode(string) {
+ var codePoints = ucs2decode(string);
+ var length = codePoints.length;
+ var index = -1;
+ var codePoint;
+ var byteString = '';
+ while (++index < length) {
+ codePoint = codePoints[index];
+ byteString += encodeCodePoint(codePoint);
+ }
+ return byteString;
+ }
+
+ /*--------------------------------------------------------------------------*/
+
+ function readContinuationByte() {
+ if (byteIndex >= byteCount) {
+ throw Error('Invalid byte index');
+ }
+
+ var continuationByte = byteArray[byteIndex] & 0xFF;
+ byteIndex++;
+
+ if ((continuationByte & 0xC0) == 0x80) {
+ return continuationByte & 0x3F;
+ }
+
+ // If we end up here, it’s not a continuation byte
+ throw Error('Invalid continuation byte');
+ }
+
+ function decodeSymbol() {
+ var byte1;
+ var byte2;
+ var byte3;
+ var byte4;
+ var codePoint;
+
+ if (byteIndex > byteCount) {
+ throw Error('Invalid byte index');
+ }
+
+ if (byteIndex == byteCount) {
+ return false;
+ }
+
+ // Read first byte
+ byte1 = byteArray[byteIndex] & 0xFF;
+ byteIndex++;
+
+ // 1-byte sequence (no continuation bytes)
+ if ((byte1 & 0x80) == 0) {
+ return byte1;
+ }
+
+ // 2-byte sequence
+ if ((byte1 & 0xE0) == 0xC0) {
+ var byte2 = readContinuationByte();
+ codePoint = (byte1 & 0x1F) << 6 | byte2;
+ if (codePoint >= 0x80) {
+ return codePoint;
+ } else {
+ throw Error('Invalid continuation byte');
+ }
+ }
+
+ // 3-byte sequence (may include unpaired surrogates)
+ if ((byte1 & 0xF0) == 0xE0) {
+ byte2 = readContinuationByte();
+ byte3 = readContinuationByte();
+ codePoint = (byte1 & 0x0F) << 12 | byte2 << 6 | byte3;
+ if (codePoint >= 0x0800) {
+ checkScalarValue(codePoint);
+ return codePoint;
+ } else {
+ throw Error('Invalid continuation byte');
+ }
+ }
+
+ // 4-byte sequence
+ if ((byte1 & 0xF8) == 0xF0) {
+ byte2 = readContinuationByte();
+ byte3 = readContinuationByte();
+ byte4 = readContinuationByte();
+ codePoint = (byte1 & 0x0F) << 0x12 | byte2 << 0x0C | byte3 << 0x06 | byte4;
+ if (codePoint >= 0x010000 && codePoint <= 0x10FFFF) {
+ return codePoint;
+ }
+ }
+
+ throw Error('Invalid UTF-8 detected');
+ }
+
+ var byteArray;
+ var byteCount;
+ var byteIndex;
+ function utf8decode(byteString) {
+ byteArray = ucs2decode(byteString);
+ byteCount = byteArray.length;
+ byteIndex = 0;
+ var codePoints = [];
+ var tmp;
+ while ((tmp = decodeSymbol()) !== false) {
+ codePoints.push(tmp);
+ }
+ return ucs2encode(codePoints);
+ }
+
+ /*--------------------------------------------------------------------------*/
+
+ var utf8 = {
+ 'version': '2.0.0',
+ 'encode': utf8encode,
+ 'decode': utf8decode
+ };
+
+ // Some AMD build optimizers, like r.js, check for specific condition patterns
+ // like the following:
+ if (true) {
+ !(__WEBPACK_AMD_DEFINE_RESULT__ = function () {
+ return utf8;
+ }.call(exports, __webpack_require__, exports, module), __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__));
+ } else if (freeExports && !freeExports.nodeType) {
+ if (freeModule) {
+ // in Node.js or RingoJS v0.8.0+
+ freeModule.exports = utf8;
+ } else {
+ // in Narwhal or RingoJS v0.7.0-
+ var object = {};
+ var hasOwnProperty = object.hasOwnProperty;
+ for (var key in utf8) {
+ hasOwnProperty.call(utf8, key) && (freeExports[key] = utf8[key]);
+ }
+ }
+ } else {
+ // in Rhino or a web browser
+ root.utf8 = utf8;
+ }
+ })(this);
+ /* WEBPACK VAR INJECTION */}.call(exports, __webpack_require__(77)(module), (function() { return this; }())))
+
+/***/ },
+/* 79 */
+/***/ function(module, exports, __webpack_require__) {
+
+ function _toConsumableArray(arr) { if (Array.isArray(arr)) { for (var i = 0, arr2 = Array(arr.length); i < arr.length; i++) { arr2[i] = arr[i]; } return arr2; } else { return Array.from(arr); } }
+
+ /* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+ /* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+ /* 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/. */
+
+ var _require = __webpack_require__(61);
+
+ var Ci = _require.Ci;
+ var Cu = _require.Cu;
+ var components = _require.components;
+
+ var _require2 = __webpack_require__(30);
+
+ var Services = _require2.Services;
+
+ var DevToolsUtils = __webpack_require__(68);
+
+ // WARNING I swapped the sync one for the async one here
+ // const promise = require("resource://devtools/shared/deprecated-sync-thenables.js", {}).Promise;
+ var promise = __webpack_require__(66);
+
+ var events = __webpack_require__(80);
+
+ var _require3 = __webpack_require__(82);
+
+ var WebConsoleClient = _require3.WebConsoleClient;
+ /* const { DebuggerSocket } = require("../shared/security/socket");*/
+ /* const Authentication = require("../shared/security/auth");*/
+
+ var noop = () => {};
+
+ /**
+ * TODO: Get rid of this API in favor of EventTarget (bug 1042642)
+ *
+ * Add simple event notification to a prototype object. Any object that has
+ * some use for event notifications or the observer pattern in general can be
+ * augmented with the necessary facilities by passing its prototype to this
+ * function.
+ *
+ * @param aProto object
+ * The prototype object that will be modified.
+ */
+ function eventSource(aProto) {
+ /**
+ * Add a listener to the event source for a given event.
+ *
+ * @param aName string
+ * The event to listen for.
+ * @param aListener function
+ * Called when the event is fired. If the same listener
+ * is added more than once, it will be called once per
+ * addListener call.
+ */
+ aProto.addListener = function (aName, aListener) {
+ if (typeof aListener != "function") {
+ throw TypeError("Listeners must be functions.");
+ }
+
+ if (!this._listeners) {
+ this._listeners = {};
+ }
+
+ this._getListeners(aName).push(aListener);
+ };
+
+ /**
+ * Add a listener to the event source for a given event. The
+ * listener will be removed after it is called for the first time.
+ *
+ * @param aName string
+ * The event to listen for.
+ * @param aListener function
+ * Called when the event is fired.
+ */
+ aProto.addOneTimeListener = function (aName, aListener) {
+ var _this = this;
+
+ var l = function () {
+ for (var _len = arguments.length, args = Array(_len), _key = 0; _key < _len; _key++) {
+ args[_key] = arguments[_key];
+ }
+
+ _this.removeListener(aName, l);
+ aListener.apply(null, args);
+ };
+ this.addListener(aName, l);
+ };
+
+ /**
+ * Remove a listener from the event source previously added with
+ * addListener().
+ *
+ * @param aName string
+ * The event name used during addListener to add the listener.
+ * @param aListener function
+ * The callback to remove. If addListener was called multiple
+ * times, all instances will be removed.
+ */
+ aProto.removeListener = function (aName, aListener) {
+ if (!this._listeners || aListener && !this._listeners[aName]) {
+ return;
+ }
+
+ if (!aListener) {
+ this._listeners[aName] = [];
+ } else {
+ this._listeners[aName] = this._listeners[aName].filter(function (l) {
+ return l != aListener;
+ });
+ }
+ };
+
+ /**
+ * Returns the listeners for the specified event name. If none are defined it
+ * initializes an empty list and returns that.
+ *
+ * @param aName string
+ * The event name.
+ */
+ aProto._getListeners = function (aName) {
+ if (aName in this._listeners) {
+ return this._listeners[aName];
+ }
+ this._listeners[aName] = [];
+ return this._listeners[aName];
+ };
+
+ /**
+ * Notify listeners of an event.
+ *
+ * @param aName string
+ * The event to fire.
+ * @param arguments
+ * All arguments will be passed along to the listeners,
+ * including the name argument.
+ */
+ aProto.emit = function () {
+ if (!this._listeners) {
+ return;
+ }
+
+ var name = arguments[0];
+ var listeners = this._getListeners(name).slice(0);
+
+ for (var listener of listeners) {
+ try {
+ listener.apply(null, arguments);
+ } catch (e) {
+ // Prevent a bad listener from interfering with the others.
+ DevToolsUtils.reportException("notify event '" + name + "'", e);
+ }
+ }
+ };
+ }
+
+ /**
+ * Set of protocol messages that affect thread state, and the
+ * state the actor is in after each message.
+ */
+ var ThreadStateTypes = {
+ "paused": "paused",
+ "resumed": "attached",
+ "detached": "detached"
+ };
+
+ /**
+ * Set of protocol messages that are sent by the server without a prior request
+ * by the client.
+ */
+ var UnsolicitedNotifications = {
+ "consoleAPICall": "consoleAPICall",
+ "eventNotification": "eventNotification",
+ "fileActivity": "fileActivity",
+ "lastPrivateContextExited": "lastPrivateContextExited",
+ "logMessage": "logMessage",
+ "networkEvent": "networkEvent",
+ "networkEventUpdate": "networkEventUpdate",
+ "newGlobal": "newGlobal",
+ "newScript": "newScript",
+ "tabDetached": "tabDetached",
+ "tabListChanged": "tabListChanged",
+ "reflowActivity": "reflowActivity",
+ "addonListChanged": "addonListChanged",
+ "workerListChanged": "workerListChanged",
+ "serviceWorkerRegistrationListChanged": "serviceWorkerRegistrationList",
+ "tabNavigated": "tabNavigated",
+ "frameUpdate": "frameUpdate",
+ "pageError": "pageError",
+ "documentLoad": "documentLoad",
+ "enteredFrame": "enteredFrame",
+ "exitedFrame": "exitedFrame",
+ "appOpen": "appOpen",
+ "appClose": "appClose",
+ "appInstall": "appInstall",
+ "appUninstall": "appUninstall",
+ "evaluationResult": "evaluationResult",
+ "newSource": "newSource",
+ "updatedSource": "updatedSource"
+ };
+
+ /**
+ * Set of pause types that are sent by the server and not as an immediate
+ * response to a client request.
+ */
+ var UnsolicitedPauses = {
+ "resumeLimit": "resumeLimit",
+ "debuggerStatement": "debuggerStatement",
+ "breakpoint": "breakpoint",
+ "DOMEvent": "DOMEvent",
+ "watchpoint": "watchpoint",
+ "exception": "exception"
+ };
+
+ /**
+ * Creates a client for the remote debugging protocol server. This client
+ * provides the means to communicate with the server and exchange the messages
+ * required by the protocol in a traditional JavaScript API.
+ */
+ var DebuggerClient = exports.DebuggerClient = function (aTransport) {
+ this._transport = aTransport;
+ this._transport.hooks = this;
+
+ // Map actor ID to client instance for each actor type.
+ this._clients = new Map();
+
+ this._pendingRequests = new Map();
+ this._activeRequests = new Map();
+ this._eventsEnabled = true;
+
+ this.traits = {};
+
+ this.request = this.request.bind(this);
+ this.localTransport = this._transport.onOutputStreamReady === undefined;
+
+ /*
+ * As the first thing on the connection, expect a greeting packet from
+ * the connection's root actor.
+ */
+ this.mainRoot = null;
+ this.expectReply("root", aPacket => {
+ this.mainRoot = new RootClient(this, aPacket);
+ this.emit("connected", aPacket.applicationType, aPacket.traits);
+ });
+ };
+
+ /**
+ * A declarative helper for defining methods that send requests to the server.
+ *
+ * @param aPacketSkeleton
+ * The form of the packet to send. Can specify fields to be filled from
+ * the parameters by using the |args| function.
+ * @param telemetry
+ * The unique suffix of the telemetry histogram id.
+ * @param before
+ * The function to call before sending the packet. Is passed the packet,
+ * and the return value is used as the new packet. The |this| context is
+ * the instance of the client object we are defining a method for.
+ * @param after
+ * The function to call after the response is received. It is passed the
+ * response, and the return value is considered the new response that
+ * will be passed to the callback. The |this| context is the instance of
+ * the client object we are defining a method for.
+ * @return Request
+ * The `Request` object that is a Promise object and resolves once
+ * we receive the response. (See request method for more details)
+ */
+ DebuggerClient.requester = function (aPacketSkeleton) {
+ var config = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
+ var telemetry = config.telemetry;
+ var before = config.before;
+ var after = config.after;
+
+ return DevToolsUtils.makeInfallible(function () {
+ for (var _len2 = arguments.length, args = Array(_len2), _key2 = 0; _key2 < _len2; _key2++) {
+ args[_key2] = arguments[_key2];
+ }
+
+ var histogram = void 0,
+ startTime = void 0;
+ if (telemetry) {
+ var transportType = this._transport.onOutputStreamReady === undefined ? "LOCAL_" : "REMOTE_";
+ var histogramId = "DEVTOOLS_DEBUGGER_RDP_" + transportType + telemetry + "_MS";
+ histogram = Services.telemetry.getHistogramById(histogramId);
+ startTime = +new Date();
+ }
+ var outgoingPacket = {
+ to: aPacketSkeleton.to || this.actor
+ };
+
+ var maxPosition = -1;
+ for (var k of Object.keys(aPacketSkeleton)) {
+ if (aPacketSkeleton[k] instanceof DebuggerClient.Argument) {
+ var position = aPacketSkeleton[k].position;
+
+ outgoingPacket[k] = aPacketSkeleton[k].getArgument(args);
+ maxPosition = Math.max(position, maxPosition);
+ } else {
+ outgoingPacket[k] = aPacketSkeleton[k];
+ }
+ }
+
+ if (before) {
+ outgoingPacket = before.call(this, outgoingPacket);
+ }
+
+ return this.request(outgoingPacket, DevToolsUtils.makeInfallible(aResponse => {
+ if (after) {
+ var _aResponse = aResponse;
+ var from = _aResponse.from;
+
+ aResponse = after.call(this, aResponse);
+ if (!aResponse.from) {
+ aResponse.from = from;
+ }
+ }
+
+ // The callback is always the last parameter.
+ var thisCallback = args[maxPosition + 1];
+ if (thisCallback) {
+ thisCallback(aResponse);
+ }
+
+ if (histogram) {
+ histogram.add(+new Date() - startTime);
+ }
+ }, "DebuggerClient.requester request callback"));
+ }, "DebuggerClient.requester");
+ };
+
+ function args(aPos) {
+ return new DebuggerClient.Argument(aPos);
+ }
+
+ DebuggerClient.Argument = function (aPosition) {
+ this.position = aPosition;
+ };
+
+ DebuggerClient.Argument.prototype.getArgument = function (aParams) {
+ if (!(this.position in aParams)) {
+ throw new Error("Bad index into params: " + this.position);
+ }
+ return aParams[this.position];
+ };
+
+ // Expose these to save callers the trouble of importing DebuggerSocket
+ DebuggerClient.socketConnect = function (options) {
+ // Defined here instead of just copying the function to allow lazy-load
+ return DebuggerSocket.connect(options);
+ };
+ DevToolsUtils.defineLazyGetter(DebuggerClient, "Authenticators", () => {
+ return Authentication.Authenticators;
+ });
+ DevToolsUtils.defineLazyGetter(DebuggerClient, "AuthenticationResult", () => {
+ return Authentication.AuthenticationResult;
+ });
+
+ DebuggerClient.prototype = {
+ /**
+ * Connect to the server and start exchanging protocol messages.
+ *
+ * @param aOnConnected function
+ * If specified, will be called when the greeting packet is
+ * received from the debugging server.
+ *
+ * @return Promise
+ * Resolves once connected with an array whose first element
+ * is the application type, by default "browser", and the second
+ * element is the traits object (help figure out the features
+ * and behaviors of the server we connect to. See RootActor).
+ */
+ connect: function (aOnConnected) {
+ return Promise.race([new Promise((resolve, reject) => {
+ this.emit("connect");
+
+ // Also emit the event on the |DebuggerClient| object (not on the instance),
+ // so it's possible to track all instances.
+ events.emit(DebuggerClient, "connect", this);
+
+ this.addOneTimeListener("connected", (aName, aApplicationType, aTraits) => {
+ this.traits = aTraits;
+ if (aOnConnected) {
+ aOnConnected(aApplicationType, aTraits);
+ }
+ resolve([aApplicationType, aTraits]);
+ });
+
+ this._transport.ready();
+ }), new Promise((resolve, reject) => {
+ setTimeout(() => reject(new Error("Connect timeout error")), 6000);
+ })]);
+ },
+
+ /**
+ * Shut down communication with the debugging server.
+ *
+ * @param aOnClosed function
+ * If specified, will be called when the debugging connection
+ * has been closed.
+ */
+ close: function (aOnClosed) {
+ // Disable detach event notifications, because event handlers will be in a
+ // cleared scope by the time they run.
+ this._eventsEnabled = false;
+
+ var cleanup = () => {
+ this._transport.close();
+ this._transport = null;
+ };
+
+ // If the connection is already closed,
+ // there is no need to detach client
+ // as we won't be able to send any message.
+ if (this._closed) {
+ cleanup();
+ if (aOnClosed) {
+ aOnClosed();
+ }
+ return;
+ }
+
+ if (aOnClosed) {
+ this.addOneTimeListener("closed", function (aEvent) {
+ aOnClosed();
+ });
+ }
+
+ // Call each client's `detach` method by calling
+ // lastly registered ones first to give a chance
+ // to detach child clients first.
+ var clients = [].concat(_toConsumableArray(this._clients.values()));
+ this._clients.clear();
+ var detachClients = () => {
+ var client = clients.pop();
+ if (!client) {
+ // All clients detached.
+ cleanup();
+ return;
+ }
+ if (client.detach) {
+ client.detach(detachClients);
+ return;
+ }
+ detachClients();
+ };
+ detachClients();
+ },
+
+ /*
+ * This function exists only to preserve DebuggerClient's interface;
+ * new code should say 'client.mainRoot.listTabs()'.
+ */
+ listTabs: function (aOnResponse) {
+ return this.mainRoot.listTabs(aOnResponse);
+ },
+
+ /*
+ * This function exists only to preserve DebuggerClient's interface;
+ * new code should say 'client.mainRoot.listAddons()'.
+ */
+ listAddons: function (aOnResponse) {
+ return this.mainRoot.listAddons(aOnResponse);
+ },
+
+ getTab: function (aFilter) {
+ return this.mainRoot.getTab(aFilter);
+ },
+
+ /**
+ * Attach to a tab actor.
+ *
+ * @param string aTabActor
+ * The actor ID for the tab to attach.
+ * @param function aOnResponse
+ * Called with the response packet and a TabClient
+ * (which will be undefined on error).
+ */
+ attachTab: function (aTabActor) {
+ var _this2 = this;
+
+ var aOnResponse = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : noop;
+
+ if (this._clients.has(aTabActor)) {
+ var _ret = function () {
+ var cachedTab = _this2._clients.get(aTabActor);
+ var cachedResponse = {
+ cacheDisabled: cachedTab.cacheDisabled,
+ javascriptEnabled: cachedTab.javascriptEnabled,
+ traits: cachedTab.traits
+ };
+ DevToolsUtils.executeSoon(() => aOnResponse(cachedResponse, cachedTab));
+ return {
+ v: promise.resolve([cachedResponse, cachedTab])
+ };
+ }();
+
+ if (typeof _ret === "object") return _ret.v;
+ }
+
+ var packet = {
+ to: aTabActor,
+ type: "attach"
+ };
+ return this.request(packet).then(aResponse => {
+ var tabClient = void 0;
+ if (!aResponse.error) {
+ tabClient = new TabClient(this, aResponse);
+ this.registerClient(tabClient);
+ }
+ aOnResponse(aResponse, tabClient);
+ return [aResponse, tabClient];
+ });
+ },
+
+ attachWorker: function DC_attachWorker(aWorkerActor) {
+ var aOnResponse = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : noop;
+
+ var workerClient = this._clients.get(aWorkerActor);
+ if (workerClient !== undefined) {
+ var _ret2 = function () {
+ var response = {
+ from: workerClient.actor,
+ type: "attached",
+ url: workerClient.url
+ };
+ DevToolsUtils.executeSoon(() => aOnResponse(response, workerClient));
+ return {
+ v: promise.resolve([response, workerClient])
+ };
+ }();
+
+ if (typeof _ret2 === "object") return _ret2.v;
+ }
+
+ return this.request({ to: aWorkerActor, type: "attach" }).then(aResponse => {
+ if (aResponse.error) {
+ aOnResponse(aResponse, null);
+ return [aResponse, null];
+ }
+
+ var workerClient = new WorkerClient(this, aResponse);
+ this.registerClient(workerClient);
+ aOnResponse(aResponse, workerClient);
+ return [aResponse, workerClient];
+ });
+ },
+
+ /**
+ * Attach to an addon actor.
+ *
+ * @param string aAddonActor
+ * The actor ID for the addon to attach.
+ * @param function aOnResponse
+ * Called with the response packet and a AddonClient
+ * (which will be undefined on error).
+ */
+ attachAddon: function DC_attachAddon(aAddonActor) {
+ var aOnResponse = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : noop;
+
+ var packet = {
+ to: aAddonActor,
+ type: "attach"
+ };
+ return this.request(packet).then(aResponse => {
+ var addonClient = void 0;
+ if (!aResponse.error) {
+ addonClient = new AddonClient(this, aAddonActor);
+ this.registerClient(addonClient);
+ this.activeAddon = addonClient;
+ }
+ aOnResponse(aResponse, addonClient);
+ return [aResponse, addonClient];
+ });
+ },
+
+ /**
+ * Attach to a Web Console actor.
+ *
+ * @param string aConsoleActor
+ * The ID for the console actor to attach to.
+ * @param array aListeners
+ * The console listeners you want to start.
+ * @param function aOnResponse
+ * Called with the response packet and a WebConsoleClient
+ * instance (which will be undefined on error).
+ */
+ attachConsole: function (aConsoleActor, aListeners) {
+ var aOnResponse = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : noop;
+
+ var packet = {
+ to: aConsoleActor,
+ type: "startListeners",
+ listeners: aListeners
+ };
+
+ return this.request(packet).then(aResponse => {
+ var consoleClient = void 0;
+ if (!aResponse.error) {
+ if (this._clients.has(aConsoleActor)) {
+ consoleClient = this._clients.get(aConsoleActor);
+ } else {
+ consoleClient = new WebConsoleClient(this, aResponse);
+ this.registerClient(consoleClient);
+ }
+ }
+ aOnResponse(aResponse, consoleClient);
+ return [aResponse, consoleClient];
+ });
+ },
+
+ /**
+ * Attach to a global-scoped thread actor for chrome debugging.
+ *
+ * @param string aThreadActor
+ * The actor ID for the thread to attach.
+ * @param function aOnResponse
+ * Called with the response packet and a ThreadClient
+ * (which will be undefined on error).
+ * @param object aOptions
+ * Configuration options.
+ * - useSourceMaps: whether to use source maps or not.
+ */
+ attachThread: function (aThreadActor) {
+ var _this3 = this;
+
+ var aOnResponse = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : noop;
+ var aOptions = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
+
+ if (this._clients.has(aThreadActor)) {
+ var _ret3 = function () {
+ var client = _this3._clients.get(aThreadActor);
+ DevToolsUtils.executeSoon(() => aOnResponse({}, client));
+ return {
+ v: promise.resolve([{}, client])
+ };
+ }();
+
+ if (typeof _ret3 === "object") return _ret3.v;
+ }
+
+ var packet = {
+ to: aThreadActor,
+ type: "attach",
+ options: aOptions
+ };
+ return this.request(packet).then(aResponse => {
+ if (!aResponse.error) {
+ var threadClient = new ThreadClient(this, aThreadActor);
+ this.registerClient(threadClient);
+ }
+ aOnResponse(aResponse, threadClient);
+ return [aResponse, threadClient];
+ });
+ },
+
+ /**
+ * Attach to a trace actor.
+ *
+ * @param string aTraceActor
+ * The actor ID for the tracer to attach.
+ * @param function aOnResponse
+ * Called with the response packet and a TraceClient
+ * (which will be undefined on error).
+ */
+ attachTracer: function (aTraceActor) {
+ var _this4 = this;
+
+ var aOnResponse = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : noop;
+
+ if (this._clients.has(aTraceActor)) {
+ var _ret4 = function () {
+ var client = _this4._clients.get(aTraceActor);
+ DevToolsUtils.executeSoon(() => aOnResponse({}, client));
+ return {
+ v: promise.resolve([{}, client])
+ };
+ }();
+
+ if (typeof _ret4 === "object") return _ret4.v;
+ }
+
+ var packet = {
+ to: aTraceActor,
+ type: "attach"
+ };
+ return this.request(packet).then(aResponse => {
+ if (!aResponse.error) {
+ var traceClient = new TraceClient(this, aTraceActor);
+ this.registerClient(traceClient);
+ }
+ aOnResponse(aResponse, traceClient);
+ return [aResponse, traceClient];
+ });
+ },
+
+ /**
+ * Fetch the ChromeActor for the main process or ChildProcessActor for a
+ * a given child process ID.
+ *
+ * @param number aId
+ * The ID for the process to attach (returned by `listProcesses`).
+ * Connected to the main process if omitted, or is 0.
+ */
+ getProcess: function (aId) {
+ var packet = {
+ to: "root",
+ type: "getProcess"
+ };
+ if (typeof aId == "number") {
+ packet.id = aId;
+ }
+ return this.request(packet);
+ },
+
+ /**
+ * Release an object actor.
+ *
+ * @param string aActor
+ * The actor ID to send the request to.
+ * @param aOnResponse function
+ * If specified, will be called with the response packet when
+ * debugging server responds.
+ */
+ release: DebuggerClient.requester({
+ to: args(0),
+ type: "release"
+ }, {
+ telemetry: "RELEASE"
+ }),
+
+ /**
+ * Send a request to the debugging server.
+ *
+ * @param aRequest object
+ * A JSON packet to send to the debugging server.
+ * @param aOnResponse function
+ * If specified, will be called with the JSON response packet when
+ * debugging server responds.
+ * @return Request
+ * This object emits a number of events to allow you to respond to
+ * different parts of the request lifecycle.
+ * It is also a Promise object, with a `then` method, that is resolved
+ * whenever a JSON or a Bulk response is received; and is rejected
+ * if the response is an error.
+ * Note: This return value can be ignored if you are using JSON alone,
+ * because the callback provided in |aOnResponse| will be bound to the
+ * "json-reply" event automatically.
+ *
+ * Events emitted:
+ * * json-reply: The server replied with a JSON packet, which is
+ * passed as event data.
+ * * bulk-reply: The server replied with bulk data, which you can read
+ * using the event data object containing:
+ * * actor: Name of actor that received the packet
+ * * type: Name of actor's method that was called on receipt
+ * * length: Size of the data to be read
+ * * stream: This input stream should only be used directly if you
+ * can ensure that you will read exactly |length| bytes
+ * and will not close the stream when reading is complete
+ * * done: If you use the stream directly (instead of |copyTo|
+ * below), you must signal completion by resolving /
+ * rejecting this deferred. If it's rejected, the
+ * transport will be closed. If an Error is supplied as a
+ * rejection value, it will be logged via |dumpn|. If you
+ * do use |copyTo|, resolving is taken care of for you
+ * when copying completes.
+ * * copyTo: A helper function for getting your data out of the
+ * stream that meets the stream handling requirements
+ * above, and has the following signature:
+ * @param output nsIAsyncOutputStream
+ * The stream to copy to.
+ * @return Promise
+ * The promise is resolved when copying completes or
+ * rejected if any (unexpected) errors occur.
+ * This object also emits "progress" events for each chunk
+ * that is copied. See stream-utils.js.
+ */
+ request: function (aRequest, aOnResponse) {
+ if (!this.mainRoot) {
+ throw Error("Have not yet received a hello packet from the server.");
+ }
+ var type = aRequest.type || "";
+ if (!aRequest.to) {
+ throw Error("'" + type + "' request packet has no destination.");
+ }
+ if (this._closed) {
+ var msg = "'" + type + "' request packet to " + "'" + aRequest.to + "' " + "can't be sent as the connection is closed.";
+ var resp = { error: "connectionClosed", message: msg };
+ if (aOnResponse) {
+ aOnResponse(resp);
+ }
+ return promise.reject(resp);
+ }
+
+ var request = new Request(aRequest);
+ request.format = "json";
+ request.stack = components.stack;
+ if (aOnResponse) {
+ request.on("json-reply", aOnResponse);
+ }
+
+ this._sendOrQueueRequest(request);
+
+ // Implement a Promise like API on the returned object
+ // that resolves/rejects on request response
+ var deferred = promise.defer();
+ function listenerJson(resp) {
+ request.off("json-reply", listenerJson);
+ request.off("bulk-reply", listenerBulk);
+ if (resp.error) {
+ deferred.reject(resp);
+ } else {
+ deferred.resolve(resp);
+ }
+ }
+ function listenerBulk(resp) {
+ request.off("json-reply", listenerJson);
+ request.off("bulk-reply", listenerBulk);
+ deferred.resolve(resp);
+ }
+ request.on("json-reply", listenerJson);
+ request.on("bulk-reply", listenerBulk);
+ request.then = deferred.promise.then.bind(deferred.promise);
+
+ return request;
+ },
+
+ /**
+ * Transmit streaming data via a bulk request.
+ *
+ * This method initiates the bulk send process by queuing up the header data.
+ * The caller receives eventual access to a stream for writing.
+ *
+ * Since this opens up more options for how the server might respond (it could
+ * send back either JSON or bulk data), and the returned Request object emits
+ * events for different stages of the request process that you may want to
+ * react to.
+ *
+ * @param request Object
+ * This is modeled after the format of JSON packets above, but does not
+ * actually contain the data, but is instead just a routing header:
+ * * actor: Name of actor that will receive the packet
+ * * type: Name of actor's method that should be called on receipt
+ * * length: Size of the data to be sent
+ * @return Request
+ * This object emits a number of events to allow you to respond to
+ * different parts of the request lifecycle.
+ *
+ * Events emitted:
+ * * bulk-send-ready: Ready to send bulk data to the server, using the
+ * event data object containing:
+ * * stream: This output stream should only be used directly if
+ * you can ensure that you will write exactly |length|
+ * bytes and will not close the stream when writing is
+ * complete
+ * * done: If you use the stream directly (instead of |copyFrom|
+ * below), you must signal completion by resolving /
+ * rejecting this deferred. If it's rejected, the
+ * transport will be closed. If an Error is supplied as
+ * a rejection value, it will be logged via |dumpn|. If
+ * you do use |copyFrom|, resolving is taken care of for
+ * you when copying completes.
+ * * copyFrom: A helper function for getting your data onto the
+ * stream that meets the stream handling requirements
+ * above, and has the following signature:
+ * @param input nsIAsyncInputStream
+ * The stream to copy from.
+ * @return Promise
+ * The promise is resolved when copying completes or
+ * rejected if any (unexpected) errors occur.
+ * This object also emits "progress" events for each chunk
+ * that is copied. See stream-utils.js.
+ * * json-reply: The server replied with a JSON packet, which is
+ * passed as event data.
+ * * bulk-reply: The server replied with bulk data, which you can read
+ * using the event data object containing:
+ * * actor: Name of actor that received the packet
+ * * type: Name of actor's method that was called on receipt
+ * * length: Size of the data to be read
+ * * stream: This input stream should only be used directly if you
+ * can ensure that you will read exactly |length| bytes
+ * and will not close the stream when reading is complete
+ * * done: If you use the stream directly (instead of |copyTo|
+ * below), you must signal completion by resolving /
+ * rejecting this deferred. If it's rejected, the
+ * transport will be closed. If an Error is supplied as a
+ * rejection value, it will be logged via |dumpn|. If you
+ * do use |copyTo|, resolving is taken care of for you
+ * when copying completes.
+ * * copyTo: A helper function for getting your data out of the
+ * stream that meets the stream handling requirements
+ * above, and has the following signature:
+ * @param output nsIAsyncOutputStream
+ * The stream to copy to.
+ * @return Promise
+ * The promise is resolved when copying completes or
+ * rejected if any (unexpected) errors occur.
+ * This object also emits "progress" events for each chunk
+ * that is copied. See stream-utils.js.
+ */
+ startBulkRequest: function (request) {
+ if (!this.traits.bulk) {
+ throw Error("Server doesn't support bulk transfers");
+ }
+ if (!this.mainRoot) {
+ throw Error("Have not yet received a hello packet from the server.");
+ }
+ if (!request.type) {
+ throw Error("Bulk packet is missing the required 'type' field.");
+ }
+ if (!request.actor) {
+ throw Error("'" + request.type + "' bulk packet has no destination.");
+ }
+ if (!request.length) {
+ throw Error("'" + request.type + "' bulk packet has no length.");
+ }
+
+ request = new Request(request);
+ request.format = "bulk";
+
+ this._sendOrQueueRequest(request);
+
+ return request;
+ },
+
+ /**
+ * If a new request can be sent immediately, do so. Otherwise, queue it.
+ */
+ _sendOrQueueRequest(request) {
+ var actor = request.actor;
+ if (!this._activeRequests.has(actor)) {
+ this._sendRequest(request);
+ } else {
+ this._queueRequest(request);
+ }
+ },
+
+ /**
+ * Send a request.
+ * @throws Error if there is already an active request in flight for the same
+ * actor.
+ */
+ _sendRequest(request) {
+ var actor = request.actor;
+ this.expectReply(actor, request);
+
+ if (request.format === "json") {
+ this._transport.send(request.request);
+ return false;
+ }
+
+ this._transport.startBulkSend(request.request).then(function () {
+ for (var _len3 = arguments.length, args = Array(_len3), _key3 = 0; _key3 < _len3; _key3++) {
+ args[_key3] = arguments[_key3];
+ }
+
+ request.emit.apply(request, ["bulk-send-ready"].concat(args));
+ });
+ },
+
+ /**
+ * Queue a request to be sent later. Queues are only drained when an in
+ * flight request to a given actor completes.
+ */
+ _queueRequest(request) {
+ var actor = request.actor;
+ var queue = this._pendingRequests.get(actor) || [];
+ queue.push(request);
+ this._pendingRequests.set(actor, queue);
+ },
+
+ /**
+ * Attempt the next request to a given actor (if any).
+ */
+ _attemptNextRequest(actor) {
+ if (this._activeRequests.has(actor)) {
+ return;
+ }
+ var queue = this._pendingRequests.get(actor);
+ if (!queue) {
+ return;
+ }
+ var request = queue.shift();
+ if (queue.length === 0) {
+ this._pendingRequests.delete(actor);
+ }
+ this._sendRequest(request);
+ },
+
+ /**
+ * Arrange to hand the next reply from |aActor| to the handler bound to
+ * |aRequest|.
+ *
+ * DebuggerClient.prototype.request / startBulkRequest usually takes care of
+ * establishing the handler for a given request, but in rare cases (well,
+ * greetings from new root actors, is the only case at the moment) we must be
+ * prepared for a "reply" that doesn't correspond to any request we sent.
+ */
+ expectReply: function (aActor, aRequest) {
+ if (this._activeRequests.has(aActor)) {
+ throw Error("clashing handlers for next reply from " + uneval(aActor));
+ }
+
+ // If a handler is passed directly (as it is with the handler for the root
+ // actor greeting), create a dummy request to bind this to.
+ if (typeof aRequest === "function") {
+ var handler = aRequest;
+ aRequest = new Request();
+ aRequest.on("json-reply", handler);
+ }
+
+ this._activeRequests.set(aActor, aRequest);
+ },
+
+ // Transport hooks.
+
+ /**
+ * Called by DebuggerTransport to dispatch incoming packets as appropriate.
+ *
+ * @param aPacket object
+ * The incoming packet.
+ */
+ onPacket: function (aPacket) {
+ if (!aPacket.from) {
+ DevToolsUtils.reportException("onPacket", new Error("Server did not specify an actor, dropping packet: " + JSON.stringify(aPacket)));
+ return;
+ }
+
+ // If we have a registered Front for this actor, let it handle the packet
+ // and skip all the rest of this unpleasantness.
+ var front = this.getActor(aPacket.from);
+ if (front) {
+ front.onPacket(aPacket);
+ return;
+ }
+
+ if (this._clients.has(aPacket.from) && aPacket.type) {
+ var client = this._clients.get(aPacket.from);
+ var type = aPacket.type;
+ if (client.events.indexOf(type) != -1) {
+ client.emit(type, aPacket);
+ // we ignore the rest, as the client is expected to handle this packet.
+ return;
+ }
+ }
+
+ var activeRequest = void 0;
+ // See if we have a handler function waiting for a reply from this
+ // actor. (Don't count unsolicited notifications or pauses as
+ // replies.)
+ if (this._activeRequests.has(aPacket.from) && !(aPacket.type in UnsolicitedNotifications) && !(aPacket.type == ThreadStateTypes.paused && aPacket.why.type in UnsolicitedPauses)) {
+ activeRequest = this._activeRequests.get(aPacket.from);
+ this._activeRequests.delete(aPacket.from);
+ }
+
+ // If there is a subsequent request for the same actor, hand it off to the
+ // transport. Delivery of packets on the other end is always async, even
+ // in the local transport case.
+ this._attemptNextRequest(aPacket.from);
+
+ // Packets that indicate thread state changes get special treatment.
+ if (aPacket.type in ThreadStateTypes && this._clients.has(aPacket.from) && typeof this._clients.get(aPacket.from)._onThreadState == "function") {
+ this._clients.get(aPacket.from)._onThreadState(aPacket);
+ }
+
+ // TODO: Bug 1151156 - Remove once Gecko 40 is on b2g-stable.
+ if (!this.traits.noNeedToFakeResumptionOnNavigation) {
+ // On navigation the server resumes, so the client must resume as well.
+ // We achieve that by generating a fake resumption packet that triggers
+ // the client's thread state change listeners.
+ if (aPacket.type == UnsolicitedNotifications.tabNavigated && this._clients.has(aPacket.from) && this._clients.get(aPacket.from).thread) {
+ var thread = this._clients.get(aPacket.from).thread;
+ var resumption = { from: thread._actor, type: "resumed" };
+ thread._onThreadState(resumption);
+ }
+ }
+
+ // Only try to notify listeners on events, not responses to requests
+ // that lack a packet type.
+ if (aPacket.type) {
+ this.emit(aPacket.type, aPacket);
+ }
+
+ if (activeRequest) {
+ var emitReply = () => activeRequest.emit("json-reply", aPacket);
+ if (activeRequest.stack) {
+ Cu.callFunctionWithAsyncStack(emitReply, activeRequest.stack, "DevTools RDP");
+ } else {
+ emitReply();
+ }
+ }
+ },
+
+ /**
+ * Called by the DebuggerTransport to dispatch incoming bulk packets as
+ * appropriate.
+ *
+ * @param packet object
+ * The incoming packet, which contains:
+ * * actor: Name of actor that will receive the packet
+ * * type: Name of actor's method that should be called on receipt
+ * * length: Size of the data to be read
+ * * stream: This input stream should only be used directly if you can
+ * ensure that you will read exactly |length| bytes and will
+ * not close the stream when reading is complete
+ * * done: If you use the stream directly (instead of |copyTo|
+ * below), you must signal completion by resolving /
+ * rejecting this deferred. If it's rejected, the transport
+ * will be closed. If an Error is supplied as a rejection
+ * value, it will be logged via |dumpn|. If you do use
+ * |copyTo|, resolving is taken care of for you when copying
+ * completes.
+ * * copyTo: A helper function for getting your data out of the stream
+ * that meets the stream handling requirements above, and has
+ * the following signature:
+ * @param output nsIAsyncOutputStream
+ * The stream to copy to.
+ * @return Promise
+ * The promise is resolved when copying completes or rejected
+ * if any (unexpected) errors occur.
+ * This object also emits "progress" events for each chunk
+ * that is copied. See stream-utils.js.
+ */
+ onBulkPacket: function (packet) {
+ var actor = packet.actor;
+ var type = packet.type;
+ var length = packet.length;
+
+
+ if (!actor) {
+ DevToolsUtils.reportException("onBulkPacket", new Error("Server did not specify an actor, dropping bulk packet: " + JSON.stringify(packet)));
+ return;
+ }
+
+ // See if we have a handler function waiting for a reply from this
+ // actor.
+ if (!this._activeRequests.has(actor)) {
+ return;
+ }
+
+ var activeRequest = this._activeRequests.get(actor);
+ this._activeRequests.delete(actor);
+
+ // If there is a subsequent request for the same actor, hand it off to the
+ // transport. Delivery of packets on the other end is always async, even
+ // in the local transport case.
+ this._attemptNextRequest(actor);
+
+ activeRequest.emit("bulk-reply", packet);
+ },
+
+ /**
+ * Called by DebuggerTransport when the underlying stream is closed.
+ *
+ * @param aStatus nsresult
+ * The status code that corresponds to the reason for closing
+ * the stream.
+ */
+ onClosed: function (aStatus) {
+ this._closed = true;
+ this.emit("closed");
+
+ // Reject all pending and active requests
+ var reject = function (type, request, actor) {
+ // Server can send packets on its own and client only pass a callback
+ // to expectReply, so that there is no request object.
+ var msg = void 0;
+ if (request.request) {
+ msg = "'" + request.request.type + "' " + type + " request packet" + " to '" + actor + "' " + "can't be sent as the connection just closed.";
+ } else {
+ msg = "server side packet from '" + actor + "' can't be received " + "as the connection just closed.";
+ }
+ var packet = { error: "connectionClosed", message: msg };
+ request.emit("json-reply", packet);
+ };
+
+ var pendingRequests = new Map(this._pendingRequests);
+ this._pendingRequests.clear();
+ pendingRequests.forEach((list, actor) => {
+ list.forEach(request => reject("pending", request, actor));
+ });
+ var activeRequests = new Map(this._activeRequests);
+ this._activeRequests.clear();
+ activeRequests.forEach(reject.bind(null, "active"));
+
+ // The |_pools| array on the client-side currently is used only by
+ // protocol.js to store active fronts, mirroring the actor pools found in
+ // the server. So, read all usages of "pool" as "protocol.js front".
+ //
+ // In the normal case where we shutdown cleanly, the toolbox tells each tool
+ // to close, and they each call |destroy| on any fronts they were using.
+ // When |destroy| or |cleanup| is called on a protocol.js front, it also
+ // removes itself from the |_pools| array. Once the toolbox has shutdown,
+ // the connection is closed, and we reach here. All fronts (should have
+ // been) |destroy|ed, so |_pools| should empty.
+ //
+ // If the connection instead aborts unexpectedly, we may end up here with
+ // all fronts used during the life of the connection. So, we call |cleanup|
+ // on them clear their state, reject pending requests, and remove themselves
+ // from |_pools|. This saves the toolbox from hanging indefinitely, in case
+ // it waits for some server response before shutdown that will now never
+ // arrive.
+ for (var pool of this._pools) {
+ pool.cleanup();
+ }
+ },
+
+ registerClient: function (client) {
+ var actorID = client.actor;
+ if (!actorID) {
+ throw new Error("DebuggerServer.registerClient expects " + "a client instance with an `actor` attribute.");
+ }
+ if (!Array.isArray(client.events)) {
+ throw new Error("DebuggerServer.registerClient expects " + "a client instance with an `events` attribute " + "that is an array.");
+ }
+ if (client.events.length > 0 && typeof client.emit != "function") {
+ throw new Error("DebuggerServer.registerClient expects " + "a client instance with non-empty `events` array to" + "have an `emit` function.");
+ }
+ if (this._clients.has(actorID)) {
+ throw new Error("DebuggerServer.registerClient already registered " + "a client for this actor.");
+ }
+ this._clients.set(actorID, client);
+ },
+
+ unregisterClient: function (client) {
+ var actorID = client.actor;
+ if (!actorID) {
+ throw new Error("DebuggerServer.unregisterClient expects " + "a Client instance with a `actor` attribute.");
+ }
+ this._clients.delete(actorID);
+ },
+
+ /**
+ * Actor lifetime management, echos the server's actor pools.
+ */
+ __pools: null,
+ get _pools() {
+ if (this.__pools) {
+ return this.__pools;
+ }
+ this.__pools = new Set();
+ return this.__pools;
+ },
+
+ addActorPool: function (pool) {
+ this._pools.add(pool);
+ },
+ removeActorPool: function (pool) {
+ this._pools.delete(pool);
+ },
+ getActor: function (actorID) {
+ var pool = this.poolFor(actorID);
+ return pool ? pool.get(actorID) : null;
+ },
+
+ poolFor: function (actorID) {
+ for (var pool of this._pools) {
+ if (pool.has(actorID)) return pool;
+ }
+ return null;
+ },
+
+ /**
+ * Currently attached addon.
+ */
+ activeAddon: null
+ };
+
+ eventSource(DebuggerClient.prototype);
+
+ function Request(request) {
+ this.request = request;
+ }
+
+ Request.prototype = {
+
+ on: function (type, listener) {
+ events.on(this, type, listener);
+ },
+
+ off: function (type, listener) {
+ events.off(this, type, listener);
+ },
+
+ once: function (type, listener) {
+ events.once(this, type, listener);
+ },
+
+ emit: function (type) {
+ for (var _len4 = arguments.length, args = Array(_len4 > 1 ? _len4 - 1 : 0), _key4 = 1; _key4 < _len4; _key4++) {
+ args[_key4 - 1] = arguments[_key4];
+ }
+
+ events.emit.apply(events, [this, type].concat(args));
+ },
+
+ get actor() {
+ return this.request.to || this.request.actor;
+ }
+
+ };
+
+ /**
+ * Creates a tab client for the remote debugging protocol server. This client
+ * is a front to the tab actor created in the server side, hiding the protocol
+ * details in a traditional JavaScript API.
+ *
+ * @param aClient DebuggerClient
+ * The debugger client parent.
+ * @param aForm object
+ * The protocol form for this tab.
+ */
+ function TabClient(aClient, aForm) {
+ this.client = aClient;
+ this._actor = aForm.from;
+ this._threadActor = aForm.threadActor;
+ this.javascriptEnabled = aForm.javascriptEnabled;
+ this.cacheDisabled = aForm.cacheDisabled;
+ this.thread = null;
+ this.request = this.client.request;
+ this.traits = aForm.traits || {};
+ this.events = ["workerListChanged"];
+ }
+
+ TabClient.prototype = {
+ get actor() {
+ return this._actor;
+ },
+ get _transport() {
+ return this.client._transport;
+ },
+
+ /**
+ * Attach to a thread actor.
+ *
+ * @param object aOptions
+ * Configuration options.
+ * - useSourceMaps: whether to use source maps or not.
+ * @param function aOnResponse
+ * Called with the response packet and a ThreadClient
+ * (which will be undefined on error).
+ */
+ attachThread: function () {
+ var aOptions = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
+ var aOnResponse = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : noop;
+
+ if (this.thread) {
+ DevToolsUtils.executeSoon(() => aOnResponse({}, this.thread));
+ return promise.resolve([{}, this.thread]);
+ }
+
+ var packet = {
+ to: this._threadActor,
+ type: "attach",
+ options: aOptions
+ };
+ return this.request(packet).then(aResponse => {
+ if (!aResponse.error) {
+ this.thread = new ThreadClient(this, this._threadActor);
+ this.client.registerClient(this.thread);
+ }
+ aOnResponse(aResponse, this.thread);
+ return [aResponse, this.thread];
+ });
+ },
+
+ /**
+ * Detach the client from the tab actor.
+ *
+ * @param function aOnResponse
+ * Called with the response packet.
+ */
+ detach: DebuggerClient.requester({
+ type: "detach"
+ }, {
+ before: function (aPacket) {
+ if (this.thread) {
+ this.thread.detach();
+ }
+ return aPacket;
+ },
+ after: function (aResponse) {
+ this.client.unregisterClient(this);
+ return aResponse;
+ },
+ telemetry: "TABDETACH"
+ }),
+
+ /**
+ * Bring the window to the front.
+ */
+ focus: DebuggerClient.requester({
+ type: "focus"
+ }, {}),
+
+ /**
+ * Reload the page in this tab.
+ *
+ * @param [optional] object options
+ * An object with a `force` property indicating whether or not
+ * this reload should skip the cache
+ */
+ reload: function () {
+ var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : { force: false };
+
+ return this._reload(options);
+ },
+ _reload: DebuggerClient.requester({
+ type: "reload",
+ options: args(0)
+ }, {
+ telemetry: "RELOAD"
+ }),
+
+ /**
+ * Navigate to another URL.
+ *
+ * @param string url
+ * The URL to navigate to.
+ */
+ navigateTo: DebuggerClient.requester({
+ type: "navigateTo",
+ url: args(0)
+ }, {
+ telemetry: "NAVIGATETO"
+ }),
+
+ /**
+ * Reconfigure the tab actor.
+ *
+ * @param object aOptions
+ * A dictionary object of the new options to use in the tab actor.
+ * @param function aOnResponse
+ * Called with the response packet.
+ */
+ reconfigure: DebuggerClient.requester({
+ type: "reconfigure",
+ options: args(0)
+ }, {
+ telemetry: "RECONFIGURETAB"
+ }),
+
+ listWorkers: DebuggerClient.requester({
+ type: "listWorkers"
+ }, {
+ telemetry: "LISTWORKERS"
+ }),
+
+ attachWorker: function (aWorkerActor, aOnResponse) {
+ this.client.attachWorker(aWorkerActor, aOnResponse);
+ },
+
+ /**
+ * Resolve a location ({ url, line, column }) to its current
+ * source mapping location.
+ *
+ * @param {String} arg[0].url
+ * @param {Number} arg[0].line
+ * @param {Number?} arg[0].column
+ */
+ resolveLocation: DebuggerClient.requester({
+ type: "resolveLocation",
+ location: args(0)
+ })
+ };
+
+ eventSource(TabClient.prototype);
+
+ function WorkerClient(aClient, aForm) {
+ this.client = aClient;
+ this._actor = aForm.from;
+ this._isClosed = false;
+ this._url = aForm.url;
+
+ this._onClose = this._onClose.bind(this);
+
+ this.addListener("close", this._onClose);
+
+ this.traits = {};
+ }
+
+ WorkerClient.prototype = {
+ get _transport() {
+ return this.client._transport;
+ },
+
+ get request() {
+ return this.client.request;
+ },
+
+ get actor() {
+ return this._actor;
+ },
+
+ get url() {
+ return this._url;
+ },
+
+ get isClosed() {
+ return this._isClosed;
+ },
+
+ detach: DebuggerClient.requester({ type: "detach" }, {
+ after: function (aResponse) {
+ if (this.thread) {
+ this.client.unregisterClient(this.thread);
+ }
+ this.client.unregisterClient(this);
+ return aResponse;
+ },
+
+ telemetry: "WORKERDETACH"
+ }),
+
+ attachThread: function () {
+ var _this5 = this;
+
+ var aOptions = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
+ var aOnResponse = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : noop;
+
+ if (this.thread) {
+ var _ret5 = function () {
+ var response = [{
+ type: "connected",
+ threadActor: _this5.thread._actor,
+ consoleActor: _this5.consoleActor
+ }, _this5.thread];
+ DevToolsUtils.executeSoon(() => aOnResponse(response));
+ return {
+ v: response
+ };
+ }();
+
+ if (typeof _ret5 === "object") return _ret5.v;
+ }
+
+ // The connect call on server doesn't attach the thread as of version 44.
+ return this.request({
+ to: this._actor,
+ type: "connect",
+ options: aOptions
+ }).then(connectReponse => {
+ if (connectReponse.error) {
+ aOnResponse(connectReponse, null);
+ return [connectResponse, null];
+ }
+
+ return this.request({
+ to: connectReponse.threadActor,
+ type: "attach",
+ options: aOptions
+ }).then(attachResponse => {
+ if (attachResponse.error) {
+ aOnResponse(attachResponse, null);
+ }
+
+ this.thread = new ThreadClient(this, connectReponse.threadActor);
+ this.consoleActor = connectReponse.consoleActor;
+ this.client.registerClient(this.thread);
+
+ aOnResponse(connectReponse, this.thread);
+ return [connectResponse, this.thread];
+ });
+ });
+ },
+
+ _onClose: function () {
+ this.removeListener("close", this._onClose);
+
+ if (this.thread) {
+ this.client.unregisterClient(this.thread);
+ }
+ this.client.unregisterClient(this);
+ this._isClosed = true;
+ },
+
+ reconfigure: function () {
+ return Promise.resolve();
+ },
+
+ events: ["close"]
+ };
+
+ eventSource(WorkerClient.prototype);
+
+ function AddonClient(aClient, aActor) {
+ this._client = aClient;
+ this._actor = aActor;
+ this.request = this._client.request;
+ this.events = [];
+ }
+
+ AddonClient.prototype = {
+ get actor() {
+ return this._actor;
+ },
+ get _transport() {
+ return this._client._transport;
+ },
+
+ /**
+ * Detach the client from the addon actor.
+ *
+ * @param function aOnResponse
+ * Called with the response packet.
+ */
+ detach: DebuggerClient.requester({
+ type: "detach"
+ }, {
+ after: function (aResponse) {
+ if (this._client.activeAddon === this) {
+ this._client.activeAddon = null;
+ }
+ this._client.unregisterClient(this);
+ return aResponse;
+ },
+ telemetry: "ADDONDETACH"
+ })
+ };
+
+ /**
+ * A RootClient object represents a root actor on the server. Each
+ * DebuggerClient keeps a RootClient instance representing the root actor
+ * for the initial connection; DebuggerClient's 'listTabs' and
+ * 'listChildProcesses' methods forward to that root actor.
+ *
+ * @param aClient object
+ * The client connection to which this actor belongs.
+ * @param aGreeting string
+ * The greeting packet from the root actor we're to represent.
+ *
+ * Properties of a RootClient instance:
+ *
+ * @property actor string
+ * The name of this child's root actor.
+ * @property applicationType string
+ * The application type, as given in the root actor's greeting packet.
+ * @property traits object
+ * The traits object, as given in the root actor's greeting packet.
+ */
+ function RootClient(aClient, aGreeting) {
+ this._client = aClient;
+ this.actor = aGreeting.from;
+ this.applicationType = aGreeting.applicationType;
+ this.traits = aGreeting.traits;
+ }
+ exports.RootClient = RootClient;
+
+ RootClient.prototype = {
+ constructor: RootClient,
+
+ /**
+ * List the open tabs.
+ *
+ * @param function aOnResponse
+ * Called with the response packet.
+ */
+ listTabs: DebuggerClient.requester({ type: "listTabs" }, { telemetry: "LISTTABS" }),
+
+ /**
+ * List the installed addons.
+ *
+ * @param function aOnResponse
+ * Called with the response packet.
+ */
+ listAddons: DebuggerClient.requester({ type: "listAddons" }, { telemetry: "LISTADDONS" }),
+
+ /**
+ * List the registered workers.
+ *
+ * @param function aOnResponse
+ * Called with the response packet.
+ */
+ listWorkers: DebuggerClient.requester({ type: "listWorkers" }, { telemetry: "LISTWORKERS" }),
+
+ /**
+ * List the registered service workers.
+ *
+ * @param function aOnResponse
+ * Called with the response packet.
+ */
+ listServiceWorkerRegistrations: DebuggerClient.requester({ type: "listServiceWorkerRegistrations" }, { telemetry: "LISTSERVICEWORKERREGISTRATIONS" }),
+
+ /**
+ * List the running processes.
+ *
+ * @param function aOnResponse
+ * Called with the response packet.
+ */
+ listProcesses: DebuggerClient.requester({ type: "listProcesses" }, { telemetry: "LISTPROCESSES" }),
+
+ /**
+ * Fetch the TabActor for the currently selected tab, or for a specific
+ * tab given as first parameter.
+ *
+ * @param [optional] object aFilter
+ * A dictionary object with following optional attributes:
+ * - outerWindowID: used to match tabs in parent process
+ * - tabId: used to match tabs in child processes
+ * - tab: a reference to xul:tab element
+ * If nothing is specified, returns the actor for the currently
+ * selected tab.
+ */
+ getTab: function (aFilter) {
+ var packet = {
+ to: this.actor,
+ type: "getTab"
+ };
+
+ if (aFilter) {
+ if (typeof aFilter.outerWindowID == "number") {
+ packet.outerWindowID = aFilter.outerWindowID;
+ } else if (typeof aFilter.tabId == "number") {
+ packet.tabId = aFilter.tabId;
+ } else if ("tab" in aFilter) {
+ var browser = aFilter.tab.linkedBrowser;
+ if (browser.frameLoader.tabParent) {
+ // Tabs in child process
+ packet.tabId = browser.frameLoader.tabParent.tabId;
+ } else {
+ // Tabs in parent process
+ var windowUtils = browser.contentWindow.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindowUtils);
+ packet.outerWindowID = windowUtils.outerWindowID;
+ }
+ } else {
+ // Throw if a filter object have been passed but without
+ // any clearly idenfified filter.
+ throw new Error("Unsupported argument given to getTab request");
+ }
+ }
+
+ return this.request(packet);
+ },
+
+ /**
+ * Description of protocol's actors and methods.
+ *
+ * @param function aOnResponse
+ * Called with the response packet.
+ */
+ protocolDescription: DebuggerClient.requester({ type: "protocolDescription" }, { telemetry: "PROTOCOLDESCRIPTION" }),
+
+ /*
+ * Methods constructed by DebuggerClient.requester require these forwards
+ * on their 'this'.
+ */
+ get _transport() {
+ return this._client._transport;
+ },
+ get request() {
+ return this._client.request;
+ }
+ };
+
+ /**
+ * Creates a thread client for the remote debugging protocol server. This client
+ * is a front to the thread actor created in the server side, hiding the
+ * protocol details in a traditional JavaScript API.
+ *
+ * @param aClient DebuggerClient|TabClient
+ * The parent of the thread (tab for tab-scoped debuggers, DebuggerClient
+ * for chrome debuggers).
+ * @param aActor string
+ * The actor ID for this thread.
+ */
+ function ThreadClient(aClient, aActor) {
+ this._parent = aClient;
+ this.client = aClient instanceof DebuggerClient ? aClient : aClient.client;
+ this._actor = aActor;
+ this._frameCache = [];
+ this._scriptCache = {};
+ this._pauseGrips = {};
+ this._threadGrips = {};
+ this.request = this.client.request;
+ }
+
+ ThreadClient.prototype = {
+ _state: "paused",
+ get state() {
+ return this._state;
+ },
+ get paused() {
+ return this._state === "paused";
+ },
+
+ _pauseOnExceptions: false,
+ _ignoreCaughtExceptions: false,
+ _pauseOnDOMEvents: null,
+
+ _actor: null,
+ get actor() {
+ return this._actor;
+ },
+
+ get _transport() {
+ return this.client._transport;
+ },
+
+ _assertPaused: function (aCommand) {
+ if (!this.paused) {
+ throw Error(aCommand + " command sent while not paused. Currently " + this._state);
+ }
+ },
+
+ /**
+ * Resume a paused thread. If the optional aLimit parameter is present, then
+ * the thread will also pause when that limit is reached.
+ *
+ * @param [optional] object aLimit
+ * An object with a type property set to the appropriate limit (next,
+ * step, or finish) per the remote debugging protocol specification.
+ * Use null to specify no limit.
+ * @param function aOnResponse
+ * Called with the response packet.
+ */
+ _doResume: DebuggerClient.requester({
+ type: "resume",
+ resumeLimit: args(0)
+ }, {
+ before: function (aPacket) {
+ this._assertPaused("resume");
+
+ // Put the client in a tentative "resuming" state so we can prevent
+ // further requests that should only be sent in the paused state.
+ this._state = "resuming";
+
+ if (this._pauseOnExceptions) {
+ aPacket.pauseOnExceptions = this._pauseOnExceptions;
+ }
+ if (this._ignoreCaughtExceptions) {
+ aPacket.ignoreCaughtExceptions = this._ignoreCaughtExceptions;
+ }
+ if (this._pauseOnDOMEvents) {
+ aPacket.pauseOnDOMEvents = this._pauseOnDOMEvents;
+ }
+ return aPacket;
+ },
+ after: function (aResponse) {
+ if (aResponse.error) {
+ // There was an error resuming, back to paused state.
+ this._state = "paused";
+ }
+ return aResponse;
+ },
+ telemetry: "RESUME"
+ }),
+
+ /**
+ * Reconfigure the thread actor.
+ *
+ * @param object aOptions
+ * A dictionary object of the new options to use in the thread actor.
+ * @param function aOnResponse
+ * Called with the response packet.
+ */
+ reconfigure: DebuggerClient.requester({
+ type: "reconfigure",
+ options: args(0)
+ }, {
+ telemetry: "RECONFIGURETHREAD"
+ }),
+
+ /**
+ * Resume a paused thread.
+ */
+ resume: function (aOnResponse) {
+ return this._doResume(null, aOnResponse);
+ },
+
+ /**
+ * Resume then pause without stepping.
+ *
+ * @param function aOnResponse
+ * Called with the response packet.
+ */
+ resumeThenPause: function (aOnResponse) {
+ return this._doResume({ type: "break" }, aOnResponse);
+ },
+
+ /**
+ * Step over a function call.
+ *
+ * @param function aOnResponse
+ * Called with the response packet.
+ */
+ stepOver: function (aOnResponse) {
+ return this._doResume({ type: "next" }, aOnResponse);
+ },
+
+ /**
+ * Step into a function call.
+ *
+ * @param function aOnResponse
+ * Called with the response packet.
+ */
+ stepIn: function (aOnResponse) {
+ return this._doResume({ type: "step" }, aOnResponse);
+ },
+
+ /**
+ * Step out of a function call.
+ *
+ * @param function aOnResponse
+ * Called with the response packet.
+ */
+ stepOut: function (aOnResponse) {
+ return this._doResume({ type: "finish" }, aOnResponse);
+ },
+
+ /**
+ * Immediately interrupt a running thread.
+ *
+ * @param function aOnResponse
+ * Called with the response packet.
+ */
+ interrupt: function (aOnResponse) {
+ return this._doInterrupt(null, aOnResponse);
+ },
+
+ /**
+ * Pause execution right before the next JavaScript bytecode is executed.
+ *
+ * @param function aOnResponse
+ * Called with the response packet.
+ */
+ breakOnNext: function (aOnResponse) {
+ return this._doInterrupt("onNext", aOnResponse);
+ },
+
+ /**
+ * Interrupt a running thread.
+ *
+ * @param function aOnResponse
+ * Called with the response packet.
+ */
+ _doInterrupt: DebuggerClient.requester({
+ type: "interrupt",
+ when: args(0)
+ }, {
+ telemetry: "INTERRUPT"
+ }),
+
+ /**
+ * Enable or disable pausing when an exception is thrown.
+ *
+ * @param boolean aFlag
+ * Enables pausing if true, disables otherwise.
+ * @param function aOnResponse
+ * Called with the response packet.
+ */
+ pauseOnExceptions: function (aPauseOnExceptions, aIgnoreCaughtExceptions) {
+ var aOnResponse = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : noop;
+
+ this._pauseOnExceptions = aPauseOnExceptions;
+ this._ignoreCaughtExceptions = aIgnoreCaughtExceptions;
+
+ // Otherwise send the flag using a standard resume request.
+ if (!this.paused) {
+ return this.interrupt(aResponse => {
+ if (aResponse.error) {
+ // Can't continue if pausing failed.
+ aOnResponse(aResponse);
+ return aResponse;
+ }
+ return this.resume(aOnResponse);
+ });
+ }
+
+ aOnResponse();
+ return promise.resolve();
+ },
+
+ /**
+ * Enable pausing when the specified DOM events are triggered. Disabling
+ * pausing on an event can be realized by calling this method with the updated
+ * array of events that doesn't contain it.
+ *
+ * @param array|string events
+ * An array of strings, representing the DOM event types to pause on,
+ * or "*" to pause on all DOM events. Pass an empty array to
+ * completely disable pausing on DOM events.
+ * @param function onResponse
+ * Called with the response packet in a future turn of the event loop.
+ */
+ pauseOnDOMEvents: function (events) {
+ var onResponse = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : noop;
+
+ this._pauseOnDOMEvents = events;
+ // If the debuggee is paused, the value of the array will be communicated in
+ // the next resumption. Otherwise we have to force a pause in order to send
+ // the array.
+ if (this.paused) {
+ DevToolsUtils.executeSoon(() => onResponse({}));
+ return {};
+ }
+ return this.interrupt(response => {
+ // Can't continue if pausing failed.
+ if (response.error) {
+ onResponse(response);
+ return response;
+ }
+ return this.resume(onResponse);
+ });
+ },
+
+ /**
+ * Send a clientEvaluate packet to the debuggee. Response
+ * will be a resume packet.
+ *
+ * @param string aFrame
+ * The actor ID of the frame where the evaluation should take place.
+ * @param string aExpression
+ * The expression that will be evaluated in the scope of the frame
+ * above.
+ * @param function aOnResponse
+ * Called with the response packet.
+ */
+ eval: DebuggerClient.requester({
+ type: "clientEvaluate",
+ frame: args(0),
+ expression: args(1)
+ }, {
+ before: function (aPacket) {
+ this._assertPaused("eval");
+ // Put the client in a tentative "resuming" state so we can prevent
+ // further requests that should only be sent in the paused state.
+ this._state = "resuming";
+ return aPacket;
+ },
+ after: function (aResponse) {
+ if (aResponse.error) {
+ // There was an error resuming, back to paused state.
+ this._state = "paused";
+ }
+ return aResponse;
+ },
+ telemetry: "CLIENTEVALUATE"
+ }),
+
+ /**
+ * Detach from the thread actor.
+ *
+ * @param function aOnResponse
+ * Called with the response packet.
+ */
+ detach: DebuggerClient.requester({
+ type: "detach"
+ }, {
+ after: function (aResponse) {
+ this.client.unregisterClient(this);
+ this._parent.thread = null;
+ return aResponse;
+ },
+ telemetry: "THREADDETACH"
+ }),
+
+ /**
+ * Release multiple thread-lifetime object actors. If any pause-lifetime
+ * actors are included in the request, a |notReleasable| error will return,
+ * but all the thread-lifetime ones will have been released.
+ *
+ * @param array actors
+ * An array with actor IDs to release.
+ */
+ releaseMany: DebuggerClient.requester({
+ type: "releaseMany",
+ actors: args(0)
+ }, {
+ telemetry: "RELEASEMANY"
+ }),
+
+ /**
+ * Promote multiple pause-lifetime object actors to thread-lifetime ones.
+ *
+ * @param array actors
+ * An array with actor IDs to promote.
+ */
+ threadGrips: DebuggerClient.requester({
+ type: "threadGrips",
+ actors: args(0)
+ }, {
+ telemetry: "THREADGRIPS"
+ }),
+
+ /**
+ * Return the event listeners defined on the page.
+ *
+ * @param aOnResponse Function
+ * Called with the thread's response.
+ */
+ eventListeners: DebuggerClient.requester({
+ type: "eventListeners"
+ }, {
+ telemetry: "EVENTLISTENERS"
+ }),
+
+ /**
+ * Request the loaded sources for the current thread.
+ *
+ * @param aOnResponse Function
+ * Called with the thread's response.
+ */
+ getSources: DebuggerClient.requester({
+ type: "sources"
+ }, {
+ telemetry: "SOURCES"
+ }),
+
+ /**
+ * Clear the thread's source script cache. A scriptscleared event
+ * will be sent.
+ */
+ _clearScripts: function () {
+ if (Object.keys(this._scriptCache).length > 0) {
+ this._scriptCache = {};
+ this.emit("scriptscleared");
+ }
+ },
+
+ /**
+ * Request frames from the callstack for the current thread.
+ *
+ * @param aStart integer
+ * The number of the youngest stack frame to return (the youngest
+ * frame is 0).
+ * @param aCount integer
+ * The maximum number of frames to return, or null to return all
+ * frames.
+ * @param aOnResponse function
+ * Called with the thread's response.
+ */
+ getFrames: DebuggerClient.requester({
+ type: "frames",
+ start: args(0),
+ count: args(1)
+ }, {
+ telemetry: "FRAMES"
+ }),
+
+ /**
+ * An array of cached frames. Clients can observe the framesadded and
+ * framescleared event to keep up to date on changes to this cache,
+ * and can fill it using the fillFrames method.
+ */
+ get cachedFrames() {
+ return this._frameCache;
+ },
+
+ /**
+ * true if there are more stack frames available on the server.
+ */
+ get moreFrames() {
+ return this.paused && (!this._frameCache || this._frameCache.length == 0 || !this._frameCache[this._frameCache.length - 1].oldest);
+ },
+
+ /**
+ * Ensure that at least aTotal stack frames have been loaded in the
+ * ThreadClient's stack frame cache. A framesadded event will be
+ * sent when the stack frame cache is updated.
+ *
+ * @param aTotal number
+ * The minimum number of stack frames to be included.
+ * @param aCallback function
+ * Optional callback function called when frames have been loaded
+ * @returns true if a framesadded notification should be expected.
+ */
+ fillFrames: function (aTotal) {
+ var aCallback = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : noop;
+
+ this._assertPaused("fillFrames");
+ if (this._frameCache.length >= aTotal) {
+ return false;
+ }
+
+ var numFrames = this._frameCache.length;
+
+ this.getFrames(numFrames, aTotal - numFrames, aResponse => {
+ if (aResponse.error) {
+ aCallback(aResponse);
+ return;
+ }
+
+ var threadGrips = DevToolsUtils.values(this._threadGrips);
+
+ for (var i in aResponse.frames) {
+ var frame = aResponse.frames[i];
+ if (!frame.where.source) {
+ // Older servers use urls instead, so we need to resolve
+ // them to source actors
+ for (var grip of threadGrips) {
+ if (grip instanceof SourceClient && grip.url === frame.url) {
+ frame.where.source = grip._form;
+ }
+ }
+ }
+
+ this._frameCache[frame.depth] = frame;
+ }
+
+ // If we got as many frames as we asked for, there might be more
+ // frames available.
+ this.emit("framesadded");
+
+ aCallback(aResponse);
+ });
+
+ return true;
+ },
+
+ /**
+ * Clear the thread's stack frame cache. A framescleared event
+ * will be sent.
+ */
+ _clearFrames: function () {
+ if (this._frameCache.length > 0) {
+ this._frameCache = [];
+ this.emit("framescleared");
+ }
+ },
+
+ /**
+ * Return a ObjectClient object for the given object grip.
+ *
+ * @param aGrip object
+ * A pause-lifetime object grip returned by the protocol.
+ */
+ pauseGrip: function (aGrip) {
+ if (aGrip.actor in this._pauseGrips) {
+ return this._pauseGrips[aGrip.actor];
+ }
+
+ var client = new ObjectClient(this.client, aGrip);
+ this._pauseGrips[aGrip.actor] = client;
+ return client;
+ },
+
+ /**
+ * Get or create a long string client, checking the grip client cache if it
+ * already exists.
+ *
+ * @param aGrip Object
+ * The long string grip returned by the protocol.
+ * @param aGripCacheName String
+ * The property name of the grip client cache to check for existing
+ * clients in.
+ */
+ _longString: function (aGrip, aGripCacheName) {
+ if (aGrip.actor in this[aGripCacheName]) {
+ return this[aGripCacheName][aGrip.actor];
+ }
+
+ var client = new LongStringClient(this.client, aGrip);
+ this[aGripCacheName][aGrip.actor] = client;
+ return client;
+ },
+
+ /**
+ * Return an instance of LongStringClient for the given long string grip that
+ * is scoped to the current pause.
+ *
+ * @param aGrip Object
+ * The long string grip returned by the protocol.
+ */
+ pauseLongString: function (aGrip) {
+ return this._longString(aGrip, "_pauseGrips");
+ },
+
+ /**
+ * Return an instance of LongStringClient for the given long string grip that
+ * is scoped to the thread lifetime.
+ *
+ * @param aGrip Object
+ * The long string grip returned by the protocol.
+ */
+ threadLongString: function (aGrip) {
+ return this._longString(aGrip, "_threadGrips");
+ },
+
+ /**
+ * Clear and invalidate all the grip clients from the given cache.
+ *
+ * @param aGripCacheName
+ * The property name of the grip cache we want to clear.
+ */
+ _clearObjectClients: function (aGripCacheName) {
+ for (var id in this[aGripCacheName]) {
+ this[aGripCacheName][id].valid = false;
+ }
+ this[aGripCacheName] = {};
+ },
+
+ /**
+ * Invalidate pause-lifetime grip clients and clear the list of current grip
+ * clients.
+ */
+ _clearPauseGrips: function () {
+ this._clearObjectClients("_pauseGrips");
+ },
+
+ /**
+ * Invalidate thread-lifetime grip clients and clear the list of current grip
+ * clients.
+ */
+ _clearThreadGrips: function () {
+ this._clearObjectClients("_threadGrips");
+ },
+
+ /**
+ * Handle thread state change by doing necessary cleanup and notifying all
+ * registered listeners.
+ */
+ _onThreadState: function (aPacket) {
+ this._state = ThreadStateTypes[aPacket.type];
+ // The debugger UI may not be initialized yet so we want to keep
+ // the packet around so it knows what to pause state to display
+ // when it's initialized
+ this._lastPausePacket = aPacket.type === "resumed" ? null : aPacket;
+ this._clearFrames();
+ this._clearPauseGrips();
+ aPacket.type === ThreadStateTypes.detached && this._clearThreadGrips();
+ this.client._eventsEnabled && this.emit(aPacket.type, aPacket);
+ },
+
+ getLastPausePacket: function () {
+ return this._lastPausePacket;
+ },
+
+ /**
+ * Return an EnvironmentClient instance for the given environment actor form.
+ */
+ environment: function (aForm) {
+ return new EnvironmentClient(this.client, aForm);
+ },
+
+ /**
+ * Return an instance of SourceClient for the given source actor form.
+ */
+ source: function (aForm) {
+ if (aForm.actor in this._threadGrips) {
+ return this._threadGrips[aForm.actor];
+ }
+
+ return this._threadGrips[aForm.actor] = new SourceClient(this, aForm);
+ },
+
+ /**
+ * Request the prototype and own properties of mutlipleObjects.
+ *
+ * @param aOnResponse function
+ * Called with the request's response.
+ * @param actors [string]
+ * List of actor ID of the queried objects.
+ */
+ getPrototypesAndProperties: DebuggerClient.requester({
+ type: "prototypesAndProperties",
+ actors: args(0)
+ }, {
+ telemetry: "PROTOTYPESANDPROPERTIES"
+ }),
+
+ events: ["newSource"]
+ };
+
+ eventSource(ThreadClient.prototype);
+
+ /**
+ * Creates a tracing profiler client for the remote debugging protocol
+ * server. This client is a front to the trace actor created on the
+ * server side, hiding the protocol details in a traditional
+ * JavaScript API.
+ *
+ * @param aClient DebuggerClient
+ * The debugger client parent.
+ * @param aActor string
+ * The actor ID for this thread.
+ */
+ function TraceClient(aClient, aActor) {
+ this._client = aClient;
+ this._actor = aActor;
+ this._activeTraces = new Set();
+ this._waitingPackets = new Map();
+ this._expectedPacket = 0;
+ this.request = this._client.request;
+ this.events = [];
+ }
+
+ TraceClient.prototype = {
+ get actor() {
+ return this._actor;
+ },
+ get tracing() {
+ return this._activeTraces.size > 0;
+ },
+
+ get _transport() {
+ return this._client._transport;
+ },
+
+ /**
+ * Detach from the trace actor.
+ */
+ detach: DebuggerClient.requester({
+ type: "detach"
+ }, {
+ after: function (aResponse) {
+ this._client.unregisterClient(this);
+ return aResponse;
+ },
+ telemetry: "TRACERDETACH"
+ }),
+
+ /**
+ * Start a new trace.
+ *
+ * @param aTrace [string]
+ * An array of trace types to be recorded by the new trace.
+ *
+ * @param aName string
+ * The name of the new trace.
+ *
+ * @param aOnResponse function
+ * Called with the request's response.
+ */
+ startTrace: DebuggerClient.requester({
+ type: "startTrace",
+ name: args(1),
+ trace: args(0)
+ }, {
+ after: function (aResponse) {
+ if (aResponse.error) {
+ return aResponse;
+ }
+
+ if (!this.tracing) {
+ this._waitingPackets.clear();
+ this._expectedPacket = 0;
+ }
+ this._activeTraces.add(aResponse.name);
+
+ return aResponse;
+ },
+ telemetry: "STARTTRACE"
+ }),
+
+ /**
+ * End a trace. If a name is provided, stop the named
+ * trace. Otherwise, stop the most recently started trace.
+ *
+ * @param aName string
+ * The name of the trace to stop.
+ *
+ * @param aOnResponse function
+ * Called with the request's response.
+ */
+ stopTrace: DebuggerClient.requester({
+ type: "stopTrace",
+ name: args(0)
+ }, {
+ after: function (aResponse) {
+ if (aResponse.error) {
+ return aResponse;
+ }
+
+ this._activeTraces.delete(aResponse.name);
+
+ return aResponse;
+ },
+ telemetry: "STOPTRACE"
+ })
+ };
+
+ /**
+ * Grip clients are used to retrieve information about the relevant object.
+ *
+ * @param aClient DebuggerClient
+ * The debugger client parent.
+ * @param aGrip object
+ * A pause-lifetime object grip returned by the protocol.
+ */
+ function ObjectClient(aClient, aGrip) {
+ this._grip = aGrip;
+ this._client = aClient;
+ this.request = this._client.request;
+ }
+ exports.ObjectClient = ObjectClient;
+
+ ObjectClient.prototype = {
+ get actor() {
+ return this._grip.actor;
+ },
+ get _transport() {
+ return this._client._transport;
+ },
+
+ valid: true,
+
+ get isFrozen() {
+ return this._grip.frozen;
+ },
+ get isSealed() {
+ return this._grip.sealed;
+ },
+ get isExtensible() {
+ return this._grip.extensible;
+ },
+
+ getDefinitionSite: DebuggerClient.requester({
+ type: "definitionSite"
+ }, {
+ before: function (aPacket) {
+ if (this._grip.class != "Function") {
+ throw new Error("getDefinitionSite is only valid for function grips.");
+ }
+ return aPacket;
+ }
+ }),
+
+ /**
+ * Request the names of a function's formal parameters.
+ *
+ * @param aOnResponse function
+ * Called with an object of the form:
+ * { parameterNames:[<parameterName>, ...] }
+ * where each <parameterName> is the name of a parameter.
+ */
+ getParameterNames: DebuggerClient.requester({
+ type: "parameterNames"
+ }, {
+ before: function (aPacket) {
+ if (this._grip.class !== "Function") {
+ throw new Error("getParameterNames is only valid for function grips.");
+ }
+ return aPacket;
+ },
+ telemetry: "PARAMETERNAMES"
+ }),
+
+ /**
+ * Request the names of the properties defined on the object and not its
+ * prototype.
+ *
+ * @param aOnResponse function Called with the request's response.
+ */
+ getOwnPropertyNames: DebuggerClient.requester({
+ type: "ownPropertyNames"
+ }, {
+ telemetry: "OWNPROPERTYNAMES"
+ }),
+
+ /**
+ * Request the prototype and own properties of the object.
+ *
+ * @param aOnResponse function Called with the request's response.
+ */
+ getPrototypeAndProperties: DebuggerClient.requester({
+ type: "prototypeAndProperties"
+ }, {
+ telemetry: "PROTOTYPEANDPROPERTIES"
+ }),
+
+ /**
+ * Request a PropertyIteratorClient instance to ease listing
+ * properties for this object.
+ *
+ * @param options Object
+ * A dictionary object with various boolean attributes:
+ * - ignoreSafeGetters Boolean
+ * If true, do not iterate over safe getters.
+ * - ignoreIndexedProperties Boolean
+ * If true, filters out Array items.
+ * e.g. properties names between `0` and `object.length`.
+ * - ignoreNonIndexedProperties Boolean
+ * If true, filters out items that aren't array items
+ * e.g. properties names that are not a number between `0`
+ * and `object.length`.
+ * - sort Boolean
+ * If true, the iterator will sort the properties by name
+ * before dispatching them.
+ * @param aOnResponse function Called with the client instance.
+ */
+ enumProperties: DebuggerClient.requester({
+ type: "enumProperties",
+ options: args(0)
+ }, {
+ after: function (aResponse) {
+ if (aResponse.iterator) {
+ return { iterator: new PropertyIteratorClient(this._client, aResponse.iterator) };
+ }
+ return aResponse;
+ },
+ telemetry: "ENUMPROPERTIES"
+ }),
+
+ /**
+ * Request a PropertyIteratorClient instance to enumerate entries in a
+ * Map/Set-like object.
+ *
+ * @param aOnResponse function Called with the request's response.
+ */
+ enumEntries: DebuggerClient.requester({
+ type: "enumEntries"
+ }, {
+ before: function (packet) {
+ if (!["Map", "WeakMap", "Set", "WeakSet"].includes(this._grip.class)) {
+ throw new Error("enumEntries is only valid for Map/Set-like grips.");
+ }
+ return packet;
+ },
+ after: function (response) {
+ if (response.iterator) {
+ return {
+ iterator: new PropertyIteratorClient(this._client, response.iterator)
+ };
+ }
+ return response;
+ }
+ }),
+
+ /**
+ * Request the property descriptor of the object's specified property.
+ *
+ * @param aName string The name of the requested property.
+ * @param aOnResponse function Called with the request's response.
+ */
+ getProperty: DebuggerClient.requester({
+ type: "property",
+ name: args(0)
+ }, {
+ telemetry: "PROPERTY"
+ }),
+
+ /**
+ * Request the prototype of the object.
+ *
+ * @param aOnResponse function Called with the request's response.
+ */
+ getPrototype: DebuggerClient.requester({
+ type: "prototype"
+ }, {
+ telemetry: "PROTOTYPE"
+ }),
+
+ /**
+ * Request the display string of the object.
+ *
+ * @param aOnResponse function Called with the request's response.
+ */
+ getDisplayString: DebuggerClient.requester({
+ type: "displayString"
+ }, {
+ telemetry: "DISPLAYSTRING"
+ }),
+
+ /**
+ * Request the scope of the object.
+ *
+ * @param aOnResponse function Called with the request's response.
+ */
+ getScope: DebuggerClient.requester({
+ type: "scope"
+ }, {
+ before: function (aPacket) {
+ if (this._grip.class !== "Function") {
+ throw new Error("scope is only valid for function grips.");
+ }
+ return aPacket;
+ },
+ telemetry: "SCOPE"
+ }),
+
+ /**
+ * Request the promises directly depending on the current promise.
+ */
+ getDependentPromises: DebuggerClient.requester({
+ type: "dependentPromises"
+ }, {
+ before: function (aPacket) {
+ if (this._grip.class !== "Promise") {
+ throw new Error("getDependentPromises is only valid for promise " + "grips.");
+ }
+ return aPacket;
+ }
+ }),
+
+ /**
+ * Request the stack to the promise's allocation point.
+ */
+ getPromiseAllocationStack: DebuggerClient.requester({
+ type: "allocationStack"
+ }, {
+ before: function (aPacket) {
+ if (this._grip.class !== "Promise") {
+ throw new Error("getAllocationStack is only valid for promise grips.");
+ }
+ return aPacket;
+ }
+ }),
+
+ /**
+ * Request the stack to the promise's fulfillment point.
+ */
+ getPromiseFulfillmentStack: DebuggerClient.requester({
+ type: "fulfillmentStack"
+ }, {
+ before: function (packet) {
+ if (this._grip.class !== "Promise") {
+ throw new Error("getPromiseFulfillmentStack is only valid for " + "promise grips.");
+ }
+ return packet;
+ }
+ }),
+
+ /**
+ * Request the stack to the promise's rejection point.
+ */
+ getPromiseRejectionStack: DebuggerClient.requester({
+ type: "rejectionStack"
+ }, {
+ before: function (packet) {
+ if (this._grip.class !== "Promise") {
+ throw new Error("getPromiseRejectionStack is only valid for " + "promise grips.");
+ }
+ return packet;
+ }
+ })
+ };
+
+ /**
+ * A PropertyIteratorClient provides a way to access to property names and
+ * values of an object efficiently, slice by slice.
+ * Note that the properties can be sorted in the backend,
+ * this is controled while creating the PropertyIteratorClient
+ * from ObjectClient.enumProperties.
+ *
+ * @param aClient DebuggerClient
+ * The debugger client parent.
+ * @param aGrip Object
+ * A PropertyIteratorActor grip returned by the protocol via
+ * TabActor.enumProperties request.
+ */
+ function PropertyIteratorClient(aClient, aGrip) {
+ this._grip = aGrip;
+ this._client = aClient;
+ this.request = this._client.request;
+ }
+
+ PropertyIteratorClient.prototype = {
+ get actor() {
+ return this._grip.actor;
+ },
+
+ /**
+ * Get the total number of properties available in the iterator.
+ */
+ get count() {
+ return this._grip.count;
+ },
+
+ /**
+ * Get one or more property names that correspond to the positions in the
+ * indexes parameter.
+ *
+ * @param indexes Array
+ * An array of property indexes.
+ * @param aCallback Function
+ * The function called when we receive the property names.
+ */
+ names: DebuggerClient.requester({
+ type: "names",
+ indexes: args(0)
+ }, {}),
+
+ /**
+ * Get a set of following property value(s).
+ *
+ * @param start Number
+ * The index of the first property to fetch.
+ * @param count Number
+ * The number of properties to fetch.
+ * @param aCallback Function
+ * The function called when we receive the property values.
+ */
+ slice: DebuggerClient.requester({
+ type: "slice",
+ start: args(0),
+ count: args(1)
+ }, {}),
+
+ /**
+ * Get all the property values.
+ *
+ * @param aCallback Function
+ * The function called when we receive the property values.
+ */
+ all: DebuggerClient.requester({
+ type: "all"
+ }, {})
+ };
+
+ /**
+ * A LongStringClient provides a way to access "very long" strings from the
+ * debugger server.
+ *
+ * @param aClient DebuggerClient
+ * The debugger client parent.
+ * @param aGrip Object
+ * A pause-lifetime long string grip returned by the protocol.
+ */
+ function LongStringClient(aClient, aGrip) {
+ this._grip = aGrip;
+ this._client = aClient;
+ this.request = this._client.request;
+ }
+ exports.LongStringClient = LongStringClient;
+
+ LongStringClient.prototype = {
+ get actor() {
+ return this._grip.actor;
+ },
+ get length() {
+ return this._grip.length;
+ },
+ get initial() {
+ return this._grip.initial;
+ },
+ get _transport() {
+ return this._client._transport;
+ },
+
+ valid: true,
+
+ /**
+ * Get the substring of this LongString from aStart to aEnd.
+ *
+ * @param aStart Number
+ * The starting index.
+ * @param aEnd Number
+ * The ending index.
+ * @param aCallback Function
+ * The function called when we receive the substring.
+ */
+ substring: DebuggerClient.requester({
+ type: "substring",
+ start: args(0),
+ end: args(1)
+ }, {
+ telemetry: "SUBSTRING"
+ })
+ };
+
+ /**
+ * A SourceClient provides a way to access the source text of a script.
+ *
+ * @param aClient ThreadClient
+ * The thread client parent.
+ * @param aForm Object
+ * The form sent across the remote debugging protocol.
+ */
+ function SourceClient(aClient, aForm) {
+ this._form = aForm;
+ this._isBlackBoxed = aForm.isBlackBoxed;
+ this._isPrettyPrinted = aForm.isPrettyPrinted;
+ this._activeThread = aClient;
+ this._client = aClient.client;
+ }
+
+ SourceClient.prototype = {
+ get _transport() {
+ return this._client._transport;
+ },
+ get isBlackBoxed() {
+ return this._isBlackBoxed;
+ },
+ get isPrettyPrinted() {
+ return this._isPrettyPrinted;
+ },
+ get actor() {
+ return this._form.actor;
+ },
+ get request() {
+ return this._client.request;
+ },
+ get url() {
+ return this._form.url;
+ },
+
+ /**
+ * Black box this SourceClient's source.
+ *
+ * @param aCallback Function
+ * The callback function called when we receive the response from the server.
+ */
+ blackBox: DebuggerClient.requester({
+ type: "blackbox"
+ }, {
+ telemetry: "BLACKBOX",
+ after: function (aResponse) {
+ if (!aResponse.error) {
+ this._isBlackBoxed = true;
+ if (this._activeThread) {
+ this._activeThread.emit("blackboxchange", this);
+ }
+ }
+ return aResponse;
+ }
+ }),
+
+ /**
+ * Un-black box this SourceClient's source.
+ *
+ * @param aCallback Function
+ * The callback function called when we receive the response from the server.
+ */
+ unblackBox: DebuggerClient.requester({
+ type: "unblackbox"
+ }, {
+ telemetry: "UNBLACKBOX",
+ after: function (aResponse) {
+ if (!aResponse.error) {
+ this._isBlackBoxed = false;
+ if (this._activeThread) {
+ this._activeThread.emit("blackboxchange", this);
+ }
+ }
+ return aResponse;
+ }
+ }),
+
+ /**
+ * Get Executable Lines from a source
+ *
+ * @param aCallback Function
+ * The callback function called when we receive the response from the server.
+ */
+ getExecutableLines: function () {
+ var cb = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : noop;
+
+ var packet = {
+ to: this._form.actor,
+ type: "getExecutableLines"
+ };
+
+ return this._client.request(packet).then(res => {
+ cb(res.lines);
+ return res.lines;
+ });
+ },
+
+ /**
+ * Get a long string grip for this SourceClient's source.
+ */
+ source: function () {
+ var aCallback = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : noop;
+
+ var packet = {
+ to: this._form.actor,
+ type: "source"
+ };
+ return this._client.request(packet).then(aResponse => {
+ return this._onSourceResponse(aResponse, aCallback);
+ });
+ },
+
+ /**
+ * Pretty print this source's text.
+ */
+ prettyPrint: function (aIndent) {
+ var aCallback = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : noop;
+
+ var packet = {
+ to: this._form.actor,
+ type: "prettyPrint",
+ indent: aIndent
+ };
+ return this._client.request(packet).then(aResponse => {
+ if (!aResponse.error) {
+ this._isPrettyPrinted = true;
+ this._activeThread._clearFrames();
+ this._activeThread.emit("prettyprintchange", this);
+ }
+ return this._onSourceResponse(aResponse, aCallback);
+ });
+ },
+
+ /**
+ * Stop pretty printing this source's text.
+ */
+ disablePrettyPrint: function () {
+ var aCallback = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : noop;
+
+ var packet = {
+ to: this._form.actor,
+ type: "disablePrettyPrint"
+ };
+ return this._client.request(packet).then(aResponse => {
+ if (!aResponse.error) {
+ this._isPrettyPrinted = false;
+ this._activeThread._clearFrames();
+ this._activeThread.emit("prettyprintchange", this);
+ }
+ return this._onSourceResponse(aResponse, aCallback);
+ });
+ },
+
+ _onSourceResponse: function (aResponse, aCallback) {
+ if (aResponse.error) {
+ aCallback(aResponse);
+ return aResponse;
+ }
+
+ if (typeof aResponse.source === "string") {
+ aCallback(aResponse);
+ return aResponse;
+ }
+
+ var contentType = aResponse.contentType;
+ var source = aResponse.source;
+
+ var longString = this._activeThread.threadLongString(source);
+ return longString.substring(0, longString.length).then(function (aResponse) {
+ if (aResponse.error) {
+ aCallback(aResponse);
+ return aReponse;
+ }
+
+ var response = {
+ source: aResponse.substring,
+ contentType: contentType
+ };
+ aCallback(response);
+ return response;
+ });
+ },
+
+ /**
+ * Request to set a breakpoint in the specified location.
+ *
+ * @param object aLocation
+ * The location and condition of the breakpoint in
+ * the form of { line[, column, condition] }.
+ * @param function aOnResponse
+ * Called with the thread's response.
+ */
+ setBreakpoint: function (_ref) {
+ var line = _ref.line;
+ var column = _ref.column;
+ var condition = _ref.condition;
+ var noSliding = _ref.noSliding;
+ var aOnResponse = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : noop;
+
+ // A helper function that sets the breakpoint.
+ var doSetBreakpoint = aCallback => {
+ var root = this._client.mainRoot;
+ var location = {
+ line: line,
+ column: column
+ };
+
+ var packet = {
+ to: this.actor,
+ type: "setBreakpoint",
+ location: location,
+ condition: condition,
+ noSliding: noSliding
+ };
+
+ // Backwards compatibility: send the breakpoint request to the
+ // thread if the server doesn't support Debugger.Source actors.
+ if (!root.traits.debuggerSourceActors) {
+ packet.to = this._activeThread.actor;
+ packet.location.url = this.url;
+ }
+
+ return this._client.request(packet).then(aResponse => {
+ // Ignoring errors, since the user may be setting a breakpoint in a
+ // dead script that will reappear on a page reload.
+ var bpClient = void 0;
+ if (aResponse.actor) {
+ bpClient = new BreakpointClient(this._client, this, aResponse.actor, location, root.traits.conditionalBreakpoints ? condition : undefined);
+ }
+ aOnResponse(aResponse, bpClient);
+ if (aCallback) {
+ aCallback();
+ }
+ return [aResponse, bpClient];
+ });
+ };
+
+ // If the debuggee is paused, just set the breakpoint.
+ if (this._activeThread.paused) {
+ return doSetBreakpoint();
+ }
+ // Otherwise, force a pause in order to set the breakpoint.
+ return this._activeThread.interrupt().then(aResponse => {
+ if (aResponse.error) {
+ // Can't set the breakpoint if pausing failed.
+ aOnResponse(aResponse);
+ return aResponse;
+ }
+
+ var type = aResponse.type;
+ var why = aResponse.why;
+
+ var cleanUp = type == "paused" && why.type == "interrupted" ? () => this._activeThread.resume() : noop;
+
+ return doSetBreakpoint(cleanUp);
+ });
+ }
+ };
+
+ /**
+ * Breakpoint clients are used to remove breakpoints that are no longer used.
+ *
+ * @param aClient DebuggerClient
+ * The debugger client parent.
+ * @param aSourceClient SourceClient
+ * The source where this breakpoint exists
+ * @param aActor string
+ * The actor ID for this breakpoint.
+ * @param aLocation object
+ * The location of the breakpoint. This is an object with two properties:
+ * url and line.
+ * @param aCondition string
+ * The conditional expression of the breakpoint
+ */
+ function BreakpointClient(aClient, aSourceClient, aActor, aLocation, aCondition) {
+ this._client = aClient;
+ this._actor = aActor;
+ this.location = aLocation;
+ this.location.actor = aSourceClient.actor;
+ this.location.url = aSourceClient.url;
+ this.source = aSourceClient;
+ this.request = this._client.request;
+
+ // The condition property should only exist if it's a truthy value
+ if (aCondition) {
+ this.condition = aCondition;
+ }
+ }
+
+ BreakpointClient.prototype = {
+
+ _actor: null,
+ get actor() {
+ return this._actor;
+ },
+ get _transport() {
+ return this._client._transport;
+ },
+
+ /**
+ * Remove the breakpoint from the server.
+ */
+ remove: DebuggerClient.requester({
+ type: "delete"
+ }, {
+ telemetry: "DELETE"
+ }),
+
+ /**
+ * Determines if this breakpoint has a condition
+ */
+ hasCondition: function () {
+ var root = this._client.mainRoot;
+ // XXX bug 990137: We will remove support for client-side handling of
+ // conditional breakpoints
+ if (root.traits.conditionalBreakpoints) {
+ return "condition" in this;
+ } else {
+ return "conditionalExpression" in this;
+ }
+ },
+
+ /**
+ * Get the condition of this breakpoint. Currently we have to
+ * support locally emulated conditional breakpoints until the
+ * debugger servers are updated (see bug 990137). We used a
+ * different property when moving it server-side to ensure that we
+ * are testing the right code.
+ */
+ getCondition: function () {
+ var root = this._client.mainRoot;
+ if (root.traits.conditionalBreakpoints) {
+ return this.condition;
+ } else {
+ return this.conditionalExpression;
+ }
+ },
+
+ /**
+ * Set the condition of this breakpoint
+ */
+ setCondition: function (gThreadClient, aCondition, noSliding) {
+ var _this6 = this;
+
+ var root = this._client.mainRoot;
+ var deferred = promise.defer();
+
+ if (root.traits.conditionalBreakpoints) {
+ (function () {
+ var info = {
+ line: _this6.location.line,
+ column: _this6.location.column,
+ condition: aCondition,
+ noSliding
+ };
+
+ // Remove the current breakpoint and add a new one with the
+ // condition.
+ _this6.remove(aResponse => {
+ if (aResponse && aResponse.error) {
+ deferred.reject(aResponse);
+ return;
+ }
+
+ _this6.source.setBreakpoint(info, (aResponse, aNewBreakpoint) => {
+ if (aResponse && aResponse.error) {
+ deferred.reject(aResponse);
+ } else {
+ deferred.resolve(aNewBreakpoint);
+ }
+ });
+ });
+ })();
+ } else {
+ // The property shouldn't even exist if the condition is blank
+ if (aCondition === "") {
+ delete this.conditionalExpression;
+ } else {
+ this.conditionalExpression = aCondition;
+ }
+ deferred.resolve(this);
+ }
+
+ return deferred.promise;
+ }
+ };
+
+ eventSource(BreakpointClient.prototype);
+
+ /**
+ * Environment clients are used to manipulate the lexical environment actors.
+ *
+ * @param aClient DebuggerClient
+ * The debugger client parent.
+ * @param aForm Object
+ * The form sent across the remote debugging protocol.
+ */
+ function EnvironmentClient(aClient, aForm) {
+ this._client = aClient;
+ this._form = aForm;
+ this.request = this._client.request;
+ }
+ exports.EnvironmentClient = EnvironmentClient;
+
+ EnvironmentClient.prototype = {
+
+ get actor() {
+ return this._form.actor;
+ },
+ get _transport() {
+ return this._client._transport;
+ },
+
+ /**
+ * Fetches the bindings introduced by this lexical environment.
+ */
+ getBindings: DebuggerClient.requester({
+ type: "bindings"
+ }, {
+ telemetry: "BINDINGS"
+ }),
+
+ /**
+ * Changes the value of the identifier whose name is name (a string) to that
+ * represented by value (a grip).
+ */
+ assign: DebuggerClient.requester({
+ type: "assign",
+ name: args(0),
+ value: args(1)
+ }, {
+ telemetry: "ASSIGN"
+ })
+ };
+
+ eventSource(EnvironmentClient.prototype);
+
+/***/ },
+/* 80 */
+/***/ function(module, exports, __webpack_require__) {
+
+ /* WEBPACK VAR INJECTION */(function(module) {/* 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.metadata = {
+ "stability": "unstable"
+ };
+
+ var UNCAUGHT_ERROR = 'An error event was emitted for which there was no listener.';
+ var BAD_LISTENER = 'The event listener must be a function.';
+
+ var _require = __webpack_require__(81);
+
+ var ns = _require.ns;
+
+
+ var event = ns();
+
+ var EVENT_TYPE_PATTERN = /^on([A-Z]\w+$)/;
+ exports.EVENT_TYPE_PATTERN = EVENT_TYPE_PATTERN;
+
+ // Utility function to access given event `target` object's event listeners for
+ // the specific event `type`. If listeners for this type does not exists they
+ // will be created.
+ var observers = function observers(target, type) {
+ if (!target) throw TypeError("Event target must be an object");
+ var listeners = event(target);
+ return type in listeners ? listeners[type] : listeners[type] = [];
+ };
+
+ /**
+ * Registers an event `listener` that is called every time events of
+ * specified `type` is emitted on the given event `target`.
+ * @param {Object} target
+ * Event target object.
+ * @param {String} type
+ * The type of event.
+ * @param {Function} listener
+ * The listener function that processes the event.
+ */
+ function on(target, type, listener) {
+ if (typeof listener !== 'function') throw new Error(BAD_LISTENER);
+
+ var listeners = observers(target, type);
+ if (!~listeners.indexOf(listener)) listeners.push(listener);
+ }
+ exports.on = on;
+
+ var onceWeakMap = new WeakMap();
+
+ /**
+ * Registers an event `listener` that is called only the next time an event
+ * of the specified `type` is emitted on the given event `target`.
+ * @param {Object} target
+ * Event target object.
+ * @param {String} type
+ * The type of the event.
+ * @param {Function} listener
+ * The listener function that processes the event.
+ */
+ function once(target, type, listener) {
+ var replacement = function observer() {
+ off(target, type, observer);
+ onceWeakMap.delete(listener);
+
+ for (var _len = arguments.length, args = Array(_len), _key = 0; _key < _len; _key++) {
+ args[_key] = arguments[_key];
+ }
+
+ listener.apply(target, args);
+ };
+ onceWeakMap.set(listener, replacement);
+ on(target, type, replacement);
+ }
+ exports.once = once;
+
+ /**
+ * Execute each of the listeners in order with the supplied arguments.
+ * All the exceptions that are thrown by listeners during the emit
+ * are caught and can be handled by listeners of 'error' event. Thrown
+ * exceptions are passed as an argument to an 'error' event listener.
+ * If no 'error' listener is registered exception will be logged into an
+ * error console.
+ * @param {Object} target
+ * Event target object.
+ * @param {String} type
+ * The type of event.
+ * @params {Object|Number|String|Boolean} args
+ * Arguments that will be passed to listeners.
+ */
+ function emit(target, type) {
+ for (var _len2 = arguments.length, args = Array(_len2 > 2 ? _len2 - 2 : 0), _key2 = 2; _key2 < _len2; _key2++) {
+ args[_key2 - 2] = arguments[_key2];
+ }
+
+ emitOnObject.apply(undefined, [target, type, target].concat(args));
+ }
+ exports.emit = emit;
+
+ /**
+ * A variant of emit that allows setting the this property for event listeners
+ */
+ function emitOnObject(target, type, thisArg) {
+ var all = observers(target, '*').length;
+ var state = observers(target, type);
+ var listeners = state.slice();
+ var count = listeners.length;
+ var index = 0;
+
+ // If error event and there are no handlers (explicit or catch-all)
+ // then print error message to the console.
+
+ for (var _len3 = arguments.length, args = Array(_len3 > 3 ? _len3 - 3 : 0), _key3 = 3; _key3 < _len3; _key3++) {
+ args[_key3 - 3] = arguments[_key3];
+ }
+
+ if (count === 0 && type === 'error' && all === 0) console.exception(args[0]);
+ while (index < count) {
+ try {
+ var listener = listeners[index];
+ // Dispatch only if listener is still registered.
+ if (~state.indexOf(listener)) listener.apply(thisArg, args);
+ } catch (error) {
+ // If exception is not thrown by a error listener and error listener is
+ // registered emit `error` event. Otherwise dump exception to the console.
+ if (type !== 'error') emit(target, 'error', error);else console.exception(error);
+ }
+ index++;
+ }
+ // Also emit on `"*"` so that one could listen for all events.
+ if (type !== '*') emit.apply(undefined, [target, '*', type].concat(args));
+ }
+ exports.emitOnObject = emitOnObject;
+
+ /**
+ * Removes an event `listener` for the given event `type` on the given event
+ * `target`. If no `listener` is passed removes all listeners of the given
+ * `type`. If `type` is not passed removes all the listeners of the given
+ * event `target`.
+ * @param {Object} target
+ * The event target object.
+ * @param {String} type
+ * The type of event.
+ * @param {Function} listener
+ * The listener function that processes the event.
+ */
+ function off(target, type, listener) {
+ var length = arguments.length;
+ if (length === 3) {
+ if (onceWeakMap.has(listener)) {
+ listener = onceWeakMap.get(listener);
+ onceWeakMap.delete(listener);
+ }
+
+ var listeners = observers(target, type);
+ var index = listeners.indexOf(listener);
+ if (~index) listeners.splice(index, 1);
+ } else if (length === 2) {
+ observers(target, type).splice(0);
+ } else if (length === 1) {
+ (function () {
+ var listeners = event(target);
+ Object.keys(listeners).forEach(type => delete listeners[type]);
+ })();
+ }
+ }
+ exports.off = off;
+
+ /**
+ * Returns a number of event listeners registered for the given event `type`
+ * on the given event `target`.
+ */
+ function count(target, type) {
+ return observers(target, type).length;
+ }
+ exports.count = count;
+
+ /**
+ * Registers listeners on the given event `target` from the given `listeners`
+ * dictionary. Iterates over the listeners and if property name matches name
+ * pattern `onEventType` and property is a function, then registers it as
+ * an `eventType` listener on `target`.
+ *
+ * @param {Object} target
+ * The type of event.
+ * @param {Object} listeners
+ * Dictionary of listeners.
+ */
+ function setListeners(target, listeners) {
+ Object.keys(listeners || {}).forEach(key => {
+ var match = EVENT_TYPE_PATTERN.exec(key);
+ var type = match && match[1].toLowerCase();
+ if (!type) return;
+
+ var listener = listeners[key];
+ if (typeof listener === 'function') on(target, type, listener);
+ });
+ }
+ exports.setListeners = setListeners;
+ /* WEBPACK VAR INJECTION */}.call(exports, __webpack_require__(77)(module)))
+
+/***/ },
+/* 81 */
+/***/ function(module, exports, __webpack_require__) {
+
+ /* WEBPACK VAR INJECTION */(function(module) {/* 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.metadata = {
+ "stability": "unstable"
+ };
+
+ var create = Object.create;
+ var prototypeOf = Object.getPrototypeOf;
+
+ /**
+ * Returns a new namespace, function that may can be used to access an
+ * namespaced object of the argument argument. Namespaced object are associated
+ * with owner objects via weak references. Namespaced objects inherit from the
+ * owners ancestor namespaced object. If owner's ancestor is `null` then
+ * namespaced object inherits from given `prototype`. Namespaces can be used
+ * to define internal APIs that can be shared via enclosing `namespace`
+ * function.
+ * @examples
+ * const internals = ns();
+ * internals(object).secret = secret;
+ */
+ function ns() {
+ var map = new WeakMap();
+ return function namespace(target) {
+ if (!target) // If `target` is not an object return `target` itself.
+ return target;
+ // If target has no namespaced object yet, create one that inherits from
+ // the target prototype's namespaced object.
+ if (!map.has(target)) map.set(target, create(namespace(prototypeOf(target) || null)));
+
+ return map.get(target);
+ };
+ };
+
+ // `Namespace` is a e4x function in the scope, so we export the function also as
+ // `ns` as alias to avoid clashing.
+ exports.ns = ns;
+ exports.Namespace = ns;
+ /* WEBPACK VAR INJECTION */}.call(exports, __webpack_require__(77)(module)))
+
+/***/ },
+/* 82 */
+/***/ function(module, exports, __webpack_require__) {
+
+ /* -*- js-indent-level: 2; indent-tabs-mode: nil -*- */
+ /* vim: set ts=2 et sw=2 tw=80: */
+ /* 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";
+
+ var _require = __webpack_require__(61);
+
+ var Cc = _require.Cc;
+ var Ci = _require.Ci;
+ var Cu = _require.Cu;
+
+ var DevToolsUtils = __webpack_require__(68);
+ var EventEmitter = __webpack_require__(60);
+ var promise = __webpack_require__(66);
+
+ var _require2 = __webpack_require__(79);
+
+ var LongStringClient = _require2.LongStringClient;
+
+ /**
+ * A WebConsoleClient is used as a front end for the WebConsoleActor that is
+ * created on the server, hiding implementation details.
+ *
+ * @param object aDebuggerClient
+ * The DebuggerClient instance we live for.
+ * @param object aResponse
+ * The response packet received from the "startListeners" request sent to
+ * the WebConsoleActor.
+ */
+
+ function WebConsoleClient(aDebuggerClient, aResponse) {
+ this._actor = aResponse.from;
+ this._client = aDebuggerClient;
+ this._longStrings = {};
+ this.traits = aResponse.traits || {};
+ this.events = [];
+ this._networkRequests = new Map();
+
+ this.pendingEvaluationResults = new Map();
+ this.onEvaluationResult = this.onEvaluationResult.bind(this);
+ this.onNetworkEvent = this._onNetworkEvent.bind(this);
+ this.onNetworkEventUpdate = this._onNetworkEventUpdate.bind(this);
+
+ this._client.addListener("evaluationResult", this.onEvaluationResult);
+ this._client.addListener("networkEvent", this.onNetworkEvent);
+ this._client.addListener("networkEventUpdate", this.onNetworkEventUpdate);
+ EventEmitter.decorate(this);
+ }
+
+ exports.WebConsoleClient = WebConsoleClient;
+
+ WebConsoleClient.prototype = {
+ _longStrings: null,
+ traits: null,
+
+ /**
+ * Holds the network requests currently displayed by the Web Console. Each key
+ * represents the connection ID and the value is network request information.
+ * @private
+ * @type object
+ */
+ _networkRequests: null,
+
+ getNetworkRequest(actorId) {
+ return this._networkRequests.get(actorId);
+ },
+
+ hasNetworkRequest(actorId) {
+ return this._networkRequests.has(actorId);
+ },
+
+ removeNetworkRequest(actorId) {
+ this._networkRequests.delete(actorId);
+ },
+
+ getNetworkEvents() {
+ return this._networkRequests.values();
+ },
+
+ get actor() {
+ return this._actor;
+ },
+
+ /**
+ * The "networkEvent" message type handler. We redirect any message to
+ * the UI for displaying.
+ *
+ * @private
+ * @param string type
+ * Message type.
+ * @param object packet
+ * The message received from the server.
+ */
+ _onNetworkEvent: function (type, packet) {
+ if (packet.from == this._actor) {
+ var actor = packet.eventActor;
+ var networkInfo = {
+ _type: "NetworkEvent",
+ timeStamp: actor.timeStamp,
+ node: null,
+ actor: actor.actor,
+ discardRequestBody: true,
+ discardResponseBody: true,
+ startedDateTime: actor.startedDateTime,
+ request: {
+ url: actor.url,
+ method: actor.method
+ },
+ isXHR: actor.isXHR,
+ response: {},
+ timings: {},
+ updates: [], // track the list of network event updates
+ private: actor.private,
+ fromCache: actor.fromCache
+ };
+ this._networkRequests.set(actor.actor, networkInfo);
+
+ this.emit("networkEvent", networkInfo);
+ }
+ },
+
+ /**
+ * The "networkEventUpdate" message type handler. We redirect any message to
+ * the UI for displaying.
+ *
+ * @private
+ * @param string type
+ * Message type.
+ * @param object packet
+ * The message received from the server.
+ */
+ _onNetworkEventUpdate: function (type, packet) {
+ var networkInfo = this.getNetworkRequest(packet.from);
+ if (!networkInfo) {
+ return;
+ }
+
+ networkInfo.updates.push(packet.updateType);
+
+ switch (packet.updateType) {
+ case "requestHeaders":
+ networkInfo.request.headersSize = packet.headersSize;
+ break;
+ case "requestPostData":
+ networkInfo.discardRequestBody = packet.discardRequestBody;
+ networkInfo.request.bodySize = packet.dataSize;
+ break;
+ case "responseStart":
+ networkInfo.response.httpVersion = packet.response.httpVersion;
+ networkInfo.response.status = packet.response.status;
+ networkInfo.response.statusText = packet.response.statusText;
+ networkInfo.response.headersSize = packet.response.headersSize;
+ networkInfo.response.remoteAddress = packet.response.remoteAddress;
+ networkInfo.response.remotePort = packet.response.remotePort;
+ networkInfo.discardResponseBody = packet.response.discardResponseBody;
+ break;
+ case "responseContent":
+ networkInfo.response.content = {
+ mimeType: packet.mimeType
+ };
+ networkInfo.response.bodySize = packet.contentSize;
+ networkInfo.response.transferredSize = packet.transferredSize;
+ networkInfo.discardResponseBody = packet.discardResponseBody;
+ break;
+ case "eventTimings":
+ networkInfo.totalTime = packet.totalTime;
+ break;
+ case "securityInfo":
+ networkInfo.securityInfo = packet.state;
+ break;
+ }
+
+ this.emit("networkEventUpdate", {
+ packet: packet,
+ networkInfo
+ });
+ },
+
+ /**
+ * Retrieve the cached messages from the server.
+ *
+ * @see this.CACHED_MESSAGES
+ * @param array types
+ * The array of message types you want from the server. See
+ * this.CACHED_MESSAGES for known types.
+ * @param function aOnResponse
+ * The function invoked when the response is received.
+ */
+ getCachedMessages: function WCC_getCachedMessages(types, aOnResponse) {
+ var packet = {
+ to: this._actor,
+ type: "getCachedMessages",
+ messageTypes: types
+ };
+ this._client.request(packet, aOnResponse);
+ },
+
+ /**
+ * Inspect the properties of an object.
+ *
+ * @param string aActor
+ * The WebConsoleObjectActor ID to send the request to.
+ * @param function aOnResponse
+ * The function invoked when the response is received.
+ */
+ inspectObjectProperties: function WCC_inspectObjectProperties(aActor, aOnResponse) {
+ var packet = {
+ to: aActor,
+ type: "inspectProperties"
+ };
+ this._client.request(packet, aOnResponse);
+ },
+
+ /**
+ * Evaluate a JavaScript expression.
+ *
+ * @param string aString
+ * The code you want to evaluate.
+ * @param function aOnResponse
+ * The function invoked when the response is received.
+ * @param object [aOptions={}]
+ * Options for evaluation:
+ *
+ * - bindObjectActor: an ObjectActor ID. The OA holds a reference to
+ * a Debugger.Object that wraps a content object. This option allows
+ * you to bind |_self| to the D.O of the given OA, during string
+ * evaluation.
+ *
+ * See: Debugger.Object.executeInGlobalWithBindings() for information
+ * about bindings.
+ *
+ * Use case: the variable view needs to update objects and it does so
+ * by knowing the ObjectActor it inspects and binding |_self| to the
+ * D.O of the OA. As such, variable view sends strings like these for
+ * eval:
+ * _self["prop"] = value;
+ *
+ * - frameActor: a FrameActor ID. The FA holds a reference to
+ * a Debugger.Frame. This option allows you to evaluate the string in
+ * the frame of the given FA.
+ *
+ * - url: the url to evaluate the script as. Defaults to
+ * "debugger eval code".
+ *
+ * - selectedNodeActor: the NodeActor ID of the current selection in the
+ * Inspector, if such a selection exists. This is used by helper functions
+ * that can reference the currently selected node in the Inspector, like
+ * $0.
+ */
+ evaluateJS: function WCC_evaluateJS(aString, aOnResponse) {
+ var aOptions = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
+
+ var packet = {
+ to: this._actor,
+ type: "evaluateJS",
+ text: aString,
+ bindObjectActor: aOptions.bindObjectActor,
+ frameActor: aOptions.frameActor,
+ url: aOptions.url,
+ selectedNodeActor: aOptions.selectedNodeActor,
+ selectedObjectActor: aOptions.selectedObjectActor
+ };
+ this._client.request(packet, aOnResponse);
+ },
+
+ /**
+ * Evaluate a JavaScript expression asynchronously.
+ * See evaluateJS for parameter and response information.
+ */
+ evaluateJSAsync: function (aString, aOnResponse) {
+ var aOptions = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
+
+ // Pre-37 servers don't support async evaluation.
+ if (!this.traits.evaluateJSAsync) {
+ this.evaluateJS(aString, aOnResponse, aOptions);
+ return;
+ }
+
+ var packet = {
+ to: this._actor,
+ type: "evaluateJSAsync",
+ text: aString,
+ bindObjectActor: aOptions.bindObjectActor,
+ frameActor: aOptions.frameActor,
+ url: aOptions.url,
+ selectedNodeActor: aOptions.selectedNodeActor,
+ selectedObjectActor: aOptions.selectedObjectActor
+ };
+
+ this._client.request(packet, response => {
+ // Null check this in case the client has been detached while waiting
+ // for a response.
+ if (this.pendingEvaluationResults) {
+ this.pendingEvaluationResults.set(response.resultID, aOnResponse);
+ }
+ });
+ },
+
+ /**
+ * Handler for the actors's unsolicited evaluationResult packet.
+ */
+ onEvaluationResult: function (aNotification, aPacket) {
+ // The client on the main thread can receive notification packets from
+ // multiple webconsole actors: the one on the main thread and the ones
+ // on worker threads. So make sure we should be handling this request.
+ if (aPacket.from !== this._actor) {
+ return;
+ }
+
+ // Find the associated callback based on this ID, and fire it.
+ // In a sync evaluation, this would have already been called in
+ // direct response to the client.request function.
+ var onResponse = this.pendingEvaluationResults.get(aPacket.resultID);
+ if (onResponse) {
+ onResponse(aPacket);
+ this.pendingEvaluationResults.delete(aPacket.resultID);
+ } else {
+ DevToolsUtils.reportException("onEvaluationResult", "No response handler for an evaluateJSAsync result (resultID: " + aPacket.resultID + ")");
+ }
+ },
+
+ /**
+ * Autocomplete a JavaScript expression.
+ *
+ * @param string aString
+ * The code you want to autocomplete.
+ * @param number aCursor
+ * Cursor location inside the string. Index starts from 0.
+ * @param function aOnResponse
+ * The function invoked when the response is received.
+ * @param string aFrameActor
+ * The id of the frame actor that made the call.
+ */
+ autocomplete: function WCC_autocomplete(aString, aCursor, aOnResponse, aFrameActor) {
+ var packet = {
+ to: this._actor,
+ type: "autocomplete",
+ text: aString,
+ cursor: aCursor,
+ frameActor: aFrameActor
+ };
+ this._client.request(packet, aOnResponse);
+ },
+
+ /**
+ * Clear the cache of messages (page errors and console API calls).
+ */
+ clearMessagesCache: function WCC_clearMessagesCache() {
+ var packet = {
+ to: this._actor,
+ type: "clearMessagesCache"
+ };
+ this._client.request(packet);
+ },
+
+ /**
+ * Get Web Console-related preferences on the server.
+ *
+ * @param array aPreferences
+ * An array with the preferences you want to retrieve.
+ * @param function [aOnResponse]
+ * Optional function to invoke when the response is received.
+ */
+ getPreferences: function WCC_getPreferences(aPreferences, aOnResponse) {
+ var packet = {
+ to: this._actor,
+ type: "getPreferences",
+ preferences: aPreferences
+ };
+ this._client.request(packet, aOnResponse);
+ },
+
+ /**
+ * Set Web Console-related preferences on the server.
+ *
+ * @param object aPreferences
+ * An object with the preferences you want to change.
+ * @param function [aOnResponse]
+ * Optional function to invoke when the response is received.
+ */
+ setPreferences: function WCC_setPreferences(aPreferences, aOnResponse) {
+ var packet = {
+ to: this._actor,
+ type: "setPreferences",
+ preferences: aPreferences
+ };
+ this._client.request(packet, aOnResponse);
+ },
+
+ /**
+ * Retrieve the request headers from the given NetworkEventActor.
+ *
+ * @param string aActor
+ * The NetworkEventActor ID.
+ * @param function aOnResponse
+ * The function invoked when the response is received.
+ */
+ getRequestHeaders: function WCC_getRequestHeaders(aActor, aOnResponse) {
+ var packet = {
+ to: aActor,
+ type: "getRequestHeaders"
+ };
+ this._client.request(packet, aOnResponse);
+ },
+
+ /**
+ * Retrieve the request cookies from the given NetworkEventActor.
+ *
+ * @param string aActor
+ * The NetworkEventActor ID.
+ * @param function aOnResponse
+ * The function invoked when the response is received.
+ */
+ getRequestCookies: function WCC_getRequestCookies(aActor, aOnResponse) {
+ var packet = {
+ to: aActor,
+ type: "getRequestCookies"
+ };
+ this._client.request(packet, aOnResponse);
+ },
+
+ /**
+ * Retrieve the request post data from the given NetworkEventActor.
+ *
+ * @param string aActor
+ * The NetworkEventActor ID.
+ * @param function aOnResponse
+ * The function invoked when the response is received.
+ */
+ getRequestPostData: function WCC_getRequestPostData(aActor, aOnResponse) {
+ var packet = {
+ to: aActor,
+ type: "getRequestPostData"
+ };
+ this._client.request(packet, aOnResponse);
+ },
+
+ /**
+ * Retrieve the response headers from the given NetworkEventActor.
+ *
+ * @param string aActor
+ * The NetworkEventActor ID.
+ * @param function aOnResponse
+ * The function invoked when the response is received.
+ */
+ getResponseHeaders: function WCC_getResponseHeaders(aActor, aOnResponse) {
+ var packet = {
+ to: aActor,
+ type: "getResponseHeaders"
+ };
+ this._client.request(packet, aOnResponse);
+ },
+
+ /**
+ * Retrieve the response cookies from the given NetworkEventActor.
+ *
+ * @param string aActor
+ * The NetworkEventActor ID.
+ * @param function aOnResponse
+ * The function invoked when the response is received.
+ */
+ getResponseCookies: function WCC_getResponseCookies(aActor, aOnResponse) {
+ var packet = {
+ to: aActor,
+ type: "getResponseCookies"
+ };
+ this._client.request(packet, aOnResponse);
+ },
+
+ /**
+ * Retrieve the response content from the given NetworkEventActor.
+ *
+ * @param string aActor
+ * The NetworkEventActor ID.
+ * @param function aOnResponse
+ * The function invoked when the response is received.
+ */
+ getResponseContent: function WCC_getResponseContent(aActor, aOnResponse) {
+ var packet = {
+ to: aActor,
+ type: "getResponseContent"
+ };
+ this._client.request(packet, aOnResponse);
+ },
+
+ /**
+ * Retrieve the timing information for the given NetworkEventActor.
+ *
+ * @param string aActor
+ * The NetworkEventActor ID.
+ * @param function aOnResponse
+ * The function invoked when the response is received.
+ */
+ getEventTimings: function WCC_getEventTimings(aActor, aOnResponse) {
+ var packet = {
+ to: aActor,
+ type: "getEventTimings"
+ };
+ this._client.request(packet, aOnResponse);
+ },
+
+ /**
+ * Retrieve the security information for the given NetworkEventActor.
+ *
+ * @param string aActor
+ * The NetworkEventActor ID.
+ * @param function aOnResponse
+ * The function invoked when the response is received.
+ */
+ getSecurityInfo: function WCC_getSecurityInfo(aActor, aOnResponse) {
+ var packet = {
+ to: aActor,
+ type: "getSecurityInfo"
+ };
+ this._client.request(packet, aOnResponse);
+ },
+
+ /**
+ * Send a HTTP request with the given data.
+ *
+ * @param string aData
+ * The details of the HTTP request.
+ * @param function aOnResponse
+ * The function invoked when the response is received.
+ */
+ sendHTTPRequest: function WCC_sendHTTPRequest(aData, aOnResponse) {
+ var packet = {
+ to: this._actor,
+ type: "sendHTTPRequest",
+ request: aData
+ };
+ this._client.request(packet, aOnResponse);
+ },
+
+ /**
+ * Start the given Web Console listeners.
+ *
+ * @see this.LISTENERS
+ * @param array aListeners
+ * Array of listeners you want to start. See this.LISTENERS for
+ * known listeners.
+ * @param function aOnResponse
+ * Function to invoke when the server response is received.
+ */
+ startListeners: function WCC_startListeners(aListeners, aOnResponse) {
+ var packet = {
+ to: this._actor,
+ type: "startListeners",
+ listeners: aListeners
+ };
+ this._client.request(packet, aOnResponse);
+ },
+
+ /**
+ * Stop the given Web Console listeners.
+ *
+ * @see this.LISTENERS
+ * @param array aListeners
+ * Array of listeners you want to stop. See this.LISTENERS for
+ * known listeners.
+ * @param function aOnResponse
+ * Function to invoke when the server response is received.
+ */
+ stopListeners: function WCC_stopListeners(aListeners, aOnResponse) {
+ var packet = {
+ to: this._actor,
+ type: "stopListeners",
+ listeners: aListeners
+ };
+ this._client.request(packet, aOnResponse);
+ },
+
+ /**
+ * Return an instance of LongStringClient for the given long string grip.
+ *
+ * @param object aGrip
+ * The long string grip returned by the protocol.
+ * @return object
+ * The LongStringClient for the given long string grip.
+ */
+ longString: function WCC_longString(aGrip) {
+ if (aGrip.actor in this._longStrings) {
+ return this._longStrings[aGrip.actor];
+ }
+
+ var client = new LongStringClient(this._client, aGrip);
+ this._longStrings[aGrip.actor] = client;
+ return client;
+ },
+
+ /**
+ * Close the WebConsoleClient. This stops all the listeners on the server and
+ * detaches from the console actor.
+ *
+ * @param function aOnResponse
+ * Function to invoke when the server response is received.
+ */
+ detach: function WCC_detach(aOnResponse) {
+ this._client.removeListener("evaluationResult", this.onEvaluationResult);
+ this._client.removeListener("networkEvent", this.onNetworkEvent);
+ this._client.removeListener("networkEventUpdate", this.onNetworkEventUpdate);
+ this.stopListeners(null, aOnResponse);
+ this._longStrings = null;
+ this._client = null;
+ this.pendingEvaluationResults.clear();
+ this.pendingEvaluationResults = null;
+ this.clearNetworkRequests();
+ this._networkRequests = null;
+ },
+
+ clearNetworkRequests: function () {
+ this._networkRequests.clear();
+ },
+
+ /**
+ * Fetches the full text of a LongString.
+ *
+ * @param object | string stringGrip
+ * The long string grip containing the corresponding actor.
+ * If you pass in a plain string (by accident or because you're lazy),
+ * then a promise of the same string is simply returned.
+ * @return object Promise
+ * A promise that is resolved when the full string contents
+ * are available, or rejected if something goes wrong.
+ */
+ getString: function (stringGrip) {
+ // Make sure this is a long string.
+ if (typeof stringGrip != "object" || stringGrip.type != "longString") {
+ return promise.resolve(stringGrip); // Go home string, you're drunk.
+ }
+
+ // Fetch the long string only once.
+ if (stringGrip._fullText) {
+ return stringGrip._fullText.promise;
+ }
+
+ var deferred = stringGrip._fullText = promise.defer();
+ var actor = stringGrip.actor;
+ var initial = stringGrip.initial;
+ var length = stringGrip.length;
+
+ var longStringClient = this.longString(stringGrip);
+
+ longStringClient.substring(initial.length, length, aResponse => {
+ if (aResponse.error) {
+ DevToolsUtils.reportException("getString", aResponse.error + ": " + aResponse.message);
+
+ deferred.reject(aResponse);
+ return;
+ }
+ deferred.resolve(initial + aResponse.substring);
+ });
+
+ return deferred.promise;
+ }
+ };
+
+/***/ },
+/* 83 */
+/***/ function(module, exports, __webpack_require__) {
+
+ /* 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/. */
+
+ const { Services } = __webpack_require__(30);
+ const EventEmitter = __webpack_require__(60);
+
+ /**
+ * Shortcuts for lazily accessing and setting various preferences.
+ * Usage:
+ * let prefs = new Prefs("root.path.to.branch", {
+ * myIntPref: ["Int", "leaf.path.to.my-int-pref"],
+ * myCharPref: ["Char", "leaf.path.to.my-char-pref"],
+ * myJsonPref: ["Json", "leaf.path.to.my-json-pref"],
+ * myFloatPref: ["Float", "leaf.path.to.my-float-pref"]
+ * ...
+ * });
+ *
+ * Get/set:
+ * prefs.myCharPref = "foo";
+ * let aux = prefs.myCharPref;
+ *
+ * Observe:
+ * prefs.registerObserver();
+ * prefs.on("pref-changed", (prefName, prefValue) => {
+ * ...
+ * });
+ *
+ * @param string prefsRoot
+ * The root path to the required preferences branch.
+ * @param object prefsBlueprint
+ * An object containing { accessorName: [prefType, prefName] } keys.
+ */
+ function PrefsHelper(prefsRoot = "", prefsBlueprint = {}) {
+ EventEmitter.decorate(this);
+
+ let cache = new Map();
+
+ for (let accessorName in prefsBlueprint) {
+ let [prefType, prefName] = prefsBlueprint[accessorName];
+ map(this, cache, accessorName, prefType, prefsRoot, prefName);
+ }
+
+ let observer = makeObserver(this, cache, prefsRoot, prefsBlueprint);
+ this.registerObserver = () => observer.register();
+ this.unregisterObserver = () => observer.unregister();
+ }
+
+ /**
+ * Helper method for getting a pref value.
+ *
+ * @param Map cache
+ * @param string prefType
+ * @param string prefsRoot
+ * @param string prefName
+ * @return any
+ */
+ function get(cache, prefType, prefsRoot, prefName) {
+ let cachedPref = cache.get(prefName);
+ if (cachedPref !== undefined) {
+ return cachedPref;
+ }
+ let value = Services.prefs["get" + prefType + "Pref"](
+ [prefsRoot, prefName].join(".")
+ );
+ cache.set(prefName, value);
+ return value;
+ }
+
+ /**
+ * Helper method for setting a pref value.
+ *
+ * @param Map cache
+ * @param string prefType
+ * @param string prefsRoot
+ * @param string prefName
+ * @param any value
+ */
+ function set(cache, prefType, prefsRoot, prefName, value) {
+ Services.prefs["set" + prefType + "Pref"](
+ [prefsRoot, prefName].join("."),
+ value
+ );
+ cache.set(prefName, value);
+ }
+
+ /**
+ * Maps a property name to a pref, defining lazy getters and setters.
+ * Supported types are "Bool", "Char", "Int", "Float" (sugar around "Char"
+ * type and casting), and "Json" (which is basically just sugar for "Char"
+ * using the standard JSON serializer).
+ *
+ * @param PrefsHelper self
+ * @param Map cache
+ * @param string accessorName
+ * @param string prefType
+ * @param string prefsRoot
+ * @param string prefName
+ * @param array serializer [optional]
+ */
+ function map(self, cache, accessorName, prefType, prefsRoot, prefName,
+ serializer = { in: e => e, out: e => e }) {
+ if (prefName in self) {
+ throw new Error(`Can't use ${prefName} because it overrides a property` +
+ "on the instance.");
+ }
+ if (prefType == "Json") {
+ map(self, cache, accessorName, "Char", prefsRoot, prefName, {
+ in: JSON.parse,
+ out: JSON.stringify
+ });
+ return;
+ }
+ if (prefType == "Float") {
+ map(self, cache, accessorName, "Char", prefsRoot, prefName, {
+ in: Number.parseFloat,
+ out: (n) => n + ""
+ });
+ return;
+ }
+
+ Object.defineProperty(self, accessorName, {
+ get: () => serializer.in(get(cache, prefType, prefsRoot, prefName)),
+ set: (e) => set(cache, prefType, prefsRoot, prefName, serializer.out(e))
+ });
+ }
+
+ /**
+ * Finds the accessor for the provided pref, based on the blueprint object
+ * used in the constructor.
+ *
+ * @param PrefsHelper self
+ * @param object prefsBlueprint
+ * @return string
+ */
+ function accessorNameForPref(somePrefName, prefsBlueprint) {
+ for (let accessorName in prefsBlueprint) {
+ let [, prefName] = prefsBlueprint[accessorName];
+ if (somePrefName == prefName) {
+ return accessorName;
+ }
+ }
+ return "";
+ }
+
+ /**
+ * Creates a pref observer for `self`.
+ *
+ * @param PrefsHelper self
+ * @param Map cache
+ * @param string prefsRoot
+ * @param object prefsBlueprint
+ * @return object
+ */
+ function makeObserver(self, cache, prefsRoot, prefsBlueprint) {
+ return {
+ register: function() {
+ this._branch = Services.prefs.getBranch(prefsRoot + ".");
+ this._branch.addObserver("", this, false);
+ },
+ unregister: function() {
+ this._branch.removeObserver("", this);
+ },
+ observe: function(subject, topic, prefName) {
+ // If this particular pref isn't handled by the blueprint object,
+ // even though it's in the specified branch, ignore it.
+ let accessorName = accessorNameForPref(prefName, prefsBlueprint);
+ if (!(accessorName in self)) {
+ return;
+ }
+ cache.delete(prefName);
+ self.emit("pref-changed", accessorName, self[accessorName]);
+ }
+ };
+ }
+
+ exports.PrefsHelper = PrefsHelper;
+
+
+/***/ },
+/* 84 */
+/***/ function(module, exports, __webpack_require__) {
+
+ /* 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/. */
+
+ var promise = __webpack_require__(66);
+ var EventEmitter = __webpack_require__(60);
+
+ /* const { DebuggerServer } = require("../../server/main");*/
+
+ var _require = __webpack_require__(79);
+
+ var DebuggerClient = _require.DebuggerClient;
+
+
+ var targets = new WeakMap();
+ var promiseTargets = new WeakMap();
+
+ /**
+ * Functions for creating Targets
+ */
+ exports.TargetFactory = {
+ /**
+ * Construct a Target
+ * @param {XULTab} tab
+ * The tab to use in creating a new target.
+ *
+ * @return A target object
+ */
+ forTab: function (tab) {
+ var target = targets.get(tab);
+ if (target == null) {
+ target = new TabTarget(tab);
+ targets.set(tab, target);
+ }
+ return target;
+ },
+
+ /**
+ * Return a promise of a Target for a remote tab.
+ * @param {Object} options
+ * The options object has the following properties:
+ * {
+ * form: the remote protocol form of a tab,
+ * client: a DebuggerClient instance
+ * (caller owns this and is responsible for closing),
+ * chrome: true if the remote target is the whole process
+ * }
+ *
+ * @return A promise of a target object
+ */
+ forRemoteTab: function (options) {
+ var targetPromise = promiseTargets.get(options);
+ if (targetPromise == null) {
+ (function () {
+ var target = new TabTarget(options);
+ targetPromise = target.makeRemote().then(() => target);
+ promiseTargets.set(options, targetPromise);
+ })();
+ }
+ return targetPromise;
+ },
+
+ forWorker: function (workerClient) {
+ var target = targets.get(workerClient);
+ if (target == null) {
+ target = new WorkerTarget(workerClient);
+ targets.set(workerClient, target);
+ }
+ return target;
+ },
+
+ /**
+ * Creating a target for a tab that is being closed is a problem because it
+ * allows a leak as a result of coming after the close event which normally
+ * clears things up. This function allows us to ask if there is a known
+ * target for a tab without creating a target
+ * @return true/false
+ */
+ isKnownTab: function (tab) {
+ return targets.has(tab);
+ }
+ };
+
+ /**
+ * A Target represents something that we can debug. Targets are generally
+ * read-only. Any changes that you wish to make to a target should be done via
+ * a Tool that attaches to the target. i.e. a Target is just a pointer saying
+ * "the thing to debug is over there".
+ *
+ * Providing a generalized abstraction of a web-page or web-browser (available
+ * either locally or remotely) is beyond the scope of this class (and maybe
+ * also beyond the scope of this universe) However Target does attempt to
+ * abstract some common events and read-only properties common to many Tools.
+ *
+ * Supported read-only properties:
+ * - name, isRemote, url
+ *
+ * Target extends EventEmitter and provides support for the following events:
+ * - close: The target window has been closed. All tools attached to this
+ * target should close. This event is not currently cancelable.
+ * - navigate: The target window has navigated to a different URL
+ *
+ * Optional events:
+ * - will-navigate: The target window will navigate to a different URL
+ * - hidden: The target is not visible anymore (for TargetTab, another tab is
+ * selected)
+ * - visible: The target is visible (for TargetTab, tab is selected)
+ *
+ * Comparing Targets: 2 instances of a Target object can point at the same
+ * thing, so t1 !== t2 and t1 != t2 even when they represent the same object.
+ * To compare to targets use 't1.equals(t2)'.
+ */
+
+ /**
+ * A TabTarget represents a page living in a browser tab. Generally these will
+ * be web pages served over http(s), but they don't have to be.
+ */
+ function TabTarget(tab) {
+ EventEmitter.decorate(this);
+ this.destroy = this.destroy.bind(this);
+ this._handleThreadState = this._handleThreadState.bind(this);
+ this.on("thread-resumed", this._handleThreadState);
+ this.on("thread-paused", this._handleThreadState);
+ this.activeTab = this.activeConsole = null;
+ // Only real tabs need initialization here. Placeholder objects for remote
+ // targets will be initialized after a makeRemote method call.
+ if (tab && !["client", "form", "chrome"].every(tab.hasOwnProperty, tab)) {
+ this._tab = tab;
+ this._setupListeners();
+ } else {
+ this._form = tab.form;
+ this._client = tab.client;
+ this._chrome = tab.chrome;
+ }
+ // Default isTabActor to true if not explicitly specified
+ if (typeof tab.isTabActor == "boolean") {
+ this._isTabActor = tab.isTabActor;
+ } else {
+ this._isTabActor = true;
+ }
+ }
+
+ TabTarget.prototype = {
+ _webProgressListener: null,
+
+ /**
+ * Returns a promise for the protocol description from the root actor. Used
+ * internally with `target.actorHasMethod`. Takes advantage of caching if
+ * definition was fetched previously with the corresponding actor information.
+ * Actors are lazily loaded, so not only must the tool using a specific actor
+ * be in use, the actors are only registered after invoking a method (for
+ * performance reasons, added in bug 988237), so to use these actor detection
+ * methods, one must already be communicating with a specific actor of that
+ * type.
+ *
+ * Must be a remote target.
+ *
+ * @return {Promise}
+ * {
+ * "category": "actor",
+ * "typeName": "longstractor",
+ * "methods": [{
+ * "name": "substring",
+ * "request": {
+ * "type": "substring",
+ * "start": {
+ * "_arg": 0,
+ * "type": "primitive"
+ * },
+ * "end": {
+ * "_arg": 1,
+ * "type": "primitive"
+ * }
+ * },
+ * "response": {
+ * "substring": {
+ * "_retval": "primitive"
+ * }
+ * }
+ * }],
+ * "events": {}
+ * }
+ */
+ getActorDescription: function (actorName) {
+ if (!this.client) {
+ throw new Error("TabTarget#getActorDescription() can only be called on " + "remote tabs.");
+ }
+
+ var deferred = promise.defer();
+
+ if (this._protocolDescription && this._protocolDescription.types[actorName]) {
+ deferred.resolve(this._protocolDescription.types[actorName]);
+ } else {
+ this.client.mainRoot.protocolDescription(description => {
+ this._protocolDescription = description;
+ deferred.resolve(description.types[actorName]);
+ });
+ }
+
+ return deferred.promise;
+ },
+
+ /**
+ * Returns a boolean indicating whether or not the specific actor
+ * type exists. Must be a remote target.
+ *
+ * @param {String} actorName
+ * @return {Boolean}
+ */
+ hasActor: function (actorName) {
+ if (!this.client) {
+ throw new Error("TabTarget#hasActor() can only be called on remote " + "tabs.");
+ }
+ if (this.form) {
+ return !!this.form[actorName + "Actor"];
+ }
+ return false;
+ },
+
+ /**
+ * Queries the protocol description to see if an actor has
+ * an available method. The actor must already be lazily-loaded (read
+ * the restrictions in the `getActorDescription` comments),
+ * so this is for use inside of tool. Returns a promise that
+ * resolves to a boolean. Must be a remote target.
+ *
+ * @param {String} actorName
+ * @param {String} methodName
+ * @return {Promise}
+ */
+ actorHasMethod: function (actorName, methodName) {
+ if (!this.client) {
+ throw new Error("TabTarget#actorHasMethod() can only be called on " + "remote tabs.");
+ }
+ return this.getActorDescription(actorName).then(desc => {
+ if (desc && desc.methods) {
+ return !!desc.methods.find(method => method.name === methodName);
+ }
+ return false;
+ });
+ },
+
+ /**
+ * Returns a trait from the root actor.
+ *
+ * @param {String} traitName
+ * @return {Mixed}
+ */
+ getTrait: function (traitName) {
+ if (!this.client) {
+ throw new Error("TabTarget#getTrait() can only be called on remote " + "tabs.");
+ }
+
+ // If the targeted actor exposes traits and has a defined value for this
+ // traits, override the root actor traits
+ if (this.form.traits && traitName in this.form.traits) {
+ return this.form.traits[traitName];
+ }
+
+ return this.client.traits[traitName];
+ },
+
+ get tab() {
+ return this._tab;
+ },
+
+ get form() {
+ return this._form;
+ },
+
+ // Get a promise of the root form returned by a listTabs request. This promise
+ // is cached.
+ get root() {
+ if (!this._root) {
+ this._root = this._getRoot();
+ }
+ return this._root;
+ },
+
+ _getRoot: function () {
+ return new Promise((resolve, reject) => {
+ this.client.listTabs(response => {
+ if (response.error) {
+ reject(new Error(response.error + ": " + response.message));
+ return;
+ }
+
+ resolve(response);
+ });
+ });
+ },
+
+ get client() {
+ return this._client;
+ },
+
+ // Tells us if we are debugging content document
+ // or if we are debugging chrome stuff.
+ // Allows to controls which features are available against
+ // a chrome or a content document.
+ get chrome() {
+ return this._chrome;
+ },
+
+ // Tells us if the related actor implements TabActor interface
+ // and requires to call `attach` request before being used
+ // and `detach` during cleanup
+ get isTabActor() {
+ return this._isTabActor;
+ },
+
+ get window() {
+ // XXX - this is a footgun for e10s - there .contentWindow will be null,
+ // and even though .contentWindowAsCPOW *might* work, it will not work
+ // in all contexts. Consumers of .window need to be refactored to not
+ // rely on this.
+ // if (Services.appinfo.processType != Ci.nsIXULRuntime.PROCESS_TYPE_DEFAULT) {
+ // console.error("The .window getter on devtools' |target| object isn't " +
+ // "e10s friendly!\n" + Error().stack);
+ // }
+ // Be extra careful here, since this may be called by HS_getHudByWindow
+ // during shutdown.
+ if (this._tab && this._tab.linkedBrowser) {
+ return this._tab.linkedBrowser.contentWindow;
+ }
+ return null;
+ },
+
+ get name() {
+ if (this._tab && this._tab.linkedBrowser.contentDocument) {
+ return this._tab.linkedBrowser.contentDocument.title;
+ }
+ if (this.isAddon) {
+ return this._form.name;
+ }
+ return this._form.title;
+ },
+
+ get url() {
+ return this._tab ? this._tab.linkedBrowser.currentURI.spec : this._form.url;
+ },
+
+ get isRemote() {
+ return !this.isLocalTab;
+ },
+
+ get isAddon() {
+ return !!(this._form && this._form.actor && this._form.actor.match(/conn\d+\.addon\d+/));
+ },
+
+ get isLocalTab() {
+ return !!this._tab;
+ },
+
+ get isMultiProcess() {
+ return !this.window;
+ },
+
+ get isThreadPaused() {
+ return !!this._isThreadPaused;
+ },
+
+ /**
+ * Adds remote protocol capabilities to the target, so that it can be used
+ * for tools that support the Remote Debugging Protocol even for local
+ * connections.
+ */
+ makeRemote: function () {
+ if (this._remote) {
+ return this._remote.promise;
+ }
+
+ this._remote = promise.defer();
+
+ if (this.isLocalTab) {
+ // Since a remote protocol connection will be made, let's start the
+ // DebuggerServer here, once and for all tools.
+ if (!DebuggerServer.initialized) {
+ DebuggerServer.init();
+ DebuggerServer.addBrowserActors();
+ }
+
+ this._client = new DebuggerClient(DebuggerServer.connectPipe());
+ // A local TabTarget will never perform chrome debugging.
+ this._chrome = false;
+ }
+
+ this._setupRemoteListeners();
+
+ var attachTab = () => {
+ this._client.attachTab(this._form.actor, (response, tabClient) => {
+ if (!tabClient) {
+ this._remote.reject("Unable to attach to the tab");
+ return;
+ }
+ this.activeTab = tabClient;
+ this.threadActor = response.threadActor;
+ attachConsole();
+ });
+ };
+
+ var onConsoleAttached = (response, consoleClient) => {
+ if (!consoleClient) {
+ this._remote.reject("Unable to attach to the console");
+ return;
+ }
+ this.activeConsole = consoleClient;
+ this._remote.resolve(null);
+ };
+
+ var attachConsole = () => {
+ this._client.attachConsole(this._form.consoleActor, ["NetworkActivity"], onConsoleAttached);
+ };
+
+ if (this.isLocalTab) {
+ this._client.connect(() => {
+ this._client.getTab({ tab: this.tab }).then(response => {
+ this._form = response.tab;
+ attachTab();
+ });
+ });
+ } else if (this.isTabActor) {
+ // In the remote debugging case, the protocol connection will have been
+ // already initialized in the connection screen code.
+ attachTab();
+ } else {
+ // AddonActor and chrome debugging on RootActor doesn't inherits from
+ // TabActor and doesn't need to be attached.
+ attachConsole();
+ }
+
+ return this._remote.promise;
+ },
+
+ /**
+ * Listen to the different events.
+ */
+ _setupListeners: function () {
+ this._webProgressListener = new TabWebProgressListener(this);
+ this.tab.linkedBrowser.addProgressListener(this._webProgressListener);
+ this.tab.addEventListener("TabClose", this);
+ this.tab.parentNode.addEventListener("TabSelect", this);
+ this.tab.ownerDocument.defaultView.addEventListener("unload", this);
+ },
+
+ /**
+ * Teardown event listeners.
+ */
+ _teardownListeners: function () {
+ if (this._webProgressListener) {
+ this._webProgressListener.destroy();
+ }
+
+ this._tab.ownerDocument.defaultView.removeEventListener("unload", this);
+ this._tab.removeEventListener("TabClose", this);
+ this._tab.parentNode.removeEventListener("TabSelect", this);
+ },
+
+ /**
+ * Setup listeners for remote debugging, updating existing ones as necessary.
+ */
+ _setupRemoteListeners: function () {
+ this.client.addListener("closed", this.destroy);
+
+ this._onTabDetached = (aType, aPacket) => {
+ // We have to filter message to ensure that this detach is for this tab
+ if (aPacket.from == this._form.actor) {
+ this.destroy();
+ }
+ };
+ this.client.addListener("tabDetached", this._onTabDetached);
+
+ this._onTabNavigated = (aType, aPacket) => {
+ var event = Object.create(null);
+ event.url = aPacket.url;
+ event.title = aPacket.title;
+ event.nativeConsoleAPI = aPacket.nativeConsoleAPI;
+ event.isFrameSwitching = aPacket.isFrameSwitching;
+ // Send any stored event payload (DOMWindow or nsIRequest) for backwards
+ // compatibility with non-remotable tools.
+ if (aPacket.state == "start") {
+ event._navPayload = this._navRequest;
+ this.emit("will-navigate", event);
+ this._navRequest = null;
+ } else {
+ event._navPayload = this._navWindow;
+ this.emit("navigate", event);
+ this._navWindow = null;
+ }
+ };
+ this.client.addListener("tabNavigated", this._onTabNavigated);
+
+ this._onFrameUpdate = (aType, aPacket) => {
+ this.emit("frame-update", aPacket);
+ };
+ this.client.addListener("frameUpdate", this._onFrameUpdate);
+ },
+
+ /**
+ * Teardown listeners for remote debugging.
+ */
+ _teardownRemoteListeners: function () {
+ this.client.removeListener("closed", this.destroy);
+ this.client.removeListener("tabNavigated", this._onTabNavigated);
+ this.client.removeListener("tabDetached", this._onTabDetached);
+ this.client.removeListener("frameUpdate", this._onFrameUpdate);
+ },
+
+ /**
+ * Handle tabs events.
+ */
+ handleEvent: function (event) {
+ switch (event.type) {
+ case "TabClose":
+ case "unload":
+ this.destroy();
+ break;
+ case "TabSelect":
+ if (this.tab.selected) {
+ this.emit("visible", event);
+ } else {
+ this.emit("hidden", event);
+ }
+ break;
+ }
+ },
+
+ /**
+ * Handle script status.
+ */
+ _handleThreadState: function (event) {
+ switch (event) {
+ case "thread-resumed":
+ this._isThreadPaused = false;
+ break;
+ case "thread-paused":
+ this._isThreadPaused = true;
+ break;
+ }
+ },
+
+ /**
+ * Target is not alive anymore.
+ */
+ destroy: function () {
+ // If several things call destroy then we give them all the same
+ // destruction promise so we're sure to destroy only once
+ if (this._destroyer) {
+ return this._destroyer.promise;
+ }
+
+ this._destroyer = promise.defer();
+
+ // Before taking any action, notify listeners that destruction is imminent.
+ this.emit("close");
+
+ // First of all, do cleanup tasks that pertain to both remoted and
+ // non-remoted targets.
+ this.off("thread-resumed", this._handleThreadState);
+ this.off("thread-paused", this._handleThreadState);
+
+ if (this._tab) {
+ this._teardownListeners();
+ }
+
+ var cleanupAndResolve = () => {
+ this._cleanup();
+ this._destroyer.resolve(null);
+ };
+ // If this target was not remoted, the promise will be resolved before the
+ // function returns.
+ if (this._tab && !this._client) {
+ cleanupAndResolve();
+ } else if (this._client) {
+ // If, on the other hand, this target was remoted, the promise will be
+ // resolved after the remote connection is closed.
+ this._teardownRemoteListeners();
+
+ if (this.isLocalTab) {
+ // We started with a local tab and created the client ourselves, so we
+ // should close it.
+ this._client.close(cleanupAndResolve);
+ } else if (this.activeTab) {
+ // The client was handed to us, so we are not responsible for closing
+ // it. We just need to detach from the tab, if already attached.
+ // |detach| may fail if the connection is already dead, so proceed with
+ // cleanup directly after this.
+ this.activeTab.detach();
+ cleanupAndResolve();
+ } else {
+ cleanupAndResolve();
+ }
+ }
+
+ return this._destroyer.promise;
+ },
+
+ /**
+ * Clean up references to what this target points to.
+ */
+ _cleanup: function () {
+ if (this._tab) {
+ targets.delete(this._tab);
+ } else {
+ promiseTargets.delete(this._form);
+ }
+ this.activeTab = null;
+ this.activeConsole = null;
+ this._client = null;
+ this._tab = null;
+ this._form = null;
+ this._remote = null;
+ },
+
+ toString: function () {
+ var id = this._tab ? this._tab : this._form && this._form.actor;
+ return `TabTarget:${ id }`;
+ }
+ };
+
+ function WorkerTarget(workerClient) {
+ EventEmitter.decorate(this);
+ this._workerClient = workerClient;
+ }
+
+ /**
+ * A WorkerTarget represents a worker. Unlike TabTarget, which can represent
+ * either a local or remote tab, WorkerTarget always represents a remote worker.
+ * Moreover, unlike TabTarget, which is constructed with a placeholder object
+ * for remote tabs (from which a TabClient can then be lazily obtained),
+ * WorkerTarget is constructed with a WorkerClient directly.
+ *
+ * WorkerClient is designed to mimic the interface of TabClient as closely as
+ * possible. This allows us to debug workers as if they were ordinary tabs,
+ * requiring only minimal changes to the rest of the frontend.
+ */
+ WorkerTarget.prototype = {
+ destroy: function () {},
+
+ get isRemote() {
+ return true;
+ },
+
+ get isTabActor() {
+ return true;
+ },
+
+ get url() {
+ return this._workerClient.url;
+ },
+
+ get isWorkerTarget() {
+ return true;
+ },
+
+ get form() {
+ return {
+ consoleActor: this._workerClient.consoleActor
+ };
+ },
+
+ get activeTab() {
+ return this._workerClient;
+ },
+
+ get client() {
+ return this._workerClient.client;
+ },
+
+ destroy: function () {},
+
+ hasActor: function (name) {
+ return false;
+ },
+
+ getTrait: function () {
+ return undefined;
+ },
+
+ makeRemote: function () {
+ return Promise.resolve();
+ }
+ };
+
+/***/ },
+/* 85 */
+/***/ function(module, exports, __webpack_require__) {
+
+ /* 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";
+
+ var EventEmitter = __webpack_require__(60);
+
+ function WebSocketDebuggerTransport(socket) {
+ EventEmitter.decorate(this);
+
+ this.active = false;
+ this.hooks = null;
+ this.socket = socket;
+ }
+
+ WebSocketDebuggerTransport.prototype = {
+ ready() {
+ if (this.active) {
+ return;
+ }
+
+ this.socket.addEventListener("message", this);
+ this.socket.addEventListener("close", this);
+
+ this.active = true;
+ },
+
+ send(object) {
+ this.emit("send", object);
+ if (this.socket) {
+ this.socket.send(JSON.stringify(object));
+ }
+ },
+
+ startBulkSend() {
+ throw new Error("Bulk send is not supported by WebSocket transport");
+ },
+
+ close() {
+ this.emit("close");
+ this.active = false;
+
+ this.socket.removeEventListener("message", this);
+ this.socket.removeEventListener("close", this);
+ this.socket.close();
+ this.socket = null;
+
+ if (this.hooks) {
+ this.hooks.onClosed();
+ this.hooks = null;
+ }
+ },
+
+ handleEvent(event) {
+ switch (event.type) {
+ case "message":
+ this.onMessage(event);
+ break;
+ case "close":
+ this.close();
+ break;
+ }
+ },
+
+ onMessage(_ref) {
+ var data = _ref.data;
+
+ if (typeof data !== "string") {
+ throw new Error("Binary messages are not supported by WebSocket transport");
+ }
+
+ var object = JSON.parse(data);
+ this.emit("packet", object);
+ if (this.hooks) {
+ this.hooks.onPacket(object);
+ }
+ }
+ };
+
+ module.exports = WebSocketDebuggerTransport;
+
+/***/ },
+/* 86 */
+/***/ function(module, exports, __webpack_require__) {
+
+ /* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+ /* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+ /* 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";
+
+ var EventEmitter = __webpack_require__(60);
+
+ /**
+ * A partial implementation of the Menu API provided by electron:
+ * https://github.com/electron/electron/blob/master/docs/api/menu.md.
+ *
+ * Extra features:
+ * - Emits an 'open' and 'close' event when the menu is opened/closed
+
+ * @param String id (non standard)
+ * Needed so tests can confirm the XUL implementation is working
+ */
+ function Menu() {
+ var _ref = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
+
+ var _ref$id = _ref.id;
+ var id = _ref$id === undefined ? null : _ref$id;
+
+ this.menuitems = [];
+ this.id = id;
+
+ Object.defineProperty(this, "items", {
+ get() {
+ return this.menuitems;
+ }
+ });
+
+ EventEmitter.decorate(this);
+ }
+
+ /**
+ * Add an item to the end of the Menu
+ *
+ * @param {MenuItem} menuItem
+ */
+ Menu.prototype.append = function (menuItem) {
+ this.menuitems.push(menuItem);
+ };
+
+ /**
+ * Add an item to a specified position in the menu
+ *
+ * @param {int} pos
+ * @param {MenuItem} menuItem
+ */
+ Menu.prototype.insert = function (pos, menuItem) {
+ throw Error("Not implemented");
+ };
+
+ /**
+ * Show the Menu at a specified location on the screen
+ *
+ * Missing features:
+ * - browserWindow - BrowserWindow (optional) - Default is null.
+ * - positioningItem Number - (optional) OS X
+ *
+ * @param {int} screenX
+ * @param {int} screenY
+ * @param Toolbox toolbox (non standard)
+ * Needed so we in which window to inject XUL
+ */
+ Menu.prototype.popup = function (screenX, screenY, toolbox) {
+ var doc = toolbox.doc;
+ var popupset = doc.querySelector("popupset");
+ // See bug 1285229, on Windows, opening the same popup multiple times in a
+ // row ends up duplicating the popup. The newly inserted popup doesn't
+ // dismiss the old one. So remove any previously displayed popup before
+ // opening a new one.
+ var popup = popupset.querySelector("menupopup[menu-api=\"true\"]");
+ if (popup) {
+ popup.hidePopup();
+ }
+
+ popup = this.createPopup(doc);
+ popup.setAttribute("menu-api", "true");
+
+ if (this.id) {
+ popup.id = this.id;
+ }
+ this._createMenuItems(popup);
+
+ // Remove the menu from the DOM once it's hidden.
+ popup.addEventListener("popuphidden", e => {
+ if (e.target === popup) {
+ popup.remove();
+ this.emit("close", popup);
+ }
+ });
+
+ popup.addEventListener("popupshown", e => {
+ if (e.target === popup) {
+ this.emit("open", popup);
+ }
+ });
+
+ popupset.appendChild(popup);
+ popup.openPopupAtScreen(screenX, screenY, true);
+ };
+
+ Menu.prototype.createPopup = function (doc) {
+ return doc.createElement("menupopup");
+ };
+
+ Menu.prototype._createMenuItems = function (parent) {
+ var doc = parent.ownerDocument;
+ this.menuitems.forEach(item => {
+ if (!item.visible) {
+ return;
+ }
+
+ if (item.submenu) {
+ var menupopup = doc.createElement("menupopup");
+ item.submenu._createMenuItems(menupopup);
+
+ var menu = doc.createElement("menu");
+ menu.appendChild(menupopup);
+ menu.setAttribute("label", item.label);
+ if (item.disabled) {
+ menu.setAttribute("disabled", "true");
+ }
+ if (item.accesskey) {
+ menu.setAttribute("accesskey", item.accesskey);
+ }
+ if (item.id) {
+ menu.id = item.id;
+ }
+ parent.appendChild(menu);
+ } else if (item.type === "separator") {
+ var menusep = doc.createElement("menuseparator");
+ parent.appendChild(menusep);
+ } else {
+ var menuitem = doc.createElement("menuitem");
+ menuitem.setAttribute("label", item.label);
+ menuitem.textContent = item.label;
+ menuitem.addEventListener("command", () => item.click());
+
+ if (item.type === "checkbox") {
+ menuitem.setAttribute("type", "checkbox");
+ }
+ if (item.type === "radio") {
+ menuitem.setAttribute("type", "radio");
+ }
+ if (item.disabled) {
+ menuitem.setAttribute("disabled", "true");
+ }
+ if (item.checked) {
+ menuitem.setAttribute("checked", "true");
+ }
+ if (item.accesskey) {
+ menuitem.setAttribute("accesskey", item.accesskey);
+ }
+ if (item.id) {
+ menuitem.id = item.id;
+ }
+
+ parent.appendChild(menuitem);
+ }
+ });
+ };
+
+ Menu.setApplicationMenu = () => {
+ throw Error("Not implemented");
+ };
+
+ Menu.sendActionToFirstResponder = () => {
+ throw Error("Not implemented");
+ };
+
+ Menu.buildFromTemplate = () => {
+ throw Error("Not implemented");
+ };
+
+ module.exports = Menu;
+
+/***/ },
+/* 87 */
+/***/ function(module, exports) {
+
+ /* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+ /* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+ /* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+ "use strict";
+
+ /**
+ * A partial implementation of the MenuItem API provided by electron:
+ * https://github.com/electron/electron/blob/master/docs/api/menu-item.md.
+ *
+ * Missing features:
+ * - id String - Unique within a single menu. If defined then it can be used
+ * as a reference to this item by the position attribute.
+ * - role String - Define the action of the menu item; when specified the
+ * click property will be ignored
+ * - sublabel String
+ * - accelerator Accelerator
+ * - icon NativeImage
+ * - position String - This field allows fine-grained definition of the
+ * specific location within a given menu.
+ *
+ * Implemented features:
+ * @param Object options
+ * Function click
+ * Will be called with click(menuItem, browserWindow) when the menu item
+ * is clicked
+ * String type
+ * Can be normal, separator, submenu, checkbox or radio
+ * String label
+ * Boolean enabled
+ * If false, the menu item will be greyed out and unclickable.
+ * Boolean checked
+ * Should only be specified for checkbox or radio type menu items.
+ * Menu submenu
+ * Should be specified for submenu type menu items. If submenu is specified,
+ * the type: 'submenu' can be omitted. If the value is not a Menu then it
+ * will be automatically converted to one using Menu.buildFromTemplate.
+ * Boolean visible
+ * If false, the menu item will be entirely hidden.
+ */
+
+ function MenuItem() {
+ var _ref = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
+
+ var _ref$accesskey = _ref.accesskey;
+ var accesskey = _ref$accesskey === undefined ? null : _ref$accesskey;
+ var _ref$checked = _ref.checked;
+ var checked = _ref$checked === undefined ? false : _ref$checked;
+ var _ref$click = _ref.click;
+ var click = _ref$click === undefined ? () => {} : _ref$click;
+ var _ref$disabled = _ref.disabled;
+ var disabled = _ref$disabled === undefined ? false : _ref$disabled;
+ var _ref$label = _ref.label;
+ var label = _ref$label === undefined ? "" : _ref$label;
+ var _ref$id = _ref.id;
+ var id = _ref$id === undefined ? null : _ref$id;
+ var _ref$submenu = _ref.submenu;
+ var submenu = _ref$submenu === undefined ? null : _ref$submenu;
+ var _ref$type = _ref.type;
+ var type = _ref$type === undefined ? "normal" : _ref$type;
+ var _ref$visible = _ref.visible;
+ var visible = _ref$visible === undefined ? true : _ref$visible;
+
+ this.accesskey = accesskey;
+ this.checked = checked;
+ this.click = click;
+ this.disabled = disabled;
+ this.id = id;
+ this.label = label;
+ this.submenu = submenu;
+ this.type = type;
+ this.visible = visible;
+ }
+
+ module.exports = MenuItem;
+
+/***/ },
+/* 88 */
+/***/ function(module, exports, __webpack_require__) {
+
+ var _require = __webpack_require__(89);
+
+ var isDevelopment = _require.isDevelopment;
+ var isTesting = _require.isTesting;
+
+
+ function debugGlobal(field, value) {
+ if (isDevelopment() || isTesting()) {
+ window[field] = value;
+ }
+ }
+
+ function injectGlobals(_ref) {
+ var store = _ref.store;
+
+ debugGlobal("store", store);
+ debugGlobal("injectDebuggee", __webpack_require__(140));
+ debugGlobal("serializeStore", () => {
+ return JSON.parse(JSON.stringify(store.getState()));
+ });
+ }
+
+ module.exports = {
+ debugGlobal,
+ injectGlobals
+ };
+
+/***/ },
+/* 89 */
+/***/ function(module, exports, __webpack_require__) {
+
+ var feature = __webpack_require__(90);
+
+ module.exports = feature;
+
+/***/ },
+/* 90 */
+/***/ function(module, exports, __webpack_require__) {
+
+ var pick = __webpack_require__(91);
+ var config = void 0;
+
+ var flag = __webpack_require__(139);
+
+ /**
+ * Gets a config value for a given key
+ * e.g "chrome.webSocketPort"
+ */
+ function getValue(key) {
+ return pick(config, key);
+ }
+
+ function isEnabled(key) {
+ return config.features[key];
+ }
+
+ function isDevelopment() {
+ if (isFirefoxPanel()) {
+ // Default to production if compiling for the Firefox panel
+ return ("production") === "development";
+ }
+ return ("production") !== "production";
+ }
+
+ function isTesting() {
+ return flag.testing;
+ }
+
+ function isFirefoxPanel() {
+ return ("firefox-panel") == "firefox-panel";
+ }
+
+ function isFirefox() {
+ return (/firefox/i.test(navigator.userAgent)
+ );
+ }
+
+ function setConfig(value) {
+ config = value;
+ }
+
+ function getConfig() {
+ return config;
+ }
+
+ module.exports = {
+ isEnabled,
+ getValue,
+ isDevelopment,
+ isTesting,
+ isFirefoxPanel,
+ isFirefox,
+ getConfig,
+ setConfig
+ };
+
+/***/ },
+/* 91 */
+/***/ function(module, exports, __webpack_require__) {
+
+ var baseGet = __webpack_require__(92);
+
+ /**
+ * Gets the value at `path` of `object`. If the resolved value is
+ * `undefined`, the `defaultValue` is returned in its place.
+ *
+ * @static
+ * @memberOf _
+ * @since 3.7.0
+ * @category Object
+ * @param {Object} object The object to query.
+ * @param {Array|string} path The path of the property to get.
+ * @param {*} [defaultValue] The value returned for `undefined` resolved values.
+ * @returns {*} Returns the resolved value.
+ * @example
+ *
+ * var object = { 'a': [{ 'b': { 'c': 3 } }] };
+ *
+ * _.get(object, 'a[0].b.c');
+ * // => 3
+ *
+ * _.get(object, ['a', '0', 'b', 'c']);
+ * // => 3
+ *
+ * _.get(object, 'a.b.c', 'default');
+ * // => 'default'
+ */
+ function get(object, path, defaultValue) {
+ var result = object == null ? undefined : baseGet(object, path);
+ return result === undefined ? defaultValue : result;
+ }
+
+ module.exports = get;
+
+
+/***/ },
+/* 92 */
+/***/ function(module, exports, __webpack_require__) {
+
+ var castPath = __webpack_require__(93),
+ isKey = __webpack_require__(137),
+ toKey = __webpack_require__(138);
+
+ /**
+ * The base implementation of `_.get` without support for default values.
+ *
+ * @private
+ * @param {Object} object The object to query.
+ * @param {Array|string} path The path of the property to get.
+ * @returns {*} Returns the resolved value.
+ */
+ function baseGet(object, path) {
+ path = isKey(path, object) ? [path] : castPath(path);
+
+ var index = 0,
+ length = path.length;
+
+ while (object != null && index < length) {
+ object = object[toKey(path[index++])];
+ }
+ return (index && index == length) ? object : undefined;
+ }
+
+ module.exports = baseGet;
+
+
+/***/ },
+/* 93 */
+/***/ function(module, exports, __webpack_require__) {
+
+ var isArray = __webpack_require__(94),
+ stringToPath = __webpack_require__(95);
+
+ /**
+ * Casts `value` to a path array if it's not one.
+ *
+ * @private
+ * @param {*} value The value to inspect.
+ * @returns {Array} Returns the cast property path array.
+ */
+ function castPath(value) {
+ return isArray(value) ? value : stringToPath(value);
+ }
+
+ module.exports = castPath;
+
+
+/***/ },
+/* 94 */
+/***/ function(module, exports) {
+
+ /**
+ * Checks if `value` is classified as an `Array` object.
+ *
+ * @static
+ * @memberOf _
+ * @since 0.1.0
+ * @category Lang
+ * @param {*} value The value to check.
+ * @returns {boolean} Returns `true` if `value` is an array, else `false`.
+ * @example
+ *
+ * _.isArray([1, 2, 3]);
+ * // => true
+ *
+ * _.isArray(document.body.children);
+ * // => false
+ *
+ * _.isArray('abc');
+ * // => false
+ *
+ * _.isArray(_.noop);
+ * // => false
+ */
+ var isArray = Array.isArray;
+
+ module.exports = isArray;
+
+
+/***/ },
+/* 95 */
+/***/ function(module, exports, __webpack_require__) {
+
+ var memoizeCapped = __webpack_require__(96),
+ toString = __webpack_require__(132);
+
+ /** Used to match property names within property paths. */
+ var reLeadingDot = /^\./,
+ rePropName = /[^.[\]]+|\[(?:(-?\d+(?:\.\d+)?)|(["'])((?:(?!\2)[^\\]|\\.)*?)\2)\]|(?=(?:\.|\[\])(?:\.|\[\]|$))/g;
+
+ /** Used to match backslashes in property paths. */
+ var reEscapeChar = /\\(\\)?/g;
+
+ /**
+ * Converts `string` to a property path array.
+ *
+ * @private
+ * @param {string} string The string to convert.
+ * @returns {Array} Returns the property path array.
+ */
+ var stringToPath = memoizeCapped(function(string) {
+ string = toString(string);
+
+ var result = [];
+ if (reLeadingDot.test(string)) {
+ result.push('');
+ }
+ string.replace(rePropName, function(match, number, quote, string) {
+ result.push(quote ? string.replace(reEscapeChar, '$1') : (number || match));
+ });
+ return result;
+ });
+
+ module.exports = stringToPath;
+
+
+/***/ },
+/* 96 */
+/***/ function(module, exports, __webpack_require__) {
+
+ var memoize = __webpack_require__(97);
+
+ /** Used as the maximum memoize cache size. */
+ var MAX_MEMOIZE_SIZE = 500;
+
+ /**
+ * A specialized version of `_.memoize` which clears the memoized function's
+ * cache when it exceeds `MAX_MEMOIZE_SIZE`.
+ *
+ * @private
+ * @param {Function} func The function to have its output memoized.
+ * @returns {Function} Returns the new memoized function.
+ */
+ function memoizeCapped(func) {
+ var result = memoize(func, function(key) {
+ if (cache.size === MAX_MEMOIZE_SIZE) {
+ cache.clear();
+ }
+ return key;
+ });
+
+ var cache = result.cache;
+ return result;
+ }
+
+ module.exports = memoizeCapped;
+
+
+/***/ },
+/* 97 */
+/***/ function(module, exports, __webpack_require__) {
+
+ var MapCache = __webpack_require__(98);
+
+ /** Error message constants. */
+ var FUNC_ERROR_TEXT = 'Expected a function';
+
+ /**
+ * Creates a function that memoizes the result of `func`. If `resolver` is
+ * provided, it determines the cache key for storing the result based on the
+ * arguments provided to the memoized function. By default, the first argument
+ * provided to the memoized function is used as the map cache key. The `func`
+ * is invoked with the `this` binding of the memoized function.
+ *
+ * **Note:** The cache is exposed as the `cache` property on the memoized
+ * function. Its creation may be customized by replacing the `_.memoize.Cache`
+ * constructor with one whose instances implement the
+ * [`Map`](http://ecma-international.org/ecma-262/7.0/#sec-properties-of-the-map-prototype-object)
+ * method interface of `delete`, `get`, `has`, and `set`.
+ *
+ * @static
+ * @memberOf _
+ * @since 0.1.0
+ * @category Function
+ * @param {Function} func The function to have its output memoized.
+ * @param {Function} [resolver] The function to resolve the cache key.
+ * @returns {Function} Returns the new memoized function.
+ * @example
+ *
+ * var object = { 'a': 1, 'b': 2 };
+ * var other = { 'c': 3, 'd': 4 };
+ *
+ * var values = _.memoize(_.values);
+ * values(object);
+ * // => [1, 2]
+ *
+ * values(other);
+ * // => [3, 4]
+ *
+ * object.a = 2;
+ * values(object);
+ * // => [1, 2]
+ *
+ * // Modify the result cache.
+ * values.cache.set(object, ['a', 'b']);
+ * values(object);
+ * // => ['a', 'b']
+ *
+ * // Replace `_.memoize.Cache`.
+ * _.memoize.Cache = WeakMap;
+ */
+ function memoize(func, resolver) {
+ if (typeof func != 'function' || (resolver && typeof resolver != 'function')) {
+ throw new TypeError(FUNC_ERROR_TEXT);
+ }
+ var memoized = function() {
+ var args = arguments,
+ key = resolver ? resolver.apply(this, args) : args[0],
+ cache = memoized.cache;
+
+ if (cache.has(key)) {
+ return cache.get(key);
+ }
+ var result = func.apply(this, args);
+ memoized.cache = cache.set(key, result) || cache;
+ return result;
+ };
+ memoized.cache = new (memoize.Cache || MapCache);
+ return memoized;
+ }
+
+ // Expose `MapCache`.
+ memoize.Cache = MapCache;
+
+ module.exports = memoize;
+
+
+/***/ },
+/* 98 */
+/***/ function(module, exports, __webpack_require__) {
+
+ var mapCacheClear = __webpack_require__(99),
+ mapCacheDelete = __webpack_require__(126),
+ mapCacheGet = __webpack_require__(129),
+ mapCacheHas = __webpack_require__(130),
+ mapCacheSet = __webpack_require__(131);
+
+ /**
+ * Creates a map cache object to store key-value pairs.
+ *
+ * @private
+ * @constructor
+ * @param {Array} [entries] The key-value pairs to cache.
+ */
+ function MapCache(entries) {
+ var index = -1,
+ length = entries ? entries.length : 0;
+
+ this.clear();
+ while (++index < length) {
+ var entry = entries[index];
+ this.set(entry[0], entry[1]);
+ }
+ }
+
+ // Add methods to `MapCache`.
+ MapCache.prototype.clear = mapCacheClear;
+ MapCache.prototype['delete'] = mapCacheDelete;
+ MapCache.prototype.get = mapCacheGet;
+ MapCache.prototype.has = mapCacheHas;
+ MapCache.prototype.set = mapCacheSet;
+
+ module.exports = MapCache;
+
+
+/***/ },
+/* 99 */
+/***/ function(module, exports, __webpack_require__) {
+
+ var Hash = __webpack_require__(100),
+ ListCache = __webpack_require__(117),
+ Map = __webpack_require__(125);
+
+ /**
+ * Removes all key-value entries from the map.
+ *
+ * @private
+ * @name clear
+ * @memberOf MapCache
+ */
+ function mapCacheClear() {
+ this.size = 0;
+ this.__data__ = {
+ 'hash': new Hash,
+ 'map': new (Map || ListCache),
+ 'string': new Hash
+ };
+ }
+
+ module.exports = mapCacheClear;
+
+
+/***/ },
+/* 100 */
+/***/ function(module, exports, __webpack_require__) {
+
+ var hashClear = __webpack_require__(101),
+ hashDelete = __webpack_require__(113),
+ hashGet = __webpack_require__(114),
+ hashHas = __webpack_require__(115),
+ hashSet = __webpack_require__(116);
+
+ /**
+ * Creates a hash object.
+ *
+ * @private
+ * @constructor
+ * @param {Array} [entries] The key-value pairs to cache.
+ */
+ function Hash(entries) {
+ var index = -1,
+ length = entries ? entries.length : 0;
+
+ this.clear();
+ while (++index < length) {
+ var entry = entries[index];
+ this.set(entry[0], entry[1]);
+ }
+ }
+
+ // Add methods to `Hash`.
+ Hash.prototype.clear = hashClear;
+ Hash.prototype['delete'] = hashDelete;
+ Hash.prototype.get = hashGet;
+ Hash.prototype.has = hashHas;
+ Hash.prototype.set = hashSet;
+
+ module.exports = Hash;
+
+
+/***/ },
+/* 101 */
+/***/ function(module, exports, __webpack_require__) {
+
+ var nativeCreate = __webpack_require__(102);
+
+ /**
+ * Removes all key-value entries from the hash.
+ *
+ * @private
+ * @name clear
+ * @memberOf Hash
+ */
+ function hashClear() {
+ this.__data__ = nativeCreate ? nativeCreate(null) : {};
+ this.size = 0;
+ }
+
+ module.exports = hashClear;
+
+
+/***/ },
+/* 102 */
+/***/ function(module, exports, __webpack_require__) {
+
+ var getNative = __webpack_require__(103);
+
+ /* Built-in method references that are verified to be native. */
+ var nativeCreate = getNative(Object, 'create');
+
+ module.exports = nativeCreate;
+
+
+/***/ },
+/* 103 */
+/***/ function(module, exports, __webpack_require__) {
+
+ var baseIsNative = __webpack_require__(104),
+ getValue = __webpack_require__(112);
+
+ /**
+ * Gets the native function at `key` of `object`.
+ *
+ * @private
+ * @param {Object} object The object to query.
+ * @param {string} key The key of the method to get.
+ * @returns {*} Returns the function if it's native, else `undefined`.
+ */
+ function getNative(object, key) {
+ var value = getValue(object, key);
+ return baseIsNative(value) ? value : undefined;
+ }
+
+ module.exports = getNative;
+
+
+/***/ },
+/* 104 */
+/***/ function(module, exports, __webpack_require__) {
+
+ var isFunction = __webpack_require__(105),
+ isMasked = __webpack_require__(107),
+ isObject = __webpack_require__(106),
+ toSource = __webpack_require__(111);
+
+ /**
+ * Used to match `RegExp`
+ * [syntax characters](http://ecma-international.org/ecma-262/7.0/#sec-patterns).
+ */
+ var reRegExpChar = /[\\^$.*+?()[\]{}|]/g;
+
+ /** Used to detect host constructors (Safari). */
+ var reIsHostCtor = /^\[object .+?Constructor\]$/;
+
+ /** Used for built-in method references. */
+ var funcProto = Function.prototype,
+ objectProto = Object.prototype;
+
+ /** Used to resolve the decompiled source of functions. */
+ var funcToString = funcProto.toString;
+
+ /** Used to check objects for own properties. */
+ var hasOwnProperty = objectProto.hasOwnProperty;
+
+ /** Used to detect if a method is native. */
+ var reIsNative = RegExp('^' +
+ funcToString.call(hasOwnProperty).replace(reRegExpChar, '\\$&')
+ .replace(/hasOwnProperty|(function).*?(?=\\\()| for .+?(?=\\\])/g, '$1.*?') + '$'
+ );
+
+ /**
+ * The base implementation of `_.isNative` without bad shim checks.
+ *
+ * @private
+ * @param {*} value The value to check.
+ * @returns {boolean} Returns `true` if `value` is a native function,
+ * else `false`.
+ */
+ function baseIsNative(value) {
+ if (!isObject(value) || isMasked(value)) {
+ return false;
+ }
+ var pattern = isFunction(value) ? reIsNative : reIsHostCtor;
+ return pattern.test(toSource(value));
+ }
+
+ module.exports = baseIsNative;
+
+
+/***/ },
+/* 105 */
+/***/ function(module, exports, __webpack_require__) {
+
+ var isObject = __webpack_require__(106);
+
+ /** `Object#toString` result references. */
+ var funcTag = '[object Function]',
+ genTag = '[object GeneratorFunction]',
+ proxyTag = '[object Proxy]';
+
+ /** Used for built-in method references. */
+ var objectProto = Object.prototype;
+
+ /**
+ * Used to resolve the
+ * [`toStringTag`](http://ecma-international.org/ecma-262/7.0/#sec-object.prototype.tostring)
+ * of values.
+ */
+ var objectToString = objectProto.toString;
+
+ /**
+ * Checks if `value` is classified as a `Function` object.
+ *
+ * @static
+ * @memberOf _
+ * @since 0.1.0
+ * @category Lang
+ * @param {*} value The value to check.
+ * @returns {boolean} Returns `true` if `value` is a function, else `false`.
+ * @example
+ *
+ * _.isFunction(_);
+ * // => true
+ *
+ * _.isFunction(/abc/);
+ * // => false
+ */
+ function isFunction(value) {
+ // The use of `Object#toString` avoids issues with the `typeof` operator
+ // in Safari 9 which returns 'object' for typed array and other constructors.
+ var tag = isObject(value) ? objectToString.call(value) : '';
+ return tag == funcTag || tag == genTag || tag == proxyTag;
+ }
+
+ module.exports = isFunction;
+
+
+/***/ },
+/* 106 */
+/***/ function(module, exports) {
+
+ /**
+ * Checks if `value` is the
+ * [language type](http://www.ecma-international.org/ecma-262/7.0/#sec-ecmascript-language-types)
+ * of `Object`. (e.g. arrays, functions, objects, regexes, `new Number(0)`, and `new String('')`)
+ *
+ * @static
+ * @memberOf _
+ * @since 0.1.0
+ * @category Lang
+ * @param {*} value The value to check.
+ * @returns {boolean} Returns `true` if `value` is an object, else `false`.
+ * @example
+ *
+ * _.isObject({});
+ * // => true
+ *
+ * _.isObject([1, 2, 3]);
+ * // => true
+ *
+ * _.isObject(_.noop);
+ * // => true
+ *
+ * _.isObject(null);
+ * // => false
+ */
+ function isObject(value) {
+ var type = typeof value;
+ return value != null && (type == 'object' || type == 'function');
+ }
+
+ module.exports = isObject;
+
+
+/***/ },
+/* 107 */
+/***/ function(module, exports, __webpack_require__) {
+
+ var coreJsData = __webpack_require__(108);
+
+ /** Used to detect methods masquerading as native. */
+ var maskSrcKey = (function() {
+ var uid = /[^.]+$/.exec(coreJsData && coreJsData.keys && coreJsData.keys.IE_PROTO || '');
+ return uid ? ('Symbol(src)_1.' + uid) : '';
+ }());
+
+ /**
+ * Checks if `func` has its source masked.
+ *
+ * @private
+ * @param {Function} func The function to check.
+ * @returns {boolean} Returns `true` if `func` is masked, else `false`.
+ */
+ function isMasked(func) {
+ return !!maskSrcKey && (maskSrcKey in func);
+ }
+
+ module.exports = isMasked;
+
+
+/***/ },
+/* 108 */
+/***/ function(module, exports, __webpack_require__) {
+
+ var root = __webpack_require__(109);
+
+ /** Used to detect overreaching core-js shims. */
+ var coreJsData = root['__core-js_shared__'];
+
+ module.exports = coreJsData;
+
+
+/***/ },
+/* 109 */
+/***/ function(module, exports, __webpack_require__) {
+
+ var freeGlobal = __webpack_require__(110);
+
+ /** Detect free variable `self`. */
+ var freeSelf = typeof self == 'object' && self && self.Object === Object && self;
+
+ /** Used as a reference to the global object. */
+ var root = freeGlobal || freeSelf || Function('return this')();
+
+ module.exports = root;
+
+
+/***/ },
+/* 110 */
+/***/ function(module, exports) {
+
+ /* WEBPACK VAR INJECTION */(function(global) {/** Detect free variable `global` from Node.js. */
+ var freeGlobal = typeof global == 'object' && global && global.Object === Object && global;
+
+ module.exports = freeGlobal;
+
+ /* WEBPACK VAR INJECTION */}.call(exports, (function() { return this; }())))
+
+/***/ },
+/* 111 */
+/***/ function(module, exports) {
+
+ /** Used for built-in method references. */
+ var funcProto = Function.prototype;
+
+ /** Used to resolve the decompiled source of functions. */
+ var funcToString = funcProto.toString;
+
+ /**
+ * Converts `func` to its source code.
+ *
+ * @private
+ * @param {Function} func The function to process.
+ * @returns {string} Returns the source code.
+ */
+ function toSource(func) {
+ if (func != null) {
+ try {
+ return funcToString.call(func);
+ } catch (e) {}
+ try {
+ return (func + '');
+ } catch (e) {}
+ }
+ return '';
+ }
+
+ module.exports = toSource;
+
+
+/***/ },
+/* 112 */
+/***/ function(module, exports) {
+
+ /**
+ * Gets the value at `key` of `object`.
+ *
+ * @private
+ * @param {Object} [object] The object to query.
+ * @param {string} key The key of the property to get.
+ * @returns {*} Returns the property value.
+ */
+ function getValue(object, key) {
+ return object == null ? undefined : object[key];
+ }
+
+ module.exports = getValue;
+
+
+/***/ },
+/* 113 */
+/***/ function(module, exports) {
+
+ /**
+ * Removes `key` and its value from the hash.
+ *
+ * @private
+ * @name delete
+ * @memberOf Hash
+ * @param {Object} hash The hash to modify.
+ * @param {string} key The key of the value to remove.
+ * @returns {boolean} Returns `true` if the entry was removed, else `false`.
+ */
+ function hashDelete(key) {
+ var result = this.has(key) && delete this.__data__[key];
+ this.size -= result ? 1 : 0;
+ return result;
+ }
+
+ module.exports = hashDelete;
+
+
+/***/ },
+/* 114 */
+/***/ function(module, exports, __webpack_require__) {
+
+ var nativeCreate = __webpack_require__(102);
+
+ /** Used to stand-in for `undefined` hash values. */
+ var HASH_UNDEFINED = '__lodash_hash_undefined__';
+
+ /** Used for built-in method references. */
+ var objectProto = Object.prototype;
+
+ /** Used to check objects for own properties. */
+ var hasOwnProperty = objectProto.hasOwnProperty;
+
+ /**
+ * Gets the hash value for `key`.
+ *
+ * @private
+ * @name get
+ * @memberOf Hash
+ * @param {string} key The key of the value to get.
+ * @returns {*} Returns the entry value.
+ */
+ function hashGet(key) {
+ var data = this.__data__;
+ if (nativeCreate) {
+ var result = data[key];
+ return result === HASH_UNDEFINED ? undefined : result;
+ }
+ return hasOwnProperty.call(data, key) ? data[key] : undefined;
+ }
+
+ module.exports = hashGet;
+
+
+/***/ },
+/* 115 */
+/***/ function(module, exports, __webpack_require__) {
+
+ var nativeCreate = __webpack_require__(102);
+
+ /** Used for built-in method references. */
+ var objectProto = Object.prototype;
+
+ /** Used to check objects for own properties. */
+ var hasOwnProperty = objectProto.hasOwnProperty;
+
+ /**
+ * Checks if a hash value for `key` exists.
+ *
+ * @private
+ * @name has
+ * @memberOf Hash
+ * @param {string} key The key of the entry to check.
+ * @returns {boolean} Returns `true` if an entry for `key` exists, else `false`.
+ */
+ function hashHas(key) {
+ var data = this.__data__;
+ return nativeCreate ? data[key] !== undefined : hasOwnProperty.call(data, key);
+ }
+
+ module.exports = hashHas;
+
+
+/***/ },
+/* 116 */
+/***/ function(module, exports, __webpack_require__) {
+
+ var nativeCreate = __webpack_require__(102);
+
+ /** Used to stand-in for `undefined` hash values. */
+ var HASH_UNDEFINED = '__lodash_hash_undefined__';
+
+ /**
+ * Sets the hash `key` to `value`.
+ *
+ * @private
+ * @name set
+ * @memberOf Hash
+ * @param {string} key The key of the value to set.
+ * @param {*} value The value to set.
+ * @returns {Object} Returns the hash instance.
+ */
+ function hashSet(key, value) {
+ var data = this.__data__;
+ this.size += this.has(key) ? 0 : 1;
+ data[key] = (nativeCreate && value === undefined) ? HASH_UNDEFINED : value;
+ return this;
+ }
+
+ module.exports = hashSet;
+
+
+/***/ },
+/* 117 */
+/***/ function(module, exports, __webpack_require__) {
+
+ var listCacheClear = __webpack_require__(118),
+ listCacheDelete = __webpack_require__(119),
+ listCacheGet = __webpack_require__(122),
+ listCacheHas = __webpack_require__(123),
+ listCacheSet = __webpack_require__(124);
+
+ /**
+ * Creates an list cache object.
+ *
+ * @private
+ * @constructor
+ * @param {Array} [entries] The key-value pairs to cache.
+ */
+ function ListCache(entries) {
+ var index = -1,
+ length = entries ? entries.length : 0;
+
+ this.clear();
+ while (++index < length) {
+ var entry = entries[index];
+ this.set(entry[0], entry[1]);
+ }
+ }
+
+ // Add methods to `ListCache`.
+ ListCache.prototype.clear = listCacheClear;
+ ListCache.prototype['delete'] = listCacheDelete;
+ ListCache.prototype.get = listCacheGet;
+ ListCache.prototype.has = listCacheHas;
+ ListCache.prototype.set = listCacheSet;
+
+ module.exports = ListCache;
+
+
+/***/ },
+/* 118 */
+/***/ function(module, exports) {
+
+ /**
+ * Removes all key-value entries from the list cache.
+ *
+ * @private
+ * @name clear
+ * @memberOf ListCache
+ */
+ function listCacheClear() {
+ this.__data__ = [];
+ this.size = 0;
+ }
+
+ module.exports = listCacheClear;
+
+
+/***/ },
+/* 119 */
+/***/ function(module, exports, __webpack_require__) {
+
+ var assocIndexOf = __webpack_require__(120);
+
+ /** Used for built-in method references. */
+ var arrayProto = Array.prototype;
+
+ /** Built-in value references. */
+ var splice = arrayProto.splice;
+
+ /**
+ * Removes `key` and its value from the list cache.
+ *
+ * @private
+ * @name delete
+ * @memberOf ListCache
+ * @param {string} key The key of the value to remove.
+ * @returns {boolean} Returns `true` if the entry was removed, else `false`.
+ */
+ function listCacheDelete(key) {
+ var data = this.__data__,
+ index = assocIndexOf(data, key);
+
+ if (index < 0) {
+ return false;
+ }
+ var lastIndex = data.length - 1;
+ if (index == lastIndex) {
+ data.pop();
+ } else {
+ splice.call(data, index, 1);
+ }
+ --this.size;
+ return true;
+ }
+
+ module.exports = listCacheDelete;
+
+
+/***/ },
+/* 120 */
+/***/ function(module, exports, __webpack_require__) {
+
+ var eq = __webpack_require__(121);
+
+ /**
+ * Gets the index at which the `key` is found in `array` of key-value pairs.
+ *
+ * @private
+ * @param {Array} array The array to inspect.
+ * @param {*} key The key to search for.
+ * @returns {number} Returns the index of the matched value, else `-1`.
+ */
+ function assocIndexOf(array, key) {
+ var length = array.length;
+ while (length--) {
+ if (eq(array[length][0], key)) {
+ return length;
+ }
+ }
+ return -1;
+ }
+
+ module.exports = assocIndexOf;
+
+
+/***/ },
+/* 121 */
+/***/ function(module, exports) {
+
+ /**
+ * Performs a
+ * [`SameValueZero`](http://ecma-international.org/ecma-262/7.0/#sec-samevaluezero)
+ * comparison between two values to determine if they are equivalent.
+ *
+ * @static
+ * @memberOf _
+ * @since 4.0.0
+ * @category Lang
+ * @param {*} value The value to compare.
+ * @param {*} other The other value to compare.
+ * @returns {boolean} Returns `true` if the values are equivalent, else `false`.
+ * @example
+ *
+ * var object = { 'a': 1 };
+ * var other = { 'a': 1 };
+ *
+ * _.eq(object, object);
+ * // => true
+ *
+ * _.eq(object, other);
+ * // => false
+ *
+ * _.eq('a', 'a');
+ * // => true
+ *
+ * _.eq('a', Object('a'));
+ * // => false
+ *
+ * _.eq(NaN, NaN);
+ * // => true
+ */
+ function eq(value, other) {
+ return value === other || (value !== value && other !== other);
+ }
+
+ module.exports = eq;
+
+
+/***/ },
+/* 122 */
+/***/ function(module, exports, __webpack_require__) {
+
+ var assocIndexOf = __webpack_require__(120);
+
+ /**
+ * Gets the list cache value for `key`.
+ *
+ * @private
+ * @name get
+ * @memberOf ListCache
+ * @param {string} key The key of the value to get.
+ * @returns {*} Returns the entry value.
+ */
+ function listCacheGet(key) {
+ var data = this.__data__,
+ index = assocIndexOf(data, key);
+
+ return index < 0 ? undefined : data[index][1];
+ }
+
+ module.exports = listCacheGet;
+
+
+/***/ },
+/* 123 */
+/***/ function(module, exports, __webpack_require__) {
+
+ var assocIndexOf = __webpack_require__(120);
+
+ /**
+ * Checks if a list cache value for `key` exists.
+ *
+ * @private
+ * @name has
+ * @memberOf ListCache
+ * @param {string} key The key of the entry to check.
+ * @returns {boolean} Returns `true` if an entry for `key` exists, else `false`.
+ */
+ function listCacheHas(key) {
+ return assocIndexOf(this.__data__, key) > -1;
+ }
+
+ module.exports = listCacheHas;
+
+
+/***/ },
+/* 124 */
+/***/ function(module, exports, __webpack_require__) {
+
+ var assocIndexOf = __webpack_require__(120);
+
+ /**
+ * Sets the list cache `key` to `value`.
+ *
+ * @private
+ * @name set
+ * @memberOf ListCache
+ * @param {string} key The key of the value to set.
+ * @param {*} value The value to set.
+ * @returns {Object} Returns the list cache instance.
+ */
+ function listCacheSet(key, value) {
+ var data = this.__data__,
+ index = assocIndexOf(data, key);
+
+ if (index < 0) {
+ ++this.size;
+ data.push([key, value]);
+ } else {
+ data[index][1] = value;
+ }
+ return this;
+ }
+
+ module.exports = listCacheSet;
+
+
+/***/ },
+/* 125 */
+/***/ function(module, exports, __webpack_require__) {
+
+ var getNative = __webpack_require__(103),
+ root = __webpack_require__(109);
+
+ /* Built-in method references that are verified to be native. */
+ var Map = getNative(root, 'Map');
+
+ module.exports = Map;
+
+
+/***/ },
+/* 126 */
+/***/ function(module, exports, __webpack_require__) {
+
+ var getMapData = __webpack_require__(127);
+
+ /**
+ * Removes `key` and its value from the map.
+ *
+ * @private
+ * @name delete
+ * @memberOf MapCache
+ * @param {string} key The key of the value to remove.
+ * @returns {boolean} Returns `true` if the entry was removed, else `false`.
+ */
+ function mapCacheDelete(key) {
+ var result = getMapData(this, key)['delete'](key);
+ this.size -= result ? 1 : 0;
+ return result;
+ }
+
+ module.exports = mapCacheDelete;
+
+
+/***/ },
+/* 127 */
+/***/ function(module, exports, __webpack_require__) {
+
+ var isKeyable = __webpack_require__(128);
+
+ /**
+ * Gets the data for `map`.
+ *
+ * @private
+ * @param {Object} map The map to query.
+ * @param {string} key The reference key.
+ * @returns {*} Returns the map data.
+ */
+ function getMapData(map, key) {
+ var data = map.__data__;
+ return isKeyable(key)
+ ? data[typeof key == 'string' ? 'string' : 'hash']
+ : data.map;
+ }
+
+ module.exports = getMapData;
+
+
+/***/ },
+/* 128 */
+/***/ function(module, exports) {
+
+ /**
+ * Checks if `value` is suitable for use as unique object key.
+ *
+ * @private
+ * @param {*} value The value to check.
+ * @returns {boolean} Returns `true` if `value` is suitable, else `false`.
+ */
+ function isKeyable(value) {
+ var type = typeof value;
+ return (type == 'string' || type == 'number' || type == 'symbol' || type == 'boolean')
+ ? (value !== '__proto__')
+ : (value === null);
+ }
+
+ module.exports = isKeyable;
+
+
+/***/ },
+/* 129 */
+/***/ function(module, exports, __webpack_require__) {
+
+ var getMapData = __webpack_require__(127);
+
+ /**
+ * Gets the map value for `key`.
+ *
+ * @private
+ * @name get
+ * @memberOf MapCache
+ * @param {string} key The key of the value to get.
+ * @returns {*} Returns the entry value.
+ */
+ function mapCacheGet(key) {
+ return getMapData(this, key).get(key);
+ }
+
+ module.exports = mapCacheGet;
+
+
+/***/ },
+/* 130 */
+/***/ function(module, exports, __webpack_require__) {
+
+ var getMapData = __webpack_require__(127);
+
+ /**
+ * Checks if a map value for `key` exists.
+ *
+ * @private
+ * @name has
+ * @memberOf MapCache
+ * @param {string} key The key of the entry to check.
+ * @returns {boolean} Returns `true` if an entry for `key` exists, else `false`.
+ */
+ function mapCacheHas(key) {
+ return getMapData(this, key).has(key);
+ }
+
+ module.exports = mapCacheHas;
+
+
+/***/ },
+/* 131 */
+/***/ function(module, exports, __webpack_require__) {
+
+ var getMapData = __webpack_require__(127);
+
+ /**
+ * Sets the map `key` to `value`.
+ *
+ * @private
+ * @name set
+ * @memberOf MapCache
+ * @param {string} key The key of the value to set.
+ * @param {*} value The value to set.
+ * @returns {Object} Returns the map cache instance.
+ */
+ function mapCacheSet(key, value) {
+ var data = getMapData(this, key),
+ size = data.size;
+
+ data.set(key, value);
+ this.size += data.size == size ? 0 : 1;
+ return this;
+ }
+
+ module.exports = mapCacheSet;
+
+
+/***/ },
+/* 132 */
+/***/ function(module, exports, __webpack_require__) {
+
+ var baseToString = __webpack_require__(133);
+
+ /**
+ * Converts `value` to a string. An empty string is returned for `null`
+ * and `undefined` values. The sign of `-0` is preserved.
+ *
+ * @static
+ * @memberOf _
+ * @since 4.0.0
+ * @category Lang
+ * @param {*} value The value to convert.
+ * @returns {string} Returns the converted string.
+ * @example
+ *
+ * _.toString(null);
+ * // => ''
+ *
+ * _.toString(-0);
+ * // => '-0'
+ *
+ * _.toString([1, 2, 3]);
+ * // => '1,2,3'
+ */
+ function toString(value) {
+ return value == null ? '' : baseToString(value);
+ }
+
+ module.exports = toString;
+
+
+/***/ },
+/* 133 */
+/***/ function(module, exports, __webpack_require__) {
+
+ var Symbol = __webpack_require__(134),
+ arrayMap = __webpack_require__(135),
+ isArray = __webpack_require__(94),
+ isSymbol = __webpack_require__(136);
+
+ /** Used as references for various `Number` constants. */
+ var INFINITY = 1 / 0;
+
+ /** Used to convert symbols to primitives and strings. */
+ var symbolProto = Symbol ? Symbol.prototype : undefined,
+ symbolToString = symbolProto ? symbolProto.toString : undefined;
+
+ /**
+ * The base implementation of `_.toString` which doesn't convert nullish
+ * values to empty strings.
+ *
+ * @private
+ * @param {*} value The value to process.
+ * @returns {string} Returns the string.
+ */
+ function baseToString(value) {
+ // Exit early for strings to avoid a performance hit in some environments.
+ if (typeof value == 'string') {
+ return value;
+ }
+ if (isArray(value)) {
+ // Recursively convert values (susceptible to call stack limits).
+ return arrayMap(value, baseToString) + '';
+ }
+ if (isSymbol(value)) {
+ return symbolToString ? symbolToString.call(value) : '';
+ }
+ var result = (value + '');
+ return (result == '0' && (1 / value) == -INFINITY) ? '-0' : result;
+ }
+
+ module.exports = baseToString;
+
+
+/***/ },
+/* 134 */
+/***/ function(module, exports, __webpack_require__) {
+
+ var root = __webpack_require__(109);
+
+ /** Built-in value references. */
+ var Symbol = root.Symbol;
+
+ module.exports = Symbol;
+
+
+/***/ },
+/* 135 */
+/***/ function(module, exports) {
+
+ /**
+ * A specialized version of `_.map` for arrays without support for iteratee
+ * shorthands.
+ *
+ * @private
+ * @param {Array} [array] The array to iterate over.
+ * @param {Function} iteratee The function invoked per iteration.
+ * @returns {Array} Returns the new mapped array.
+ */
+ function arrayMap(array, iteratee) {
+ var index = -1,
+ length = array ? array.length : 0,
+ result = Array(length);
+
+ while (++index < length) {
+ result[index] = iteratee(array[index], index, array);
+ }
+ return result;
+ }
+
+ module.exports = arrayMap;
+
+
+/***/ },
+/* 136 */
+/***/ function(module, exports, __webpack_require__) {
+
+ var isObjectLike = __webpack_require__(8);
+
+ /** `Object#toString` result references. */
+ var symbolTag = '[object Symbol]';
+
+ /** Used for built-in method references. */
+ var objectProto = Object.prototype;
+
+ /**
+ * Used to resolve the
+ * [`toStringTag`](http://ecma-international.org/ecma-262/7.0/#sec-object.prototype.tostring)
+ * of values.
+ */
+ var objectToString = objectProto.toString;
+
+ /**
+ * Checks if `value` is classified as a `Symbol` primitive or object.
+ *
+ * @static
+ * @memberOf _
+ * @since 4.0.0
+ * @category Lang
+ * @param {*} value The value to check.
+ * @returns {boolean} Returns `true` if `value` is a symbol, else `false`.
+ * @example
+ *
+ * _.isSymbol(Symbol.iterator);
+ * // => true
+ *
+ * _.isSymbol('abc');
+ * // => false
+ */
+ function isSymbol(value) {
+ return typeof value == 'symbol' ||
+ (isObjectLike(value) && objectToString.call(value) == symbolTag);
+ }
+
+ module.exports = isSymbol;
+
+
+/***/ },
+/* 137 */
+/***/ function(module, exports, __webpack_require__) {
+
+ var isArray = __webpack_require__(94),
+ isSymbol = __webpack_require__(136);
+
+ /** Used to match property names within property paths. */
+ var reIsDeepProp = /\.|\[(?:[^[\]]*|(["'])(?:(?!\1)[^\\]|\\.)*?\1)\]/,
+ reIsPlainProp = /^\w*$/;
+
+ /**
+ * Checks if `value` is a property name and not a property path.
+ *
+ * @private
+ * @param {*} value The value to check.
+ * @param {Object} [object] The object to query keys on.
+ * @returns {boolean} Returns `true` if `value` is a property name, else `false`.
+ */
+ function isKey(value, object) {
+ if (isArray(value)) {
+ return false;
+ }
+ var type = typeof value;
+ if (type == 'number' || type == 'symbol' || type == 'boolean' ||
+ value == null || isSymbol(value)) {
+ return true;
+ }
+ return reIsPlainProp.test(value) || !reIsDeepProp.test(value) ||
+ (object != null && value in Object(object));
+ }
+
+ module.exports = isKey;
+
+
+/***/ },
+/* 138 */
+/***/ function(module, exports, __webpack_require__) {
+
+ var isSymbol = __webpack_require__(136);
+
+ /** Used as references for various `Number` constants. */
+ var INFINITY = 1 / 0;
+
+ /**
+ * Converts `value` to a string key if it's not a string or symbol.
+ *
+ * @private
+ * @param {*} value The value to inspect.
+ * @returns {string|symbol} Returns the key.
+ */
+ function toKey(value) {
+ if (typeof value == 'string' || isSymbol(value)) {
+ return value;
+ }
+ var result = (value + '');
+ return (result == '0' && (1 / value) == -INFINITY) ? '-0' : result;
+ }
+
+ module.exports = toKey;
+
+
+/***/ },
+/* 139 */
+/***/ function(module, exports) {
+
+ module.exports = devtoolsRequire("devtools/shared/flags");
+
+/***/ },
+/* 140 */
+/***/ function(module, exports) {
+
+ function Debuggee() {
+ function $(selector) {
+ var element = document.querySelector(selector);
+ console.log("$", selector, element);
+
+ if (!element) {
+ throw new Error("Element not found, try changing the selector");
+ }
+ return element;
+ }
+
+ function mouseEvent(eventType) {
+ return new MouseEvent(eventType, {
+ "view": window,
+ "bubbles": true,
+ "cancelable": true
+ });
+ }
+
+ var specialKeysMap = {
+ "{enter}": 13
+ };
+
+ // Special character examples {enter}, {esc}, {leftarrow} ..
+ function isSpecialCharacter(text) {
+ return text.match(/^\{.*\}$/);
+ }
+
+ function keyInfo(key, eventType) {
+ var charCodeAt = void 0;
+
+ if (key.length > 1) {
+ charCodeAt = specialKeysMap[key];
+ } else {
+ charCodeAt = key.toUpperCase().charCodeAt(0);
+ }
+
+ return {
+ charCode: eventType == "keypress" ? 0 : charCodeAt,
+ keyCode: charCodeAt,
+ which: charCodeAt
+ };
+ }
+
+ function keyEvent(eventType, key) {
+ var event = new Event(eventType, {
+ bubbles: true,
+ cancelable: false,
+ view: window
+ });
+
+ var _keyInfo = keyInfo(key, eventType);
+
+ var charCode = _keyInfo.charCode;
+ var keyCode = _keyInfo.keyCode;
+ var which = _keyInfo.which;
+
+
+ return Object.assign(event, {
+ charCode: charCode,
+ keyCode: keyCode,
+ which: which,
+ detail: 0,
+ layerX: 0,
+ layerY: 0,
+ pageX: 0,
+ pageY: 0
+ });
+ }
+
+ function sendKey(element, key) {
+ element.dispatchEvent(keyEvent("keydown", key));
+ element.dispatchEvent(keyEvent("keypress", key));
+ if (key.length == 1) {
+ element.value += key;
+ }
+ element.dispatchEvent(keyEvent("keyup", key));
+ }
+
+ function click(selector) {
+ var element = $(selector);
+ console.log("click", selector);
+ element.dispatchEvent(mouseEvent("click"));
+ }
+
+ function dblclick(selector) {
+ var element = $(selector);
+ console.log("dblclick", selector);
+ element.dispatchEvent(mouseEvent("dblclick"));
+ }
+
+ function type(selector, text) {
+ var element = $(selector);
+ console.log("type", selector, text);
+ element.select();
+
+ if (isSpecialCharacter(text)) {
+ sendKey(element, text);
+ } else {
+ var chars = text.split("");
+ chars.forEach(char => sendKey(element, char));
+ }
+ }
+
+ return {
+ click,
+ dblclick,
+ type
+ };
+ }
+
+ var debuggeeStatement = `window.dbg = (${ Debuggee })()`;
+ var injectedDebuggee = void 0;
+
+ function injectDebuggee() {
+ if (injectedDebuggee) {
+ return Promise.resolve(injectedDebuggee);
+ }
+
+ return window.client.debuggeeCommand(debuggeeStatement).then(result => {
+ injectedDebuggee = result;
+ });
+ }
+
+ module.exports = injectDebuggee;
+
+/***/ },
+/* 141 */
+/***/ function(module, exports, __webpack_require__) {
+
+ var _require = __webpack_require__(142);
+
+ var Task = _require.Task;
+
+ var firefox = __webpack_require__(143);
+ var chrome = __webpack_require__(205);
+
+ var _require2 = __webpack_require__(88);
+
+ var debugGlobal = _require2.debugGlobal;
+
+
+ var clientType = void 0;
+ function getClient() {
+ if (clientType === "chrome") {
+ return chrome.clientCommands;
+ }
+
+ return firefox.clientCommands;
+ }
+
+ function startDebugging(connTarget, actions) {
+ if (connTarget.type === "node") {
+ return startDebuggingNode(connTarget.param, actions);
+ }
+
+ var target = connTarget.type === "chrome" ? chrome : firefox;
+ return startDebuggingTab(target, connTarget.param, actions);
+ }
+
+ function startDebuggingNode(url, actions) {
+ clientType = "chrome";
+ return chrome.connectNode(`ws://${ url }`).then(() => {
+ chrome.initPage(actions);
+ });
+ }
+
+ function startDebuggingTab(targetEnv, tabId, actions) {
+ return Task.spawn(function* () {
+ var tabs = yield targetEnv.connectClient();
+ var tab = tabs.find(t => t.id.indexOf(tabId) !== -1);
+ yield targetEnv.connectTab(tab.tab);
+ targetEnv.initPage(actions);
+
+ clientType = targetEnv === firefox ? "firefox" : "chrome";
+ debugGlobal("client", targetEnv.clientCommands);
+
+ return tabs;
+ });
+ }
+
+ function connectClients(onConnect) {
+ firefox.connectClient().then(onConnect);
+ chrome.connectClient().then(onConnect);
+ }
+
+ module.exports = {
+ getClient,
+ connectClients,
+ startDebugging,
+ firefox,
+ chrome
+ };
+
+/***/ },
+/* 142 */
+/***/ function(module, exports) {
+
+ /* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+ /* vim: set ts=2 et sw=2 tw=80 filetype=javascript: */
+ /* 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/. */
+
+ /**
+ * This object provides the public module functions.
+ */
+ var Task = {
+ // XXX: Not sure if this works in all cases...
+ async: function (task) {
+ return function () {
+ return Task.spawn(task, this, arguments);
+ };
+ },
+
+ /**
+ * Creates and starts a new task.
+ * @param task A generator function
+ * @return A promise, resolved when the task terminates
+ */
+ spawn: function (task, scope, args) {
+ return new Promise(function (resolve, reject) {
+ var iterator = task.apply(scope, args);
+
+ var callNext = lastValue => {
+ var iteration = iterator.next(lastValue);
+ Promise.resolve(iteration.value).then(value => {
+ if (iteration.done) {
+ resolve(value);
+ } else {
+ callNext(value);
+ }
+ }).catch(error => {
+ reject(error);
+ iterator.throw(error);
+ });
+ };
+
+ callNext(undefined);
+ });
+ }
+ };
+
+ module.exports = { Task };
+
+/***/ },
+/* 143 */
+/***/ function(module, exports, __webpack_require__) {
+
+ var _require = __webpack_require__(28);
+
+ var DebuggerClient = _require.DebuggerClient;
+ var DebuggerTransport = _require.DebuggerTransport;
+ var TargetFactory = _require.TargetFactory;
+ var WebsocketTransport = _require.WebsocketTransport;
+
+ var defer = __webpack_require__(144);
+
+ var _require2 = __webpack_require__(89);
+
+ var getValue = _require2.getValue;
+
+ var _require3 = __webpack_require__(145);
+
+ var Tab = _require3.Tab;
+
+ var _require4 = __webpack_require__(202);
+
+ var setupCommands = _require4.setupCommands;
+ var clientCommands = _require4.clientCommands;
+
+ var _require5 = __webpack_require__(203);
+
+ var setupEvents = _require5.setupEvents;
+ var clientEvents = _require5.clientEvents;
+
+ var _require6 = __webpack_require__(204);
+
+ var createSource = _require6.createSource;
+
+
+ var debuggerClient = null;
+ var threadClient = null;
+ var tabTarget = null;
+
+ function getThreadClient() {
+ return threadClient;
+ }
+
+ function setThreadClient(client) {
+ threadClient = client;
+ }
+
+ function getTabTarget() {
+ return tabTarget;
+ }
+
+ function setTabTarget(target) {
+ tabTarget = target;
+ }
+
+ function lookupTabTarget(tab) {
+ var options = { client: debuggerClient, form: tab, chrome: false };
+ return TargetFactory.forRemoteTab(options);
+ }
+
+ function createTabs(tabs) {
+ return tabs.map(tab => {
+ return Tab({
+ title: tab.title,
+ url: tab.url,
+ id: tab.actor,
+ tab,
+ browser: "firefox"
+ });
+ });
+ }
+
+ function connectClient() {
+ var deferred = defer();
+ var useProxy = !getValue("firefox.webSocketConnection");
+ var portPref = useProxy ? "firefox.proxyPort" : "firefox.webSocketPort";
+ var webSocketPort = getValue(portPref);
+
+ var socket = new WebSocket(`ws://${ document.location.hostname }:${ webSocketPort }`);
+ var transport = useProxy ? new DebuggerTransport(socket) : new WebsocketTransport(socket);
+ debuggerClient = new DebuggerClient(transport);
+
+ debuggerClient.connect().then(() => {
+ return debuggerClient.listTabs().then(response => {
+ deferred.resolve(createTabs(response.tabs));
+ });
+ }).catch(err => {
+ console.log(err);
+ deferred.resolve([]);
+ });
+
+ return deferred.promise;
+ }
+
+ function connectTab(tab) {
+ return new Promise((resolve, reject) => {
+ window.addEventListener("beforeunload", () => {
+ getTabTarget() && getTabTarget().destroy();
+ });
+
+ lookupTabTarget(tab).then(target => {
+ tabTarget = target;
+ target.activeTab.attachThread({}, (res, _threadClient) => {
+ threadClient = _threadClient;
+ threadClient.resume();
+ resolve();
+ });
+ });
+ });
+ }
+
+ function initPage(actions) {
+ tabTarget = getTabTarget();
+ threadClient = getThreadClient();
+
+ setupCommands({ threadClient, tabTarget, debuggerClient });
+
+ tabTarget.on("will-navigate", actions.willNavigate);
+ tabTarget.on("navigate", actions.navigated);
+
+ // Listen to all the requested events.
+ setupEvents({ threadClient, actions });
+ Object.keys(clientEvents).forEach(eventName => {
+ threadClient.addListener(eventName, clientEvents[eventName]);
+ });
+
+ // In Firefox, we need to initially request all of the sources. This
+ // usually fires off individual `newSource` notifications as the
+ // debugger finds them, but there may be existing sources already in
+ // the debugger (if it's paused already, or if loading the page from
+ // bfcache) so explicity fire `newSource` events for all returned
+ // sources.
+ return threadClient.getSources().then((_ref) => {
+ var sources = _ref.sources;
+
+ actions.newSources(sources.map(createSource));
+
+ // If the threadClient is already paused, make sure to show a
+ // paused state.
+ var pausedPacket = threadClient.getLastPausePacket();
+ if (pausedPacket) {
+ clientEvents.paused(null, pausedPacket);
+ }
+ });
+ }
+
+ module.exports = {
+ connectClient,
+ connectTab,
+ clientCommands,
+ getThreadClient,
+ setThreadClient,
+ getTabTarget,
+ setTabTarget,
+ initPage
+ };
+
+/***/ },
+/* 144 */
+/***/ function(module, exports) {
+
+ module.exports = function defer() {
+ var resolve = void 0,
+ reject = void 0;
+ var promise = new Promise(function () {
+ resolve = arguments[0];
+ reject = arguments[1];
+ });
+ return {
+ resolve: resolve,
+ reject: reject,
+ promise: promise
+ };
+ };
+
+/***/ },
+/* 145 */
+/***/ function(module, exports, __webpack_require__) {
+
+ var t = __webpack_require__(146);
+
+ var Tab = t.struct({
+ title: t.String,
+ url: t.String,
+ id: t.String,
+ tab: t.Object,
+ browser: t.enums.of(["chrome", "firefox"])
+ }, "Tab");
+
+ var SourceText = t.struct({
+ text: t.String,
+ contentType: t.String
+ });
+
+ var Source = t.struct({
+ id: t.String,
+ url: t.union([t.String, t.Nil]),
+ isPrettyPrinted: t.Boolean,
+ sourceMapURL: t.union([t.String, t.Nil])
+ }, "Source");
+
+ var Location = t.struct({
+ sourceId: t.String,
+ line: t.Number,
+ column: t.union([t.Number, t.Nil])
+ }, "Location");
+
+ var Breakpoint = t.struct({
+ id: t.String,
+ loading: t.Boolean,
+ disabled: t.Boolean,
+ text: t.String,
+ condition: t.union([t.String, t.Nil])
+ });
+
+ var BreakpointResult = t.struct({
+ id: t.String,
+ actualLocation: Location
+ });
+
+ var Frame = t.struct({
+ id: t.String,
+ displayName: t.String,
+ location: Location,
+ this: t.union([t.Object, t.Nil]),
+ scope: t.union([t.Object, t.Nil])
+ }, "Frame");
+
+ module.exports = {
+ Tab,
+ Source,
+ SourceText,
+ Location,
+ Breakpoint,
+ BreakpointResult,
+ Frame
+ };
+
+/***/ },
+/* 146 */
+/***/ function(module, exports, __webpack_require__) {
+
+ /*! @preserve
+ *
+ * tcomb.js - Type checking and DDD for JavaScript
+ *
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2014-2016 Giulio Canti
+ *
+ */
+
+ // core
+ var t = __webpack_require__(147);
+
+ // types
+ t.Any = __webpack_require__(153);
+ t.Array = __webpack_require__(161);
+ t.Boolean = __webpack_require__(162);
+ t.Date = __webpack_require__(164);
+ t.Error = __webpack_require__(165);
+ t.Function = __webpack_require__(166);
+ t.Nil = __webpack_require__(167);
+ t.Number = __webpack_require__(168);
+ t.Integer = __webpack_require__(170);
+ t.IntegerT = t.Integer;
+ t.Object = __webpack_require__(176);
+ t.RegExp = __webpack_require__(177);
+ t.String = __webpack_require__(178);
+ t.Type = __webpack_require__(179);
+ t.TypeT = t.Type;
+
+ // short alias are deprecated
+ t.Arr = t.Array;
+ t.Bool = t.Boolean;
+ t.Dat = t.Date;
+ t.Err = t.Error;
+ t.Func = t.Function;
+ t.Num = t.Number;
+ t.Obj = t.Object;
+ t.Re = t.RegExp;
+ t.Str = t.String;
+
+ // combinators
+ t.dict = __webpack_require__(180);
+ t.declare = __webpack_require__(181);
+ t.enums = __webpack_require__(184);
+ t.irreducible = __webpack_require__(154);
+ t.list = __webpack_require__(185);
+ t.maybe = __webpack_require__(186);
+ t.refinement = __webpack_require__(171);
+ t.struct = __webpack_require__(188);
+ t.tuple = __webpack_require__(194);
+ t.union = __webpack_require__(195);
+ t.func = __webpack_require__(196);
+ t.intersection = __webpack_require__(197);
+ t.subtype = t.refinement;
+ t.inter = __webpack_require__(198); // IE8 alias
+ t['interface'] = t.inter;
+
+ // functions
+ t.assert = t;
+ t.update = __webpack_require__(200);
+ t.mixin = __webpack_require__(182);
+ t.isType = __webpack_require__(158);
+ t.is = __webpack_require__(175);
+ t.getTypeName = __webpack_require__(157);
+ t.match = __webpack_require__(201);
+
+ module.exports = t;
+
+
+/***/ },
+/* 147 */
+/***/ function(module, exports, __webpack_require__) {
+
+ var isFunction = __webpack_require__(148);
+ var isNil = __webpack_require__(149);
+ var fail = __webpack_require__(150);
+ var stringify = __webpack_require__(151);
+
+ function assert(guard, message) {
+ if (guard !== true) {
+ if (isFunction(message)) { // handle lazy messages
+ message = message();
+ }
+ else if (isNil(message)) { // use a default message
+ message = 'Assert failed (turn on "Pause on exceptions" in your Source panel)';
+ }
+ assert.fail(message);
+ }
+ }
+
+ assert.fail = fail;
+ assert.stringify = stringify;
+
+ module.exports = assert;
+
+/***/ },
+/* 148 */
+/***/ function(module, exports) {
+
+ module.exports = function isFunction(x) {
+ return typeof x === 'function';
+ };
+
+/***/ },
+/* 149 */
+/***/ function(module, exports) {
+
+ module.exports = function isNil(x) {
+ return x === null || x === void 0;
+ };
+
+/***/ },
+/* 150 */
+/***/ function(module, exports) {
+
+ module.exports = function fail(message) {
+ throw new TypeError('[tcomb] ' + message);
+ };
+
+/***/ },
+/* 151 */
+/***/ function(module, exports, __webpack_require__) {
+
+ var getFunctionName = __webpack_require__(152);
+
+ function replacer(key, value) {
+ if (typeof value === 'function') {
+ return getFunctionName(value);
+ }
+ return value;
+ }
+
+ module.exports = function stringify(x) {
+ try { // handle "Converting circular structure to JSON" error
+ return JSON.stringify(x, replacer, 2);
+ }
+ catch (e) {
+ return String(x);
+ }
+ };
+
+/***/ },
+/* 152 */
+/***/ function(module, exports) {
+
+ module.exports = function getFunctionName(f) {
+ return f.displayName || f.name || '<function' + f.length + '>';
+ };
+
+/***/ },
+/* 153 */
+/***/ function(module, exports, __webpack_require__) {
+
+ var irreducible = __webpack_require__(154);
+
+ module.exports = irreducible('Any', function () { return true; });
+
+
+/***/ },
+/* 154 */
+/***/ function(module, exports, __webpack_require__) {
+
+ var assert = __webpack_require__(147);
+ var isString = __webpack_require__(155);
+ var isFunction = __webpack_require__(148);
+ var forbidNewOperator = __webpack_require__(156);
+
+ module.exports = function irreducible(name, predicate) {
+
+ if (false) {
+ assert(isString(name), function () { return 'Invalid argument name ' + assert.stringify(name) + ' supplied to irreducible(name, predicate) (expected a string)'; });
+ assert(isFunction(predicate), 'Invalid argument predicate ' + assert.stringify(predicate) + ' supplied to irreducible(name, predicate) (expected a function)');
+ }
+
+ function Irreducible(value, path) {
+
+ if (false) {
+ forbidNewOperator(this, Irreducible);
+ path = path || [name];
+ assert(predicate(value), function () { return 'Invalid value ' + assert.stringify(value) + ' supplied to ' + path.join('/'); });
+ }
+
+ return value;
+ }
+
+ Irreducible.meta = {
+ kind: 'irreducible',
+ name: name,
+ predicate: predicate,
+ identity: true
+ };
+
+ Irreducible.displayName = name;
+
+ Irreducible.is = predicate;
+
+ return Irreducible;
+ };
+
+
+/***/ },
+/* 155 */
+/***/ function(module, exports) {
+
+ module.exports = function isString(x) {
+ return typeof x === 'string';
+ };
+
+/***/ },
+/* 156 */
+/***/ function(module, exports, __webpack_require__) {
+
+ var assert = __webpack_require__(147);
+ var getTypeName = __webpack_require__(157);
+
+ module.exports = function forbidNewOperator(x, type) {
+ assert(!(x instanceof type), function () { return 'Cannot use the new operator to instantiate the type ' + getTypeName(type); });
+ };
+
+/***/ },
+/* 157 */
+/***/ function(module, exports, __webpack_require__) {
+
+ var isType = __webpack_require__(158);
+ var getFunctionName = __webpack_require__(152);
+
+ module.exports = function getTypeName(ctor) {
+ if (isType(ctor)) {
+ return ctor.displayName;
+ }
+ return getFunctionName(ctor);
+ };
+
+/***/ },
+/* 158 */
+/***/ function(module, exports, __webpack_require__) {
+
+ var isFunction = __webpack_require__(148);
+ var isObject = __webpack_require__(159);
+
+ module.exports = function isType(x) {
+ return isFunction(x) && isObject(x.meta);
+ };
+
+/***/ },
+/* 159 */
+/***/ function(module, exports, __webpack_require__) {
+
+ var isNil = __webpack_require__(149);
+ var isArray = __webpack_require__(160);
+
+ module.exports = function isObject(x) {
+ return !isNil(x) && typeof x === 'object' && !isArray(x);
+ };
+
+/***/ },
+/* 160 */
+/***/ function(module, exports) {
+
+ module.exports = function isArray(x) {
+ return Array.isArray ? Array.isArray(x) : x instanceof Array;
+ };
+
+/***/ },
+/* 161 */
+/***/ function(module, exports, __webpack_require__) {
+
+ var irreducible = __webpack_require__(154);
+ var isArray = __webpack_require__(160);
+
+ module.exports = irreducible('Array', isArray);
+
+
+/***/ },
+/* 162 */
+/***/ function(module, exports, __webpack_require__) {
+
+ var irreducible = __webpack_require__(154);
+ var isBoolean = __webpack_require__(163);
+
+ module.exports = irreducible('Boolean', isBoolean);
+
+
+/***/ },
+/* 163 */
+/***/ function(module, exports) {
+
+ module.exports = function isBoolean(x) {
+ return x === true || x === false;
+ };
+
+/***/ },
+/* 164 */
+/***/ function(module, exports, __webpack_require__) {
+
+ var irreducible = __webpack_require__(154);
+
+ module.exports = irreducible('Date', function (x) { return x instanceof Date; });
+
+
+/***/ },
+/* 165 */
+/***/ function(module, exports, __webpack_require__) {
+
+ var irreducible = __webpack_require__(154);
+
+ module.exports = irreducible('Error', function (x) { return x instanceof Error; });
+
+
+/***/ },
+/* 166 */
+/***/ function(module, exports, __webpack_require__) {
+
+ var irreducible = __webpack_require__(154);
+ var isFunction = __webpack_require__(148);
+
+ module.exports = irreducible('Function', isFunction);
+
+
+/***/ },
+/* 167 */
+/***/ function(module, exports, __webpack_require__) {
+
+ var irreducible = __webpack_require__(154);
+ var isNil = __webpack_require__(149);
+
+ module.exports = irreducible('Nil', isNil);
+
+
+/***/ },
+/* 168 */
+/***/ function(module, exports, __webpack_require__) {
+
+ var irreducible = __webpack_require__(154);
+ var isNumber = __webpack_require__(169);
+
+ module.exports = irreducible('Number', isNumber);
+
+
+/***/ },
+/* 169 */
+/***/ function(module, exports) {
+
+ module.exports = function isNumber(x) {
+ return typeof x === 'number' && isFinite(x) && !isNaN(x);
+ };
+
+/***/ },
+/* 170 */
+/***/ function(module, exports, __webpack_require__) {
+
+ var refinement = __webpack_require__(171);
+ var Number = __webpack_require__(168);
+
+ module.exports = refinement(Number, function (x) { return x % 1 === 0; }, 'Integer');
+
+
+/***/ },
+/* 171 */
+/***/ function(module, exports, __webpack_require__) {
+
+ var assert = __webpack_require__(147);
+ var isTypeName = __webpack_require__(172);
+ var isFunction = __webpack_require__(148);
+ var forbidNewOperator = __webpack_require__(156);
+ var isIdentity = __webpack_require__(173);
+ var create = __webpack_require__(174);
+ var is = __webpack_require__(175);
+ var getTypeName = __webpack_require__(157);
+ var getFunctionName = __webpack_require__(152);
+
+ function getDefaultName(type, predicate) {
+ return '{' + getTypeName(type) + ' | ' + getFunctionName(predicate) + '}';
+ }
+
+ function refinement(type, predicate, name) {
+
+ if (false) {
+ assert(isFunction(type), function () { return 'Invalid argument type ' + assert.stringify(type) + ' supplied to refinement(type, predicate, [name]) combinator (expected a type)'; });
+ assert(isFunction(predicate), function () { return 'Invalid argument predicate supplied to refinement(type, predicate, [name]) combinator (expected a function)'; });
+ assert(isTypeName(name), function () { return 'Invalid argument name ' + assert.stringify(name) + ' supplied to refinement(type, predicate, [name]) combinator (expected a string)'; });
+ }
+
+ var displayName = name || getDefaultName(type, predicate);
+ var identity = isIdentity(type);
+
+ function Refinement(value, path) {
+
+ if (false) {
+ if (identity) {
+ forbidNewOperator(this, Refinement);
+ }
+ path = path || [displayName];
+ }
+
+ var x = create(type, value, path);
+
+ if (false) {
+ assert(predicate(x), function () { return 'Invalid value ' + assert.stringify(value) + ' supplied to ' + path.join('/'); });
+ }
+
+ return x;
+ }
+
+ Refinement.meta = {
+ kind: 'subtype',
+ type: type,
+ predicate: predicate,
+ name: name,
+ identity: identity
+ };
+
+ Refinement.displayName = displayName;
+
+ Refinement.is = function (x) {
+ return is(x, type) && predicate(x);
+ };
+
+ Refinement.update = function (instance, patch) {
+ return Refinement(assert.update(instance, patch));
+ };
+
+ return Refinement;
+ }
+
+ refinement.getDefaultName = getDefaultName;
+ module.exports = refinement;
+
+
+/***/ },
+/* 172 */
+/***/ function(module, exports, __webpack_require__) {
+
+ var isNil = __webpack_require__(149);
+ var isString = __webpack_require__(155);
+
+ module.exports = function isTypeName(name) {
+ return isNil(name) || isString(name);
+ };
+
+/***/ },
+/* 173 */
+/***/ function(module, exports, __webpack_require__) {
+
+ var assert = __webpack_require__(147);
+ var Boolean = __webpack_require__(162);
+ var isType = __webpack_require__(158);
+ var getTypeName = __webpack_require__(157);
+
+ // return true if the type constructor behaves like the identity function
+ module.exports = function isIdentity(type) {
+ if (isType(type)) {
+ if (false) {
+ assert(Boolean.is(type.meta.identity), function () { return 'Invalid meta identity ' + assert.stringify(type.meta.identity) + ' supplied to type ' + getTypeName(type); });
+ }
+ return type.meta.identity;
+ }
+ // for tcomb the other constructors, like ES6 classes, are identity-like
+ return true;
+ };
+
+/***/ },
+/* 174 */
+/***/ function(module, exports, __webpack_require__) {
+
+ var isType = __webpack_require__(158);
+ var getFunctionName = __webpack_require__(152);
+ var assert = __webpack_require__(147);
+ var stringify = __webpack_require__(151);
+
+ // creates an instance of a type, handling the optional new operator
+ module.exports = function create(type, value, path) {
+ if (isType(type)) {
+ return !type.meta.identity && typeof value === 'object' && value !== null ? new type(value, path): type(value, path);
+ }
+
+ if (false) {
+ // here type should be a class constructor and value some instance, just check membership and return the value
+ path = path || [getFunctionName(type)];
+ assert(value instanceof type, function () { return 'Invalid value ' + stringify(value) + ' supplied to ' + path.join('/'); });
+ }
+
+ return value;
+ };
+
+/***/ },
+/* 175 */
+/***/ function(module, exports, __webpack_require__) {
+
+ var isType = __webpack_require__(158);
+
+ // returns true if x is an instance of type
+ module.exports = function is(x, type) {
+ if (isType(type)) {
+ return type.is(x);
+ }
+ return x instanceof type; // type should be a class constructor
+ };
+
+
+/***/ },
+/* 176 */
+/***/ function(module, exports, __webpack_require__) {
+
+ var irreducible = __webpack_require__(154);
+ var isObject = __webpack_require__(159);
+
+ module.exports = irreducible('Object', isObject);
+
+
+/***/ },
+/* 177 */
+/***/ function(module, exports, __webpack_require__) {
+
+ var irreducible = __webpack_require__(154);
+
+ module.exports = irreducible('RegExp', function (x) { return x instanceof RegExp; });
+
+
+/***/ },
+/* 178 */
+/***/ function(module, exports, __webpack_require__) {
+
+ var irreducible = __webpack_require__(154);
+ var isString = __webpack_require__(155);
+
+ module.exports = irreducible('String', isString);
+
+
+/***/ },
+/* 179 */
+/***/ function(module, exports, __webpack_require__) {
+
+ var irreducible = __webpack_require__(154);
+ var isType = __webpack_require__(158);
+
+ module.exports = irreducible('Type', isType);
+
+/***/ },
+/* 180 */
+/***/ function(module, exports, __webpack_require__) {
+
+ var assert = __webpack_require__(147);
+ var isTypeName = __webpack_require__(172);
+ var isFunction = __webpack_require__(148);
+ var getTypeName = __webpack_require__(157);
+ var isIdentity = __webpack_require__(173);
+ var isObject = __webpack_require__(159);
+ var create = __webpack_require__(174);
+ var is = __webpack_require__(175);
+
+ function getDefaultName(domain, codomain) {
+ return '{[key: ' + getTypeName(domain) + ']: ' + getTypeName(codomain) + '}';
+ }
+
+ function dict(domain, codomain, name) {
+
+ if (false) {
+ assert(isFunction(domain), function () { return 'Invalid argument domain ' + assert.stringify(domain) + ' supplied to dict(domain, codomain, [name]) combinator (expected a type)'; });
+ assert(isFunction(codomain), function () { return 'Invalid argument codomain ' + assert.stringify(codomain) + ' supplied to dict(domain, codomain, [name]) combinator (expected a type)'; });
+ assert(isTypeName(name), function () { return 'Invalid argument name ' + assert.stringify(name) + ' supplied to dict(domain, codomain, [name]) combinator (expected a string)'; });
+ }
+
+ var displayName = name || getDefaultName(domain, codomain);
+ var domainNameCache = getTypeName(domain);
+ var codomainNameCache = getTypeName(codomain);
+ var identity = isIdentity(domain) && isIdentity(codomain);
+
+ function Dict(value, path) {
+
+ if (true) {
+ if (identity) {
+ return value; // just trust the input if elements must not be hydrated
+ }
+ }
+
+ if (false) {
+ path = path || [displayName];
+ assert(isObject(value), function () { return 'Invalid value ' + assert.stringify(value) + ' supplied to ' + path.join('/'); });
+ }
+
+ var idempotent = true; // will remain true if I can reutilise the input
+ var ret = {}; // make a temporary copy, will be discarded if idempotent remains true
+ for (var k in value) {
+ if (value.hasOwnProperty(k)) {
+ k = create(domain, k, ( false ? path.concat(domainNameCache) : null ));
+ var actual = value[k];
+ var instance = create(codomain, actual, ( false ? path.concat(k + ': ' + codomainNameCache) : null ));
+ idempotent = idempotent && ( actual === instance );
+ ret[k] = instance;
+ }
+ }
+
+ if (idempotent) { // implements idempotency
+ ret = value;
+ }
+
+ if (false) {
+ Object.freeze(ret);
+ }
+
+ return ret;
+ }
+
+ Dict.meta = {
+ kind: 'dict',
+ domain: domain,
+ codomain: codomain,
+ name: name,
+ identity: identity
+ };
+
+ Dict.displayName = displayName;
+
+ Dict.is = function (x) {
+ if (!isObject(x)) {
+ return false;
+ }
+ for (var k in x) {
+ if (x.hasOwnProperty(k)) {
+ if (!is(k, domain) || !is(x[k], codomain)) {
+ return false;
+ }
+ }
+ }
+ return true;
+ };
+
+ Dict.update = function (instance, patch) {
+ return Dict(assert.update(instance, patch));
+ };
+
+ return Dict;
+ }
+
+ dict.getDefaultName = getDefaultName;
+ module.exports = dict;
+
+
+/***/ },
+/* 181 */
+/***/ function(module, exports, __webpack_require__) {
+
+ var assert = __webpack_require__(147);
+ var isTypeName = __webpack_require__(172);
+ var isType = __webpack_require__(158);
+ var isNil = __webpack_require__(149);
+ var mixin = __webpack_require__(182);
+ var getTypeName = __webpack_require__(157);
+ var isUnion = __webpack_require__(183);
+
+ // All the .declare-d types should be clearly different from each other thus they should have
+ // different names when a name was not explicitly provided.
+ var nextDeclareUniqueId = 1;
+
+ module.exports = function declare(name) {
+ if (false) {
+ assert(isTypeName(name), function () { return 'Invalid argument name ' + name + ' supplied to declare([name]) (expected a string)'; });
+ }
+
+ var type;
+
+ function Declare(value, path) {
+ if (false) {
+ assert(!isNil(type), function () { return 'Type declared but not defined, don\'t forget to call .define on every declared type'; });
+ if (isUnion(type)) {
+ assert(type.dispatch === Declare.dispatch, function () { return 'Please define the custom ' + name + '.dispatch function before calling ' + name + '.define()'; });
+ }
+ }
+ return type(value, path);
+ }
+
+ Declare.define = function (spec) {
+ if (false) {
+ assert(isType(spec), function () { return 'Invalid argument type ' + assert.stringify(spec) + ' supplied to define(type) (expected a type)'; });
+ assert(isNil(type), function () { return 'Declare.define(type) can only be invoked once'; });
+ assert(isNil(spec.meta.name) && Object.keys(spec.prototype).length === 0, function () { return 'Invalid argument type ' + assert.stringify(spec) + ' supplied to define(type) (expected a fresh, unnamed type)'; });
+ }
+
+ if (isUnion(spec) && Declare.hasOwnProperty('dispatch')) {
+ spec.dispatch = Declare.dispatch;
+ }
+ type = spec;
+ mixin(Declare, type, true); // true because it overwrites Declare.displayName
+ if (name) {
+ type.displayName = Declare.displayName = name;
+ Declare.meta.name = name;
+ }
+ Declare.meta.identity = type.meta.identity;
+ Declare.prototype = type.prototype;
+ return Declare;
+ };
+
+ Declare.displayName = name || ( getTypeName(Declare) + "$" + nextDeclareUniqueId++ );
+ // in general I can't say if this type will be an identity, for safety setting to false
+ Declare.meta = { identity: false };
+ Declare.prototype = null;
+ return Declare;
+ };
+
+
+/***/ },
+/* 182 */
+/***/ function(module, exports, __webpack_require__) {
+
+ var isNil = __webpack_require__(149);
+ var assert = __webpack_require__(147);
+
+ // safe mixin, cannot override props unless specified
+ module.exports = function mixin(target, source, overwrite) {
+ if (isNil(source)) { return target; }
+ for (var k in source) {
+ if (source.hasOwnProperty(k)) {
+ if (overwrite !== true) {
+ if (false) {
+ assert(!target.hasOwnProperty(k) || target[k] === source[k], function () { return 'Invalid call to mixin(target, source, [overwrite]): cannot overwrite property "' + k + '" of target object'; });
+ }
+ }
+ target[k] = source[k];
+ }
+ }
+ return target;
+ };
+
+/***/ },
+/* 183 */
+/***/ function(module, exports, __webpack_require__) {
+
+ var isType = __webpack_require__(158);
+
+ module.exports = function isUnion(x) {
+ return isType(x) && ( x.meta.kind === 'union' );
+ };
+
+/***/ },
+/* 184 */
+/***/ function(module, exports, __webpack_require__) {
+
+ var assert = __webpack_require__(147);
+ var isTypeName = __webpack_require__(172);
+ var forbidNewOperator = __webpack_require__(156);
+ var isString = __webpack_require__(155);
+ var isObject = __webpack_require__(159);
+
+ function getDefaultName(map) {
+ return Object.keys(map).map(function (k) { return assert.stringify(k); }).join(' | ');
+ }
+
+ function enums(map, name) {
+
+ if (false) {
+ assert(isObject(map), function () { return 'Invalid argument map ' + assert.stringify(map) + ' supplied to enums(map, [name]) combinator (expected a dictionary of String -> String | Number)'; });
+ assert(isTypeName(name), function () { return 'Invalid argument name ' + assert.stringify(name) + ' supplied to enums(map, [name]) combinator (expected a string)'; });
+ }
+
+ var displayName = name || getDefaultName(map);
+
+ function Enums(value, path) {
+
+ if (false) {
+ forbidNewOperator(this, Enums);
+ path = path || [displayName];
+ assert(Enums.is(value), function () { return 'Invalid value ' + assert.stringify(value) + ' supplied to ' + path.join('/') + ' (expected one of ' + assert.stringify(Object.keys(map)) + ')'; });
+ }
+
+ return value;
+ }
+
+ Enums.meta = {
+ kind: 'enums',
+ map: map,
+ name: name,
+ identity: true
+ };
+
+ Enums.displayName = displayName;
+
+ Enums.is = function (x) {
+ return map.hasOwnProperty(x);
+ };
+
+ return Enums;
+ }
+
+ enums.of = function (keys, name) {
+ keys = isString(keys) ? keys.split(' ') : keys;
+ var value = {};
+ keys.forEach(function (k) {
+ value[k] = k;
+ });
+ return enums(value, name);
+ };
+
+ enums.getDefaultName = getDefaultName;
+ module.exports = enums;
+
+
+
+/***/ },
+/* 185 */
+/***/ function(module, exports, __webpack_require__) {
+
+ var assert = __webpack_require__(147);
+ var isTypeName = __webpack_require__(172);
+ var isFunction = __webpack_require__(148);
+ var getTypeName = __webpack_require__(157);
+ var isIdentity = __webpack_require__(173);
+ var create = __webpack_require__(174);
+ var is = __webpack_require__(175);
+ var isArray = __webpack_require__(160);
+
+ function getDefaultName(type) {
+ return 'Array<' + getTypeName(type) + '>';
+ }
+
+ function list(type, name) {
+
+ if (false) {
+ assert(isFunction(type), function () { return 'Invalid argument type ' + assert.stringify(type) + ' supplied to list(type, [name]) combinator (expected a type)'; });
+ assert(isTypeName(name), function () { return 'Invalid argument name ' + assert.stringify(name) + ' supplied to list(type, [name]) combinator (expected a string)'; });
+ }
+
+ var displayName = name || getDefaultName(type);
+ var typeNameCache = getTypeName(type);
+ var identity = isIdentity(type); // the list is identity iif type is identity
+
+ function List(value, path) {
+
+ if (true) {
+ if (identity) {
+ return value; // just trust the input if elements must not be hydrated
+ }
+ }
+
+ if (false) {
+ path = path || [displayName];
+ assert(isArray(value), function () { return 'Invalid value ' + assert.stringify(value) + ' supplied to ' + path.join('/') + ' (expected an array of ' + typeNameCache + ')'; });
+ }
+
+ var idempotent = true; // will remain true if I can reutilise the input
+ var ret = []; // make a temporary copy, will be discarded if idempotent remains true
+ for (var i = 0, len = value.length; i < len; i++ ) {
+ var actual = value[i];
+ var instance = create(type, actual, ( false ? path.concat(i + ': ' + typeNameCache) : null ));
+ idempotent = idempotent && ( actual === instance );
+ ret.push(instance);
+ }
+
+ if (idempotent) { // implements idempotency
+ ret = value;
+ }
+
+ if (false) {
+ Object.freeze(ret);
+ }
+
+ return ret;
+ }
+
+ List.meta = {
+ kind: 'list',
+ type: type,
+ name: name,
+ identity: identity
+ };
+
+ List.displayName = displayName;
+
+ List.is = function (x) {
+ return isArray(x) && x.every(function (e) {
+ return is(e, type);
+ });
+ };
+
+ List.update = function (instance, patch) {
+ return List(assert.update(instance, patch));
+ };
+
+ return List;
+ }
+
+ list.getDefaultName = getDefaultName;
+ module.exports = list;
+
+
+/***/ },
+/* 186 */
+/***/ function(module, exports, __webpack_require__) {
+
+ var assert = __webpack_require__(147);
+ var isTypeName = __webpack_require__(172);
+ var isFunction = __webpack_require__(148);
+ var isMaybe = __webpack_require__(187);
+ var isIdentity = __webpack_require__(173);
+ var Any = __webpack_require__(153);
+ var create = __webpack_require__(174);
+ var Nil = __webpack_require__(167);
+ var forbidNewOperator = __webpack_require__(156);
+ var is = __webpack_require__(175);
+ var getTypeName = __webpack_require__(157);
+
+ function getDefaultName(type) {
+ return '?' + getTypeName(type);
+ }
+
+ function maybe(type, name) {
+
+ if (isMaybe(type) || type === Any || type === Nil) { // makes the combinator idempotent and handle Any, Nil
+ return type;
+ }
+
+ if (false) {
+ assert(isFunction(type), function () { return 'Invalid argument type ' + assert.stringify(type) + ' supplied to maybe(type, [name]) combinator (expected a type)'; });
+ assert(isTypeName(name), function () { return 'Invalid argument name ' + assert.stringify(name) + ' supplied to maybe(type, [name]) combinator (expected a string)'; });
+ }
+
+ var displayName = name || getDefaultName(type);
+ var identity = isIdentity(type);
+
+ function Maybe(value, path) {
+ if (false) {
+ if (identity) {
+ forbidNewOperator(this, Maybe);
+ }
+ }
+ return Nil.is(value) ? value : create(type, value, path);
+ }
+
+ Maybe.meta = {
+ kind: 'maybe',
+ type: type,
+ name: name,
+ identity: identity
+ };
+
+ Maybe.displayName = displayName;
+
+ Maybe.is = function (x) {
+ return Nil.is(x) || is(x, type);
+ };
+
+ return Maybe;
+ }
+
+ maybe.getDefaultName = getDefaultName;
+ module.exports = maybe;
+
+
+/***/ },
+/* 187 */
+/***/ function(module, exports, __webpack_require__) {
+
+ var isType = __webpack_require__(158);
+
+ module.exports = function isMaybe(x) {
+ return isType(x) && ( x.meta.kind === 'maybe' );
+ };
+
+/***/ },
+/* 188 */
+/***/ function(module, exports, __webpack_require__) {
+
+ var assert = __webpack_require__(147);
+ var isTypeName = __webpack_require__(172);
+ var String = __webpack_require__(178);
+ var Function = __webpack_require__(166);
+ var isBoolean = __webpack_require__(163);
+ var isObject = __webpack_require__(159);
+ var isNil = __webpack_require__(149);
+ var create = __webpack_require__(174);
+ var getTypeName = __webpack_require__(157);
+ var dict = __webpack_require__(180);
+ var getDefaultInterfaceName = __webpack_require__(189);
+ var extend = __webpack_require__(190);
+
+ function getDefaultName(props) {
+ return 'Struct' + getDefaultInterfaceName(props);
+ }
+
+ function extendStruct(mixins, name) {
+ return extend(struct, mixins, name);
+ }
+
+ function getOptions(options) {
+ if (!isObject(options)) {
+ options = isNil(options) ? {} : { name: options };
+ }
+ if (!options.hasOwnProperty('strict')) {
+ options.strict = struct.strict;
+ }
+ if (!options.hasOwnProperty('defaultProps')) {
+ options.defaultProps = {};
+ }
+ return options;
+ }
+
+ function struct(props, options) {
+
+ options = getOptions(options);
+ var name = options.name;
+ var strict = options.strict;
+ var defaultProps = options.defaultProps;
+
+ if (false) {
+ assert(dict(String, Function).is(props), function () { return 'Invalid argument props ' + assert.stringify(props) + ' supplied to struct(props, [options]) combinator (expected a dictionary String -> Type)'; });
+ assert(isTypeName(name), function () { return 'Invalid argument name ' + assert.stringify(name) + ' supplied to struct(props, [options]) combinator (expected a string)'; });
+ assert(isBoolean(strict), function () { return 'Invalid argument strict ' + assert.stringify(strict) + ' supplied to struct(props, [options]) combinator (expected a boolean)'; });
+ assert(isObject(defaultProps), function () { return 'Invalid argument defaultProps ' + assert.stringify(defaultProps) + ' supplied to struct(props, [options]) combinator (expected an object)'; });
+ }
+
+ var displayName = name || getDefaultName(props);
+
+ function Struct(value, path) {
+
+ if (Struct.is(value)) { // implements idempotency
+ return value;
+ }
+
+ if (false) {
+ path = path || [displayName];
+ assert(isObject(value), function () { return 'Invalid value ' + assert.stringify(value) + ' supplied to ' + path.join('/') + ' (expected an object)'; });
+ // strictness
+ if (strict) {
+ for (k in value) {
+ if (value.hasOwnProperty(k)) {
+ assert(props.hasOwnProperty(k), function () { return 'Invalid additional prop "' + k + '" supplied to ' + path.join('/'); });
+ }
+ }
+ }
+ }
+
+ if (!(this instanceof Struct)) { // `new` is optional
+ return new Struct(value, path);
+ }
+
+ for (var k in props) {
+ if (props.hasOwnProperty(k)) {
+ var expected = props[k];
+ var actual = value[k];
+ // apply defaults
+ if (actual === undefined) {
+ actual = defaultProps[k];
+ }
+ this[k] = create(expected, actual, ( false ? path.concat(k + ': ' + getTypeName(expected)) : null ));
+ }
+ }
+
+ if (false) {
+ Object.freeze(this);
+ }
+
+ }
+
+ Struct.meta = {
+ kind: 'struct',
+ props: props,
+ name: name,
+ identity: false,
+ strict: strict,
+ defaultProps: defaultProps
+ };
+
+ Struct.displayName = displayName;
+
+ Struct.is = function (x) {
+ return x instanceof Struct;
+ };
+
+ Struct.update = function (instance, patch) {
+ return new Struct(assert.update(instance, patch));
+ };
+
+ Struct.extend = function (xs, name) {
+ return extendStruct([Struct].concat(xs), name);
+ };
+
+ return Struct;
+ }
+
+ struct.strict = false;
+ struct.getOptions = getOptions;
+ struct.getDefaultName = getDefaultName;
+ struct.extend = extendStruct;
+ module.exports = struct;
+
+
+/***/ },
+/* 189 */
+/***/ function(module, exports, __webpack_require__) {
+
+ var getTypeName = __webpack_require__(157);
+
+ function getDefaultInterfaceName(props) {
+ return '{' + Object.keys(props).map(function (prop) {
+ return prop + ': ' + getTypeName(props[prop]);
+ }).join(', ') + '}';
+ }
+
+ module.exports = getDefaultInterfaceName;
+
+
+/***/ },
+/* 190 */
+/***/ function(module, exports, __webpack_require__) {
+
+ var assert = __webpack_require__(147);
+ var isFunction = __webpack_require__(148);
+ var isArray = __webpack_require__(160);
+ var mixin = __webpack_require__(182);
+ var isStruct = __webpack_require__(191);
+ var isInterface = __webpack_require__(192);
+ var isObject = __webpack_require__(159);
+ var refinement = __webpack_require__(171);
+ var decompose = __webpack_require__(193);
+
+ function compose(predicates, unrefinedType) {
+ return predicates.reduce(function (type, predicate) {
+ return refinement(type, predicate);
+ }, unrefinedType);
+ }
+
+ function getProps(type) {
+ return isObject(type) ? type : type.meta.props;
+ }
+
+ function getDefaultProps(type) {
+ return isObject(type) ? null : type.meta.defaultProps;
+ }
+
+ function pushAll(arr, elements) {
+ Array.prototype.push.apply(arr, elements);
+ }
+
+ function extend(combinator, mixins, options) {
+ if (false) {
+ assert(isFunction(combinator), function () { return 'Invalid argument combinator supplied to extend(combinator, mixins, options), expected a function'; });
+ assert(isArray(mixins), function () { return 'Invalid argument mixins supplied to extend(combinator, mixins, options), expected an array'; });
+ }
+ var props = {};
+ var prototype = {};
+ var predicates = [];
+ var defaultProps = {};
+ mixins.forEach(function (x, i) {
+ var decomposition = decompose(x);
+ var unrefinedType = decomposition.unrefinedType;
+ if (false) {
+ assert(isObject(unrefinedType) || isStruct(unrefinedType) || isInterface(unrefinedType), function () { return 'Invalid argument mixins[' + i + '] supplied to extend(combinator, mixins, options), expected an object, struct, interface or a refinement (of struct or interface)'; });
+ }
+ pushAll(predicates, decomposition.predicates);
+ mixin(props, getProps(unrefinedType));
+ mixin(prototype, unrefinedType.prototype);
+ mixin(defaultProps, getDefaultProps(unrefinedType), true);
+ });
+ options = combinator.getOptions(options);
+ options.defaultProps = mixin(defaultProps, options.defaultProps, true);
+ var result = compose(predicates, combinator(props, options));
+ mixin(result.prototype, prototype);
+ return result;
+ }
+
+ module.exports = extend;
+
+/***/ },
+/* 191 */
+/***/ function(module, exports, __webpack_require__) {
+
+ var isType = __webpack_require__(158);
+
+ module.exports = function isStruct(x) {
+ return isType(x) && ( x.meta.kind === 'struct' );
+ };
+
+/***/ },
+/* 192 */
+/***/ function(module, exports, __webpack_require__) {
+
+ var isType = __webpack_require__(158);
+
+ module.exports = function isInterface(x) {
+ return isType(x) && ( x.meta.kind === 'interface' );
+ };
+
+/***/ },
+/* 193 */
+/***/ function(module, exports, __webpack_require__) {
+
+ var isType = __webpack_require__(158);
+
+ function isRefinement(type) {
+ return isType(type) && type.meta.kind === 'subtype';
+ }
+
+ function getPredicates(type) {
+ return isRefinement(type) ?
+ [type.meta.predicate].concat(getPredicates(type.meta.type)) :
+ [];
+ }
+
+ function getUnrefinedType(type) {
+ return isRefinement(type) ?
+ getUnrefinedType(type.meta.type) :
+ type;
+ }
+
+ function decompose(type) {
+ return {
+ predicates: getPredicates(type),
+ unrefinedType: getUnrefinedType(type)
+ };
+ }
+
+ module.exports = decompose;
+
+/***/ },
+/* 194 */
+/***/ function(module, exports, __webpack_require__) {
+
+ var assert = __webpack_require__(147);
+ var isTypeName = __webpack_require__(172);
+ var isFunction = __webpack_require__(148);
+ var getTypeName = __webpack_require__(157);
+ var isIdentity = __webpack_require__(173);
+ var isArray = __webpack_require__(160);
+ var create = __webpack_require__(174);
+ var is = __webpack_require__(175);
+
+ function getDefaultName(types) {
+ return '[' + types.map(getTypeName).join(', ') + ']';
+ }
+
+ function tuple(types, name) {
+
+ if (false) {
+ assert(isArray(types) && types.every(isFunction), function () { return 'Invalid argument types ' + assert.stringify(types) + ' supplied to tuple(types, [name]) combinator (expected an array of types)'; });
+ assert(isTypeName(name), function () { return 'Invalid argument name ' + assert.stringify(name) + ' supplied to tuple(types, [name]) combinator (expected a string)'; });
+ }
+
+ var displayName = name || getDefaultName(types);
+ var identity = types.every(isIdentity);
+
+ function Tuple(value, path) {
+
+ if (true) {
+ if (identity) {
+ return value;
+ }
+ }
+
+ if (false) {
+ path = path || [displayName];
+ assert(isArray(value) && value.length === types.length, function () { return 'Invalid value ' + assert.stringify(value) + ' supplied to ' + path.join('/') + ' (expected an array of length ' + types.length + ')'; });
+ }
+
+ var idempotent = true;
+ var ret = [];
+ for (var i = 0, len = types.length; i < len; i++) {
+ var expected = types[i];
+ var actual = value[i];
+ var instance = create(expected, actual, ( false ? path.concat(i + ': ' + getTypeName(expected)) : null ));
+ idempotent = idempotent && ( actual === instance );
+ ret.push(instance);
+ }
+
+ if (idempotent) { // implements idempotency
+ ret = value;
+ }
+
+ if (false) {
+ Object.freeze(ret);
+ }
+
+ return ret;
+ }
+
+ Tuple.meta = {
+ kind: 'tuple',
+ types: types,
+ name: name,
+ identity: identity
+ };
+
+ Tuple.displayName = displayName;
+
+ Tuple.is = function (x) {
+ return isArray(x) &&
+ x.length === types.length &&
+ types.every(function (type, i) {
+ return is(x[i], type);
+ });
+ };
+
+ Tuple.update = function (instance, patch) {
+ return Tuple(assert.update(instance, patch));
+ };
+
+ return Tuple;
+ }
+
+ tuple.getDefaultName = getDefaultName;
+ module.exports = tuple;
+
+/***/ },
+/* 195 */
+/***/ function(module, exports, __webpack_require__) {
+
+ var assert = __webpack_require__(147);
+ var isTypeName = __webpack_require__(172);
+ var isFunction = __webpack_require__(148);
+ var getTypeName = __webpack_require__(157);
+ var isIdentity = __webpack_require__(173);
+ var isArray = __webpack_require__(160);
+ var create = __webpack_require__(174);
+ var is = __webpack_require__(175);
+ var forbidNewOperator = __webpack_require__(156);
+ var isUnion = __webpack_require__(183);
+ var isNil = __webpack_require__(149);
+
+ function getDefaultName(types) {
+ return types.map(getTypeName).join(' | ');
+ }
+
+ function union(types, name) {
+
+ if (false) {
+ assert(isArray(types) && types.every(isFunction) && types.length >= 2, function () { return 'Invalid argument types ' + assert.stringify(types) + ' supplied to union(types, [name]) combinator (expected an array of at least 2 types)'; });
+ assert(isTypeName(name), function () { return 'Invalid argument name ' + assert.stringify(name) + ' supplied to union(types, [name]) combinator (expected a string)'; });
+ }
+
+ var displayName = name || getDefaultName(types);
+ var identity = types.every(isIdentity);
+
+ function Union(value, path) {
+
+ if (true) {
+ if (identity) {
+ return value;
+ }
+ }
+
+ var type = Union.dispatch(value);
+ if (!type && Union.is(value)) {
+ return value;
+ }
+
+ if (false) {
+ if (identity) {
+ forbidNewOperator(this, Union);
+ }
+ path = path || [displayName];
+ assert(isFunction(type), function () { return 'Invalid value ' + assert.stringify(value) + ' supplied to ' + path.join('/') + ' (no constructor returned by dispatch)'; });
+ path[path.length - 1] += '(' + getTypeName(type) + ')';
+ }
+
+ return create(type, value, path);
+ }
+
+ Union.meta = {
+ kind: 'union',
+ types: types,
+ name: name,
+ identity: identity
+ };
+
+ Union.displayName = displayName;
+
+ Union.is = function (x) {
+ return types.some(function (type) {
+ return is(x, type);
+ });
+ };
+
+ Union.dispatch = function (x) { // default dispatch implementation
+ for (var i = 0, len = types.length; i < len; i++ ) {
+ var type = types[i];
+ if (isUnion(type)) { // handle union of unions
+ var t = type.dispatch(x);
+ if (!isNil(t)) {
+ return t;
+ }
+ }
+ else if (is(x, type)) {
+ return type;
+ }
+ }
+ };
+
+ Union.update = function (instance, patch) {
+ return Union(assert.update(instance, patch));
+ };
+
+ return Union;
+ }
+
+ union.getDefaultName = getDefaultName;
+ module.exports = union;
+
+
+
+/***/ },
+/* 196 */
+/***/ function(module, exports, __webpack_require__) {
+
+ var assert = __webpack_require__(147);
+ var isTypeName = __webpack_require__(172);
+ var FunctionType = __webpack_require__(166);
+ var isArray = __webpack_require__(160);
+ var list = __webpack_require__(185);
+ var isObject = __webpack_require__(159);
+ var create = __webpack_require__(174);
+ var isNil = __webpack_require__(149);
+ var isBoolean = __webpack_require__(163);
+ var tuple = __webpack_require__(194);
+ var getFunctionName = __webpack_require__(152);
+ var getTypeName = __webpack_require__(157);
+ var isType = __webpack_require__(158);
+
+ function getDefaultName(domain, codomain) {
+ return '(' + domain.map(getTypeName).join(', ') + ') => ' + getTypeName(codomain);
+ }
+
+ function isInstrumented(f) {
+ return FunctionType.is(f) && isObject(f.instrumentation);
+ }
+
+ function getOptionalArgumentsIndex(types) {
+ var end = types.length;
+ var areAllMaybes = false;
+ for (var i = end - 1; i >= 0; i--) {
+ var type = types[i];
+ if (!isType(type) || type.meta.kind !== 'maybe') {
+ return (i + 1);
+ } else {
+ areAllMaybes = true;
+ }
+ }
+ return areAllMaybes ? 0 : end;
+ }
+
+ function func(domain, codomain, name) {
+
+ domain = isArray(domain) ? domain : [domain]; // handle handy syntax for unary functions
+
+ if (false) {
+ assert(list(FunctionType).is(domain), function () { return 'Invalid argument domain ' + assert.stringify(domain) + ' supplied to func(domain, codomain, [name]) combinator (expected an array of types)'; });
+ assert(FunctionType.is(codomain), function () { return 'Invalid argument codomain ' + assert.stringify(codomain) + ' supplied to func(domain, codomain, [name]) combinator (expected a type)'; });
+ assert(isTypeName(name), function () { return 'Invalid argument name ' + assert.stringify(name) + ' supplied to func(domain, codomain, [name]) combinator (expected a string)'; });
+ }
+
+ var displayName = name || getDefaultName(domain, codomain);
+ var domainLength = domain.length;
+ var optionalArgumentsIndex = getOptionalArgumentsIndex(domain);
+
+ function FuncType(value, path) {
+
+ if (!isInstrumented(value)) { // automatically instrument the function
+ return FuncType.of(value);
+ }
+
+ if (false) {
+ path = path || [displayName];
+ assert(FuncType.is(value), function () { return 'Invalid value ' + assert.stringify(value) + ' supplied to ' + path.join('/'); });
+ }
+
+ return value;
+ }
+
+ FuncType.meta = {
+ kind: 'func',
+ domain: domain,
+ codomain: codomain,
+ name: name,
+ identity: true
+ };
+
+ FuncType.displayName = displayName;
+
+ FuncType.is = function (x) {
+ return isInstrumented(x) &&
+ x.instrumentation.domain.length === domainLength &&
+ x.instrumentation.domain.every(function (type, i) {
+ return type === domain[i];
+ }) &&
+ x.instrumentation.codomain === codomain;
+ };
+
+ FuncType.of = function (f, curried) {
+
+ if (false) {
+ assert(FunctionType.is(f), function () { return 'Invalid argument f supplied to func.of ' + displayName + ' (expected a function)'; });
+ assert(isNil(curried) || isBoolean(curried), function () { return 'Invalid argument curried ' + assert.stringify(curried) + ' supplied to func.of ' + displayName + ' (expected a boolean)'; });
+ }
+
+ if (FuncType.is(f)) { // makes FuncType.of idempotent
+ return f;
+ }
+
+ function fn() {
+ var args = Array.prototype.slice.call(arguments);
+ var argsLength = args.length;
+
+ if (false) {
+ // type-check arguments
+ var tupleLength = curried ? argsLength : Math.max(argsLength, optionalArgumentsIndex);
+ tuple(domain.slice(0, tupleLength), 'arguments of function ' + displayName)(args);
+ }
+
+ if (curried && argsLength < domainLength) {
+ if (false) {
+ assert(argsLength > 0, 'Invalid arguments.length = 0 for curried function ' + displayName);
+ }
+ var g = Function.prototype.bind.apply(f, [this].concat(args));
+ var newDomain = func(domain.slice(argsLength), codomain);
+ return newDomain.of(g, true);
+ }
+ else {
+ return create(codomain, f.apply(this, args));
+ }
+ }
+
+ fn.instrumentation = {
+ domain: domain,
+ codomain: codomain,
+ f: f
+ };
+
+ fn.displayName = getFunctionName(f);
+
+ return fn;
+
+ };
+
+ return FuncType;
+
+ }
+
+ func.getDefaultName = getDefaultName;
+ func.getOptionalArgumentsIndex = getOptionalArgumentsIndex;
+ module.exports = func;
+
+
+/***/ },
+/* 197 */
+/***/ function(module, exports, __webpack_require__) {
+
+ var assert = __webpack_require__(147);
+ var isTypeName = __webpack_require__(172);
+ var isFunction = __webpack_require__(148);
+ var isArray = __webpack_require__(160);
+ var forbidNewOperator = __webpack_require__(173);
+ var is = __webpack_require__(175);
+ var getTypeName = __webpack_require__(157);
+ var isIdentity = __webpack_require__(173);
+
+ function getDefaultName(types) {
+ return types.map(getTypeName).join(' & ');
+ }
+
+ function intersection(types, name) {
+
+ if (false) {
+ assert(isArray(types) && types.every(isFunction) && types.length >= 2, function () { return 'Invalid argument types ' + assert.stringify(types) + ' supplied to intersection(types, [name]) combinator (expected an array of at least 2 types)'; });
+ assert(isTypeName(name), function () { return 'Invalid argument name ' + assert.stringify(name) + ' supplied to intersection(types, [name]) combinator (expected a string)'; });
+ }
+
+ var displayName = name || getDefaultName(types);
+ var identity = types.every(isIdentity);
+
+ function Intersection(value, path) {
+
+ if (false) {
+ if (identity) {
+ forbidNewOperator(this, Intersection);
+ }
+ path = path || [displayName];
+ assert(Intersection.is(value), function () { return 'Invalid value ' + assert.stringify(value) + ' supplied to ' + path.join('/'); });
+ }
+
+ return value;
+ }
+
+ Intersection.meta = {
+ kind: 'intersection',
+ types: types,
+ name: name,
+ identity: identity
+ };
+
+ Intersection.displayName = displayName;
+
+ Intersection.is = function (x) {
+ return types.every(function (type) {
+ return is(x, type);
+ });
+ };
+
+ Intersection.update = function (instance, patch) {
+ return Intersection(assert.update(instance, patch));
+ };
+
+ return Intersection;
+ }
+
+ intersection.getDefaultName = getDefaultName;
+ module.exports = intersection;
+
+
+
+/***/ },
+/* 198 */
+/***/ function(module, exports, __webpack_require__) {
+
+ var assert = __webpack_require__(147);
+ var isTypeName = __webpack_require__(172);
+ var String = __webpack_require__(178);
+ var Function = __webpack_require__(166);
+ var isBoolean = __webpack_require__(163);
+ var isObject = __webpack_require__(159);
+ var isNil = __webpack_require__(149);
+ var create = __webpack_require__(174);
+ var getTypeName = __webpack_require__(157);
+ var dict = __webpack_require__(180);
+ var getDefaultInterfaceName = __webpack_require__(189);
+ var isIdentity = __webpack_require__(173);
+ var is = __webpack_require__(175);
+ var extend = __webpack_require__(190);
+ var assign = __webpack_require__(199);
+
+ function extendInterface(mixins, name) {
+ return extend(inter, mixins, name);
+ }
+
+ function getOptions(options) {
+ if (!isObject(options)) {
+ options = isNil(options) ? {} : { name: options };
+ }
+ if (!options.hasOwnProperty('strict')) {
+ options.strict = inter.strict;
+ }
+ return options;
+ }
+
+ function inter(props, options) {
+
+ options = getOptions(options);
+ var name = options.name;
+ var strict = options.strict;
+
+ if (false) {
+ assert(dict(String, Function).is(props), function () { return 'Invalid argument props ' + assert.stringify(props) + ' supplied to interface(props, [options]) combinator (expected a dictionary String -> Type)'; });
+ assert(isTypeName(name), function () { return 'Invalid argument name ' + assert.stringify(name) + ' supplied to interface(props, [options]) combinator (expected a string)'; });
+ assert(isBoolean(strict), function () { return 'Invalid argument strict ' + assert.stringify(strict) + ' supplied to struct(props, [options]) combinator (expected a boolean)'; });
+ }
+
+ var displayName = name || getDefaultInterfaceName(props);
+ var identity = Object.keys(props).map(function (prop) { return props[prop]; }).every(isIdentity);
+
+ function Interface(value, path) {
+
+ if (true) {
+ if (identity) {
+ return value; // just trust the input if elements must not be hydrated
+ }
+ }
+
+ if (false) {
+ path = path || [displayName];
+ assert(!isNil(value), function () { return 'Invalid value ' + value + ' supplied to ' + path.join('/'); });
+ // strictness
+ if (strict) {
+ for (var k in value) {
+ assert(props.hasOwnProperty(k), function () { return 'Invalid additional prop "' + k + '" supplied to ' + path.join('/'); });
+ }
+ }
+ }
+
+ var idempotent = true;
+ var ret = identity ? {} : assign({}, value);
+ for (var prop in props) {
+ var expected = props[prop];
+ var actual = value[prop];
+ var instance = create(expected, actual, ( false ? path.concat(prop + ': ' + getTypeName(expected)) : null ));
+ idempotent = idempotent && ( actual === instance );
+ ret[prop] = instance;
+ }
+
+ if (idempotent) { // implements idempotency
+ ret = value;
+ }
+
+ if (false) {
+ Object.freeze(ret);
+ }
+
+ return ret;
+
+ }
+
+ Interface.meta = {
+ kind: 'interface',
+ props: props,
+ name: name,
+ identity: identity,
+ strict: strict
+ };
+
+ Interface.displayName = displayName;
+
+ Interface.is = function (x) {
+ if (isNil(x)) {
+ return false;
+ }
+ if (strict) {
+ for (var k in x) {
+ if (!props.hasOwnProperty(k)) {
+ return false;
+ }
+ }
+ }
+ for (var prop in props) {
+ if (!is(x[prop], props[prop])) {
+ return false;
+ }
+ }
+ return true;
+ };
+
+ Interface.update = function (instance, patch) {
+ return Interface(assert.update(instance, patch));
+ };
+
+ Interface.extend = function (xs, name) {
+ return extendInterface([Interface].concat(xs), name);
+ };
+
+ return Interface;
+ }
+
+ inter.strict = false;
+ inter.getOptions = getOptions;
+ inter.getDefaultName = getDefaultInterfaceName;
+ inter.extend = extendInterface;
+ module.exports = inter;
+
+
+/***/ },
+/* 199 */
+/***/ function(module, exports) {
+
+ function assign(x, y) {
+ for (var k in y) {
+ x[k] = y[k];
+ }
+ return x;
+ }
+
+ module.exports = assign;
+
+/***/ },
+/* 200 */
+/***/ function(module, exports, __webpack_require__) {
+
+ var assert = __webpack_require__(147);
+ var isObject = __webpack_require__(159);
+ var isFunction = __webpack_require__(148);
+ var isArray = __webpack_require__(160);
+ var isNumber = __webpack_require__(169);
+ var assign = __webpack_require__(199);
+
+ function getShallowCopy(x) {
+ if (isObject(x)) {
+ if (x instanceof Date || x instanceof RegExp) {
+ return x;
+ }
+ return assign({}, x);
+ }
+ if (isArray(x)) {
+ return x.concat();
+ }
+ return x;
+ }
+
+ function isCommand(k) {
+ return update.commands.hasOwnProperty(k);
+ }
+
+ function getCommand(k) {
+ return update.commands[k];
+ }
+
+ function update(instance, patch) {
+
+ if (false) {
+ assert(isObject(patch), function () { return 'Invalid argument patch ' + assert.stringify(patch) + ' supplied to function update(instance, patch): expected an object containing commands'; });
+ }
+
+ var value = instance;
+ var isChanged = false;
+ var newValue;
+ for (var k in patch) {
+ if (patch.hasOwnProperty(k)) {
+ if (isCommand(k)) {
+ newValue = getCommand(k)(patch[k], value);
+ if (newValue !== instance) {
+ isChanged = true;
+ value = newValue;
+ } else {
+ value = instance;
+ }
+ }
+ else {
+ if (value === instance) {
+ value = getShallowCopy(instance);
+ }
+ newValue = update(value[k], patch[k]);
+ isChanged = isChanged || ( newValue !== value[k] );
+ value[k] = newValue;
+ }
+ }
+ }
+ return isChanged ? value : instance;
+ }
+
+ // built-in commands
+
+ function $apply(f, value) {
+ if (false) {
+ assert(isFunction(f), 'Invalid argument f supplied to immutability helper { $apply: f } (expected a function)');
+ }
+ return f(value);
+ }
+
+ function $push(elements, arr) {
+ if (false) {
+ assert(isArray(elements), 'Invalid argument elements supplied to immutability helper { $push: elements } (expected an array)');
+ assert(isArray(arr), 'Invalid value supplied to immutability helper $push (expected an array)');
+ }
+ if (elements.length > 0) {
+ return arr.concat(elements);
+ }
+ return arr;
+ }
+
+ function $remove(keys, obj) {
+ if (false) {
+ assert(isArray(keys), 'Invalid argument keys supplied to immutability helper { $remove: keys } (expected an array)');
+ assert(isObject(obj), 'Invalid value supplied to immutability helper $remove (expected an object)');
+ }
+ if (keys.length > 0) {
+ obj = getShallowCopy(obj);
+ for (var i = 0, len = keys.length; i < len; i++ ) {
+ delete obj[keys[i]];
+ }
+ }
+ return obj;
+ }
+
+ function $set(value) {
+ return value;
+ }
+
+ function $splice(splices, arr) {
+ if (false) {
+ assert(isArray(splices) && splices.every(isArray), 'Invalid argument splices supplied to immutability helper { $splice: splices } (expected an array of arrays)');
+ assert(isArray(arr), 'Invalid value supplied to immutability helper $splice (expected an array)');
+ }
+ if (splices.length > 0) {
+ arr = getShallowCopy(arr);
+ return splices.reduce(function (acc, splice) {
+ acc.splice.apply(acc, splice);
+ return acc;
+ }, arr);
+ }
+ return arr;
+ }
+
+ function $swap(config, arr) {
+ if (false) {
+ assert(isObject(config), 'Invalid argument config supplied to immutability helper { $swap: config } (expected an object)');
+ assert(isNumber(config.from), 'Invalid argument config.from supplied to immutability helper { $swap: config } (expected a number)');
+ assert(isNumber(config.to), 'Invalid argument config.to supplied to immutability helper { $swap: config } (expected a number)');
+ assert(isArray(arr), 'Invalid value supplied to immutability helper $swap (expected an array)');
+ }
+ if (config.from !== config.to) {
+ arr = getShallowCopy(arr);
+ var element = arr[config.to];
+ arr[config.to] = arr[config.from];
+ arr[config.from] = element;
+ }
+ return arr;
+ }
+
+ function $unshift(elements, arr) {
+ if (false) {
+ assert(isArray(elements), 'Invalid argument elements supplied to immutability helper {$unshift: elements} (expected an array)');
+ assert(isArray(arr), 'Invalid value supplied to immutability helper $unshift (expected an array)');
+ }
+ if (elements.length > 0) {
+ return elements.concat(arr);
+ }
+ return arr;
+ }
+
+ function $merge(whatToMerge, value) {
+ var isChanged = false;
+ var result = getShallowCopy(value);
+ for (var k in whatToMerge) {
+ if (whatToMerge.hasOwnProperty(k)) {
+ result[k] = whatToMerge[k];
+ isChanged = isChanged || ( result[k] !== value[k] );
+ }
+ }
+ return isChanged ? result : value;
+ }
+
+ update.commands = {
+ $apply: $apply,
+ $push: $push,
+ $remove: $remove,
+ $set: $set,
+ $splice: $splice,
+ $swap: $swap,
+ $unshift: $unshift,
+ $merge: $merge
+ };
+
+ module.exports = update;
+
+
+/***/ },
+/* 201 */
+/***/ function(module, exports, __webpack_require__) {
+
+ var assert = __webpack_require__(147);
+ var isFunction = __webpack_require__(148);
+ var isType = __webpack_require__(158);
+ var Any = __webpack_require__(153);
+
+ module.exports = function match(x) {
+ var type, guard, f, count;
+ for (var i = 1, len = arguments.length; i < len; ) {
+ type = arguments[i];
+ guard = arguments[i + 1];
+ f = arguments[i + 2];
+
+ if (isFunction(f) && !isType(f)) {
+ i = i + 3;
+ }
+ else {
+ f = guard;
+ guard = Any.is;
+ i = i + 2;
+ }
+
+ if (false) {
+ count = (count || 0) + 1;
+ assert(isType(type), function () { return 'Invalid type in clause #' + count; });
+ assert(isFunction(guard), function () { return 'Invalid guard in clause #' + count; });
+ assert(isFunction(f), function () { return 'Invalid block in clause #' + count; });
+ }
+
+ if (type.is(x) && guard(x)) {
+ return f(x);
+ }
+ }
+ assert.fail('Match error');
+ };
+
+
+/***/ },
+/* 202 */
+/***/ function(module, exports, __webpack_require__) {
+
+ var _require = __webpack_require__(145);
+
+ var BreakpointResult = _require.BreakpointResult;
+ var Location = _require.Location;
+
+ var defer = __webpack_require__(144);
+
+ var bpClients = void 0;
+ var threadClient = void 0;
+ var tabTarget = void 0;
+ var debuggerClient = void 0;
+
+ function setupCommands(dependencies) {
+ threadClient = dependencies.threadClient;
+ tabTarget = dependencies.tabTarget;
+ debuggerClient = dependencies.debuggerClient;
+ bpClients = {};
+ }
+
+ function resume() {
+ return new Promise(resolve => {
+ threadClient.resume(resolve);
+ });
+ }
+
+ function stepIn() {
+ return new Promise(resolve => {
+ threadClient.stepIn(resolve);
+ });
+ }
+
+ function stepOver() {
+ return new Promise(resolve => {
+ threadClient.stepOver(resolve);
+ });
+ }
+
+ function stepOut() {
+ return new Promise(resolve => {
+ threadClient.stepOut(resolve);
+ });
+ }
+
+ function breakOnNext() {
+ return threadClient.breakOnNext();
+ }
+
+ function sourceContents(sourceId) {
+ var sourceClient = threadClient.source({ actor: sourceId });
+ return sourceClient.source();
+ }
+
+ function setBreakpoint(location, condition, noSliding) {
+ var sourceClient = threadClient.source({ actor: location.sourceId });
+
+ return sourceClient.setBreakpoint({
+ line: location.line,
+ column: location.column,
+ condition,
+ noSliding
+ }).then(res => onNewBreakpoint(location, res));
+ }
+
+ function onNewBreakpoint(location, res) {
+ var bpClient = res[1];
+ var actualLocation = res[0].actualLocation;
+ bpClients[bpClient.actor] = bpClient;
+
+ // Firefox only returns `actualLocation` if it actually changed,
+ // but we want it always to exist. Format `actualLocation` if it
+ // exists, otherwise use `location`.
+ actualLocation = actualLocation ? {
+ sourceId: actualLocation.source.actor,
+ line: actualLocation.line,
+ column: actualLocation.column
+ } : location;
+
+ return BreakpointResult({
+ id: bpClient.actor,
+ actualLocation: Location(actualLocation)
+ });
+ }
+
+ function removeBreakpoint(breakpointId) {
+ var bpClient = bpClients[breakpointId];
+ bpClients[breakpointId] = null;
+ return bpClient.remove();
+ }
+
+ function setBreakpointCondition(breakpointId, location, condition, noSliding) {
+ var bpClient = bpClients[breakpointId];
+ bpClients[breakpointId] = null;
+
+ return bpClient.setCondition(threadClient, condition, noSliding).then(_bpClient => onNewBreakpoint(location, [{}, _bpClient]));
+ }
+
+ function evaluate(script, _ref) {
+ var frameId = _ref.frameId;
+
+ var deferred = defer();
+ tabTarget.activeConsole.evaluateJS(script, result => {
+ deferred.resolve(result);
+ }, { frameActor: frameId });
+
+ return deferred.promise;
+ }
+
+ function debuggeeCommand(script) {
+ tabTarget.activeConsole.evaluateJS(script, () => {});
+
+ var consoleActor = tabTarget.form.consoleActor;
+ var request = debuggerClient._activeRequests.get(consoleActor);
+ request.emit("json-reply", {});
+ debuggerClient._activeRequests.delete(consoleActor);
+
+ return Promise.resolve();
+ }
+
+ function navigate(url) {
+ return tabTarget.activeTab.navigateTo(url);
+ }
+
+ function reload() {
+ return tabTarget.activeTab.reload();
+ }
+
+ function getProperties(grip) {
+ var objClient = threadClient.pauseGrip(grip);
+ return objClient.getPrototypeAndProperties();
+ }
+
+ function pauseOnExceptions(shouldPauseOnExceptions, shouldIgnoreCaughtExceptions) {
+ return threadClient.pauseOnExceptions(shouldPauseOnExceptions, shouldIgnoreCaughtExceptions);
+ }
+
+ function prettyPrint(sourceId, indentSize) {
+ var sourceClient = threadClient.source({ actor: sourceId });
+ return sourceClient.prettyPrint(indentSize);
+ }
+
+ function disablePrettyPrint(sourceId) {
+ var sourceClient = threadClient.source({ actor: sourceId });
+ return sourceClient.disablePrettyPrint();
+ }
+
+ var clientCommands = {
+ resume,
+ stepIn,
+ stepOut,
+ stepOver,
+ breakOnNext,
+ sourceContents,
+ setBreakpoint,
+ removeBreakpoint,
+ setBreakpointCondition,
+ evaluate,
+ debuggeeCommand,
+ navigate,
+ reload,
+ getProperties,
+ pauseOnExceptions,
+ prettyPrint,
+ disablePrettyPrint
+ };
+
+ module.exports = {
+ setupCommands,
+ clientCommands
+ };
+
+/***/ },
+/* 203 */
+/***/ function(module, exports, __webpack_require__) {
+
+ var paused = (() => {
+ var _ref = _asyncToGenerator(function* (_, packet) {
+ // If paused by an explicit interrupt, which are generated by the
+ // slow script dialog and internal events such as setting
+ // breakpoints, ignore the event.
+ if (packet.why.type === "interrupted" && !packet.why.onNext) {
+ return;
+ }
+
+ // Eagerly fetch the frames
+ var response = yield threadClient.getFrames(0, CALL_STACK_PAGE_SIZE);
+ var frames = response.frames.map(createFrame);
+
+ var pause = Object.assign({}, packet, {
+ frame: createFrame(packet.frame),
+ frames: frames
+ });
+
+ actions.paused(pause);
+ });
+
+ return function paused(_x, _x2) {
+ return _ref.apply(this, arguments);
+ };
+ })();
+
+ function _asyncToGenerator(fn) { return function () { var gen = fn.apply(this, arguments); return new Promise(function (resolve, reject) { function step(key, arg) { try { var info = gen[key](arg); var value = info.value; } catch (error) { reject(error); return; } if (info.done) { resolve(value); } else { return Promise.resolve(value).then(function (value) { step("next", value); }, function (err) { step("throw", err); }); } } return step("next"); }); }; }
+
+ var _require = __webpack_require__(204);
+
+ var createFrame = _require.createFrame;
+ var createSource = _require.createSource;
+
+
+ var CALL_STACK_PAGE_SIZE = 1000;
+
+ var threadClient = void 0;
+ var actions = void 0;
+
+ function setupEvents(dependencies) {
+ threadClient = dependencies.threadClient;
+ actions = dependencies.actions;
+ }
+
+ function resumed(_, packet) {
+ actions.resumed(packet);
+ }
+
+ function newSource(_, _ref2) {
+ var source = _ref2.source;
+
+ actions.newSource(createSource(source));
+ }
+
+ var clientEvents = {
+ paused,
+ resumed,
+ newSource
+ };
+
+ module.exports = {
+ setupEvents,
+ clientEvents
+ };
+
+/***/ },
+/* 204 */
+/***/ function(module, exports, __webpack_require__) {
+
+ var _require = __webpack_require__(145);
+
+ var Source = _require.Source;
+ var Frame = _require.Frame;
+ var Location = _require.Location;
+
+
+ function createFrame(frame) {
+ var title = void 0;
+ if (frame.type == "call") {
+ var c = frame.callee;
+ title = c.name || c.userDisplayName || c.displayName || "(anonymous)";
+ } else {
+ title = "(" + frame.type + ")";
+ }
+
+ return Frame({
+ id: frame.actor,
+ displayName: title,
+ location: Location({
+ sourceId: frame.where.source.actor,
+ line: frame.where.line,
+ column: frame.where.column
+ }),
+ this: frame.this,
+ scope: frame.environment
+ });
+ }
+
+ function createSource(source) {
+ return Source({
+ id: source.actor,
+ url: source.url,
+ isPrettyPrinted: false,
+ sourceMapURL: source.sourceMapURL
+ });
+ }
+
+ module.exports = { createFrame, createSource };
+
+/***/ },
+/* 205 */
+/***/ function(module, exports, __webpack_require__) {
+
+ /* eslint-disable */
+
+ var _require = __webpack_require__(206);
+
+ var connect = _require.connect;
+
+ var defer = __webpack_require__(144);
+
+ var _require2 = __webpack_require__(145);
+
+ var Tab = _require2.Tab;
+
+ var _require3 = __webpack_require__(89);
+
+ var isEnabled = _require3.isEnabled;
+ var getValue = _require3.getValue;
+
+ var networkRequest = __webpack_require__(207);
+
+ var _require4 = __webpack_require__(208);
+
+ var setupCommands = _require4.setupCommands;
+ var clientCommands = _require4.clientCommands;
+
+ var _require5 = __webpack_require__(209);
+
+ var setupEvents = _require5.setupEvents;
+ var clientEvents = _require5.clientEvents;
+ var pageEvents = _require5.pageEvents;
+
+ // TODO: figure out a way to avoid patching native prototypes.
+ // Unfortunately the Chrome client requires it to work.
+
+ Array.prototype.peekLast = function () {
+ return this[this.length - 1];
+ };
+
+ var connection = void 0;
+
+ function createTabs(tabs) {
+
+ return tabs.filter(tab => {
+ var isPage = tab.type == "page";
+ return isPage;
+ }).map(tab => {
+ return Tab({
+ title: tab.title,
+ url: tab.url,
+ id: tab.id,
+ tab,
+ browser: "chrome"
+ });
+ });
+ }
+
+ function connectClient() {
+ var deferred = defer();
+
+ if (!getValue("chrome.debug")) {
+ return deferred.resolve(createTabs([]));
+ }
+
+ var webSocketPort = getValue("chrome.webSocketPort");
+ var url = `http://localhost:${ webSocketPort }/json/list`;
+ networkRequest(url).then(res => {
+ deferred.resolve(createTabs(JSON.parse(res.content)));
+ }).catch(err => {
+ console.log(err);
+ deferred.reject();
+ });
+
+ return deferred.promise;
+ }
+
+ function connectTab(tab) {
+ return connect(tab.webSocketDebuggerUrl).then(conn => {
+ connection = conn;
+ });
+ }
+
+ function connectNode(url) {
+ return connect(url).then(conn => {
+ connection = conn;
+ });
+ }
+
+ function initPage(actions) {
+ var agents = connection._agents;
+
+ setupCommands({ agents: agents });
+ setupEvents({ actions, agents });
+
+ agents.Debugger.enable();
+ agents.Debugger.setPauseOnExceptions("none");
+ agents.Debugger.setAsyncCallStackDepth(0);
+
+ agents.Runtime.enable();
+ agents.Runtime.run();
+
+ agents.Page.enable();
+
+ connection.registerDispatcher("Debugger", clientEvents);
+ connection.registerDispatcher("Page", pageEvents);
+ }
+
+ module.exports = {
+ connectClient,
+ clientCommands,
+ connectNode,
+ connectTab,
+ initPage
+ };
+
+/***/ },
+/* 206 */
+/***/ function(module, exports) {
+
+ module.exports = {};
+
+/***/ },
+/* 207 */
+/***/ function(module, exports) {
+
+ function networkRequest(url, opts) {
+ return new Promise((resolve, reject) => {
+ var req = new XMLHttpRequest();
+
+ req.addEventListener("readystatechange", () => {
+ if (req.readyState === XMLHttpRequest.DONE) {
+ if (req.status === 200) {
+ resolve({ content: req.responseText });
+ } else {
+ resolve(req.statusText);
+ }
+ }
+ });
+
+ // Not working yet.
+ // if (!opts.loadFromCache) {
+ // req.channel.loadFlags = (
+ // Components.interfaces.nsIRequest.LOAD_BYPASS_CACHE |
+ // Components.interfaces.nsIRequest.INHIBIT_CACHING |
+ // Components.interfaces.nsIRequest.LOAD_ANONYMOUS
+ // );
+ // }
+
+ req.open("GET", url);
+ req.send();
+ });
+ }
+
+ module.exports = networkRequest;
+
+/***/ },
+/* 208 */
+/***/ function(module, exports, __webpack_require__) {
+
+ var _require = __webpack_require__(145);
+
+ var BreakpointResult = _require.BreakpointResult;
+ var Location = _require.Location;
+
+
+ var debuggerAgent = void 0;
+ var runtimeAgent = void 0;
+ var pageAgent = void 0;
+
+ function setupCommands(_ref) {
+ var agents = _ref.agents;
+
+ debuggerAgent = agents.Debugger;
+ runtimeAgent = agents.Runtime;
+ pageAgent = agents.Page;
+ }
+
+ function resume() {
+ return debuggerAgent.resume();
+ }
+
+ function stepIn() {
+ return debuggerAgent.stepInto();
+ }
+
+ function stepOver() {
+ return debuggerAgent.stepOver();
+ }
+
+ function stepOut() {
+ return debuggerAgent.stepOut();
+ }
+
+ function pauseOnExceptions(toggle) {
+ var state = toggle ? "uncaught" : "none";
+ return debuggerAgent.setPauseOnExceptions(state);
+ }
+
+ function breakOnNext() {
+ return debuggerAgent.pause();
+ }
+
+ function sourceContents(sourceId) {
+ return debuggerAgent.getScriptSource(sourceId, (err, contents) => ({
+ source: contents,
+ contentType: null
+ }));
+ }
+
+ function setBreakpoint(location, condition) {
+ return new Promise((resolve, reject) => {
+ return debuggerAgent.setBreakpoint({
+ scriptId: location.sourceId,
+ lineNumber: location.line - 1,
+ columnNumber: location.column
+ }, (err, breakpointId, actualLocation) => {
+ if (err) {
+ reject(err);
+ return;
+ }
+
+ actualLocation = actualLocation ? {
+ sourceId: actualLocation.scriptId,
+ line: actualLocation.lineNumber + 1,
+ column: actualLocation.columnNumber
+ } : location;
+
+ resolve(BreakpointResult({
+ id: breakpointId,
+ actualLocation: Location(actualLocation)
+ }));
+ });
+ });
+ }
+
+ function removeBreakpoint(breakpointId) {
+ // TODO: resolve promise when request is completed.
+ return new Promise((resolve, reject) => {
+ resolve(debuggerAgent.removeBreakpoint(breakpointId));
+ });
+ }
+
+ function evaluate(script) {
+ return runtimeAgent.evaluate(script, (_, result) => {
+ return result;
+ });
+ }
+
+ function debuggeeCommand(script) {
+ evaluate(script);
+ return Promise.resolve();
+ }
+
+ function navigate(url) {
+ return pageAgent.navigate(url, (_, result) => {
+ return result;
+ });
+ }
+
+ var clientCommands = {
+ resume,
+ stepIn,
+ stepOut,
+ stepOver,
+ pauseOnExceptions,
+ breakOnNext,
+ sourceContents,
+ setBreakpoint,
+ removeBreakpoint,
+ evaluate,
+ debuggeeCommand,
+ navigate
+ };
+
+ module.exports = {
+ setupCommands,
+ clientCommands
+ };
+
+/***/ },
+/* 209 */
+/***/ function(module, exports, __webpack_require__) {
+
+ var paused = (() => {
+ var _ref = _asyncToGenerator(function* (callFrames, reason, data, hitBreakpoints, asyncStackTrace) {
+ var frames = callFrames.map(function (frame) {
+ return Frame({
+ id: frame.callFrameId,
+ displayName: frame.functionName,
+ location: Location({
+ sourceId: frame.location.scriptId,
+ line: frame.location.lineNumber + 1,
+ column: frame.location.columnNumber
+ })
+ });
+ });
+
+ var frame = frames[0];
+ var why = Object.assign({}, {
+ type: reason
+ }, data);
+
+ pageAgent.setOverlayMessage("Paused in debugger.html");
+
+ yield actions.paused({ frame, why, frames });
+ });
+
+ return function paused(_x, _x2, _x3, _x4, _x5) {
+ return _ref.apply(this, arguments);
+ };
+ })();
+
+ function _asyncToGenerator(fn) { return function () { var gen = fn.apply(this, arguments); return new Promise(function (resolve, reject) { function step(key, arg) { try { var info = gen[key](arg); var value = info.value; } catch (error) { reject(error); return; } if (info.done) { resolve(value); } else { return Promise.resolve(value).then(function (value) { step("next", value); }, function (err) { step("throw", err); }); } } return step("next"); }); }; }
+
+ var _require = __webpack_require__(145);
+
+ var Source = _require.Source;
+ var Location = _require.Location;
+ var Frame = _require.Frame;
+
+
+ var actions = void 0;
+ var pageAgent = void 0;
+
+ function setupEvents(dependencies) {
+ actions = dependencies.actions;
+ pageAgent = dependencies.agents.Page;
+ }
+
+ // Debugger Events
+ function scriptParsed(scriptId, url, startLine, startColumn, endLine, endColumn, executionContextId, hash, isContentScript, isInternalScript, isLiveEdit, sourceMapURL, hasSourceURL, deprecatedCommentWasUsed) {
+ if (isContentScript) {
+ return;
+ }
+
+ actions.newSource(Source({
+ id: scriptId,
+ url,
+ sourceMapURL,
+ isPrettyPrinted: false
+ }));
+ }
+
+ function scriptFailedToParse() {}
+
+ function resumed() {
+ pageAgent.setOverlayMessage(undefined);
+ actions.resumed();
+ }
+
+ function globalObjectCleared() {}
+
+ // Page Events
+ function frameNavigated(frame) {
+ actions.navigate();
+ }
+
+ function frameStartedLoading() {
+ actions.willNavigate();
+ }
+
+ function domContentEventFired() {}
+
+ function loadEventFired() {}
+
+ function frameStoppedLoading() {}
+
+ var clientEvents = {
+ scriptParsed,
+ scriptFailedToParse,
+ paused,
+ resumed,
+ globalObjectCleared
+ };
+
+ var pageEvents = {
+ frameNavigated,
+ frameStartedLoading,
+ domContentEventFired,
+ loadEventFired,
+ frameStoppedLoading
+ };
+
+ module.exports = {
+ setupEvents,
+ pageEvents,
+ clientEvents
+ };
+
+/***/ },
+/* 210 */
+/***/ function(module, exports, __webpack_require__) {
+
+ var React = __webpack_require__(2);
+ var dom = React.DOM;
+
+ var classnames = __webpack_require__(211);
+
+ var _require = __webpack_require__(89);
+
+ var getValue = _require.getValue;
+ var isDevelopment = _require.isDevelopment;
+
+
+ __webpack_require__(212);
+
+ function themeClass() {
+ var theme = getValue("theme");
+ return `theme-${ theme }`;
+ }
+
+ module.exports = function (component) {
+ return dom.div({
+ className: classnames("theme-body", { [themeClass()]: isDevelopment() }),
+ style: { flex: 1 }
+ }, React.createElement(component));
+ };
+
+/***/ },
+/* 211 */
+/***/ function(module, exports, __webpack_require__) {
+
+ var __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;/*!
+ Copyright (c) 2016 Jed Watson.
+ Licensed under the MIT License (MIT), see
+ http://jedwatson.github.io/classnames
+ */
+ /* global define */
+
+ (function () {
+ 'use strict';
+
+ var hasOwn = {}.hasOwnProperty;
+
+ function classNames () {
+ var classes = [];
+
+ for (var i = 0; i < arguments.length; i++) {
+ var arg = arguments[i];
+ if (!arg) continue;
+
+ var argType = typeof arg;
+
+ if (argType === 'string' || argType === 'number') {
+ classes.push(arg);
+ } else if (Array.isArray(arg)) {
+ classes.push(classNames.apply(null, arg));
+ } else if (argType === 'object') {
+ for (var key in arg) {
+ if (hasOwn.call(arg, key) && arg[key]) {
+ classes.push(key);
+ }
+ }
+ }
+ }
+
+ return classes.join(' ');
+ }
+
+ if (typeof module !== 'undefined' && module.exports) {
+ module.exports = classNames;
+ } else if (true) {
+ // register as 'classnames', consistent with npm package name
+ !(__WEBPACK_AMD_DEFINE_ARRAY__ = [], __WEBPACK_AMD_DEFINE_RESULT__ = function () {
+ return classNames;
+ }.apply(exports, __WEBPACK_AMD_DEFINE_ARRAY__), __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__));
+ } else {
+ window.classNames = classNames;
+ }
+ }());
+
+
+/***/ },
+/* 212 */
+/***/ function(module, exports) {
+
+ // removed by extract-text-webpack-plugin
+
+/***/ },
+/* 213 */,
+/* 214 */,
+/* 215 */,
+/* 216 */
+/***/ function(module, exports, __webpack_require__) {
+
+
+
+ /* 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/. */
+ /* global window */
+
+ var _require = __webpack_require__(3);
+
+ var createStore = _require.createStore;
+ var applyMiddleware = _require.applyMiddleware;
+
+ var _require2 = __webpack_require__(217);
+
+ var waitUntilService = _require2.waitUntilService;
+
+ var _require3 = __webpack_require__(218);
+
+ var log = _require3.log;
+
+ var _require4 = __webpack_require__(219);
+
+ var history = _require4.history;
+
+ var _require5 = __webpack_require__(220);
+
+ var promise = _require5.promise;
+
+ var _require6 = __webpack_require__(225);
+
+ var thunk = _require6.thunk;
+
+
+ /**
+ * This creates a dispatcher with all the standard middleware in place
+ * that all code requires. It can also be optionally configured in
+ * various ways, such as logging and recording.
+ *
+ * @param {object} opts:
+ * - log: log all dispatched actions to console
+ * - history: an array to store every action in. Should only be
+ * used in tests.
+ * - middleware: array of middleware to be included in the redux store
+ */
+ var configureStore = function () {
+ var opts = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
+
+ var middleware = [thunk(opts.makeThunkArgs), promise,
+
+ // Order is important: services must go last as they always
+ // operate on "already transformed" actions. Actions going through
+ // them shouldn't have any special fields like promises, they
+ // should just be normal JSON objects.
+ waitUntilService];
+
+ if (opts.history) {
+ middleware.push(history(opts.history));
+ }
+
+ if (opts.middleware) {
+ opts.middleware.forEach(fn => middleware.push(fn));
+ }
+
+ if (opts.log) {
+ middleware.push(log);
+ }
+
+ // Hook in the redux devtools browser extension if it exists
+ var devtoolsExt = typeof window === "object" && window.devToolsExtension ? window.devToolsExtension() : f => f;
+
+ return applyMiddleware.apply(undefined, middleware)(devtoolsExt(createStore));
+ };
+
+ module.exports = configureStore;
+
+/***/ },
+/* 217 */
+/***/ function(module, exports) {
+
+ /* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+ "use strict";
+
+ /**
+ * A middleware which acts like a service, because it is stateful
+ * and "long-running" in the background. It provides the ability
+ * for actions to install a function to be run once when a specific
+ * condition is met by an action coming through the system. Think of
+ * it as a thunk that blocks until the condition is met. Example:
+ *
+ * ```js
+ * const services = { WAIT_UNTIL: require('wait-service').NAME };
+ *
+ * { type: services.WAIT_UNTIL,
+ * predicate: action => action.type === constants.ADD_ITEM,
+ * run: (dispatch, getState, action) => {
+ * // Do anything here. You only need to accept the arguments
+ * // if you need them. `action` is the action that satisfied
+ * // the predicate.
+ * }
+ * }
+ * ```
+ */
+
+ var NAME = exports.NAME = "@@service/waitUntil";
+
+ function waitUntilService(_ref) {
+ var dispatch = _ref.dispatch;
+ var getState = _ref.getState;
+
+ var pending = [];
+
+ function checkPending(action) {
+ var readyRequests = [];
+ var stillPending = [];
+
+ // Find the pending requests whose predicates are satisfied with
+ // this action. Wait to run the requests until after we update the
+ // pending queue because the request handler may synchronously
+ // dispatch again and run this service (that use case is
+ // completely valid).
+ for (var request of pending) {
+ if (request.predicate(action)) {
+ readyRequests.push(request);
+ } else {
+ stillPending.push(request);
+ }
+ }
+
+ pending = stillPending;
+ for (var _request of readyRequests) {
+ _request.run(dispatch, getState, action);
+ }
+ }
+
+ return next => action => {
+ if (action.type === NAME) {
+ pending.push(action);
+ return null;
+ }
+ var result = next(action);
+ checkPending(action);
+ return result;
+ };
+ }
+ exports.waitUntilService = waitUntilService;
+
+/***/ },
+/* 218 */
+/***/ function(module, exports) {
+
+ /**
+ * A middleware that logs all actions coming through the system
+ * to the console.
+ */
+ function log(_ref) {
+ var dispatch = _ref.dispatch;
+ var getState = _ref.getState;
+
+ return next => action => {
+ var actionText = JSON.stringify(action, null, 2);
+ var truncatedActionText = actionText.slice(0, 1000) + "...";
+ console.log(`[DISPATCH ${ action.type }]`, action, truncatedActionText);
+ next(action);
+ };
+ }
+
+ exports.log = log;
+
+/***/ },
+/* 219 */
+/***/ function(module, exports, __webpack_require__) {
+
+ /* 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/. */
+
+ var _require = __webpack_require__(89);
+
+ var isDevelopment = _require.isDevelopment;
+
+ /**
+ * A middleware that stores every action coming through the store in the passed
+ * in logging object. Should only be used for tests, as it collects all
+ * action information, which will cause memory bloat.
+ */
+
+ exports.history = function () {
+ var log = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : [];
+ return (_ref) => {
+ var dispatch = _ref.dispatch;
+ var getState = _ref.getState;
+
+ if (isDevelopment()) {
+ console.warn("Using history middleware stores all actions in state for " + "testing and devtools is not currently running in test " + "mode. Be sure this is intentional.");
+ }
+ return next => action => {
+ log.push(action);
+ next(action);
+ };
+ };
+ };
+
+/***/ },
+/* 220 */
+/***/ function(module, exports, __webpack_require__) {
+
+ /* 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/. */
+
+ var defer = __webpack_require__(144);
+
+ var _require = __webpack_require__(221);
+
+ var entries = _require.entries;
+ var toObject = _require.toObject;
+
+ var _require2 = __webpack_require__(223);
+
+ var executeSoon = _require2.executeSoon;
+
+
+ var PROMISE = exports.PROMISE = "@@dispatch/promise";
+ var seqIdVal = 1;
+
+ function seqIdGen() {
+ return seqIdVal++;
+ }
+
+ function promiseMiddleware(_ref) {
+ var dispatch = _ref.dispatch;
+ var getState = _ref.getState;
+
+ return next => action => {
+ if (!(PROMISE in action)) {
+ return next(action);
+ }
+
+ var promiseInst = action[PROMISE];
+ var seqId = seqIdGen().toString();
+
+ // Create a new action that doesn't have the promise field and has
+ // the `seqId` field that represents the sequence id
+ action = Object.assign(toObject(entries(action).filter(pair => pair[0] !== PROMISE)), { seqId });
+
+ dispatch(Object.assign({}, action, { status: "start" }));
+
+ // Return the promise so action creators can still compose if they
+ // want to.
+ var deferred = defer();
+ promiseInst.then(value => {
+ executeSoon(() => {
+ dispatch(Object.assign({}, action, {
+ status: "done",
+ value: value
+ }));
+ deferred.resolve(value);
+ });
+ }, error => {
+ executeSoon(() => {
+ dispatch(Object.assign({}, action, {
+ status: "error",
+ error: error.message || error
+ }));
+ deferred.reject(error);
+ });
+ });
+ return deferred.promise;
+ };
+ }
+
+ exports.promise = promiseMiddleware;
+
+/***/ },
+/* 221 */
+/***/ function(module, exports, __webpack_require__) {
+
+ var _slicedToArray = function () { function sliceIterator(arr, i) { var _arr = []; var _n = true; var _d = false; var _e = undefined; try { for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i["return"]) _i["return"](); } finally { if (_d) throw _e; } } return _arr; } return function (arr, i) { if (Array.isArray(arr)) { return arr; } else if (Symbol.iterator in Object(arr)) { return sliceIterator(arr, i); } else { throw new TypeError("Invalid attempt to destructure non-iterable instance"); } }; }();
+
+ function _toConsumableArray(arr) { if (Array.isArray(arr)) { for (var i = 0, arr2 = Array(arr.length); i < arr.length; i++) { arr2[i] = arr[i]; } return arr2; } else { return Array.from(arr); } }
+
+ /* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+ /* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+ /* 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/. */
+
+ var co = __webpack_require__(222);
+
+ function asPaused(client, func) {
+ if (client.state != "paused") {
+ return co(function* () {
+ yield client.interrupt();
+ var result = void 0;
+
+ try {
+ result = yield func();
+ } catch (e) {
+ // Try to put the debugger back in a working state by resuming
+ // it
+ yield client.resume();
+ throw e;
+ }
+
+ yield client.resume();
+ return result;
+ });
+ }
+ return func();
+ }
+
+ function handleError(err) {
+ console.log("ERROR: ", err);
+ }
+
+ function promisify(context, method) {
+ for (var _len = arguments.length, args = Array(_len > 2 ? _len - 2 : 0), _key = 2; _key < _len; _key++) {
+ args[_key - 2] = arguments[_key];
+ }
+
+ return new Promise((resolve, reject) => {
+ args.push(response => {
+ if (response.error) {
+ reject(response);
+ } else {
+ resolve(response);
+ }
+ });
+ method.apply(context, args);
+ });
+ }
+
+ function truncateStr(str, size) {
+ if (str.length > size) {
+ return str.slice(0, size) + "...";
+ }
+ return str;
+ }
+
+ function endTruncateStr(str, size) {
+ if (str.length > size) {
+ return "..." + str.slice(str.length - size);
+ }
+ return str;
+ }
+
+ var msgId = 1;
+ function workerTask(worker, method) {
+ return function () {
+ for (var _len2 = arguments.length, args = Array(_len2), _key2 = 0; _key2 < _len2; _key2++) {
+ args[_key2] = arguments[_key2];
+ }
+
+ return new Promise((resolve, reject) => {
+ var id = msgId++;
+ worker.postMessage({ id, method, args });
+
+ var listener = (_ref) => {
+ var result = _ref.data;
+
+ if (result.id !== id) {
+ return;
+ }
+
+ worker.removeEventListener("message", listener);
+ if (result.error) {
+ reject(result.error);
+ } else {
+ resolve(result.response);
+ }
+ };
+
+ worker.addEventListener("message", listener);
+ });
+ };
+ }
+
+ /**
+ * Interleaves two arrays element by element, returning the combined array, like
+ * a zip. In the case of arrays with different sizes, undefined values will be
+ * interleaved at the end along with the extra values of the larger array.
+ *
+ * @param Array a
+ * @param Array b
+ * @returns Array
+ * The combined array, in the form [a1, b1, a2, b2, ...]
+ */
+ function zip(a, b) {
+ if (!b) {
+ return a;
+ }
+ if (!a) {
+ return b;
+ }
+ var pairs = [];
+ for (var i = 0, aLength = a.length, bLength = b.length; i < aLength || i < bLength; i++) {
+ pairs.push([a[i], b[i]]);
+ }
+ return pairs;
+ }
+
+ /**
+ * Converts an object into an array with 2-element arrays as key/value
+ * pairs of the object. `{ foo: 1, bar: 2}` would become
+ * `[[foo, 1], [bar 2]]` (order not guaranteed);
+ *
+ * @param object obj
+ * @returns array
+ */
+ function entries(obj) {
+ return Object.keys(obj).map(k => [k, obj[k]]);
+ }
+
+ function mapObject(obj, iteratee) {
+ return toObject(entries(obj).map((_ref2) => {
+ var _ref3 = _slicedToArray(_ref2, 2);
+
+ var key = _ref3[0];
+ var value = _ref3[1];
+
+ return [key, iteratee(key, value)];
+ }));
+ }
+
+ /**
+ * Takes an array of 2-element arrays as key/values pairs and
+ * constructs an object using them.
+ */
+ function toObject(arr) {
+ var obj = {};
+ for (var pair of arr) {
+ obj[pair[0]] = pair[1];
+ }
+ return obj;
+ }
+
+ /**
+ * Composes the given functions into a single function, which will
+ * apply the results of each function right-to-left, starting with
+ * applying the given arguments to the right-most function.
+ * `compose(foo, bar, baz)` === `args => foo(bar(baz(args)`
+ *
+ * @param ...function funcs
+ * @returns function
+ */
+ function compose() {
+ for (var _len3 = arguments.length, funcs = Array(_len3), _key3 = 0; _key3 < _len3; _key3++) {
+ funcs[_key3] = arguments[_key3];
+ }
+
+ return function () {
+ for (var _len4 = arguments.length, args = Array(_len4), _key4 = 0; _key4 < _len4; _key4++) {
+ args[_key4] = arguments[_key4];
+ }
+
+ var initialValue = funcs[funcs.length - 1].apply(null, args);
+ var leftFuncs = funcs.slice(0, -1);
+ return leftFuncs.reduceRight((composed, f) => f(composed), initialValue);
+ };
+ }
+
+ function updateObj(obj, fields) {
+ return Object.assign({}, obj, fields);
+ }
+
+ function throttle(func, ms) {
+ var timeout = void 0,
+ _this = void 0;
+ return function () {
+ for (var _len5 = arguments.length, args = Array(_len5), _key5 = 0; _key5 < _len5; _key5++) {
+ args[_key5] = arguments[_key5];
+ }
+
+ _this = this;
+ if (!timeout) {
+ timeout = setTimeout(() => {
+ func.apply.apply(func, [_this].concat(_toConsumableArray(args)));
+ timeout = null;
+ }, ms);
+ }
+ };
+ }
+
+ module.exports = {
+ asPaused,
+ handleError,
+ promisify,
+ truncateStr,
+ endTruncateStr,
+ workerTask,
+ zip,
+ entries,
+ toObject,
+ mapObject,
+ compose,
+ updateObj,
+ throttle
+ };
+
+/***/ },
+/* 222 */
+/***/ function(module, exports) {
+
+
+ /**
+ * slice() reference.
+ */
+
+ var slice = Array.prototype.slice;
+
+ /**
+ * Expose `co`.
+ */
+
+ module.exports = co['default'] = co.co = co;
+
+ /**
+ * Wrap the given generator `fn` into a
+ * function that returns a promise.
+ * This is a separate function so that
+ * every `co()` call doesn't create a new,
+ * unnecessary closure.
+ *
+ * @param {GeneratorFunction} fn
+ * @return {Function}
+ * @api public
+ */
+
+ co.wrap = function (fn) {
+ createPromise.__generatorFunction__ = fn;
+ return createPromise;
+ function createPromise() {
+ return co.call(this, fn.apply(this, arguments));
+ }
+ };
+
+ /**
+ * Execute the generator function or a generator
+ * and return a promise.
+ *
+ * @param {Function} fn
+ * @return {Promise}
+ * @api public
+ */
+
+ function co(gen) {
+ var ctx = this;
+ var args = slice.call(arguments, 1)
+
+ // we wrap everything in a promise to avoid promise chaining,
+ // which leads to memory leak errors.
+ // see https://github.com/tj/co/issues/180
+ return new Promise(function(resolve, reject) {
+ if (typeof gen === 'function') gen = gen.apply(ctx, args);
+ if (!gen || typeof gen.next !== 'function') return resolve(gen);
+
+ onFulfilled();
+
+ /**
+ * @param {Mixed} res
+ * @return {Promise}
+ * @api private
+ */
+
+ function onFulfilled(res) {
+ var ret;
+ try {
+ ret = gen.next(res);
+ } catch (e) {
+ return reject(e);
+ }
+ next(ret);
+ }
+
+ /**
+ * @param {Error} err
+ * @return {Promise}
+ * @api private
+ */
+
+ function onRejected(err) {
+ var ret;
+ try {
+ ret = gen.throw(err);
+ } catch (e) {
+ return reject(e);
+ }
+ next(ret);
+ }
+
+ /**
+ * Get the next value in the generator,
+ * return a promise.
+ *
+ * @param {Object} ret
+ * @return {Promise}
+ * @api private
+ */
+
+ function next(ret) {
+ if (ret.done) return resolve(ret.value);
+ var value = toPromise.call(ctx, ret.value);
+ if (value && isPromise(value)) return value.then(onFulfilled, onRejected);
+ return onRejected(new TypeError('You may only yield a function, promise, generator, array, or object, '
+ + 'but the following object was passed: "' + String(ret.value) + '"'));
+ }
+ });
+ }
+
+ /**
+ * Convert a `yield`ed value into a promise.
+ *
+ * @param {Mixed} obj
+ * @return {Promise}
+ * @api private
+ */
+
+ function toPromise(obj) {
+ if (!obj) return obj;
+ if (isPromise(obj)) return obj;
+ if (isGeneratorFunction(obj) || isGenerator(obj)) return co.call(this, obj);
+ if ('function' == typeof obj) return thunkToPromise.call(this, obj);
+ if (Array.isArray(obj)) return arrayToPromise.call(this, obj);
+ if (isObject(obj)) return objectToPromise.call(this, obj);
+ return obj;
+ }
+
+ /**
+ * Convert a thunk to a promise.
+ *
+ * @param {Function}
+ * @return {Promise}
+ * @api private
+ */
+
+ function thunkToPromise(fn) {
+ var ctx = this;
+ return new Promise(function (resolve, reject) {
+ fn.call(ctx, function (err, res) {
+ if (err) return reject(err);
+ if (arguments.length > 2) res = slice.call(arguments, 1);
+ resolve(res);
+ });
+ });
+ }
+
+ /**
+ * Convert an array of "yieldables" to a promise.
+ * Uses `Promise.all()` internally.
+ *
+ * @param {Array} obj
+ * @return {Promise}
+ * @api private
+ */
+
+ function arrayToPromise(obj) {
+ return Promise.all(obj.map(toPromise, this));
+ }
+
+ /**
+ * Convert an object of "yieldables" to a promise.
+ * Uses `Promise.all()` internally.
+ *
+ * @param {Object} obj
+ * @return {Promise}
+ * @api private
+ */
+
+ function objectToPromise(obj){
+ var results = new obj.constructor();
+ var keys = Object.keys(obj);
+ var promises = [];
+ for (var i = 0; i < keys.length; i++) {
+ var key = keys[i];
+ var promise = toPromise.call(this, obj[key]);
+ if (promise && isPromise(promise)) defer(promise, key);
+ else results[key] = obj[key];
+ }
+ return Promise.all(promises).then(function () {
+ return results;
+ });
+
+ function defer(promise, key) {
+ // predefine the key in the result
+ results[key] = undefined;
+ promises.push(promise.then(function (res) {
+ results[key] = res;
+ }));
+ }
+ }
+
+ /**
+ * Check if `obj` is a promise.
+ *
+ * @param {Object} obj
+ * @return {Boolean}
+ * @api private
+ */
+
+ function isPromise(obj) {
+ return 'function' == typeof obj.then;
+ }
+
+ /**
+ * Check if `obj` is a generator.
+ *
+ * @param {Mixed} obj
+ * @return {Boolean}
+ * @api private
+ */
+
+ function isGenerator(obj) {
+ return 'function' == typeof obj.next && 'function' == typeof obj.throw;
+ }
+
+ /**
+ * Check if `obj` is a generator function.
+ *
+ * @param {Mixed} obj
+ * @return {Boolean}
+ * @api private
+ */
+ function isGeneratorFunction(obj) {
+ var constructor = obj.constructor;
+ if (!constructor) return false;
+ if ('GeneratorFunction' === constructor.name || 'GeneratorFunction' === constructor.displayName) return true;
+ return isGenerator(constructor.prototype);
+ }
+
+ /**
+ * Check for plain object.
+ *
+ * @param {Mixed} val
+ * @return {Boolean}
+ * @api private
+ */
+
+ function isObject(val) {
+ return Object == val.constructor;
+ }
+
+
+/***/ },
+/* 223 */
+/***/ function(module, exports, __webpack_require__) {
+
+ var assert = __webpack_require__(224);
+
+ function reportException(who, exception) {
+ var msg = who + " threw an exception: ";
+ console.error(msg, exception);
+ }
+
+ function executeSoon(fn) {
+ setTimeout(fn, 0);
+ }
+
+ module.exports = {
+ reportException,
+ executeSoon,
+ assert
+ };
+
+/***/ },
+/* 224 */
+/***/ function(module, exports) {
+
+ function assert(condition, message) {
+ if (!condition) {
+ throw new Error("Assertion failure: " + message);
+ }
+ }
+
+ module.exports = assert;
+
+/***/ },
+/* 225 */
+/***/ function(module, exports) {
+
+
+ /**
+ * A middleware that allows thunks (functions) to be dispatched. If
+ * it's a thunk, it is called with an argument that contains
+ * `dispatch`, `getState`, and any additional args passed in via the
+ * middleware constructure. This allows the action to create multiple
+ * actions (most likely asynchronously).
+ */
+ function thunk(makeArgs) {
+ return (_ref) => {
+ var dispatch = _ref.dispatch;
+ var getState = _ref.getState;
+
+ var args = { dispatch, getState };
+
+ return next => action => {
+ return typeof action === "function" ? action(makeArgs ? makeArgs(args, getState()) : args) : next(action);
+ };
+ };
+ }
+ exports.thunk = thunk;
+
+/***/ },
+/* 226 */
+/***/ function(module, exports, __webpack_require__) {
+
+ var tabs = __webpack_require__(227);
+
+ module.exports = {
+ tabs
+ };
+
+/***/ },
+/* 227 */
+/***/ function(module, exports, __webpack_require__) {
+
+ /* 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/. */
+
+ var constants = __webpack_require__(228);
+ var Immutable = __webpack_require__(229);
+ var fromJS = __webpack_require__(230);
+
+ var initialState = fromJS({
+ tabs: {},
+ selectedTab: null
+ });
+
+ function update() {
+ var state = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : initialState;
+ var action = arguments[1];
+
+ switch (action.type) {
+ case constants.ADD_TABS:
+ var tabs = action.value;
+ if (!tabs) {
+ return state;
+ }
+
+ return state.mergeIn(["tabs"], Immutable.Map(tabs.map(tab => {
+ tab = Object.assign({}, tab, { id: getTabId(tab) });
+ return [tab.id, Immutable.Map(tab)];
+ })));
+ case constants.SELECT_TAB:
+ var tab = state.getIn(["tabs", action.id]);
+ return state.setIn(["selectedTab"], tab);
+ }
+
+ return state;
+ }
+
+ function getTabId(tab) {
+ var id = tab.id;
+ var isFirefox = tab.browser == "firefox";
+
+ // NOTE: we're getting the last part of the actor because
+ // we want to ignore the connection id
+ if (isFirefox) {
+ id = tab.id.split(".").pop();
+ }
+
+ return id;
+ }
+
+ module.exports = update;
+
+/***/ },
+/* 228 */
+/***/ function(module, exports) {
+
+ /* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+ /* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+ /* 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/. */
+
+ exports.ADD_TABS = "ADD_TABS";
+ exports.SELECT_TAB = "SELECT_TAB";
+
+/***/ },
+/* 229 */
+/***/ function(module, exports, __webpack_require__) {
+
+ /**
+ * Copyright (c) 2014-2015, Facebook, Inc.
+ * All rights reserved.
+ *
+ * This source code is licensed under the BSD-style license found in the
+ * LICENSE file in the root directory of this source tree. An additional grant
+ * of patent rights can be found in the PATENTS file in the same directory.
+ */
+
+ (function (global, factory) {
+ true ? module.exports = factory() :
+ typeof define === 'function' && define.amd ? define(factory) :
+ (global.Immutable = factory());
+ }(this, function () { 'use strict';var SLICE$0 = Array.prototype.slice;
+
+ function createClass(ctor, superClass) {
+ if (superClass) {
+ ctor.prototype = Object.create(superClass.prototype);
+ }
+ ctor.prototype.constructor = ctor;
+ }
+
+ function Iterable(value) {
+ return isIterable(value) ? value : Seq(value);
+ }
+
+
+ createClass(KeyedIterable, Iterable);
+ function KeyedIterable(value) {
+ return isKeyed(value) ? value : KeyedSeq(value);
+ }
+
+
+ createClass(IndexedIterable, Iterable);
+ function IndexedIterable(value) {
+ return isIndexed(value) ? value : IndexedSeq(value);
+ }
+
+
+ createClass(SetIterable, Iterable);
+ function SetIterable(value) {
+ return isIterable(value) && !isAssociative(value) ? value : SetSeq(value);
+ }
+
+
+
+ function isIterable(maybeIterable) {
+ return !!(maybeIterable && maybeIterable[IS_ITERABLE_SENTINEL]);
+ }
+
+ function isKeyed(maybeKeyed) {
+ return !!(maybeKeyed && maybeKeyed[IS_KEYED_SENTINEL]);
+ }
+
+ function isIndexed(maybeIndexed) {
+ return !!(maybeIndexed && maybeIndexed[IS_INDEXED_SENTINEL]);
+ }
+
+ function isAssociative(maybeAssociative) {
+ return isKeyed(maybeAssociative) || isIndexed(maybeAssociative);
+ }
+
+ function isOrdered(maybeOrdered) {
+ return !!(maybeOrdered && maybeOrdered[IS_ORDERED_SENTINEL]);
+ }
+
+ Iterable.isIterable = isIterable;
+ Iterable.isKeyed = isKeyed;
+ Iterable.isIndexed = isIndexed;
+ Iterable.isAssociative = isAssociative;
+ Iterable.isOrdered = isOrdered;
+
+ Iterable.Keyed = KeyedIterable;
+ Iterable.Indexed = IndexedIterable;
+ Iterable.Set = SetIterable;
+
+
+ var IS_ITERABLE_SENTINEL = '@@__IMMUTABLE_ITERABLE__@@';
+ var IS_KEYED_SENTINEL = '@@__IMMUTABLE_KEYED__@@';
+ var IS_INDEXED_SENTINEL = '@@__IMMUTABLE_INDEXED__@@';
+ var IS_ORDERED_SENTINEL = '@@__IMMUTABLE_ORDERED__@@';
+
+ // Used for setting prototype methods that IE8 chokes on.
+ var DELETE = 'delete';
+
+ // Constants describing the size of trie nodes.
+ var SHIFT = 5; // Resulted in best performance after ______?
+ var SIZE = 1 << SHIFT;
+ var MASK = SIZE - 1;
+
+ // A consistent shared value representing "not set" which equals nothing other
+ // than itself, and nothing that could be provided externally.
+ var NOT_SET = {};
+
+ // Boolean references, Rough equivalent of `bool &`.
+ var CHANGE_LENGTH = { value: false };
+ var DID_ALTER = { value: false };
+
+ function MakeRef(ref) {
+ ref.value = false;
+ return ref;
+ }
+
+ function SetRef(ref) {
+ ref && (ref.value = true);
+ }
+
+ // A function which returns a value representing an "owner" for transient writes
+ // to tries. The return value will only ever equal itself, and will not equal
+ // the return of any subsequent call of this function.
+ function OwnerID() {}
+
+ // http://jsperf.com/copy-array-inline
+ function arrCopy(arr, offset) {
+ offset = offset || 0;
+ var len = Math.max(0, arr.length - offset);
+ var newArr = new Array(len);
+ for (var ii = 0; ii < len; ii++) {
+ newArr[ii] = arr[ii + offset];
+ }
+ return newArr;
+ }
+
+ function ensureSize(iter) {
+ if (iter.size === undefined) {
+ iter.size = iter.__iterate(returnTrue);
+ }
+ return iter.size;
+ }
+
+ function wrapIndex(iter, index) {
+ // This implements "is array index" which the ECMAString spec defines as:
+ //
+ // A String property name P is an array index if and only if
+ // ToString(ToUint32(P)) is equal to P and ToUint32(P) is not equal
+ // to 2^32−1.
+ //
+ // http://www.ecma-international.org/ecma-262/6.0/#sec-array-exotic-objects
+ if (typeof index !== 'number') {
+ var uint32Index = index >>> 0; // N >>> 0 is shorthand for ToUint32
+ if ('' + uint32Index !== index || uint32Index === 4294967295) {
+ return NaN;
+ }
+ index = uint32Index;
+ }
+ return index < 0 ? ensureSize(iter) + index : index;
+ }
+
+ function returnTrue() {
+ return true;
+ }
+
+ function wholeSlice(begin, end, size) {
+ return (begin === 0 || (size !== undefined && begin <= -size)) &&
+ (end === undefined || (size !== undefined && end >= size));
+ }
+
+ function resolveBegin(begin, size) {
+ return resolveIndex(begin, size, 0);
+ }
+
+ function resolveEnd(end, size) {
+ return resolveIndex(end, size, size);
+ }
+
+ function resolveIndex(index, size, defaultIndex) {
+ return index === undefined ?
+ defaultIndex :
+ index < 0 ?
+ Math.max(0, size + index) :
+ size === undefined ?
+ index :
+ Math.min(size, index);
+ }
+
+ /* global Symbol */
+
+ var ITERATE_KEYS = 0;
+ var ITERATE_VALUES = 1;
+ var ITERATE_ENTRIES = 2;
+
+ var REAL_ITERATOR_SYMBOL = typeof Symbol === 'function' && Symbol.iterator;
+ var FAUX_ITERATOR_SYMBOL = '@@iterator';
+
+ var ITERATOR_SYMBOL = REAL_ITERATOR_SYMBOL || FAUX_ITERATOR_SYMBOL;
+
+
+ function Iterator(next) {
+ this.next = next;
+ }
+
+ Iterator.prototype.toString = function() {
+ return '[Iterator]';
+ };
+
+
+ Iterator.KEYS = ITERATE_KEYS;
+ Iterator.VALUES = ITERATE_VALUES;
+ Iterator.ENTRIES = ITERATE_ENTRIES;
+
+ Iterator.prototype.inspect =
+ Iterator.prototype.toSource = function () { return this.toString(); }
+ Iterator.prototype[ITERATOR_SYMBOL] = function () {
+ return this;
+ };
+
+
+ function iteratorValue(type, k, v, iteratorResult) {
+ var value = type === 0 ? k : type === 1 ? v : [k, v];
+ iteratorResult ? (iteratorResult.value = value) : (iteratorResult = {
+ value: value, done: false
+ });
+ return iteratorResult;
+ }
+
+ function iteratorDone() {
+ return { value: undefined, done: true };
+ }
+
+ function hasIterator(maybeIterable) {
+ return !!getIteratorFn(maybeIterable);
+ }
+
+ function isIterator(maybeIterator) {
+ return maybeIterator && typeof maybeIterator.next === 'function';
+ }
+
+ function getIterator(iterable) {
+ var iteratorFn = getIteratorFn(iterable);
+ return iteratorFn && iteratorFn.call(iterable);
+ }
+
+ function getIteratorFn(iterable) {
+ var iteratorFn = iterable && (
+ (REAL_ITERATOR_SYMBOL && iterable[REAL_ITERATOR_SYMBOL]) ||
+ iterable[FAUX_ITERATOR_SYMBOL]
+ );
+ if (typeof iteratorFn === 'function') {
+ return iteratorFn;
+ }
+ }
+
+ function isArrayLike(value) {
+ return value && typeof value.length === 'number';
+ }
+
+ createClass(Seq, Iterable);
+ function Seq(value) {
+ return value === null || value === undefined ? emptySequence() :
+ isIterable(value) ? value.toSeq() : seqFromValue(value);
+ }
+
+ Seq.of = function(/*...values*/) {
+ return Seq(arguments);
+ };
+
+ Seq.prototype.toSeq = function() {
+ return this;
+ };
+
+ Seq.prototype.toString = function() {
+ return this.__toString('Seq {', '}');
+ };
+
+ Seq.prototype.cacheResult = function() {
+ if (!this._cache && this.__iterateUncached) {
+ this._cache = this.entrySeq().toArray();
+ this.size = this._cache.length;
+ }
+ return this;
+ };
+
+ // abstract __iterateUncached(fn, reverse)
+
+ Seq.prototype.__iterate = function(fn, reverse) {
+ return seqIterate(this, fn, reverse, true);
+ };
+
+ // abstract __iteratorUncached(type, reverse)
+
+ Seq.prototype.__iterator = function(type, reverse) {
+ return seqIterator(this, type, reverse, true);
+ };
+
+
+
+ createClass(KeyedSeq, Seq);
+ function KeyedSeq(value) {
+ return value === null || value === undefined ?
+ emptySequence().toKeyedSeq() :
+ isIterable(value) ?
+ (isKeyed(value) ? value.toSeq() : value.fromEntrySeq()) :
+ keyedSeqFromValue(value);
+ }
+
+ KeyedSeq.prototype.toKeyedSeq = function() {
+ return this;
+ };
+
+
+
+ createClass(IndexedSeq, Seq);
+ function IndexedSeq(value) {
+ return value === null || value === undefined ? emptySequence() :
+ !isIterable(value) ? indexedSeqFromValue(value) :
+ isKeyed(value) ? value.entrySeq() : value.toIndexedSeq();
+ }
+
+ IndexedSeq.of = function(/*...values*/) {
+ return IndexedSeq(arguments);
+ };
+
+ IndexedSeq.prototype.toIndexedSeq = function() {
+ return this;
+ };
+
+ IndexedSeq.prototype.toString = function() {
+ return this.__toString('Seq [', ']');
+ };
+
+ IndexedSeq.prototype.__iterate = function(fn, reverse) {
+ return seqIterate(this, fn, reverse, false);
+ };
+
+ IndexedSeq.prototype.__iterator = function(type, reverse) {
+ return seqIterator(this, type, reverse, false);
+ };
+
+
+
+ createClass(SetSeq, Seq);
+ function SetSeq(value) {
+ return (
+ value === null || value === undefined ? emptySequence() :
+ !isIterable(value) ? indexedSeqFromValue(value) :
+ isKeyed(value) ? value.entrySeq() : value
+ ).toSetSeq();
+ }
+
+ SetSeq.of = function(/*...values*/) {
+ return SetSeq(arguments);
+ };
+
+ SetSeq.prototype.toSetSeq = function() {
+ return this;
+ };
+
+
+
+ Seq.isSeq = isSeq;
+ Seq.Keyed = KeyedSeq;
+ Seq.Set = SetSeq;
+ Seq.Indexed = IndexedSeq;
+
+ var IS_SEQ_SENTINEL = '@@__IMMUTABLE_SEQ__@@';
+
+ Seq.prototype[IS_SEQ_SENTINEL] = true;
+
+
+
+ createClass(ArraySeq, IndexedSeq);
+ function ArraySeq(array) {
+ this._array = array;
+ this.size = array.length;
+ }
+
+ ArraySeq.prototype.get = function(index, notSetValue) {
+ return this.has(index) ? this._array[wrapIndex(this, index)] : notSetValue;
+ };
+
+ ArraySeq.prototype.__iterate = function(fn, reverse) {
+ var array = this._array;
+ var maxIndex = array.length - 1;
+ for (var ii = 0; ii <= maxIndex; ii++) {
+ if (fn(array[reverse ? maxIndex - ii : ii], ii, this) === false) {
+ return ii + 1;
+ }
+ }
+ return ii;
+ };
+
+ ArraySeq.prototype.__iterator = function(type, reverse) {
+ var array = this._array;
+ var maxIndex = array.length - 1;
+ var ii = 0;
+ return new Iterator(function()
+ {return ii > maxIndex ?
+ iteratorDone() :
+ iteratorValue(type, ii, array[reverse ? maxIndex - ii++ : ii++])}
+ );
+ };
+
+
+
+ createClass(ObjectSeq, KeyedSeq);
+ function ObjectSeq(object) {
+ var keys = Object.keys(object);
+ this._object = object;
+ this._keys = keys;
+ this.size = keys.length;
+ }
+
+ ObjectSeq.prototype.get = function(key, notSetValue) {
+ if (notSetValue !== undefined && !this.has(key)) {
+ return notSetValue;
+ }
+ return this._object[key];
+ };
+
+ ObjectSeq.prototype.has = function(key) {
+ return this._object.hasOwnProperty(key);
+ };
+
+ ObjectSeq.prototype.__iterate = function(fn, reverse) {
+ var object = this._object;
+ var keys = this._keys;
+ var maxIndex = keys.length - 1;
+ for (var ii = 0; ii <= maxIndex; ii++) {
+ var key = keys[reverse ? maxIndex - ii : ii];
+ if (fn(object[key], key, this) === false) {
+ return ii + 1;
+ }
+ }
+ return ii;
+ };
+
+ ObjectSeq.prototype.__iterator = function(type, reverse) {
+ var object = this._object;
+ var keys = this._keys;
+ var maxIndex = keys.length - 1;
+ var ii = 0;
+ return new Iterator(function() {
+ var key = keys[reverse ? maxIndex - ii : ii];
+ return ii++ > maxIndex ?
+ iteratorDone() :
+ iteratorValue(type, key, object[key]);
+ });
+ };
+
+ ObjectSeq.prototype[IS_ORDERED_SENTINEL] = true;
+
+
+ createClass(IterableSeq, IndexedSeq);
+ function IterableSeq(iterable) {
+ this._iterable = iterable;
+ this.size = iterable.length || iterable.size;
+ }
+
+ IterableSeq.prototype.__iterateUncached = function(fn, reverse) {
+ if (reverse) {
+ return this.cacheResult().__iterate(fn, reverse);
+ }
+ var iterable = this._iterable;
+ var iterator = getIterator(iterable);
+ var iterations = 0;
+ if (isIterator(iterator)) {
+ var step;
+ while (!(step = iterator.next()).done) {
+ if (fn(step.value, iterations++, this) === false) {
+ break;
+ }
+ }
+ }
+ return iterations;
+ };
+
+ IterableSeq.prototype.__iteratorUncached = function(type, reverse) {
+ if (reverse) {
+ return this.cacheResult().__iterator(type, reverse);
+ }
+ var iterable = this._iterable;
+ var iterator = getIterator(iterable);
+ if (!isIterator(iterator)) {
+ return new Iterator(iteratorDone);
+ }
+ var iterations = 0;
+ return new Iterator(function() {
+ var step = iterator.next();
+ return step.done ? step : iteratorValue(type, iterations++, step.value);
+ });
+ };
+
+
+
+ createClass(IteratorSeq, IndexedSeq);
+ function IteratorSeq(iterator) {
+ this._iterator = iterator;
+ this._iteratorCache = [];
+ }
+
+ IteratorSeq.prototype.__iterateUncached = function(fn, reverse) {
+ if (reverse) {
+ return this.cacheResult().__iterate(fn, reverse);
+ }
+ var iterator = this._iterator;
+ var cache = this._iteratorCache;
+ var iterations = 0;
+ while (iterations < cache.length) {
+ if (fn(cache[iterations], iterations++, this) === false) {
+ return iterations;
+ }
+ }
+ var step;
+ while (!(step = iterator.next()).done) {
+ var val = step.value;
+ cache[iterations] = val;
+ if (fn(val, iterations++, this) === false) {
+ break;
+ }
+ }
+ return iterations;
+ };
+
+ IteratorSeq.prototype.__iteratorUncached = function(type, reverse) {
+ if (reverse) {
+ return this.cacheResult().__iterator(type, reverse);
+ }
+ var iterator = this._iterator;
+ var cache = this._iteratorCache;
+ var iterations = 0;
+ return new Iterator(function() {
+ if (iterations >= cache.length) {
+ var step = iterator.next();
+ if (step.done) {
+ return step;
+ }
+ cache[iterations] = step.value;
+ }
+ return iteratorValue(type, iterations, cache[iterations++]);
+ });
+ };
+
+
+
+
+ // # pragma Helper functions
+
+ function isSeq(maybeSeq) {
+ return !!(maybeSeq && maybeSeq[IS_SEQ_SENTINEL]);
+ }
+
+ var EMPTY_SEQ;
+
+ function emptySequence() {
+ return EMPTY_SEQ || (EMPTY_SEQ = new ArraySeq([]));
+ }
+
+ function keyedSeqFromValue(value) {
+ var seq =
+ Array.isArray(value) ? new ArraySeq(value).fromEntrySeq() :
+ isIterator(value) ? new IteratorSeq(value).fromEntrySeq() :
+ hasIterator(value) ? new IterableSeq(value).fromEntrySeq() :
+ typeof value === 'object' ? new ObjectSeq(value) :
+ undefined;
+ if (!seq) {
+ throw new TypeError(
+ 'Expected Array or iterable object of [k, v] entries, '+
+ 'or keyed object: ' + value
+ );
+ }
+ return seq;
+ }
+
+ function indexedSeqFromValue(value) {
+ var seq = maybeIndexedSeqFromValue(value);
+ if (!seq) {
+ throw new TypeError(
+ 'Expected Array or iterable object of values: ' + value
+ );
+ }
+ return seq;
+ }
+
+ function seqFromValue(value) {
+ var seq = maybeIndexedSeqFromValue(value) ||
+ (typeof value === 'object' && new ObjectSeq(value));
+ if (!seq) {
+ throw new TypeError(
+ 'Expected Array or iterable object of values, or keyed object: ' + value
+ );
+ }
+ return seq;
+ }
+
+ function maybeIndexedSeqFromValue(value) {
+ return (
+ isArrayLike(value) ? new ArraySeq(value) :
+ isIterator(value) ? new IteratorSeq(value) :
+ hasIterator(value) ? new IterableSeq(value) :
+ undefined
+ );
+ }
+
+ function seqIterate(seq, fn, reverse, useKeys) {
+ var cache = seq._cache;
+ if (cache) {
+ var maxIndex = cache.length - 1;
+ for (var ii = 0; ii <= maxIndex; ii++) {
+ var entry = cache[reverse ? maxIndex - ii : ii];
+ if (fn(entry[1], useKeys ? entry[0] : ii, seq) === false) {
+ return ii + 1;
+ }
+ }
+ return ii;
+ }
+ return seq.__iterateUncached(fn, reverse);
+ }
+
+ function seqIterator(seq, type, reverse, useKeys) {
+ var cache = seq._cache;
+ if (cache) {
+ var maxIndex = cache.length - 1;
+ var ii = 0;
+ return new Iterator(function() {
+ var entry = cache[reverse ? maxIndex - ii : ii];
+ return ii++ > maxIndex ?
+ iteratorDone() :
+ iteratorValue(type, useKeys ? entry[0] : ii - 1, entry[1]);
+ });
+ }
+ return seq.__iteratorUncached(type, reverse);
+ }
+
+ function fromJS(json, converter) {
+ return converter ?
+ fromJSWith(converter, json, '', {'': json}) :
+ fromJSDefault(json);
+ }
+
+ function fromJSWith(converter, json, key, parentJSON) {
+ if (Array.isArray(json)) {
+ return converter.call(parentJSON, key, IndexedSeq(json).map(function(v, k) {return fromJSWith(converter, v, k, json)}));
+ }
+ if (isPlainObj(json)) {
+ return converter.call(parentJSON, key, KeyedSeq(json).map(function(v, k) {return fromJSWith(converter, v, k, json)}));
+ }
+ return json;
+ }
+
+ function fromJSDefault(json) {
+ if (Array.isArray(json)) {
+ return IndexedSeq(json).map(fromJSDefault).toList();
+ }
+ if (isPlainObj(json)) {
+ return KeyedSeq(json).map(fromJSDefault).toMap();
+ }
+ return json;
+ }
+
+ function isPlainObj(value) {
+ return value && (value.constructor === Object || value.constructor === undefined);
+ }
+
+ /**
+ * An extension of the "same-value" algorithm as [described for use by ES6 Map
+ * and Set](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map#Key_equality)
+ *
+ * NaN is considered the same as NaN, however -0 and 0 are considered the same
+ * value, which is different from the algorithm described by
+ * [`Object.is`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/is).
+ *
+ * This is extended further to allow Objects to describe the values they
+ * represent, by way of `valueOf` or `equals` (and `hashCode`).
+ *
+ * Note: because of this extension, the key equality of Immutable.Map and the
+ * value equality of Immutable.Set will differ from ES6 Map and Set.
+ *
+ * ### Defining custom values
+ *
+ * The easiest way to describe the value an object represents is by implementing
+ * `valueOf`. For example, `Date` represents a value by returning a unix
+ * timestamp for `valueOf`:
+ *
+ * var date1 = new Date(1234567890000); // Fri Feb 13 2009 ...
+ * var date2 = new Date(1234567890000);
+ * date1.valueOf(); // 1234567890000
+ * assert( date1 !== date2 );
+ * assert( Immutable.is( date1, date2 ) );
+ *
+ * Note: overriding `valueOf` may have other implications if you use this object
+ * where JavaScript expects a primitive, such as implicit string coercion.
+ *
+ * For more complex types, especially collections, implementing `valueOf` may
+ * not be performant. An alternative is to implement `equals` and `hashCode`.
+ *
+ * `equals` takes another object, presumably of similar type, and returns true
+ * if the it is equal. Equality is symmetrical, so the same result should be
+ * returned if this and the argument are flipped.
+ *
+ * assert( a.equals(b) === b.equals(a) );
+ *
+ * `hashCode` returns a 32bit integer number representing the object which will
+ * be used to determine how to store the value object in a Map or Set. You must
+ * provide both or neither methods, one must not exist without the other.
+ *
+ * Also, an important relationship between these methods must be upheld: if two
+ * values are equal, they *must* return the same hashCode. If the values are not
+ * equal, they might have the same hashCode; this is called a hash collision,
+ * and while undesirable for performance reasons, it is acceptable.
+ *
+ * if (a.equals(b)) {
+ * assert( a.hashCode() === b.hashCode() );
+ * }
+ *
+ * All Immutable collections implement `equals` and `hashCode`.
+ *
+ */
+ function is(valueA, valueB) {
+ if (valueA === valueB || (valueA !== valueA && valueB !== valueB)) {
+ return true;
+ }
+ if (!valueA || !valueB) {
+ return false;
+ }
+ if (typeof valueA.valueOf === 'function' &&
+ typeof valueB.valueOf === 'function') {
+ valueA = valueA.valueOf();
+ valueB = valueB.valueOf();
+ if (valueA === valueB || (valueA !== valueA && valueB !== valueB)) {
+ return true;
+ }
+ if (!valueA || !valueB) {
+ return false;
+ }
+ }
+ if (typeof valueA.equals === 'function' &&
+ typeof valueB.equals === 'function' &&
+ valueA.equals(valueB)) {
+ return true;
+ }
+ return false;
+ }
+
+ function deepEqual(a, b) {
+ if (a === b) {
+ return true;
+ }
+
+ if (
+ !isIterable(b) ||
+ a.size !== undefined && b.size !== undefined && a.size !== b.size ||
+ a.__hash !== undefined && b.__hash !== undefined && a.__hash !== b.__hash ||
+ isKeyed(a) !== isKeyed(b) ||
+ isIndexed(a) !== isIndexed(b) ||
+ isOrdered(a) !== isOrdered(b)
+ ) {
+ return false;
+ }
+
+ if (a.size === 0 && b.size === 0) {
+ return true;
+ }
+
+ var notAssociative = !isAssociative(a);
+
+ if (isOrdered(a)) {
+ var entries = a.entries();
+ return b.every(function(v, k) {
+ var entry = entries.next().value;
+ return entry && is(entry[1], v) && (notAssociative || is(entry[0], k));
+ }) && entries.next().done;
+ }
+
+ var flipped = false;
+
+ if (a.size === undefined) {
+ if (b.size === undefined) {
+ if (typeof a.cacheResult === 'function') {
+ a.cacheResult();
+ }
+ } else {
+ flipped = true;
+ var _ = a;
+ a = b;
+ b = _;
+ }
+ }
+
+ var allEqual = true;
+ var bSize = b.__iterate(function(v, k) {
+ if (notAssociative ? !a.has(v) :
+ flipped ? !is(v, a.get(k, NOT_SET)) : !is(a.get(k, NOT_SET), v)) {
+ allEqual = false;
+ return false;
+ }
+ });
+
+ return allEqual && a.size === bSize;
+ }
+
+ createClass(Repeat, IndexedSeq);
+
+ function Repeat(value, times) {
+ if (!(this instanceof Repeat)) {
+ return new Repeat(value, times);
+ }
+ this._value = value;
+ this.size = times === undefined ? Infinity : Math.max(0, times);
+ if (this.size === 0) {
+ if (EMPTY_REPEAT) {
+ return EMPTY_REPEAT;
+ }
+ EMPTY_REPEAT = this;
+ }
+ }
+
+ Repeat.prototype.toString = function() {
+ if (this.size === 0) {
+ return 'Repeat []';
+ }
+ return 'Repeat [ ' + this._value + ' ' + this.size + ' times ]';
+ };
+
+ Repeat.prototype.get = function(index, notSetValue) {
+ return this.has(index) ? this._value : notSetValue;
+ };
+
+ Repeat.prototype.includes = function(searchValue) {
+ return is(this._value, searchValue);
+ };
+
+ Repeat.prototype.slice = function(begin, end) {
+ var size = this.size;
+ return wholeSlice(begin, end, size) ? this :
+ new Repeat(this._value, resolveEnd(end, size) - resolveBegin(begin, size));
+ };
+
+ Repeat.prototype.reverse = function() {
+ return this;
+ };
+
+ Repeat.prototype.indexOf = function(searchValue) {
+ if (is(this._value, searchValue)) {
+ return 0;
+ }
+ return -1;
+ };
+
+ Repeat.prototype.lastIndexOf = function(searchValue) {
+ if (is(this._value, searchValue)) {
+ return this.size;
+ }
+ return -1;
+ };
+
+ Repeat.prototype.__iterate = function(fn, reverse) {
+ for (var ii = 0; ii < this.size; ii++) {
+ if (fn(this._value, ii, this) === false) {
+ return ii + 1;
+ }
+ }
+ return ii;
+ };
+
+ Repeat.prototype.__iterator = function(type, reverse) {var this$0 = this;
+ var ii = 0;
+ return new Iterator(function()
+ {return ii < this$0.size ? iteratorValue(type, ii++, this$0._value) : iteratorDone()}
+ );
+ };
+
+ Repeat.prototype.equals = function(other) {
+ return other instanceof Repeat ?
+ is(this._value, other._value) :
+ deepEqual(other);
+ };
+
+
+ var EMPTY_REPEAT;
+
+ function invariant(condition, error) {
+ if (!condition) throw new Error(error);
+ }
+
+ createClass(Range, IndexedSeq);
+
+ function Range(start, end, step) {
+ if (!(this instanceof Range)) {
+ return new Range(start, end, step);
+ }
+ invariant(step !== 0, 'Cannot step a Range by 0');
+ start = start || 0;
+ if (end === undefined) {
+ end = Infinity;
+ }
+ step = step === undefined ? 1 : Math.abs(step);
+ if (end < start) {
+ step = -step;
+ }
+ this._start = start;
+ this._end = end;
+ this._step = step;
+ this.size = Math.max(0, Math.ceil((end - start) / step - 1) + 1);
+ if (this.size === 0) {
+ if (EMPTY_RANGE) {
+ return EMPTY_RANGE;
+ }
+ EMPTY_RANGE = this;
+ }
+ }
+
+ Range.prototype.toString = function() {
+ if (this.size === 0) {
+ return 'Range []';
+ }
+ return 'Range [ ' +
+ this._start + '...' + this._end +
+ (this._step !== 1 ? ' by ' + this._step : '') +
+ ' ]';
+ };
+
+ Range.prototype.get = function(index, notSetValue) {
+ return this.has(index) ?
+ this._start + wrapIndex(this, index) * this._step :
+ notSetValue;
+ };
+
+ Range.prototype.includes = function(searchValue) {
+ var possibleIndex = (searchValue - this._start) / this._step;
+ return possibleIndex >= 0 &&
+ possibleIndex < this.size &&
+ possibleIndex === Math.floor(possibleIndex);
+ };
+
+ Range.prototype.slice = function(begin, end) {
+ if (wholeSlice(begin, end, this.size)) {
+ return this;
+ }
+ begin = resolveBegin(begin, this.size);
+ end = resolveEnd(end, this.size);
+ if (end <= begin) {
+ return new Range(0, 0);
+ }
+ return new Range(this.get(begin, this._end), this.get(end, this._end), this._step);
+ };
+
+ Range.prototype.indexOf = function(searchValue) {
+ var offsetValue = searchValue - this._start;
+ if (offsetValue % this._step === 0) {
+ var index = offsetValue / this._step;
+ if (index >= 0 && index < this.size) {
+ return index
+ }
+ }
+ return -1;
+ };
+
+ Range.prototype.lastIndexOf = function(searchValue) {
+ return this.indexOf(searchValue);
+ };
+
+ Range.prototype.__iterate = function(fn, reverse) {
+ var maxIndex = this.size - 1;
+ var step = this._step;
+ var value = reverse ? this._start + maxIndex * step : this._start;
+ for (var ii = 0; ii <= maxIndex; ii++) {
+ if (fn(value, ii, this) === false) {
+ return ii + 1;
+ }
+ value += reverse ? -step : step;
+ }
+ return ii;
+ };
+
+ Range.prototype.__iterator = function(type, reverse) {
+ var maxIndex = this.size - 1;
+ var step = this._step;
+ var value = reverse ? this._start + maxIndex * step : this._start;
+ var ii = 0;
+ return new Iterator(function() {
+ var v = value;
+ value += reverse ? -step : step;
+ return ii > maxIndex ? iteratorDone() : iteratorValue(type, ii++, v);
+ });
+ };
+
+ Range.prototype.equals = function(other) {
+ return other instanceof Range ?
+ this._start === other._start &&
+ this._end === other._end &&
+ this._step === other._step :
+ deepEqual(this, other);
+ };
+
+
+ var EMPTY_RANGE;
+
+ createClass(Collection, Iterable);
+ function Collection() {
+ throw TypeError('Abstract');
+ }
+
+
+ createClass(KeyedCollection, Collection);function KeyedCollection() {}
+
+ createClass(IndexedCollection, Collection);function IndexedCollection() {}
+
+ createClass(SetCollection, Collection);function SetCollection() {}
+
+
+ Collection.Keyed = KeyedCollection;
+ Collection.Indexed = IndexedCollection;
+ Collection.Set = SetCollection;
+
+ var imul =
+ typeof Math.imul === 'function' && Math.imul(0xffffffff, 2) === -2 ?
+ Math.imul :
+ function imul(a, b) {
+ a = a | 0; // int
+ b = b | 0; // int
+ var c = a & 0xffff;
+ var d = b & 0xffff;
+ // Shift by 0 fixes the sign on the high part.
+ return (c * d) + ((((a >>> 16) * d + c * (b >>> 16)) << 16) >>> 0) | 0; // int
+ };
+
+ // v8 has an optimization for storing 31-bit signed numbers.
+ // Values which have either 00 or 11 as the high order bits qualify.
+ // This function drops the highest order bit in a signed number, maintaining
+ // the sign bit.
+ function smi(i32) {
+ return ((i32 >>> 1) & 0x40000000) | (i32 & 0xBFFFFFFF);
+ }
+
+ function hash(o) {
+ if (o === false || o === null || o === undefined) {
+ return 0;
+ }
+ if (typeof o.valueOf === 'function') {
+ o = o.valueOf();
+ if (o === false || o === null || o === undefined) {
+ return 0;
+ }
+ }
+ if (o === true) {
+ return 1;
+ }
+ var type = typeof o;
+ if (type === 'number') {
+ if (o !== o || o === Infinity) {
+ return 0;
+ }
+ var h = o | 0;
+ if (h !== o) {
+ h ^= o * 0xFFFFFFFF;
+ }
+ while (o > 0xFFFFFFFF) {
+ o /= 0xFFFFFFFF;
+ h ^= o;
+ }
+ return smi(h);
+ }
+ if (type === 'string') {
+ return o.length > STRING_HASH_CACHE_MIN_STRLEN ? cachedHashString(o) : hashString(o);
+ }
+ if (typeof o.hashCode === 'function') {
+ return o.hashCode();
+ }
+ if (type === 'object') {
+ return hashJSObj(o);
+ }
+ if (typeof o.toString === 'function') {
+ return hashString(o.toString());
+ }
+ throw new Error('Value type ' + type + ' cannot be hashed.');
+ }
+
+ function cachedHashString(string) {
+ var hash = stringHashCache[string];
+ if (hash === undefined) {
+ hash = hashString(string);
+ if (STRING_HASH_CACHE_SIZE === STRING_HASH_CACHE_MAX_SIZE) {
+ STRING_HASH_CACHE_SIZE = 0;
+ stringHashCache = {};
+ }
+ STRING_HASH_CACHE_SIZE++;
+ stringHashCache[string] = hash;
+ }
+ return hash;
+ }
+
+ // http://jsperf.com/hashing-strings
+ function hashString(string) {
+ // This is the hash from JVM
+ // The hash code for a string is computed as
+ // s[0] * 31 ^ (n - 1) + s[1] * 31 ^ (n - 2) + ... + s[n - 1],
+ // where s[i] is the ith character of the string and n is the length of
+ // the string. We "mod" the result to make it between 0 (inclusive) and 2^31
+ // (exclusive) by dropping high bits.
+ var hash = 0;
+ for (var ii = 0; ii < string.length; ii++) {
+ hash = 31 * hash + string.charCodeAt(ii) | 0;
+ }
+ return smi(hash);
+ }
+
+ function hashJSObj(obj) {
+ var hash;
+ if (usingWeakMap) {
+ hash = weakMap.get(obj);
+ if (hash !== undefined) {
+ return hash;
+ }
+ }
+
+ hash = obj[UID_HASH_KEY];
+ if (hash !== undefined) {
+ return hash;
+ }
+
+ if (!canDefineProperty) {
+ hash = obj.propertyIsEnumerable && obj.propertyIsEnumerable[UID_HASH_KEY];
+ if (hash !== undefined) {
+ return hash;
+ }
+
+ hash = getIENodeHash(obj);
+ if (hash !== undefined) {
+ return hash;
+ }
+ }
+
+ hash = ++objHashUID;
+ if (objHashUID & 0x40000000) {
+ objHashUID = 0;
+ }
+
+ if (usingWeakMap) {
+ weakMap.set(obj, hash);
+ } else if (isExtensible !== undefined && isExtensible(obj) === false) {
+ throw new Error('Non-extensible objects are not allowed as keys.');
+ } else if (canDefineProperty) {
+ Object.defineProperty(obj, UID_HASH_KEY, {
+ 'enumerable': false,
+ 'configurable': false,
+ 'writable': false,
+ 'value': hash
+ });
+ } else if (obj.propertyIsEnumerable !== undefined &&
+ obj.propertyIsEnumerable === obj.constructor.prototype.propertyIsEnumerable) {
+ // Since we can't define a non-enumerable property on the object
+ // we'll hijack one of the less-used non-enumerable properties to
+ // save our hash on it. Since this is a function it will not show up in
+ // `JSON.stringify` which is what we want.
+ obj.propertyIsEnumerable = function() {
+ return this.constructor.prototype.propertyIsEnumerable.apply(this, arguments);
+ };
+ obj.propertyIsEnumerable[UID_HASH_KEY] = hash;
+ } else if (obj.nodeType !== undefined) {
+ // At this point we couldn't get the IE `uniqueID` to use as a hash
+ // and we couldn't use a non-enumerable property to exploit the
+ // dontEnum bug so we simply add the `UID_HASH_KEY` on the node
+ // itself.
+ obj[UID_HASH_KEY] = hash;
+ } else {
+ throw new Error('Unable to set a non-enumerable property on object.');
+ }
+
+ return hash;
+ }
+
+ // Get references to ES5 object methods.
+ var isExtensible = Object.isExtensible;
+
+ // True if Object.defineProperty works as expected. IE8 fails this test.
+ var canDefineProperty = (function() {
+ try {
+ Object.defineProperty({}, '@', {});
+ return true;
+ } catch (e) {
+ return false;
+ }
+ }());
+
+ // IE has a `uniqueID` property on DOM nodes. We can construct the hash from it
+ // and avoid memory leaks from the IE cloneNode bug.
+ function getIENodeHash(node) {
+ if (node && node.nodeType > 0) {
+ switch (node.nodeType) {
+ case 1: // Element
+ return node.uniqueID;
+ case 9: // Document
+ return node.documentElement && node.documentElement.uniqueID;
+ }
+ }
+ }
+
+ // If possible, use a WeakMap.
+ var usingWeakMap = typeof WeakMap === 'function';
+ var weakMap;
+ if (usingWeakMap) {
+ weakMap = new WeakMap();
+ }
+
+ var objHashUID = 0;
+
+ var UID_HASH_KEY = '__immutablehash__';
+ if (typeof Symbol === 'function') {
+ UID_HASH_KEY = Symbol(UID_HASH_KEY);
+ }
+
+ var STRING_HASH_CACHE_MIN_STRLEN = 16;
+ var STRING_HASH_CACHE_MAX_SIZE = 255;
+ var STRING_HASH_CACHE_SIZE = 0;
+ var stringHashCache = {};
+
+ function assertNotInfinite(size) {
+ invariant(
+ size !== Infinity,
+ 'Cannot perform this action with an infinite size.'
+ );
+ }
+
+ createClass(Map, KeyedCollection);
+
+ // @pragma Construction
+
+ function Map(value) {
+ return value === null || value === undefined ? emptyMap() :
+ isMap(value) && !isOrdered(value) ? value :
+ emptyMap().withMutations(function(map ) {
+ var iter = KeyedIterable(value);
+ assertNotInfinite(iter.size);
+ iter.forEach(function(v, k) {return map.set(k, v)});
+ });
+ }
+
+ Map.of = function() {var keyValues = SLICE$0.call(arguments, 0);
+ return emptyMap().withMutations(function(map ) {
+ for (var i = 0; i < keyValues.length; i += 2) {
+ if (i + 1 >= keyValues.length) {
+ throw new Error('Missing value for key: ' + keyValues[i]);
+ }
+ map.set(keyValues[i], keyValues[i + 1]);
+ }
+ });
+ };
+
+ Map.prototype.toString = function() {
+ return this.__toString('Map {', '}');
+ };
+
+ // @pragma Access
+
+ Map.prototype.get = function(k, notSetValue) {
+ return this._root ?
+ this._root.get(0, undefined, k, notSetValue) :
+ notSetValue;
+ };
+
+ // @pragma Modification
+
+ Map.prototype.set = function(k, v) {
+ return updateMap(this, k, v);
+ };
+
+ Map.prototype.setIn = function(keyPath, v) {
+ return this.updateIn(keyPath, NOT_SET, function() {return v});
+ };
+
+ Map.prototype.remove = function(k) {
+ return updateMap(this, k, NOT_SET);
+ };
+
+ Map.prototype.deleteIn = function(keyPath) {
+ return this.updateIn(keyPath, function() {return NOT_SET});
+ };
+
+ Map.prototype.update = function(k, notSetValue, updater) {
+ return arguments.length === 1 ?
+ k(this) :
+ this.updateIn([k], notSetValue, updater);
+ };
+
+ Map.prototype.updateIn = function(keyPath, notSetValue, updater) {
+ if (!updater) {
+ updater = notSetValue;
+ notSetValue = undefined;
+ }
+ var updatedValue = updateInDeepMap(
+ this,
+ forceIterator(keyPath),
+ notSetValue,
+ updater
+ );
+ return updatedValue === NOT_SET ? undefined : updatedValue;
+ };
+
+ Map.prototype.clear = function() {
+ if (this.size === 0) {
+ return this;
+ }
+ if (this.__ownerID) {
+ this.size = 0;
+ this._root = null;
+ this.__hash = undefined;
+ this.__altered = true;
+ return this;
+ }
+ return emptyMap();
+ };
+
+ // @pragma Composition
+
+ Map.prototype.merge = function(/*...iters*/) {
+ return mergeIntoMapWith(this, undefined, arguments);
+ };
+
+ Map.prototype.mergeWith = function(merger) {var iters = SLICE$0.call(arguments, 1);
+ return mergeIntoMapWith(this, merger, iters);
+ };
+
+ Map.prototype.mergeIn = function(keyPath) {var iters = SLICE$0.call(arguments, 1);
+ return this.updateIn(
+ keyPath,
+ emptyMap(),
+ function(m ) {return typeof m.merge === 'function' ?
+ m.merge.apply(m, iters) :
+ iters[iters.length - 1]}
+ );
+ };
+
+ Map.prototype.mergeDeep = function(/*...iters*/) {
+ return mergeIntoMapWith(this, deepMerger, arguments);
+ };
+
+ Map.prototype.mergeDeepWith = function(merger) {var iters = SLICE$0.call(arguments, 1);
+ return mergeIntoMapWith(this, deepMergerWith(merger), iters);
+ };
+
+ Map.prototype.mergeDeepIn = function(keyPath) {var iters = SLICE$0.call(arguments, 1);
+ return this.updateIn(
+ keyPath,
+ emptyMap(),
+ function(m ) {return typeof m.mergeDeep === 'function' ?
+ m.mergeDeep.apply(m, iters) :
+ iters[iters.length - 1]}
+ );
+ };
+
+ Map.prototype.sort = function(comparator) {
+ // Late binding
+ return OrderedMap(sortFactory(this, comparator));
+ };
+
+ Map.prototype.sortBy = function(mapper, comparator) {
+ // Late binding
+ return OrderedMap(sortFactory(this, comparator, mapper));
+ };
+
+ // @pragma Mutability
+
+ Map.prototype.withMutations = function(fn) {
+ var mutable = this.asMutable();
+ fn(mutable);
+ return mutable.wasAltered() ? mutable.__ensureOwner(this.__ownerID) : this;
+ };
+
+ Map.prototype.asMutable = function() {
+ return this.__ownerID ? this : this.__ensureOwner(new OwnerID());
+ };
+
+ Map.prototype.asImmutable = function() {
+ return this.__ensureOwner();
+ };
+
+ Map.prototype.wasAltered = function() {
+ return this.__altered;
+ };
+
+ Map.prototype.__iterator = function(type, reverse) {
+ return new MapIterator(this, type, reverse);
+ };
+
+ Map.prototype.__iterate = function(fn, reverse) {var this$0 = this;
+ var iterations = 0;
+ this._root && this._root.iterate(function(entry ) {
+ iterations++;
+ return fn(entry[1], entry[0], this$0);
+ }, reverse);
+ return iterations;
+ };
+
+ Map.prototype.__ensureOwner = function(ownerID) {
+ if (ownerID === this.__ownerID) {
+ return this;
+ }
+ if (!ownerID) {
+ this.__ownerID = ownerID;
+ this.__altered = false;
+ return this;
+ }
+ return makeMap(this.size, this._root, ownerID, this.__hash);
+ };
+
+
+ function isMap(maybeMap) {
+ return !!(maybeMap && maybeMap[IS_MAP_SENTINEL]);
+ }
+
+ Map.isMap = isMap;
+
+ var IS_MAP_SENTINEL = '@@__IMMUTABLE_MAP__@@';
+
+ var MapPrototype = Map.prototype;
+ MapPrototype[IS_MAP_SENTINEL] = true;
+ MapPrototype[DELETE] = MapPrototype.remove;
+ MapPrototype.removeIn = MapPrototype.deleteIn;
+
+
+ // #pragma Trie Nodes
+
+
+
+ function ArrayMapNode(ownerID, entries) {
+ this.ownerID = ownerID;
+ this.entries = entries;
+ }
+
+ ArrayMapNode.prototype.get = function(shift, keyHash, key, notSetValue) {
+ var entries = this.entries;
+ for (var ii = 0, len = entries.length; ii < len; ii++) {
+ if (is(key, entries[ii][0])) {
+ return entries[ii][1];
+ }
+ }
+ return notSetValue;
+ };
+
+ ArrayMapNode.prototype.update = function(ownerID, shift, keyHash, key, value, didChangeSize, didAlter) {
+ var removed = value === NOT_SET;
+
+ var entries = this.entries;
+ var idx = 0;
+ for (var len = entries.length; idx < len; idx++) {
+ if (is(key, entries[idx][0])) {
+ break;
+ }
+ }
+ var exists = idx < len;
+
+ if (exists ? entries[idx][1] === value : removed) {
+ return this;
+ }
+
+ SetRef(didAlter);
+ (removed || !exists) && SetRef(didChangeSize);
+
+ if (removed && entries.length === 1) {
+ return; // undefined
+ }
+
+ if (!exists && !removed && entries.length >= MAX_ARRAY_MAP_SIZE) {
+ return createNodes(ownerID, entries, key, value);
+ }
+
+ var isEditable = ownerID && ownerID === this.ownerID;
+ var newEntries = isEditable ? entries : arrCopy(entries);
+
+ if (exists) {
+ if (removed) {
+ idx === len - 1 ? newEntries.pop() : (newEntries[idx] = newEntries.pop());
+ } else {
+ newEntries[idx] = [key, value];
+ }
+ } else {
+ newEntries.push([key, value]);
+ }
+
+ if (isEditable) {
+ this.entries = newEntries;
+ return this;
+ }
+
+ return new ArrayMapNode(ownerID, newEntries);
+ };
+
+
+
+
+ function BitmapIndexedNode(ownerID, bitmap, nodes) {
+ this.ownerID = ownerID;
+ this.bitmap = bitmap;
+ this.nodes = nodes;
+ }
+
+ BitmapIndexedNode.prototype.get = function(shift, keyHash, key, notSetValue) {
+ if (keyHash === undefined) {
+ keyHash = hash(key);
+ }
+ var bit = (1 << ((shift === 0 ? keyHash : keyHash >>> shift) & MASK));
+ var bitmap = this.bitmap;
+ return (bitmap & bit) === 0 ? notSetValue :
+ this.nodes[popCount(bitmap & (bit - 1))].get(shift + SHIFT, keyHash, key, notSetValue);
+ };
+
+ BitmapIndexedNode.prototype.update = function(ownerID, shift, keyHash, key, value, didChangeSize, didAlter) {
+ if (keyHash === undefined) {
+ keyHash = hash(key);
+ }
+ var keyHashFrag = (shift === 0 ? keyHash : keyHash >>> shift) & MASK;
+ var bit = 1 << keyHashFrag;
+ var bitmap = this.bitmap;
+ var exists = (bitmap & bit) !== 0;
+
+ if (!exists && value === NOT_SET) {
+ return this;
+ }
+
+ var idx = popCount(bitmap & (bit - 1));
+ var nodes = this.nodes;
+ var node = exists ? nodes[idx] : undefined;
+ var newNode = updateNode(node, ownerID, shift + SHIFT, keyHash, key, value, didChangeSize, didAlter);
+
+ if (newNode === node) {
+ return this;
+ }
+
+ if (!exists && newNode && nodes.length >= MAX_BITMAP_INDEXED_SIZE) {
+ return expandNodes(ownerID, nodes, bitmap, keyHashFrag, newNode);
+ }
+
+ if (exists && !newNode && nodes.length === 2 && isLeafNode(nodes[idx ^ 1])) {
+ return nodes[idx ^ 1];
+ }
+
+ if (exists && newNode && nodes.length === 1 && isLeafNode(newNode)) {
+ return newNode;
+ }
+
+ var isEditable = ownerID && ownerID === this.ownerID;
+ var newBitmap = exists ? newNode ? bitmap : bitmap ^ bit : bitmap | bit;
+ var newNodes = exists ? newNode ?
+ setIn(nodes, idx, newNode, isEditable) :
+ spliceOut(nodes, idx, isEditable) :
+ spliceIn(nodes, idx, newNode, isEditable);
+
+ if (isEditable) {
+ this.bitmap = newBitmap;
+ this.nodes = newNodes;
+ return this;
+ }
+
+ return new BitmapIndexedNode(ownerID, newBitmap, newNodes);
+ };
+
+
+
+
+ function HashArrayMapNode(ownerID, count, nodes) {
+ this.ownerID = ownerID;
+ this.count = count;
+ this.nodes = nodes;
+ }
+
+ HashArrayMapNode.prototype.get = function(shift, keyHash, key, notSetValue) {
+ if (keyHash === undefined) {
+ keyHash = hash(key);
+ }
+ var idx = (shift === 0 ? keyHash : keyHash >>> shift) & MASK;
+ var node = this.nodes[idx];
+ return node ? node.get(shift + SHIFT, keyHash, key, notSetValue) : notSetValue;
+ };
+
+ HashArrayMapNode.prototype.update = function(ownerID, shift, keyHash, key, value, didChangeSize, didAlter) {
+ if (keyHash === undefined) {
+ keyHash = hash(key);
+ }
+ var idx = (shift === 0 ? keyHash : keyHash >>> shift) & MASK;
+ var removed = value === NOT_SET;
+ var nodes = this.nodes;
+ var node = nodes[idx];
+
+ if (removed && !node) {
+ return this;
+ }
+
+ var newNode = updateNode(node, ownerID, shift + SHIFT, keyHash, key, value, didChangeSize, didAlter);
+ if (newNode === node) {
+ return this;
+ }
+
+ var newCount = this.count;
+ if (!node) {
+ newCount++;
+ } else if (!newNode) {
+ newCount--;
+ if (newCount < MIN_HASH_ARRAY_MAP_SIZE) {
+ return packNodes(ownerID, nodes, newCount, idx);
+ }
+ }
+
+ var isEditable = ownerID && ownerID === this.ownerID;
+ var newNodes = setIn(nodes, idx, newNode, isEditable);
+
+ if (isEditable) {
+ this.count = newCount;
+ this.nodes = newNodes;
+ return this;
+ }
+
+ return new HashArrayMapNode(ownerID, newCount, newNodes);
+ };
+
+
+
+
+ function HashCollisionNode(ownerID, keyHash, entries) {
+ this.ownerID = ownerID;
+ this.keyHash = keyHash;
+ this.entries = entries;
+ }
+
+ HashCollisionNode.prototype.get = function(shift, keyHash, key, notSetValue) {
+ var entries = this.entries;
+ for (var ii = 0, len = entries.length; ii < len; ii++) {
+ if (is(key, entries[ii][0])) {
+ return entries[ii][1];
+ }
+ }
+ return notSetValue;
+ };
+
+ HashCollisionNode.prototype.update = function(ownerID, shift, keyHash, key, value, didChangeSize, didAlter) {
+ if (keyHash === undefined) {
+ keyHash = hash(key);
+ }
+
+ var removed = value === NOT_SET;
+
+ if (keyHash !== this.keyHash) {
+ if (removed) {
+ return this;
+ }
+ SetRef(didAlter);
+ SetRef(didChangeSize);
+ return mergeIntoNode(this, ownerID, shift, keyHash, [key, value]);
+ }
+
+ var entries = this.entries;
+ var idx = 0;
+ for (var len = entries.length; idx < len; idx++) {
+ if (is(key, entries[idx][0])) {
+ break;
+ }
+ }
+ var exists = idx < len;
+
+ if (exists ? entries[idx][1] === value : removed) {
+ return this;
+ }
+
+ SetRef(didAlter);
+ (removed || !exists) && SetRef(didChangeSize);
+
+ if (removed && len === 2) {
+ return new ValueNode(ownerID, this.keyHash, entries[idx ^ 1]);
+ }
+
+ var isEditable = ownerID && ownerID === this.ownerID;
+ var newEntries = isEditable ? entries : arrCopy(entries);
+
+ if (exists) {
+ if (removed) {
+ idx === len - 1 ? newEntries.pop() : (newEntries[idx] = newEntries.pop());
+ } else {
+ newEntries[idx] = [key, value];
+ }
+ } else {
+ newEntries.push([key, value]);
+ }
+
+ if (isEditable) {
+ this.entries = newEntries;
+ return this;
+ }
+
+ return new HashCollisionNode(ownerID, this.keyHash, newEntries);
+ };
+
+
+
+
+ function ValueNode(ownerID, keyHash, entry) {
+ this.ownerID = ownerID;
+ this.keyHash = keyHash;
+ this.entry = entry;
+ }
+
+ ValueNode.prototype.get = function(shift, keyHash, key, notSetValue) {
+ return is(key, this.entry[0]) ? this.entry[1] : notSetValue;
+ };
+
+ ValueNode.prototype.update = function(ownerID, shift, keyHash, key, value, didChangeSize, didAlter) {
+ var removed = value === NOT_SET;
+ var keyMatch = is(key, this.entry[0]);
+ if (keyMatch ? value === this.entry[1] : removed) {
+ return this;
+ }
+
+ SetRef(didAlter);
+
+ if (removed) {
+ SetRef(didChangeSize);
+ return; // undefined
+ }
+
+ if (keyMatch) {
+ if (ownerID && ownerID === this.ownerID) {
+ this.entry[1] = value;
+ return this;
+ }
+ return new ValueNode(ownerID, this.keyHash, [key, value]);
+ }
+
+ SetRef(didChangeSize);
+ return mergeIntoNode(this, ownerID, shift, hash(key), [key, value]);
+ };
+
+
+
+ // #pragma Iterators
+
+ ArrayMapNode.prototype.iterate =
+ HashCollisionNode.prototype.iterate = function (fn, reverse) {
+ var entries = this.entries;
+ for (var ii = 0, maxIndex = entries.length - 1; ii <= maxIndex; ii++) {
+ if (fn(entries[reverse ? maxIndex - ii : ii]) === false) {
+ return false;
+ }
+ }
+ }
+
+ BitmapIndexedNode.prototype.iterate =
+ HashArrayMapNode.prototype.iterate = function (fn, reverse) {
+ var nodes = this.nodes;
+ for (var ii = 0, maxIndex = nodes.length - 1; ii <= maxIndex; ii++) {
+ var node = nodes[reverse ? maxIndex - ii : ii];
+ if (node && node.iterate(fn, reverse) === false) {
+ return false;
+ }
+ }
+ }
+
+ ValueNode.prototype.iterate = function (fn, reverse) {
+ return fn(this.entry);
+ }
+
+ createClass(MapIterator, Iterator);
+
+ function MapIterator(map, type, reverse) {
+ this._type = type;
+ this._reverse = reverse;
+ this._stack = map._root && mapIteratorFrame(map._root);
+ }
+
+ MapIterator.prototype.next = function() {
+ var type = this._type;
+ var stack = this._stack;
+ while (stack) {
+ var node = stack.node;
+ var index = stack.index++;
+ var maxIndex;
+ if (node.entry) {
+ if (index === 0) {
+ return mapIteratorValue(type, node.entry);
+ }
+ } else if (node.entries) {
+ maxIndex = node.entries.length - 1;
+ if (index <= maxIndex) {
+ return mapIteratorValue(type, node.entries[this._reverse ? maxIndex - index : index]);
+ }
+ } else {
+ maxIndex = node.nodes.length - 1;
+ if (index <= maxIndex) {
+ var subNode = node.nodes[this._reverse ? maxIndex - index : index];
+ if (subNode) {
+ if (subNode.entry) {
+ return mapIteratorValue(type, subNode.entry);
+ }
+ stack = this._stack = mapIteratorFrame(subNode, stack);
+ }
+ continue;
+ }
+ }
+ stack = this._stack = this._stack.__prev;
+ }
+ return iteratorDone();
+ };
+
+
+ function mapIteratorValue(type, entry) {
+ return iteratorValue(type, entry[0], entry[1]);
+ }
+
+ function mapIteratorFrame(node, prev) {
+ return {
+ node: node,
+ index: 0,
+ __prev: prev
+ };
+ }
+
+ function makeMap(size, root, ownerID, hash) {
+ var map = Object.create(MapPrototype);
+ map.size = size;
+ map._root = root;
+ map.__ownerID = ownerID;
+ map.__hash = hash;
+ map.__altered = false;
+ return map;
+ }
+
+ var EMPTY_MAP;
+ function emptyMap() {
+ return EMPTY_MAP || (EMPTY_MAP = makeMap(0));
+ }
+
+ function updateMap(map, k, v) {
+ var newRoot;
+ var newSize;
+ if (!map._root) {
+ if (v === NOT_SET) {
+ return map;
+ }
+ newSize = 1;
+ newRoot = new ArrayMapNode(map.__ownerID, [[k, v]]);
+ } else {
+ var didChangeSize = MakeRef(CHANGE_LENGTH);
+ var didAlter = MakeRef(DID_ALTER);
+ newRoot = updateNode(map._root, map.__ownerID, 0, undefined, k, v, didChangeSize, didAlter);
+ if (!didAlter.value) {
+ return map;
+ }
+ newSize = map.size + (didChangeSize.value ? v === NOT_SET ? -1 : 1 : 0);
+ }
+ if (map.__ownerID) {
+ map.size = newSize;
+ map._root = newRoot;
+ map.__hash = undefined;
+ map.__altered = true;
+ return map;
+ }
+ return newRoot ? makeMap(newSize, newRoot) : emptyMap();
+ }
+
+ function updateNode(node, ownerID, shift, keyHash, key, value, didChangeSize, didAlter) {
+ if (!node) {
+ if (value === NOT_SET) {
+ return node;
+ }
+ SetRef(didAlter);
+ SetRef(didChangeSize);
+ return new ValueNode(ownerID, keyHash, [key, value]);
+ }
+ return node.update(ownerID, shift, keyHash, key, value, didChangeSize, didAlter);
+ }
+
+ function isLeafNode(node) {
+ return node.constructor === ValueNode || node.constructor === HashCollisionNode;
+ }
+
+ function mergeIntoNode(node, ownerID, shift, keyHash, entry) {
+ if (node.keyHash === keyHash) {
+ return new HashCollisionNode(ownerID, keyHash, [node.entry, entry]);
+ }
+
+ var idx1 = (shift === 0 ? node.keyHash : node.keyHash >>> shift) & MASK;
+ var idx2 = (shift === 0 ? keyHash : keyHash >>> shift) & MASK;
+
+ var newNode;
+ var nodes = idx1 === idx2 ?
+ [mergeIntoNode(node, ownerID, shift + SHIFT, keyHash, entry)] :
+ ((newNode = new ValueNode(ownerID, keyHash, entry)), idx1 < idx2 ? [node, newNode] : [newNode, node]);
+
+ return new BitmapIndexedNode(ownerID, (1 << idx1) | (1 << idx2), nodes);
+ }
+
+ function createNodes(ownerID, entries, key, value) {
+ if (!ownerID) {
+ ownerID = new OwnerID();
+ }
+ var node = new ValueNode(ownerID, hash(key), [key, value]);
+ for (var ii = 0; ii < entries.length; ii++) {
+ var entry = entries[ii];
+ node = node.update(ownerID, 0, undefined, entry[0], entry[1]);
+ }
+ return node;
+ }
+
+ function packNodes(ownerID, nodes, count, excluding) {
+ var bitmap = 0;
+ var packedII = 0;
+ var packedNodes = new Array(count);
+ for (var ii = 0, bit = 1, len = nodes.length; ii < len; ii++, bit <<= 1) {
+ var node = nodes[ii];
+ if (node !== undefined && ii !== excluding) {
+ bitmap |= bit;
+ packedNodes[packedII++] = node;
+ }
+ }
+ return new BitmapIndexedNode(ownerID, bitmap, packedNodes);
+ }
+
+ function expandNodes(ownerID, nodes, bitmap, including, node) {
+ var count = 0;
+ var expandedNodes = new Array(SIZE);
+ for (var ii = 0; bitmap !== 0; ii++, bitmap >>>= 1) {
+ expandedNodes[ii] = bitmap & 1 ? nodes[count++] : undefined;
+ }
+ expandedNodes[including] = node;
+ return new HashArrayMapNode(ownerID, count + 1, expandedNodes);
+ }
+
+ function mergeIntoMapWith(map, merger, iterables) {
+ var iters = [];
+ for (var ii = 0; ii < iterables.length; ii++) {
+ var value = iterables[ii];
+ var iter = KeyedIterable(value);
+ if (!isIterable(value)) {
+ iter = iter.map(function(v ) {return fromJS(v)});
+ }
+ iters.push(iter);
+ }
+ return mergeIntoCollectionWith(map, merger, iters);
+ }
+
+ function deepMerger(existing, value, key) {
+ return existing && existing.mergeDeep && isIterable(value) ?
+ existing.mergeDeep(value) :
+ is(existing, value) ? existing : value;
+ }
+
+ function deepMergerWith(merger) {
+ return function(existing, value, key) {
+ if (existing && existing.mergeDeepWith && isIterable(value)) {
+ return existing.mergeDeepWith(merger, value);
+ }
+ var nextValue = merger(existing, value, key);
+ return is(existing, nextValue) ? existing : nextValue;
+ };
+ }
+
+ function mergeIntoCollectionWith(collection, merger, iters) {
+ iters = iters.filter(function(x ) {return x.size !== 0});
+ if (iters.length === 0) {
+ return collection;
+ }
+ if (collection.size === 0 && !collection.__ownerID && iters.length === 1) {
+ return collection.constructor(iters[0]);
+ }
+ return collection.withMutations(function(collection ) {
+ var mergeIntoMap = merger ?
+ function(value, key) {
+ collection.update(key, NOT_SET, function(existing )
+ {return existing === NOT_SET ? value : merger(existing, value, key)}
+ );
+ } :
+ function(value, key) {
+ collection.set(key, value);
+ }
+ for (var ii = 0; ii < iters.length; ii++) {
+ iters[ii].forEach(mergeIntoMap);
+ }
+ });
+ }
+
+ function updateInDeepMap(existing, keyPathIter, notSetValue, updater) {
+ var isNotSet = existing === NOT_SET;
+ var step = keyPathIter.next();
+ if (step.done) {
+ var existingValue = isNotSet ? notSetValue : existing;
+ var newValue = updater(existingValue);
+ return newValue === existingValue ? existing : newValue;
+ }
+ invariant(
+ isNotSet || (existing && existing.set),
+ 'invalid keyPath'
+ );
+ var key = step.value;
+ var nextExisting = isNotSet ? NOT_SET : existing.get(key, NOT_SET);
+ var nextUpdated = updateInDeepMap(
+ nextExisting,
+ keyPathIter,
+ notSetValue,
+ updater
+ );
+ return nextUpdated === nextExisting ? existing :
+ nextUpdated === NOT_SET ? existing.remove(key) :
+ (isNotSet ? emptyMap() : existing).set(key, nextUpdated);
+ }
+
+ function popCount(x) {
+ x = x - ((x >> 1) & 0x55555555);
+ x = (x & 0x33333333) + ((x >> 2) & 0x33333333);
+ x = (x + (x >> 4)) & 0x0f0f0f0f;
+ x = x + (x >> 8);
+ x = x + (x >> 16);
+ return x & 0x7f;
+ }
+
+ function setIn(array, idx, val, canEdit) {
+ var newArray = canEdit ? array : arrCopy(array);
+ newArray[idx] = val;
+ return newArray;
+ }
+
+ function spliceIn(array, idx, val, canEdit) {
+ var newLen = array.length + 1;
+ if (canEdit && idx + 1 === newLen) {
+ array[idx] = val;
+ return array;
+ }
+ var newArray = new Array(newLen);
+ var after = 0;
+ for (var ii = 0; ii < newLen; ii++) {
+ if (ii === idx) {
+ newArray[ii] = val;
+ after = -1;
+ } else {
+ newArray[ii] = array[ii + after];
+ }
+ }
+ return newArray;
+ }
+
+ function spliceOut(array, idx, canEdit) {
+ var newLen = array.length - 1;
+ if (canEdit && idx === newLen) {
+ array.pop();
+ return array;
+ }
+ var newArray = new Array(newLen);
+ var after = 0;
+ for (var ii = 0; ii < newLen; ii++) {
+ if (ii === idx) {
+ after = 1;
+ }
+ newArray[ii] = array[ii + after];
+ }
+ return newArray;
+ }
+
+ var MAX_ARRAY_MAP_SIZE = SIZE / 4;
+ var MAX_BITMAP_INDEXED_SIZE = SIZE / 2;
+ var MIN_HASH_ARRAY_MAP_SIZE = SIZE / 4;
+
+ createClass(List, IndexedCollection);
+
+ // @pragma Construction
+
+ function List(value) {
+ var empty = emptyList();
+ if (value === null || value === undefined) {
+ return empty;
+ }
+ if (isList(value)) {
+ return value;
+ }
+ var iter = IndexedIterable(value);
+ var size = iter.size;
+ if (size === 0) {
+ return empty;
+ }
+ assertNotInfinite(size);
+ if (size > 0 && size < SIZE) {
+ return makeList(0, size, SHIFT, null, new VNode(iter.toArray()));
+ }
+ return empty.withMutations(function(list ) {
+ list.setSize(size);
+ iter.forEach(function(v, i) {return list.set(i, v)});
+ });
+ }
+
+ List.of = function(/*...values*/) {
+ return this(arguments);
+ };
+
+ List.prototype.toString = function() {
+ return this.__toString('List [', ']');
+ };
+
+ // @pragma Access
+
+ List.prototype.get = function(index, notSetValue) {
+ index = wrapIndex(this, index);
+ if (index >= 0 && index < this.size) {
+ index += this._origin;
+ var node = listNodeFor(this, index);
+ return node && node.array[index & MASK];
+ }
+ return notSetValue;
+ };
+
+ // @pragma Modification
+
+ List.prototype.set = function(index, value) {
+ return updateList(this, index, value);
+ };
+
+ List.prototype.remove = function(index) {
+ return !this.has(index) ? this :
+ index === 0 ? this.shift() :
+ index === this.size - 1 ? this.pop() :
+ this.splice(index, 1);
+ };
+
+ List.prototype.insert = function(index, value) {
+ return this.splice(index, 0, value);
+ };
+
+ List.prototype.clear = function() {
+ if (this.size === 0) {
+ return this;
+ }
+ if (this.__ownerID) {
+ this.size = this._origin = this._capacity = 0;
+ this._level = SHIFT;
+ this._root = this._tail = null;
+ this.__hash = undefined;
+ this.__altered = true;
+ return this;
+ }
+ return emptyList();
+ };
+
+ List.prototype.push = function(/*...values*/) {
+ var values = arguments;
+ var oldSize = this.size;
+ return this.withMutations(function(list ) {
+ setListBounds(list, 0, oldSize + values.length);
+ for (var ii = 0; ii < values.length; ii++) {
+ list.set(oldSize + ii, values[ii]);
+ }
+ });
+ };
+
+ List.prototype.pop = function() {
+ return setListBounds(this, 0, -1);
+ };
+
+ List.prototype.unshift = function(/*...values*/) {
+ var values = arguments;
+ return this.withMutations(function(list ) {
+ setListBounds(list, -values.length);
+ for (var ii = 0; ii < values.length; ii++) {
+ list.set(ii, values[ii]);
+ }
+ });
+ };
+
+ List.prototype.shift = function() {
+ return setListBounds(this, 1);
+ };
+
+ // @pragma Composition
+
+ List.prototype.merge = function(/*...iters*/) {
+ return mergeIntoListWith(this, undefined, arguments);
+ };
+
+ List.prototype.mergeWith = function(merger) {var iters = SLICE$0.call(arguments, 1);
+ return mergeIntoListWith(this, merger, iters);
+ };
+
+ List.prototype.mergeDeep = function(/*...iters*/) {
+ return mergeIntoListWith(this, deepMerger, arguments);
+ };
+
+ List.prototype.mergeDeepWith = function(merger) {var iters = SLICE$0.call(arguments, 1);
+ return mergeIntoListWith(this, deepMergerWith(merger), iters);
+ };
+
+ List.prototype.setSize = function(size) {
+ return setListBounds(this, 0, size);
+ };
+
+ // @pragma Iteration
+
+ List.prototype.slice = function(begin, end) {
+ var size = this.size;
+ if (wholeSlice(begin, end, size)) {
+ return this;
+ }
+ return setListBounds(
+ this,
+ resolveBegin(begin, size),
+ resolveEnd(end, size)
+ );
+ };
+
+ List.prototype.__iterator = function(type, reverse) {
+ var index = 0;
+ var values = iterateList(this, reverse);
+ return new Iterator(function() {
+ var value = values();
+ return value === DONE ?
+ iteratorDone() :
+ iteratorValue(type, index++, value);
+ });
+ };
+
+ List.prototype.__iterate = function(fn, reverse) {
+ var index = 0;
+ var values = iterateList(this, reverse);
+ var value;
+ while ((value = values()) !== DONE) {
+ if (fn(value, index++, this) === false) {
+ break;
+ }
+ }
+ return index;
+ };
+
+ List.prototype.__ensureOwner = function(ownerID) {
+ if (ownerID === this.__ownerID) {
+ return this;
+ }
+ if (!ownerID) {
+ this.__ownerID = ownerID;
+ return this;
+ }
+ return makeList(this._origin, this._capacity, this._level, this._root, this._tail, ownerID, this.__hash);
+ };
+
+
+ function isList(maybeList) {
+ return !!(maybeList && maybeList[IS_LIST_SENTINEL]);
+ }
+
+ List.isList = isList;
+
+ var IS_LIST_SENTINEL = '@@__IMMUTABLE_LIST__@@';
+
+ var ListPrototype = List.prototype;
+ ListPrototype[IS_LIST_SENTINEL] = true;
+ ListPrototype[DELETE] = ListPrototype.remove;
+ ListPrototype.setIn = MapPrototype.setIn;
+ ListPrototype.deleteIn =
+ ListPrototype.removeIn = MapPrototype.removeIn;
+ ListPrototype.update = MapPrototype.update;
+ ListPrototype.updateIn = MapPrototype.updateIn;
+ ListPrototype.mergeIn = MapPrototype.mergeIn;
+ ListPrototype.mergeDeepIn = MapPrototype.mergeDeepIn;
+ ListPrototype.withMutations = MapPrototype.withMutations;
+ ListPrototype.asMutable = MapPrototype.asMutable;
+ ListPrototype.asImmutable = MapPrototype.asImmutable;
+ ListPrototype.wasAltered = MapPrototype.wasAltered;
+
+
+
+ function VNode(array, ownerID) {
+ this.array = array;
+ this.ownerID = ownerID;
+ }
+
+ // TODO: seems like these methods are very similar
+
+ VNode.prototype.removeBefore = function(ownerID, level, index) {
+ if (index === level ? 1 << level : 0 || this.array.length === 0) {
+ return this;
+ }
+ var originIndex = (index >>> level) & MASK;
+ if (originIndex >= this.array.length) {
+ return new VNode([], ownerID);
+ }
+ var removingFirst = originIndex === 0;
+ var newChild;
+ if (level > 0) {
+ var oldChild = this.array[originIndex];
+ newChild = oldChild && oldChild.removeBefore(ownerID, level - SHIFT, index);
+ if (newChild === oldChild && removingFirst) {
+ return this;
+ }
+ }
+ if (removingFirst && !newChild) {
+ return this;
+ }
+ var editable = editableVNode(this, ownerID);
+ if (!removingFirst) {
+ for (var ii = 0; ii < originIndex; ii++) {
+ editable.array[ii] = undefined;
+ }
+ }
+ if (newChild) {
+ editable.array[originIndex] = newChild;
+ }
+ return editable;
+ };
+
+ VNode.prototype.removeAfter = function(ownerID, level, index) {
+ if (index === (level ? 1 << level : 0) || this.array.length === 0) {
+ return this;
+ }
+ var sizeIndex = ((index - 1) >>> level) & MASK;
+ if (sizeIndex >= this.array.length) {
+ return this;
+ }
+
+ var newChild;
+ if (level > 0) {
+ var oldChild = this.array[sizeIndex];
+ newChild = oldChild && oldChild.removeAfter(ownerID, level - SHIFT, index);
+ if (newChild === oldChild && sizeIndex === this.array.length - 1) {
+ return this;
+ }
+ }
+
+ var editable = editableVNode(this, ownerID);
+ editable.array.splice(sizeIndex + 1);
+ if (newChild) {
+ editable.array[sizeIndex] = newChild;
+ }
+ return editable;
+ };
+
+
+
+ var DONE = {};
+
+ function iterateList(list, reverse) {
+ var left = list._origin;
+ var right = list._capacity;
+ var tailPos = getTailOffset(right);
+ var tail = list._tail;
+
+ return iterateNodeOrLeaf(list._root, list._level, 0);
+
+ function iterateNodeOrLeaf(node, level, offset) {
+ return level === 0 ?
+ iterateLeaf(node, offset) :
+ iterateNode(node, level, offset);
+ }
+
+ function iterateLeaf(node, offset) {
+ var array = offset === tailPos ? tail && tail.array : node && node.array;
+ var from = offset > left ? 0 : left - offset;
+ var to = right - offset;
+ if (to > SIZE) {
+ to = SIZE;
+ }
+ return function() {
+ if (from === to) {
+ return DONE;
+ }
+ var idx = reverse ? --to : from++;
+ return array && array[idx];
+ };
+ }
+
+ function iterateNode(node, level, offset) {
+ var values;
+ var array = node && node.array;
+ var from = offset > left ? 0 : (left - offset) >> level;
+ var to = ((right - offset) >> level) + 1;
+ if (to > SIZE) {
+ to = SIZE;
+ }
+ return function() {
+ do {
+ if (values) {
+ var value = values();
+ if (value !== DONE) {
+ return value;
+ }
+ values = null;
+ }
+ if (from === to) {
+ return DONE;
+ }
+ var idx = reverse ? --to : from++;
+ values = iterateNodeOrLeaf(
+ array && array[idx], level - SHIFT, offset + (idx << level)
+ );
+ } while (true);
+ };
+ }
+ }
+
+ function makeList(origin, capacity, level, root, tail, ownerID, hash) {
+ var list = Object.create(ListPrototype);
+ list.size = capacity - origin;
+ list._origin = origin;
+ list._capacity = capacity;
+ list._level = level;
+ list._root = root;
+ list._tail = tail;
+ list.__ownerID = ownerID;
+ list.__hash = hash;
+ list.__altered = false;
+ return list;
+ }
+
+ var EMPTY_LIST;
+ function emptyList() {
+ return EMPTY_LIST || (EMPTY_LIST = makeList(0, 0, SHIFT));
+ }
+
+ function updateList(list, index, value) {
+ index = wrapIndex(list, index);
+
+ if (index !== index) {
+ return list;
+ }
+
+ if (index >= list.size || index < 0) {
+ return list.withMutations(function(list ) {
+ index < 0 ?
+ setListBounds(list, index).set(0, value) :
+ setListBounds(list, 0, index + 1).set(index, value)
+ });
+ }
+
+ index += list._origin;
+
+ var newTail = list._tail;
+ var newRoot = list._root;
+ var didAlter = MakeRef(DID_ALTER);
+ if (index >= getTailOffset(list._capacity)) {
+ newTail = updateVNode(newTail, list.__ownerID, 0, index, value, didAlter);
+ } else {
+ newRoot = updateVNode(newRoot, list.__ownerID, list._level, index, value, didAlter);
+ }
+
+ if (!didAlter.value) {
+ return list;
+ }
+
+ if (list.__ownerID) {
+ list._root = newRoot;
+ list._tail = newTail;
+ list.__hash = undefined;
+ list.__altered = true;
+ return list;
+ }
+ return makeList(list._origin, list._capacity, list._level, newRoot, newTail);
+ }
+
+ function updateVNode(node, ownerID, level, index, value, didAlter) {
+ var idx = (index >>> level) & MASK;
+ var nodeHas = node && idx < node.array.length;
+ if (!nodeHas && value === undefined) {
+ return node;
+ }
+
+ var newNode;
+
+ if (level > 0) {
+ var lowerNode = node && node.array[idx];
+ var newLowerNode = updateVNode(lowerNode, ownerID, level - SHIFT, index, value, didAlter);
+ if (newLowerNode === lowerNode) {
+ return node;
+ }
+ newNode = editableVNode(node, ownerID);
+ newNode.array[idx] = newLowerNode;
+ return newNode;
+ }
+
+ if (nodeHas && node.array[idx] === value) {
+ return node;
+ }
+
+ SetRef(didAlter);
+
+ newNode = editableVNode(node, ownerID);
+ if (value === undefined && idx === newNode.array.length - 1) {
+ newNode.array.pop();
+ } else {
+ newNode.array[idx] = value;
+ }
+ return newNode;
+ }
+
+ function editableVNode(node, ownerID) {
+ if (ownerID && node && ownerID === node.ownerID) {
+ return node;
+ }
+ return new VNode(node ? node.array.slice() : [], ownerID);
+ }
+
+ function listNodeFor(list, rawIndex) {
+ if (rawIndex >= getTailOffset(list._capacity)) {
+ return list._tail;
+ }
+ if (rawIndex < 1 << (list._level + SHIFT)) {
+ var node = list._root;
+ var level = list._level;
+ while (node && level > 0) {
+ node = node.array[(rawIndex >>> level) & MASK];
+ level -= SHIFT;
+ }
+ return node;
+ }
+ }
+
+ function setListBounds(list, begin, end) {
+ // Sanitize begin & end using this shorthand for ToInt32(argument)
+ // http://www.ecma-international.org/ecma-262/6.0/#sec-toint32
+ if (begin !== undefined) {
+ begin = begin | 0;
+ }
+ if (end !== undefined) {
+ end = end | 0;
+ }
+ var owner = list.__ownerID || new OwnerID();
+ var oldOrigin = list._origin;
+ var oldCapacity = list._capacity;
+ var newOrigin = oldOrigin + begin;
+ var newCapacity = end === undefined ? oldCapacity : end < 0 ? oldCapacity + end : oldOrigin + end;
+ if (newOrigin === oldOrigin && newCapacity === oldCapacity) {
+ return list;
+ }
+
+ // If it's going to end after it starts, it's empty.
+ if (newOrigin >= newCapacity) {
+ return list.clear();
+ }
+
+ var newLevel = list._level;
+ var newRoot = list._root;
+
+ // New origin might need creating a higher root.
+ var offsetShift = 0;
+ while (newOrigin + offsetShift < 0) {
+ newRoot = new VNode(newRoot && newRoot.array.length ? [undefined, newRoot] : [], owner);
+ newLevel += SHIFT;
+ offsetShift += 1 << newLevel;
+ }
+ if (offsetShift) {
+ newOrigin += offsetShift;
+ oldOrigin += offsetShift;
+ newCapacity += offsetShift;
+ oldCapacity += offsetShift;
+ }
+
+ var oldTailOffset = getTailOffset(oldCapacity);
+ var newTailOffset = getTailOffset(newCapacity);
+
+ // New size might need creating a higher root.
+ while (newTailOffset >= 1 << (newLevel + SHIFT)) {
+ newRoot = new VNode(newRoot && newRoot.array.length ? [newRoot] : [], owner);
+ newLevel += SHIFT;
+ }
+
+ // Locate or create the new tail.
+ var oldTail = list._tail;
+ var newTail = newTailOffset < oldTailOffset ?
+ listNodeFor(list, newCapacity - 1) :
+ newTailOffset > oldTailOffset ? new VNode([], owner) : oldTail;
+
+ // Merge Tail into tree.
+ if (oldTail && newTailOffset > oldTailOffset && newOrigin < oldCapacity && oldTail.array.length) {
+ newRoot = editableVNode(newRoot, owner);
+ var node = newRoot;
+ for (var level = newLevel; level > SHIFT; level -= SHIFT) {
+ var idx = (oldTailOffset >>> level) & MASK;
+ node = node.array[idx] = editableVNode(node.array[idx], owner);
+ }
+ node.array[(oldTailOffset >>> SHIFT) & MASK] = oldTail;
+ }
+
+ // If the size has been reduced, there's a chance the tail needs to be trimmed.
+ if (newCapacity < oldCapacity) {
+ newTail = newTail && newTail.removeAfter(owner, 0, newCapacity);
+ }
+
+ // If the new origin is within the tail, then we do not need a root.
+ if (newOrigin >= newTailOffset) {
+ newOrigin -= newTailOffset;
+ newCapacity -= newTailOffset;
+ newLevel = SHIFT;
+ newRoot = null;
+ newTail = newTail && newTail.removeBefore(owner, 0, newOrigin);
+
+ // Otherwise, if the root has been trimmed, garbage collect.
+ } else if (newOrigin > oldOrigin || newTailOffset < oldTailOffset) {
+ offsetShift = 0;
+
+ // Identify the new top root node of the subtree of the old root.
+ while (newRoot) {
+ var beginIndex = (newOrigin >>> newLevel) & MASK;
+ if (beginIndex !== (newTailOffset >>> newLevel) & MASK) {
+ break;
+ }
+ if (beginIndex) {
+ offsetShift += (1 << newLevel) * beginIndex;
+ }
+ newLevel -= SHIFT;
+ newRoot = newRoot.array[beginIndex];
+ }
+
+ // Trim the new sides of the new root.
+ if (newRoot && newOrigin > oldOrigin) {
+ newRoot = newRoot.removeBefore(owner, newLevel, newOrigin - offsetShift);
+ }
+ if (newRoot && newTailOffset < oldTailOffset) {
+ newRoot = newRoot.removeAfter(owner, newLevel, newTailOffset - offsetShift);
+ }
+ if (offsetShift) {
+ newOrigin -= offsetShift;
+ newCapacity -= offsetShift;
+ }
+ }
+
+ if (list.__ownerID) {
+ list.size = newCapacity - newOrigin;
+ list._origin = newOrigin;
+ list._capacity = newCapacity;
+ list._level = newLevel;
+ list._root = newRoot;
+ list._tail = newTail;
+ list.__hash = undefined;
+ list.__altered = true;
+ return list;
+ }
+ return makeList(newOrigin, newCapacity, newLevel, newRoot, newTail);
+ }
+
+ function mergeIntoListWith(list, merger, iterables) {
+ var iters = [];
+ var maxSize = 0;
+ for (var ii = 0; ii < iterables.length; ii++) {
+ var value = iterables[ii];
+ var iter = IndexedIterable(value);
+ if (iter.size > maxSize) {
+ maxSize = iter.size;
+ }
+ if (!isIterable(value)) {
+ iter = iter.map(function(v ) {return fromJS(v)});
+ }
+ iters.push(iter);
+ }
+ if (maxSize > list.size) {
+ list = list.setSize(maxSize);
+ }
+ return mergeIntoCollectionWith(list, merger, iters);
+ }
+
+ function getTailOffset(size) {
+ return size < SIZE ? 0 : (((size - 1) >>> SHIFT) << SHIFT);
+ }
+
+ createClass(OrderedMap, Map);
+
+ // @pragma Construction
+
+ function OrderedMap(value) {
+ return value === null || value === undefined ? emptyOrderedMap() :
+ isOrderedMap(value) ? value :
+ emptyOrderedMap().withMutations(function(map ) {
+ var iter = KeyedIterable(value);
+ assertNotInfinite(iter.size);
+ iter.forEach(function(v, k) {return map.set(k, v)});
+ });
+ }
+
+ OrderedMap.of = function(/*...values*/) {
+ return this(arguments);
+ };
+
+ OrderedMap.prototype.toString = function() {
+ return this.__toString('OrderedMap {', '}');
+ };
+
+ // @pragma Access
+
+ OrderedMap.prototype.get = function(k, notSetValue) {
+ var index = this._map.get(k);
+ return index !== undefined ? this._list.get(index)[1] : notSetValue;
+ };
+
+ // @pragma Modification
+
+ OrderedMap.prototype.clear = function() {
+ if (this.size === 0) {
+ return this;
+ }
+ if (this.__ownerID) {
+ this.size = 0;
+ this._map.clear();
+ this._list.clear();
+ return this;
+ }
+ return emptyOrderedMap();
+ };
+
+ OrderedMap.prototype.set = function(k, v) {
+ return updateOrderedMap(this, k, v);
+ };
+
+ OrderedMap.prototype.remove = function(k) {
+ return updateOrderedMap(this, k, NOT_SET);
+ };
+
+ OrderedMap.prototype.wasAltered = function() {
+ return this._map.wasAltered() || this._list.wasAltered();
+ };
+
+ OrderedMap.prototype.__iterate = function(fn, reverse) {var this$0 = this;
+ return this._list.__iterate(
+ function(entry ) {return entry && fn(entry[1], entry[0], this$0)},
+ reverse
+ );
+ };
+
+ OrderedMap.prototype.__iterator = function(type, reverse) {
+ return this._list.fromEntrySeq().__iterator(type, reverse);
+ };
+
+ OrderedMap.prototype.__ensureOwner = function(ownerID) {
+ if (ownerID === this.__ownerID) {
+ return this;
+ }
+ var newMap = this._map.__ensureOwner(ownerID);
+ var newList = this._list.__ensureOwner(ownerID);
+ if (!ownerID) {
+ this.__ownerID = ownerID;
+ this._map = newMap;
+ this._list = newList;
+ return this;
+ }
+ return makeOrderedMap(newMap, newList, ownerID, this.__hash);
+ };
+
+
+ function isOrderedMap(maybeOrderedMap) {
+ return isMap(maybeOrderedMap) && isOrdered(maybeOrderedMap);
+ }
+
+ OrderedMap.isOrderedMap = isOrderedMap;
+
+ OrderedMap.prototype[IS_ORDERED_SENTINEL] = true;
+ OrderedMap.prototype[DELETE] = OrderedMap.prototype.remove;
+
+
+
+ function makeOrderedMap(map, list, ownerID, hash) {
+ var omap = Object.create(OrderedMap.prototype);
+ omap.size = map ? map.size : 0;
+ omap._map = map;
+ omap._list = list;
+ omap.__ownerID = ownerID;
+ omap.__hash = hash;
+ return omap;
+ }
+
+ var EMPTY_ORDERED_MAP;
+ function emptyOrderedMap() {
+ return EMPTY_ORDERED_MAP || (EMPTY_ORDERED_MAP = makeOrderedMap(emptyMap(), emptyList()));
+ }
+
+ function updateOrderedMap(omap, k, v) {
+ var map = omap._map;
+ var list = omap._list;
+ var i = map.get(k);
+ var has = i !== undefined;
+ var newMap;
+ var newList;
+ if (v === NOT_SET) { // removed
+ if (!has) {
+ return omap;
+ }
+ if (list.size >= SIZE && list.size >= map.size * 2) {
+ newList = list.filter(function(entry, idx) {return entry !== undefined && i !== idx});
+ newMap = newList.toKeyedSeq().map(function(entry ) {return entry[0]}).flip().toMap();
+ if (omap.__ownerID) {
+ newMap.__ownerID = newList.__ownerID = omap.__ownerID;
+ }
+ } else {
+ newMap = map.remove(k);
+ newList = i === list.size - 1 ? list.pop() : list.set(i, undefined);
+ }
+ } else {
+ if (has) {
+ if (v === list.get(i)[1]) {
+ return omap;
+ }
+ newMap = map;
+ newList = list.set(i, [k, v]);
+ } else {
+ newMap = map.set(k, list.size);
+ newList = list.set(list.size, [k, v]);
+ }
+ }
+ if (omap.__ownerID) {
+ omap.size = newMap.size;
+ omap._map = newMap;
+ omap._list = newList;
+ omap.__hash = undefined;
+ return omap;
+ }
+ return makeOrderedMap(newMap, newList);
+ }
+
+ createClass(ToKeyedSequence, KeyedSeq);
+ function ToKeyedSequence(indexed, useKeys) {
+ this._iter = indexed;
+ this._useKeys = useKeys;
+ this.size = indexed.size;
+ }
+
+ ToKeyedSequence.prototype.get = function(key, notSetValue) {
+ return this._iter.get(key, notSetValue);
+ };
+
+ ToKeyedSequence.prototype.has = function(key) {
+ return this._iter.has(key);
+ };
+
+ ToKeyedSequence.prototype.valueSeq = function() {
+ return this._iter.valueSeq();
+ };
+
+ ToKeyedSequence.prototype.reverse = function() {var this$0 = this;
+ var reversedSequence = reverseFactory(this, true);
+ if (!this._useKeys) {
+ reversedSequence.valueSeq = function() {return this$0._iter.toSeq().reverse()};
+ }
+ return reversedSequence;
+ };
+
+ ToKeyedSequence.prototype.map = function(mapper, context) {var this$0 = this;
+ var mappedSequence = mapFactory(this, mapper, context);
+ if (!this._useKeys) {
+ mappedSequence.valueSeq = function() {return this$0._iter.toSeq().map(mapper, context)};
+ }
+ return mappedSequence;
+ };
+
+ ToKeyedSequence.prototype.__iterate = function(fn, reverse) {var this$0 = this;
+ var ii;
+ return this._iter.__iterate(
+ this._useKeys ?
+ function(v, k) {return fn(v, k, this$0)} :
+ ((ii = reverse ? resolveSize(this) : 0),
+ function(v ) {return fn(v, reverse ? --ii : ii++, this$0)}),
+ reverse
+ );
+ };
+
+ ToKeyedSequence.prototype.__iterator = function(type, reverse) {
+ if (this._useKeys) {
+ return this._iter.__iterator(type, reverse);
+ }
+ var iterator = this._iter.__iterator(ITERATE_VALUES, reverse);
+ var ii = reverse ? resolveSize(this) : 0;
+ return new Iterator(function() {
+ var step = iterator.next();
+ return step.done ? step :
+ iteratorValue(type, reverse ? --ii : ii++, step.value, step);
+ });
+ };
+
+ ToKeyedSequence.prototype[IS_ORDERED_SENTINEL] = true;
+
+
+ createClass(ToIndexedSequence, IndexedSeq);
+ function ToIndexedSequence(iter) {
+ this._iter = iter;
+ this.size = iter.size;
+ }
+
+ ToIndexedSequence.prototype.includes = function(value) {
+ return this._iter.includes(value);
+ };
+
+ ToIndexedSequence.prototype.__iterate = function(fn, reverse) {var this$0 = this;
+ var iterations = 0;
+ return this._iter.__iterate(function(v ) {return fn(v, iterations++, this$0)}, reverse);
+ };
+
+ ToIndexedSequence.prototype.__iterator = function(type, reverse) {
+ var iterator = this._iter.__iterator(ITERATE_VALUES, reverse);
+ var iterations = 0;
+ return new Iterator(function() {
+ var step = iterator.next();
+ return step.done ? step :
+ iteratorValue(type, iterations++, step.value, step)
+ });
+ };
+
+
+
+ createClass(ToSetSequence, SetSeq);
+ function ToSetSequence(iter) {
+ this._iter = iter;
+ this.size = iter.size;
+ }
+
+ ToSetSequence.prototype.has = function(key) {
+ return this._iter.includes(key);
+ };
+
+ ToSetSequence.prototype.__iterate = function(fn, reverse) {var this$0 = this;
+ return this._iter.__iterate(function(v ) {return fn(v, v, this$0)}, reverse);
+ };
+
+ ToSetSequence.prototype.__iterator = function(type, reverse) {
+ var iterator = this._iter.__iterator(ITERATE_VALUES, reverse);
+ return new Iterator(function() {
+ var step = iterator.next();
+ return step.done ? step :
+ iteratorValue(type, step.value, step.value, step);
+ });
+ };
+
+
+
+ createClass(FromEntriesSequence, KeyedSeq);
+ function FromEntriesSequence(entries) {
+ this._iter = entries;
+ this.size = entries.size;
+ }
+
+ FromEntriesSequence.prototype.entrySeq = function() {
+ return this._iter.toSeq();
+ };
+
+ FromEntriesSequence.prototype.__iterate = function(fn, reverse) {var this$0 = this;
+ return this._iter.__iterate(function(entry ) {
+ // Check if entry exists first so array access doesn't throw for holes
+ // in the parent iteration.
+ if (entry) {
+ validateEntry(entry);
+ var indexedIterable = isIterable(entry);
+ return fn(
+ indexedIterable ? entry.get(1) : entry[1],
+ indexedIterable ? entry.get(0) : entry[0],
+ this$0
+ );
+ }
+ }, reverse);
+ };
+
+ FromEntriesSequence.prototype.__iterator = function(type, reverse) {
+ var iterator = this._iter.__iterator(ITERATE_VALUES, reverse);
+ return new Iterator(function() {
+ while (true) {
+ var step = iterator.next();
+ if (step.done) {
+ return step;
+ }
+ var entry = step.value;
+ // Check if entry exists first so array access doesn't throw for holes
+ // in the parent iteration.
+ if (entry) {
+ validateEntry(entry);
+ var indexedIterable = isIterable(entry);
+ return iteratorValue(
+ type,
+ indexedIterable ? entry.get(0) : entry[0],
+ indexedIterable ? entry.get(1) : entry[1],
+ step
+ );
+ }
+ }
+ });
+ };
+
+
+ ToIndexedSequence.prototype.cacheResult =
+ ToKeyedSequence.prototype.cacheResult =
+ ToSetSequence.prototype.cacheResult =
+ FromEntriesSequence.prototype.cacheResult =
+ cacheResultThrough;
+
+
+ function flipFactory(iterable) {
+ var flipSequence = makeSequence(iterable);
+ flipSequence._iter = iterable;
+ flipSequence.size = iterable.size;
+ flipSequence.flip = function() {return iterable};
+ flipSequence.reverse = function () {
+ var reversedSequence = iterable.reverse.apply(this); // super.reverse()
+ reversedSequence.flip = function() {return iterable.reverse()};
+ return reversedSequence;
+ };
+ flipSequence.has = function(key ) {return iterable.includes(key)};
+ flipSequence.includes = function(key ) {return iterable.has(key)};
+ flipSequence.cacheResult = cacheResultThrough;
+ flipSequence.__iterateUncached = function (fn, reverse) {var this$0 = this;
+ return iterable.__iterate(function(v, k) {return fn(k, v, this$0) !== false}, reverse);
+ }
+ flipSequence.__iteratorUncached = function(type, reverse) {
+ if (type === ITERATE_ENTRIES) {
+ var iterator = iterable.__iterator(type, reverse);
+ return new Iterator(function() {
+ var step = iterator.next();
+ if (!step.done) {
+ var k = step.value[0];
+ step.value[0] = step.value[1];
+ step.value[1] = k;
+ }
+ return step;
+ });
+ }
+ return iterable.__iterator(
+ type === ITERATE_VALUES ? ITERATE_KEYS : ITERATE_VALUES,
+ reverse
+ );
+ }
+ return flipSequence;
+ }
+
+
+ function mapFactory(iterable, mapper, context) {
+ var mappedSequence = makeSequence(iterable);
+ mappedSequence.size = iterable.size;
+ mappedSequence.has = function(key ) {return iterable.has(key)};
+ mappedSequence.get = function(key, notSetValue) {
+ var v = iterable.get(key, NOT_SET);
+ return v === NOT_SET ?
+ notSetValue :
+ mapper.call(context, v, key, iterable);
+ };
+ mappedSequence.__iterateUncached = function (fn, reverse) {var this$0 = this;
+ return iterable.__iterate(
+ function(v, k, c) {return fn(mapper.call(context, v, k, c), k, this$0) !== false},
+ reverse
+ );
+ }
+ mappedSequence.__iteratorUncached = function (type, reverse) {
+ var iterator = iterable.__iterator(ITERATE_ENTRIES, reverse);
+ return new Iterator(function() {
+ var step = iterator.next();
+ if (step.done) {
+ return step;
+ }
+ var entry = step.value;
+ var key = entry[0];
+ return iteratorValue(
+ type,
+ key,
+ mapper.call(context, entry[1], key, iterable),
+ step
+ );
+ });
+ }
+ return mappedSequence;
+ }
+
+
+ function reverseFactory(iterable, useKeys) {
+ var reversedSequence = makeSequence(iterable);
+ reversedSequence._iter = iterable;
+ reversedSequence.size = iterable.size;
+ reversedSequence.reverse = function() {return iterable};
+ if (iterable.flip) {
+ reversedSequence.flip = function () {
+ var flipSequence = flipFactory(iterable);
+ flipSequence.reverse = function() {return iterable.flip()};
+ return flipSequence;
+ };
+ }
+ reversedSequence.get = function(key, notSetValue)
+ {return iterable.get(useKeys ? key : -1 - key, notSetValue)};
+ reversedSequence.has = function(key )
+ {return iterable.has(useKeys ? key : -1 - key)};
+ reversedSequence.includes = function(value ) {return iterable.includes(value)};
+ reversedSequence.cacheResult = cacheResultThrough;
+ reversedSequence.__iterate = function (fn, reverse) {var this$0 = this;
+ return iterable.__iterate(function(v, k) {return fn(v, k, this$0)}, !reverse);
+ };
+ reversedSequence.__iterator =
+ function(type, reverse) {return iterable.__iterator(type, !reverse)};
+ return reversedSequence;
+ }
+
+
+ function filterFactory(iterable, predicate, context, useKeys) {
+ var filterSequence = makeSequence(iterable);
+ if (useKeys) {
+ filterSequence.has = function(key ) {
+ var v = iterable.get(key, NOT_SET);
+ return v !== NOT_SET && !!predicate.call(context, v, key, iterable);
+ };
+ filterSequence.get = function(key, notSetValue) {
+ var v = iterable.get(key, NOT_SET);
+ return v !== NOT_SET && predicate.call(context, v, key, iterable) ?
+ v : notSetValue;
+ };
+ }
+ filterSequence.__iterateUncached = function (fn, reverse) {var this$0 = this;
+ var iterations = 0;
+ iterable.__iterate(function(v, k, c) {
+ if (predicate.call(context, v, k, c)) {
+ iterations++;
+ return fn(v, useKeys ? k : iterations - 1, this$0);
+ }
+ }, reverse);
+ return iterations;
+ };
+ filterSequence.__iteratorUncached = function (type, reverse) {
+ var iterator = iterable.__iterator(ITERATE_ENTRIES, reverse);
+ var iterations = 0;
+ return new Iterator(function() {
+ while (true) {
+ var step = iterator.next();
+ if (step.done) {
+ return step;
+ }
+ var entry = step.value;
+ var key = entry[0];
+ var value = entry[1];
+ if (predicate.call(context, value, key, iterable)) {
+ return iteratorValue(type, useKeys ? key : iterations++, value, step);
+ }
+ }
+ });
+ }
+ return filterSequence;
+ }
+
+
+ function countByFactory(iterable, grouper, context) {
+ var groups = Map().asMutable();
+ iterable.__iterate(function(v, k) {
+ groups.update(
+ grouper.call(context, v, k, iterable),
+ 0,
+ function(a ) {return a + 1}
+ );
+ });
+ return groups.asImmutable();
+ }
+
+
+ function groupByFactory(iterable, grouper, context) {
+ var isKeyedIter = isKeyed(iterable);
+ var groups = (isOrdered(iterable) ? OrderedMap() : Map()).asMutable();
+ iterable.__iterate(function(v, k) {
+ groups.update(
+ grouper.call(context, v, k, iterable),
+ function(a ) {return (a = a || [], a.push(isKeyedIter ? [k, v] : v), a)}
+ );
+ });
+ var coerce = iterableClass(iterable);
+ return groups.map(function(arr ) {return reify(iterable, coerce(arr))});
+ }
+
+
+ function sliceFactory(iterable, begin, end, useKeys) {
+ var originalSize = iterable.size;
+
+ // Sanitize begin & end using this shorthand for ToInt32(argument)
+ // http://www.ecma-international.org/ecma-262/6.0/#sec-toint32
+ if (begin !== undefined) {
+ begin = begin | 0;
+ }
+ if (end !== undefined) {
+ if (end === Infinity) {
+ end = originalSize;
+ } else {
+ end = end | 0;
+ }
+ }
+
+ if (wholeSlice(begin, end, originalSize)) {
+ return iterable;
+ }
+
+ var resolvedBegin = resolveBegin(begin, originalSize);
+ var resolvedEnd = resolveEnd(end, originalSize);
+
+ // begin or end will be NaN if they were provided as negative numbers and
+ // this iterable's size is unknown. In that case, cache first so there is
+ // a known size and these do not resolve to NaN.
+ if (resolvedBegin !== resolvedBegin || resolvedEnd !== resolvedEnd) {
+ return sliceFactory(iterable.toSeq().cacheResult(), begin, end, useKeys);
+ }
+
+ // Note: resolvedEnd is undefined when the original sequence's length is
+ // unknown and this slice did not supply an end and should contain all
+ // elements after resolvedBegin.
+ // In that case, resolvedSize will be NaN and sliceSize will remain undefined.
+ var resolvedSize = resolvedEnd - resolvedBegin;
+ var sliceSize;
+ if (resolvedSize === resolvedSize) {
+ sliceSize = resolvedSize < 0 ? 0 : resolvedSize;
+ }
+
+ var sliceSeq = makeSequence(iterable);
+
+ // If iterable.size is undefined, the size of the realized sliceSeq is
+ // unknown at this point unless the number of items to slice is 0
+ sliceSeq.size = sliceSize === 0 ? sliceSize : iterable.size && sliceSize || undefined;
+
+ if (!useKeys && isSeq(iterable) && sliceSize >= 0) {
+ sliceSeq.get = function (index, notSetValue) {
+ index = wrapIndex(this, index);
+ return index >= 0 && index < sliceSize ?
+ iterable.get(index + resolvedBegin, notSetValue) :
+ notSetValue;
+ }
+ }
+
+ sliceSeq.__iterateUncached = function(fn, reverse) {var this$0 = this;
+ if (sliceSize === 0) {
+ return 0;
+ }
+ if (reverse) {
+ return this.cacheResult().__iterate(fn, reverse);
+ }
+ var skipped = 0;
+ var isSkipping = true;
+ var iterations = 0;
+ iterable.__iterate(function(v, k) {
+ if (!(isSkipping && (isSkipping = skipped++ < resolvedBegin))) {
+ iterations++;
+ return fn(v, useKeys ? k : iterations - 1, this$0) !== false &&
+ iterations !== sliceSize;
+ }
+ });
+ return iterations;
+ };
+
+ sliceSeq.__iteratorUncached = function(type, reverse) {
+ if (sliceSize !== 0 && reverse) {
+ return this.cacheResult().__iterator(type, reverse);
+ }
+ // Don't bother instantiating parent iterator if taking 0.
+ var iterator = sliceSize !== 0 && iterable.__iterator(type, reverse);
+ var skipped = 0;
+ var iterations = 0;
+ return new Iterator(function() {
+ while (skipped++ < resolvedBegin) {
+ iterator.next();
+ }
+ if (++iterations > sliceSize) {
+ return iteratorDone();
+ }
+ var step = iterator.next();
+ if (useKeys || type === ITERATE_VALUES) {
+ return step;
+ } else if (type === ITERATE_KEYS) {
+ return iteratorValue(type, iterations - 1, undefined, step);
+ } else {
+ return iteratorValue(type, iterations - 1, step.value[1], step);
+ }
+ });
+ }
+
+ return sliceSeq;
+ }
+
+
+ function takeWhileFactory(iterable, predicate, context) {
+ var takeSequence = makeSequence(iterable);
+ takeSequence.__iterateUncached = function(fn, reverse) {var this$0 = this;
+ if (reverse) {
+ return this.cacheResult().__iterate(fn, reverse);
+ }
+ var iterations = 0;
+ iterable.__iterate(function(v, k, c)
+ {return predicate.call(context, v, k, c) && ++iterations && fn(v, k, this$0)}
+ );
+ return iterations;
+ };
+ takeSequence.__iteratorUncached = function(type, reverse) {var this$0 = this;
+ if (reverse) {
+ return this.cacheResult().__iterator(type, reverse);
+ }
+ var iterator = iterable.__iterator(ITERATE_ENTRIES, reverse);
+ var iterating = true;
+ return new Iterator(function() {
+ if (!iterating) {
+ return iteratorDone();
+ }
+ var step = iterator.next();
+ if (step.done) {
+ return step;
+ }
+ var entry = step.value;
+ var k = entry[0];
+ var v = entry[1];
+ if (!predicate.call(context, v, k, this$0)) {
+ iterating = false;
+ return iteratorDone();
+ }
+ return type === ITERATE_ENTRIES ? step :
+ iteratorValue(type, k, v, step);
+ });
+ };
+ return takeSequence;
+ }
+
+
+ function skipWhileFactory(iterable, predicate, context, useKeys) {
+ var skipSequence = makeSequence(iterable);
+ skipSequence.__iterateUncached = function (fn, reverse) {var this$0 = this;
+ if (reverse) {
+ return this.cacheResult().__iterate(fn, reverse);
+ }
+ var isSkipping = true;
+ var iterations = 0;
+ iterable.__iterate(function(v, k, c) {
+ if (!(isSkipping && (isSkipping = predicate.call(context, v, k, c)))) {
+ iterations++;
+ return fn(v, useKeys ? k : iterations - 1, this$0);
+ }
+ });
+ return iterations;
+ };
+ skipSequence.__iteratorUncached = function(type, reverse) {var this$0 = this;
+ if (reverse) {
+ return this.cacheResult().__iterator(type, reverse);
+ }
+ var iterator = iterable.__iterator(ITERATE_ENTRIES, reverse);
+ var skipping = true;
+ var iterations = 0;
+ return new Iterator(function() {
+ var step, k, v;
+ do {
+ step = iterator.next();
+ if (step.done) {
+ if (useKeys || type === ITERATE_VALUES) {
+ return step;
+ } else if (type === ITERATE_KEYS) {
+ return iteratorValue(type, iterations++, undefined, step);
+ } else {
+ return iteratorValue(type, iterations++, step.value[1], step);
+ }
+ }
+ var entry = step.value;
+ k = entry[0];
+ v = entry[1];
+ skipping && (skipping = predicate.call(context, v, k, this$0));
+ } while (skipping);
+ return type === ITERATE_ENTRIES ? step :
+ iteratorValue(type, k, v, step);
+ });
+ };
+ return skipSequence;
+ }
+
+
+ function concatFactory(iterable, values) {
+ var isKeyedIterable = isKeyed(iterable);
+ var iters = [iterable].concat(values).map(function(v ) {
+ if (!isIterable(v)) {
+ v = isKeyedIterable ?
+ keyedSeqFromValue(v) :
+ indexedSeqFromValue(Array.isArray(v) ? v : [v]);
+ } else if (isKeyedIterable) {
+ v = KeyedIterable(v);
+ }
+ return v;
+ }).filter(function(v ) {return v.size !== 0});
+
+ if (iters.length === 0) {
+ return iterable;
+ }
+
+ if (iters.length === 1) {
+ var singleton = iters[0];
+ if (singleton === iterable ||
+ isKeyedIterable && isKeyed(singleton) ||
+ isIndexed(iterable) && isIndexed(singleton)) {
+ return singleton;
+ }
+ }
+
+ var concatSeq = new ArraySeq(iters);
+ if (isKeyedIterable) {
+ concatSeq = concatSeq.toKeyedSeq();
+ } else if (!isIndexed(iterable)) {
+ concatSeq = concatSeq.toSetSeq();
+ }
+ concatSeq = concatSeq.flatten(true);
+ concatSeq.size = iters.reduce(
+ function(sum, seq) {
+ if (sum !== undefined) {
+ var size = seq.size;
+ if (size !== undefined) {
+ return sum + size;
+ }
+ }
+ },
+ 0
+ );
+ return concatSeq;
+ }
+
+
+ function flattenFactory(iterable, depth, useKeys) {
+ var flatSequence = makeSequence(iterable);
+ flatSequence.__iterateUncached = function(fn, reverse) {
+ var iterations = 0;
+ var stopped = false;
+ function flatDeep(iter, currentDepth) {var this$0 = this;
+ iter.__iterate(function(v, k) {
+ if ((!depth || currentDepth < depth) && isIterable(v)) {
+ flatDeep(v, currentDepth + 1);
+ } else if (fn(v, useKeys ? k : iterations++, this$0) === false) {
+ stopped = true;
+ }
+ return !stopped;
+ }, reverse);
+ }
+ flatDeep(iterable, 0);
+ return iterations;
+ }
+ flatSequence.__iteratorUncached = function(type, reverse) {
+ var iterator = iterable.__iterator(type, reverse);
+ var stack = [];
+ var iterations = 0;
+ return new Iterator(function() {
+ while (iterator) {
+ var step = iterator.next();
+ if (step.done !== false) {
+ iterator = stack.pop();
+ continue;
+ }
+ var v = step.value;
+ if (type === ITERATE_ENTRIES) {
+ v = v[1];
+ }
+ if ((!depth || stack.length < depth) && isIterable(v)) {
+ stack.push(iterator);
+ iterator = v.__iterator(type, reverse);
+ } else {
+ return useKeys ? step : iteratorValue(type, iterations++, v, step);
+ }
+ }
+ return iteratorDone();
+ });
+ }
+ return flatSequence;
+ }
+
+
+ function flatMapFactory(iterable, mapper, context) {
+ var coerce = iterableClass(iterable);
+ return iterable.toSeq().map(
+ function(v, k) {return coerce(mapper.call(context, v, k, iterable))}
+ ).flatten(true);
+ }
+
+
+ function interposeFactory(iterable, separator) {
+ var interposedSequence = makeSequence(iterable);
+ interposedSequence.size = iterable.size && iterable.size * 2 -1;
+ interposedSequence.__iterateUncached = function(fn, reverse) {var this$0 = this;
+ var iterations = 0;
+ iterable.__iterate(function(v, k)
+ {return (!iterations || fn(separator, iterations++, this$0) !== false) &&
+ fn(v, iterations++, this$0) !== false},
+ reverse
+ );
+ return iterations;
+ };
+ interposedSequence.__iteratorUncached = function(type, reverse) {
+ var iterator = iterable.__iterator(ITERATE_VALUES, reverse);
+ var iterations = 0;
+ var step;
+ return new Iterator(function() {
+ if (!step || iterations % 2) {
+ step = iterator.next();
+ if (step.done) {
+ return step;
+ }
+ }
+ return iterations % 2 ?
+ iteratorValue(type, iterations++, separator) :
+ iteratorValue(type, iterations++, step.value, step);
+ });
+ };
+ return interposedSequence;
+ }
+
+
+ function sortFactory(iterable, comparator, mapper) {
+ if (!comparator) {
+ comparator = defaultComparator;
+ }
+ var isKeyedIterable = isKeyed(iterable);
+ var index = 0;
+ var entries = iterable.toSeq().map(
+ function(v, k) {return [k, v, index++, mapper ? mapper(v, k, iterable) : v]}
+ ).toArray();
+ entries.sort(function(a, b) {return comparator(a[3], b[3]) || a[2] - b[2]}).forEach(
+ isKeyedIterable ?
+ function(v, i) { entries[i].length = 2; } :
+ function(v, i) { entries[i] = v[1]; }
+ );
+ return isKeyedIterable ? KeyedSeq(entries) :
+ isIndexed(iterable) ? IndexedSeq(entries) :
+ SetSeq(entries);
+ }
+
+
+ function maxFactory(iterable, comparator, mapper) {
+ if (!comparator) {
+ comparator = defaultComparator;
+ }
+ if (mapper) {
+ var entry = iterable.toSeq()
+ .map(function(v, k) {return [v, mapper(v, k, iterable)]})
+ .reduce(function(a, b) {return maxCompare(comparator, a[1], b[1]) ? b : a});
+ return entry && entry[0];
+ } else {
+ return iterable.reduce(function(a, b) {return maxCompare(comparator, a, b) ? b : a});
+ }
+ }
+
+ function maxCompare(comparator, a, b) {
+ var comp = comparator(b, a);
+ // b is considered the new max if the comparator declares them equal, but
+ // they are not equal and b is in fact a nullish value.
+ return (comp === 0 && b !== a && (b === undefined || b === null || b !== b)) || comp > 0;
+ }
+
+
+ function zipWithFactory(keyIter, zipper, iters) {
+ var zipSequence = makeSequence(keyIter);
+ zipSequence.size = new ArraySeq(iters).map(function(i ) {return i.size}).min();
+ // Note: this a generic base implementation of __iterate in terms of
+ // __iterator which may be more generically useful in the future.
+ zipSequence.__iterate = function(fn, reverse) {
+ /* generic:
+ var iterator = this.__iterator(ITERATE_ENTRIES, reverse);
+ var step;
+ var iterations = 0;
+ while (!(step = iterator.next()).done) {
+ iterations++;
+ if (fn(step.value[1], step.value[0], this) === false) {
+ break;
+ }
+ }
+ return iterations;
+ */
+ // indexed:
+ var iterator = this.__iterator(ITERATE_VALUES, reverse);
+ var step;
+ var iterations = 0;
+ while (!(step = iterator.next()).done) {
+ if (fn(step.value, iterations++, this) === false) {
+ break;
+ }
+ }
+ return iterations;
+ };
+ zipSequence.__iteratorUncached = function(type, reverse) {
+ var iterators = iters.map(function(i )
+ {return (i = Iterable(i), getIterator(reverse ? i.reverse() : i))}
+ );
+ var iterations = 0;
+ var isDone = false;
+ return new Iterator(function() {
+ var steps;
+ if (!isDone) {
+ steps = iterators.map(function(i ) {return i.next()});
+ isDone = steps.some(function(s ) {return s.done});
+ }
+ if (isDone) {
+ return iteratorDone();
+ }
+ return iteratorValue(
+ type,
+ iterations++,
+ zipper.apply(null, steps.map(function(s ) {return s.value}))
+ );
+ });
+ };
+ return zipSequence
+ }
+
+
+ // #pragma Helper Functions
+
+ function reify(iter, seq) {
+ return isSeq(iter) ? seq : iter.constructor(seq);
+ }
+
+ function validateEntry(entry) {
+ if (entry !== Object(entry)) {
+ throw new TypeError('Expected [K, V] tuple: ' + entry);
+ }
+ }
+
+ function resolveSize(iter) {
+ assertNotInfinite(iter.size);
+ return ensureSize(iter);
+ }
+
+ function iterableClass(iterable) {
+ return isKeyed(iterable) ? KeyedIterable :
+ isIndexed(iterable) ? IndexedIterable :
+ SetIterable;
+ }
+
+ function makeSequence(iterable) {
+ return Object.create(
+ (
+ isKeyed(iterable) ? KeyedSeq :
+ isIndexed(iterable) ? IndexedSeq :
+ SetSeq
+ ).prototype
+ );
+ }
+
+ function cacheResultThrough() {
+ if (this._iter.cacheResult) {
+ this._iter.cacheResult();
+ this.size = this._iter.size;
+ return this;
+ } else {
+ return Seq.prototype.cacheResult.call(this);
+ }
+ }
+
+ function defaultComparator(a, b) {
+ return a > b ? 1 : a < b ? -1 : 0;
+ }
+
+ function forceIterator(keyPath) {
+ var iter = getIterator(keyPath);
+ if (!iter) {
+ // Array might not be iterable in this environment, so we need a fallback
+ // to our wrapped type.
+ if (!isArrayLike(keyPath)) {
+ throw new TypeError('Expected iterable or array-like: ' + keyPath);
+ }
+ iter = getIterator(Iterable(keyPath));
+ }
+ return iter;
+ }
+
+ createClass(Record, KeyedCollection);
+
+ function Record(defaultValues, name) {
+ var hasInitialized;
+
+ var RecordType = function Record(values) {
+ if (values instanceof RecordType) {
+ return values;
+ }
+ if (!(this instanceof RecordType)) {
+ return new RecordType(values);
+ }
+ if (!hasInitialized) {
+ hasInitialized = true;
+ var keys = Object.keys(defaultValues);
+ setProps(RecordTypePrototype, keys);
+ RecordTypePrototype.size = keys.length;
+ RecordTypePrototype._name = name;
+ RecordTypePrototype._keys = keys;
+ RecordTypePrototype._defaultValues = defaultValues;
+ }
+ this._map = Map(values);
+ };
+
+ var RecordTypePrototype = RecordType.prototype = Object.create(RecordPrototype);
+ RecordTypePrototype.constructor = RecordType;
+
+ return RecordType;
+ }
+
+ Record.prototype.toString = function() {
+ return this.__toString(recordName(this) + ' {', '}');
+ };
+
+ // @pragma Access
+
+ Record.prototype.has = function(k) {
+ return this._defaultValues.hasOwnProperty(k);
+ };
+
+ Record.prototype.get = function(k, notSetValue) {
+ if (!this.has(k)) {
+ return notSetValue;
+ }
+ var defaultVal = this._defaultValues[k];
+ return this._map ? this._map.get(k, defaultVal) : defaultVal;
+ };
+
+ // @pragma Modification
+
+ Record.prototype.clear = function() {
+ if (this.__ownerID) {
+ this._map && this._map.clear();
+ return this;
+ }
+ var RecordType = this.constructor;
+ return RecordType._empty || (RecordType._empty = makeRecord(this, emptyMap()));
+ };
+
+ Record.prototype.set = function(k, v) {
+ if (!this.has(k)) {
+ throw new Error('Cannot set unknown key "' + k + '" on ' + recordName(this));
+ }
+ if (this._map && !this._map.has(k)) {
+ var defaultVal = this._defaultValues[k];
+ if (v === defaultVal) {
+ return this;
+ }
+ }
+ var newMap = this._map && this._map.set(k, v);
+ if (this.__ownerID || newMap === this._map) {
+ return this;
+ }
+ return makeRecord(this, newMap);
+ };
+
+ Record.prototype.remove = function(k) {
+ if (!this.has(k)) {
+ return this;
+ }
+ var newMap = this._map && this._map.remove(k);
+ if (this.__ownerID || newMap === this._map) {
+ return this;
+ }
+ return makeRecord(this, newMap);
+ };
+
+ Record.prototype.wasAltered = function() {
+ return this._map.wasAltered();
+ };
+
+ Record.prototype.__iterator = function(type, reverse) {var this$0 = this;
+ return KeyedIterable(this._defaultValues).map(function(_, k) {return this$0.get(k)}).__iterator(type, reverse);
+ };
+
+ Record.prototype.__iterate = function(fn, reverse) {var this$0 = this;
+ return KeyedIterable(this._defaultValues).map(function(_, k) {return this$0.get(k)}).__iterate(fn, reverse);
+ };
+
+ Record.prototype.__ensureOwner = function(ownerID) {
+ if (ownerID === this.__ownerID) {
+ return this;
+ }
+ var newMap = this._map && this._map.__ensureOwner(ownerID);
+ if (!ownerID) {
+ this.__ownerID = ownerID;
+ this._map = newMap;
+ return this;
+ }
+ return makeRecord(this, newMap, ownerID);
+ };
+
+
+ var RecordPrototype = Record.prototype;
+ RecordPrototype[DELETE] = RecordPrototype.remove;
+ RecordPrototype.deleteIn =
+ RecordPrototype.removeIn = MapPrototype.removeIn;
+ RecordPrototype.merge = MapPrototype.merge;
+ RecordPrototype.mergeWith = MapPrototype.mergeWith;
+ RecordPrototype.mergeIn = MapPrototype.mergeIn;
+ RecordPrototype.mergeDeep = MapPrototype.mergeDeep;
+ RecordPrototype.mergeDeepWith = MapPrototype.mergeDeepWith;
+ RecordPrototype.mergeDeepIn = MapPrototype.mergeDeepIn;
+ RecordPrototype.setIn = MapPrototype.setIn;
+ RecordPrototype.update = MapPrototype.update;
+ RecordPrototype.updateIn = MapPrototype.updateIn;
+ RecordPrototype.withMutations = MapPrototype.withMutations;
+ RecordPrototype.asMutable = MapPrototype.asMutable;
+ RecordPrototype.asImmutable = MapPrototype.asImmutable;
+
+
+ function makeRecord(likeRecord, map, ownerID) {
+ var record = Object.create(Object.getPrototypeOf(likeRecord));
+ record._map = map;
+ record.__ownerID = ownerID;
+ return record;
+ }
+
+ function recordName(record) {
+ return record._name || record.constructor.name || 'Record';
+ }
+
+ function setProps(prototype, names) {
+ try {
+ names.forEach(setProp.bind(undefined, prototype));
+ } catch (error) {
+ // Object.defineProperty failed. Probably IE8.
+ }
+ }
+
+ function setProp(prototype, name) {
+ Object.defineProperty(prototype, name, {
+ get: function() {
+ return this.get(name);
+ },
+ set: function(value) {
+ invariant(this.__ownerID, 'Cannot set on an immutable record.');
+ this.set(name, value);
+ }
+ });
+ }
+
+ createClass(Set, SetCollection);
+
+ // @pragma Construction
+
+ function Set(value) {
+ return value === null || value === undefined ? emptySet() :
+ isSet(value) && !isOrdered(value) ? value :
+ emptySet().withMutations(function(set ) {
+ var iter = SetIterable(value);
+ assertNotInfinite(iter.size);
+ iter.forEach(function(v ) {return set.add(v)});
+ });
+ }
+
+ Set.of = function(/*...values*/) {
+ return this(arguments);
+ };
+
+ Set.fromKeys = function(value) {
+ return this(KeyedIterable(value).keySeq());
+ };
+
+ Set.prototype.toString = function() {
+ return this.__toString('Set {', '}');
+ };
+
+ // @pragma Access
+
+ Set.prototype.has = function(value) {
+ return this._map.has(value);
+ };
+
+ // @pragma Modification
+
+ Set.prototype.add = function(value) {
+ return updateSet(this, this._map.set(value, true));
+ };
+
+ Set.prototype.remove = function(value) {
+ return updateSet(this, this._map.remove(value));
+ };
+
+ Set.prototype.clear = function() {
+ return updateSet(this, this._map.clear());
+ };
+
+ // @pragma Composition
+
+ Set.prototype.union = function() {var iters = SLICE$0.call(arguments, 0);
+ iters = iters.filter(function(x ) {return x.size !== 0});
+ if (iters.length === 0) {
+ return this;
+ }
+ if (this.size === 0 && !this.__ownerID && iters.length === 1) {
+ return this.constructor(iters[0]);
+ }
+ return this.withMutations(function(set ) {
+ for (var ii = 0; ii < iters.length; ii++) {
+ SetIterable(iters[ii]).forEach(function(value ) {return set.add(value)});
+ }
+ });
+ };
+
+ Set.prototype.intersect = function() {var iters = SLICE$0.call(arguments, 0);
+ if (iters.length === 0) {
+ return this;
+ }
+ iters = iters.map(function(iter ) {return SetIterable(iter)});
+ var originalSet = this;
+ return this.withMutations(function(set ) {
+ originalSet.forEach(function(value ) {
+ if (!iters.every(function(iter ) {return iter.includes(value)})) {
+ set.remove(value);
+ }
+ });
+ });
+ };
+
+ Set.prototype.subtract = function() {var iters = SLICE$0.call(arguments, 0);
+ if (iters.length === 0) {
+ return this;
+ }
+ iters = iters.map(function(iter ) {return SetIterable(iter)});
+ var originalSet = this;
+ return this.withMutations(function(set ) {
+ originalSet.forEach(function(value ) {
+ if (iters.some(function(iter ) {return iter.includes(value)})) {
+ set.remove(value);
+ }
+ });
+ });
+ };
+
+ Set.prototype.merge = function() {
+ return this.union.apply(this, arguments);
+ };
+
+ Set.prototype.mergeWith = function(merger) {var iters = SLICE$0.call(arguments, 1);
+ return this.union.apply(this, iters);
+ };
+
+ Set.prototype.sort = function(comparator) {
+ // Late binding
+ return OrderedSet(sortFactory(this, comparator));
+ };
+
+ Set.prototype.sortBy = function(mapper, comparator) {
+ // Late binding
+ return OrderedSet(sortFactory(this, comparator, mapper));
+ };
+
+ Set.prototype.wasAltered = function() {
+ return this._map.wasAltered();
+ };
+
+ Set.prototype.__iterate = function(fn, reverse) {var this$0 = this;
+ return this._map.__iterate(function(_, k) {return fn(k, k, this$0)}, reverse);
+ };
+
+ Set.prototype.__iterator = function(type, reverse) {
+ return this._map.map(function(_, k) {return k}).__iterator(type, reverse);
+ };
+
+ Set.prototype.__ensureOwner = function(ownerID) {
+ if (ownerID === this.__ownerID) {
+ return this;
+ }
+ var newMap = this._map.__ensureOwner(ownerID);
+ if (!ownerID) {
+ this.__ownerID = ownerID;
+ this._map = newMap;
+ return this;
+ }
+ return this.__make(newMap, ownerID);
+ };
+
+
+ function isSet(maybeSet) {
+ return !!(maybeSet && maybeSet[IS_SET_SENTINEL]);
+ }
+
+ Set.isSet = isSet;
+
+ var IS_SET_SENTINEL = '@@__IMMUTABLE_SET__@@';
+
+ var SetPrototype = Set.prototype;
+ SetPrototype[IS_SET_SENTINEL] = true;
+ SetPrototype[DELETE] = SetPrototype.remove;
+ SetPrototype.mergeDeep = SetPrototype.merge;
+ SetPrototype.mergeDeepWith = SetPrototype.mergeWith;
+ SetPrototype.withMutations = MapPrototype.withMutations;
+ SetPrototype.asMutable = MapPrototype.asMutable;
+ SetPrototype.asImmutable = MapPrototype.asImmutable;
+
+ SetPrototype.__empty = emptySet;
+ SetPrototype.__make = makeSet;
+
+ function updateSet(set, newMap) {
+ if (set.__ownerID) {
+ set.size = newMap.size;
+ set._map = newMap;
+ return set;
+ }
+ return newMap === set._map ? set :
+ newMap.size === 0 ? set.__empty() :
+ set.__make(newMap);
+ }
+
+ function makeSet(map, ownerID) {
+ var set = Object.create(SetPrototype);
+ set.size = map ? map.size : 0;
+ set._map = map;
+ set.__ownerID = ownerID;
+ return set;
+ }
+
+ var EMPTY_SET;
+ function emptySet() {
+ return EMPTY_SET || (EMPTY_SET = makeSet(emptyMap()));
+ }
+
+ createClass(OrderedSet, Set);
+
+ // @pragma Construction
+
+ function OrderedSet(value) {
+ return value === null || value === undefined ? emptyOrderedSet() :
+ isOrderedSet(value) ? value :
+ emptyOrderedSet().withMutations(function(set ) {
+ var iter = SetIterable(value);
+ assertNotInfinite(iter.size);
+ iter.forEach(function(v ) {return set.add(v)});
+ });
+ }
+
+ OrderedSet.of = function(/*...values*/) {
+ return this(arguments);
+ };
+
+ OrderedSet.fromKeys = function(value) {
+ return this(KeyedIterable(value).keySeq());
+ };
+
+ OrderedSet.prototype.toString = function() {
+ return this.__toString('OrderedSet {', '}');
+ };
+
+
+ function isOrderedSet(maybeOrderedSet) {
+ return isSet(maybeOrderedSet) && isOrdered(maybeOrderedSet);
+ }
+
+ OrderedSet.isOrderedSet = isOrderedSet;
+
+ var OrderedSetPrototype = OrderedSet.prototype;
+ OrderedSetPrototype[IS_ORDERED_SENTINEL] = true;
+
+ OrderedSetPrototype.__empty = emptyOrderedSet;
+ OrderedSetPrototype.__make = makeOrderedSet;
+
+ function makeOrderedSet(map, ownerID) {
+ var set = Object.create(OrderedSetPrototype);
+ set.size = map ? map.size : 0;
+ set._map = map;
+ set.__ownerID = ownerID;
+ return set;
+ }
+
+ var EMPTY_ORDERED_SET;
+ function emptyOrderedSet() {
+ return EMPTY_ORDERED_SET || (EMPTY_ORDERED_SET = makeOrderedSet(emptyOrderedMap()));
+ }
+
+ createClass(Stack, IndexedCollection);
+
+ // @pragma Construction
+
+ function Stack(value) {
+ return value === null || value === undefined ? emptyStack() :
+ isStack(value) ? value :
+ emptyStack().unshiftAll(value);
+ }
+
+ Stack.of = function(/*...values*/) {
+ return this(arguments);
+ };
+
+ Stack.prototype.toString = function() {
+ return this.__toString('Stack [', ']');
+ };
+
+ // @pragma Access
+
+ Stack.prototype.get = function(index, notSetValue) {
+ var head = this._head;
+ index = wrapIndex(this, index);
+ while (head && index--) {
+ head = head.next;
+ }
+ return head ? head.value : notSetValue;
+ };
+
+ Stack.prototype.peek = function() {
+ return this._head && this._head.value;
+ };
+
+ // @pragma Modification
+
+ Stack.prototype.push = function(/*...values*/) {
+ if (arguments.length === 0) {
+ return this;
+ }
+ var newSize = this.size + arguments.length;
+ var head = this._head;
+ for (var ii = arguments.length - 1; ii >= 0; ii--) {
+ head = {
+ value: arguments[ii],
+ next: head
+ };
+ }
+ if (this.__ownerID) {
+ this.size = newSize;
+ this._head = head;
+ this.__hash = undefined;
+ this.__altered = true;
+ return this;
+ }
+ return makeStack(newSize, head);
+ };
+
+ Stack.prototype.pushAll = function(iter) {
+ iter = IndexedIterable(iter);
+ if (iter.size === 0) {
+ return this;
+ }
+ assertNotInfinite(iter.size);
+ var newSize = this.size;
+ var head = this._head;
+ iter.reverse().forEach(function(value ) {
+ newSize++;
+ head = {
+ value: value,
+ next: head
+ };
+ });
+ if (this.__ownerID) {
+ this.size = newSize;
+ this._head = head;
+ this.__hash = undefined;
+ this.__altered = true;
+ return this;
+ }
+ return makeStack(newSize, head);
+ };
+
+ Stack.prototype.pop = function() {
+ return this.slice(1);
+ };
+
+ Stack.prototype.unshift = function(/*...values*/) {
+ return this.push.apply(this, arguments);
+ };
+
+ Stack.prototype.unshiftAll = function(iter) {
+ return this.pushAll(iter);
+ };
+
+ Stack.prototype.shift = function() {
+ return this.pop.apply(this, arguments);
+ };
+
+ Stack.prototype.clear = function() {
+ if (this.size === 0) {
+ return this;
+ }
+ if (this.__ownerID) {
+ this.size = 0;
+ this._head = undefined;
+ this.__hash = undefined;
+ this.__altered = true;
+ return this;
+ }
+ return emptyStack();
+ };
+
+ Stack.prototype.slice = function(begin, end) {
+ if (wholeSlice(begin, end, this.size)) {
+ return this;
+ }
+ var resolvedBegin = resolveBegin(begin, this.size);
+ var resolvedEnd = resolveEnd(end, this.size);
+ if (resolvedEnd !== this.size) {
+ // super.slice(begin, end);
+ return IndexedCollection.prototype.slice.call(this, begin, end);
+ }
+ var newSize = this.size - resolvedBegin;
+ var head = this._head;
+ while (resolvedBegin--) {
+ head = head.next;
+ }
+ if (this.__ownerID) {
+ this.size = newSize;
+ this._head = head;
+ this.__hash = undefined;
+ this.__altered = true;
+ return this;
+ }
+ return makeStack(newSize, head);
+ };
+
+ // @pragma Mutability
+
+ Stack.prototype.__ensureOwner = function(ownerID) {
+ if (ownerID === this.__ownerID) {
+ return this;
+ }
+ if (!ownerID) {
+ this.__ownerID = ownerID;
+ this.__altered = false;
+ return this;
+ }
+ return makeStack(this.size, this._head, ownerID, this.__hash);
+ };
+
+ // @pragma Iteration
+
+ Stack.prototype.__iterate = function(fn, reverse) {
+ if (reverse) {
+ return this.reverse().__iterate(fn);
+ }
+ var iterations = 0;
+ var node = this._head;
+ while (node) {
+ if (fn(node.value, iterations++, this) === false) {
+ break;
+ }
+ node = node.next;
+ }
+ return iterations;
+ };
+
+ Stack.prototype.__iterator = function(type, reverse) {
+ if (reverse) {
+ return this.reverse().__iterator(type);
+ }
+ var iterations = 0;
+ var node = this._head;
+ return new Iterator(function() {
+ if (node) {
+ var value = node.value;
+ node = node.next;
+ return iteratorValue(type, iterations++, value);
+ }
+ return iteratorDone();
+ });
+ };
+
+
+ function isStack(maybeStack) {
+ return !!(maybeStack && maybeStack[IS_STACK_SENTINEL]);
+ }
+
+ Stack.isStack = isStack;
+
+ var IS_STACK_SENTINEL = '@@__IMMUTABLE_STACK__@@';
+
+ var StackPrototype = Stack.prototype;
+ StackPrototype[IS_STACK_SENTINEL] = true;
+ StackPrototype.withMutations = MapPrototype.withMutations;
+ StackPrototype.asMutable = MapPrototype.asMutable;
+ StackPrototype.asImmutable = MapPrototype.asImmutable;
+ StackPrototype.wasAltered = MapPrototype.wasAltered;
+
+
+ function makeStack(size, head, ownerID, hash) {
+ var map = Object.create(StackPrototype);
+ map.size = size;
+ map._head = head;
+ map.__ownerID = ownerID;
+ map.__hash = hash;
+ map.__altered = false;
+ return map;
+ }
+
+ var EMPTY_STACK;
+ function emptyStack() {
+ return EMPTY_STACK || (EMPTY_STACK = makeStack(0));
+ }
+
+ /**
+ * Contributes additional methods to a constructor
+ */
+ function mixin(ctor, methods) {
+ var keyCopier = function(key ) { ctor.prototype[key] = methods[key]; };
+ Object.keys(methods).forEach(keyCopier);
+ Object.getOwnPropertySymbols &&
+ Object.getOwnPropertySymbols(methods).forEach(keyCopier);
+ return ctor;
+ }
+
+ Iterable.Iterator = Iterator;
+
+ mixin(Iterable, {
+
+ // ### Conversion to other types
+
+ toArray: function() {
+ assertNotInfinite(this.size);
+ var array = new Array(this.size || 0);
+ this.valueSeq().__iterate(function(v, i) { array[i] = v; });
+ return array;
+ },
+
+ toIndexedSeq: function() {
+ return new ToIndexedSequence(this);
+ },
+
+ toJS: function() {
+ return this.toSeq().map(
+ function(value ) {return value && typeof value.toJS === 'function' ? value.toJS() : value}
+ ).__toJS();
+ },
+
+ toJSON: function() {
+ return this.toSeq().map(
+ function(value ) {return value && typeof value.toJSON === 'function' ? value.toJSON() : value}
+ ).__toJS();
+ },
+
+ toKeyedSeq: function() {
+ return new ToKeyedSequence(this, true);
+ },
+
+ toMap: function() {
+ // Use Late Binding here to solve the circular dependency.
+ return Map(this.toKeyedSeq());
+ },
+
+ toObject: function() {
+ assertNotInfinite(this.size);
+ var object = {};
+ this.__iterate(function(v, k) { object[k] = v; });
+ return object;
+ },
+
+ toOrderedMap: function() {
+ // Use Late Binding here to solve the circular dependency.
+ return OrderedMap(this.toKeyedSeq());
+ },
+
+ toOrderedSet: function() {
+ // Use Late Binding here to solve the circular dependency.
+ return OrderedSet(isKeyed(this) ? this.valueSeq() : this);
+ },
+
+ toSet: function() {
+ // Use Late Binding here to solve the circular dependency.
+ return Set(isKeyed(this) ? this.valueSeq() : this);
+ },
+
+ toSetSeq: function() {
+ return new ToSetSequence(this);
+ },
+
+ toSeq: function() {
+ return isIndexed(this) ? this.toIndexedSeq() :
+ isKeyed(this) ? this.toKeyedSeq() :
+ this.toSetSeq();
+ },
+
+ toStack: function() {
+ // Use Late Binding here to solve the circular dependency.
+ return Stack(isKeyed(this) ? this.valueSeq() : this);
+ },
+
+ toList: function() {
+ // Use Late Binding here to solve the circular dependency.
+ return List(isKeyed(this) ? this.valueSeq() : this);
+ },
+
+
+ // ### Common JavaScript methods and properties
+
+ toString: function() {
+ return '[Iterable]';
+ },
+
+ __toString: function(head, tail) {
+ if (this.size === 0) {
+ return head + tail;
+ }
+ return head + ' ' + this.toSeq().map(this.__toStringMapper).join(', ') + ' ' + tail;
+ },
+
+
+ // ### ES6 Collection methods (ES6 Array and Map)
+
+ concat: function() {var values = SLICE$0.call(arguments, 0);
+ return reify(this, concatFactory(this, values));
+ },
+
+ includes: function(searchValue) {
+ return this.some(function(value ) {return is(value, searchValue)});
+ },
+
+ entries: function() {
+ return this.__iterator(ITERATE_ENTRIES);
+ },
+
+ every: function(predicate, context) {
+ assertNotInfinite(this.size);
+ var returnValue = true;
+ this.__iterate(function(v, k, c) {
+ if (!predicate.call(context, v, k, c)) {
+ returnValue = false;
+ return false;
+ }
+ });
+ return returnValue;
+ },
+
+ filter: function(predicate, context) {
+ return reify(this, filterFactory(this, predicate, context, true));
+ },
+
+ find: function(predicate, context, notSetValue) {
+ var entry = this.findEntry(predicate, context);
+ return entry ? entry[1] : notSetValue;
+ },
+
+ forEach: function(sideEffect, context) {
+ assertNotInfinite(this.size);
+ return this.__iterate(context ? sideEffect.bind(context) : sideEffect);
+ },
+
+ join: function(separator) {
+ assertNotInfinite(this.size);
+ separator = separator !== undefined ? '' + separator : ',';
+ var joined = '';
+ var isFirst = true;
+ this.__iterate(function(v ) {
+ isFirst ? (isFirst = false) : (joined += separator);
+ joined += v !== null && v !== undefined ? v.toString() : '';
+ });
+ return joined;
+ },
+
+ keys: function() {
+ return this.__iterator(ITERATE_KEYS);
+ },
+
+ map: function(mapper, context) {
+ return reify(this, mapFactory(this, mapper, context));
+ },
+
+ reduce: function(reducer, initialReduction, context) {
+ assertNotInfinite(this.size);
+ var reduction;
+ var useFirst;
+ if (arguments.length < 2) {
+ useFirst = true;
+ } else {
+ reduction = initialReduction;
+ }
+ this.__iterate(function(v, k, c) {
+ if (useFirst) {
+ useFirst = false;
+ reduction = v;
+ } else {
+ reduction = reducer.call(context, reduction, v, k, c);
+ }
+ });
+ return reduction;
+ },
+
+ reduceRight: function(reducer, initialReduction, context) {
+ var reversed = this.toKeyedSeq().reverse();
+ return reversed.reduce.apply(reversed, arguments);
+ },
+
+ reverse: function() {
+ return reify(this, reverseFactory(this, true));
+ },
+
+ slice: function(begin, end) {
+ return reify(this, sliceFactory(this, begin, end, true));
+ },
+
+ some: function(predicate, context) {
+ return !this.every(not(predicate), context);
+ },
+
+ sort: function(comparator) {
+ return reify(this, sortFactory(this, comparator));
+ },
+
+ values: function() {
+ return this.__iterator(ITERATE_VALUES);
+ },
+
+
+ // ### More sequential methods
+
+ butLast: function() {
+ return this.slice(0, -1);
+ },
+
+ isEmpty: function() {
+ return this.size !== undefined ? this.size === 0 : !this.some(function() {return true});
+ },
+
+ count: function(predicate, context) {
+ return ensureSize(
+ predicate ? this.toSeq().filter(predicate, context) : this
+ );
+ },
+
+ countBy: function(grouper, context) {
+ return countByFactory(this, grouper, context);
+ },
+
+ equals: function(other) {
+ return deepEqual(this, other);
+ },
+
+ entrySeq: function() {
+ var iterable = this;
+ if (iterable._cache) {
+ // We cache as an entries array, so we can just return the cache!
+ return new ArraySeq(iterable._cache);
+ }
+ var entriesSequence = iterable.toSeq().map(entryMapper).toIndexedSeq();
+ entriesSequence.fromEntrySeq = function() {return iterable.toSeq()};
+ return entriesSequence;
+ },
+
+ filterNot: function(predicate, context) {
+ return this.filter(not(predicate), context);
+ },
+
+ findEntry: function(predicate, context, notSetValue) {
+ var found = notSetValue;
+ this.__iterate(function(v, k, c) {
+ if (predicate.call(context, v, k, c)) {
+ found = [k, v];
+ return false;
+ }
+ });
+ return found;
+ },
+
+ findKey: function(predicate, context) {
+ var entry = this.findEntry(predicate, context);
+ return entry && entry[0];
+ },
+
+ findLast: function(predicate, context, notSetValue) {
+ return this.toKeyedSeq().reverse().find(predicate, context, notSetValue);
+ },
+
+ findLastEntry: function(predicate, context, notSetValue) {
+ return this.toKeyedSeq().reverse().findEntry(predicate, context, notSetValue);
+ },
+
+ findLastKey: function(predicate, context) {
+ return this.toKeyedSeq().reverse().findKey(predicate, context);
+ },
+
+ first: function() {
+ return this.find(returnTrue);
+ },
+
+ flatMap: function(mapper, context) {
+ return reify(this, flatMapFactory(this, mapper, context));
+ },
+
+ flatten: function(depth) {
+ return reify(this, flattenFactory(this, depth, true));
+ },
+
+ fromEntrySeq: function() {
+ return new FromEntriesSequence(this);
+ },
+
+ get: function(searchKey, notSetValue) {
+ return this.find(function(_, key) {return is(key, searchKey)}, undefined, notSetValue);
+ },
+
+ getIn: function(searchKeyPath, notSetValue) {
+ var nested = this;
+ // Note: in an ES6 environment, we would prefer:
+ // for (var key of searchKeyPath) {
+ var iter = forceIterator(searchKeyPath);
+ var step;
+ while (!(step = iter.next()).done) {
+ var key = step.value;
+ nested = nested && nested.get ? nested.get(key, NOT_SET) : NOT_SET;
+ if (nested === NOT_SET) {
+ return notSetValue;
+ }
+ }
+ return nested;
+ },
+
+ groupBy: function(grouper, context) {
+ return groupByFactory(this, grouper, context);
+ },
+
+ has: function(searchKey) {
+ return this.get(searchKey, NOT_SET) !== NOT_SET;
+ },
+
+ hasIn: function(searchKeyPath) {
+ return this.getIn(searchKeyPath, NOT_SET) !== NOT_SET;
+ },
+
+ isSubset: function(iter) {
+ iter = typeof iter.includes === 'function' ? iter : Iterable(iter);
+ return this.every(function(value ) {return iter.includes(value)});
+ },
+
+ isSuperset: function(iter) {
+ iter = typeof iter.isSubset === 'function' ? iter : Iterable(iter);
+ return iter.isSubset(this);
+ },
+
+ keyOf: function(searchValue) {
+ return this.findKey(function(value ) {return is(value, searchValue)});
+ },
+
+ keySeq: function() {
+ return this.toSeq().map(keyMapper).toIndexedSeq();
+ },
+
+ last: function() {
+ return this.toSeq().reverse().first();
+ },
+
+ lastKeyOf: function(searchValue) {
+ return this.toKeyedSeq().reverse().keyOf(searchValue);
+ },
+
+ max: function(comparator) {
+ return maxFactory(this, comparator);
+ },
+
+ maxBy: function(mapper, comparator) {
+ return maxFactory(this, comparator, mapper);
+ },
+
+ min: function(comparator) {
+ return maxFactory(this, comparator ? neg(comparator) : defaultNegComparator);
+ },
+
+ minBy: function(mapper, comparator) {
+ return maxFactory(this, comparator ? neg(comparator) : defaultNegComparator, mapper);
+ },
+
+ rest: function() {
+ return this.slice(1);
+ },
+
+ skip: function(amount) {
+ return this.slice(Math.max(0, amount));
+ },
+
+ skipLast: function(amount) {
+ return reify(this, this.toSeq().reverse().skip(amount).reverse());
+ },
+
+ skipWhile: function(predicate, context) {
+ return reify(this, skipWhileFactory(this, predicate, context, true));
+ },
+
+ skipUntil: function(predicate, context) {
+ return this.skipWhile(not(predicate), context);
+ },
+
+ sortBy: function(mapper, comparator) {
+ return reify(this, sortFactory(this, comparator, mapper));
+ },
+
+ take: function(amount) {
+ return this.slice(0, Math.max(0, amount));
+ },
+
+ takeLast: function(amount) {
+ return reify(this, this.toSeq().reverse().take(amount).reverse());
+ },
+
+ takeWhile: function(predicate, context) {
+ return reify(this, takeWhileFactory(this, predicate, context));
+ },
+
+ takeUntil: function(predicate, context) {
+ return this.takeWhile(not(predicate), context);
+ },
+
+ valueSeq: function() {
+ return this.toIndexedSeq();
+ },
+
+
+ // ### Hashable Object
+
+ hashCode: function() {
+ return this.__hash || (this.__hash = hashIterable(this));
+ }
+
+
+ // ### Internal
+
+ // abstract __iterate(fn, reverse)
+
+ // abstract __iterator(type, reverse)
+ });
+
+ // var IS_ITERABLE_SENTINEL = '@@__IMMUTABLE_ITERABLE__@@';
+ // var IS_KEYED_SENTINEL = '@@__IMMUTABLE_KEYED__@@';
+ // var IS_INDEXED_SENTINEL = '@@__IMMUTABLE_INDEXED__@@';
+ // var IS_ORDERED_SENTINEL = '@@__IMMUTABLE_ORDERED__@@';
+
+ var IterablePrototype = Iterable.prototype;
+ IterablePrototype[IS_ITERABLE_SENTINEL] = true;
+ IterablePrototype[ITERATOR_SYMBOL] = IterablePrototype.values;
+ IterablePrototype.__toJS = IterablePrototype.toArray;
+ IterablePrototype.__toStringMapper = quoteString;
+ IterablePrototype.inspect =
+ IterablePrototype.toSource = function() { return this.toString(); };
+ IterablePrototype.chain = IterablePrototype.flatMap;
+ IterablePrototype.contains = IterablePrototype.includes;
+
+ mixin(KeyedIterable, {
+
+ // ### More sequential methods
+
+ flip: function() {
+ return reify(this, flipFactory(this));
+ },
+
+ mapEntries: function(mapper, context) {var this$0 = this;
+ var iterations = 0;
+ return reify(this,
+ this.toSeq().map(
+ function(v, k) {return mapper.call(context, [k, v], iterations++, this$0)}
+ ).fromEntrySeq()
+ );
+ },
+
+ mapKeys: function(mapper, context) {var this$0 = this;
+ return reify(this,
+ this.toSeq().flip().map(
+ function(k, v) {return mapper.call(context, k, v, this$0)}
+ ).flip()
+ );
+ }
+
+ });
+
+ var KeyedIterablePrototype = KeyedIterable.prototype;
+ KeyedIterablePrototype[IS_KEYED_SENTINEL] = true;
+ KeyedIterablePrototype[ITERATOR_SYMBOL] = IterablePrototype.entries;
+ KeyedIterablePrototype.__toJS = IterablePrototype.toObject;
+ KeyedIterablePrototype.__toStringMapper = function(v, k) {return JSON.stringify(k) + ': ' + quoteString(v)};
+
+
+
+ mixin(IndexedIterable, {
+
+ // ### Conversion to other types
+
+ toKeyedSeq: function() {
+ return new ToKeyedSequence(this, false);
+ },
+
+
+ // ### ES6 Collection methods (ES6 Array and Map)
+
+ filter: function(predicate, context) {
+ return reify(this, filterFactory(this, predicate, context, false));
+ },
+
+ findIndex: function(predicate, context) {
+ var entry = this.findEntry(predicate, context);
+ return entry ? entry[0] : -1;
+ },
+
+ indexOf: function(searchValue) {
+ var key = this.keyOf(searchValue);
+ return key === undefined ? -1 : key;
+ },
+
+ lastIndexOf: function(searchValue) {
+ var key = this.lastKeyOf(searchValue);
+ return key === undefined ? -1 : key;
+ },
+
+ reverse: function() {
+ return reify(this, reverseFactory(this, false));
+ },
+
+ slice: function(begin, end) {
+ return reify(this, sliceFactory(this, begin, end, false));
+ },
+
+ splice: function(index, removeNum /*, ...values*/) {
+ var numArgs = arguments.length;
+ removeNum = Math.max(removeNum | 0, 0);
+ if (numArgs === 0 || (numArgs === 2 && !removeNum)) {
+ return this;
+ }
+ // If index is negative, it should resolve relative to the size of the
+ // collection. However size may be expensive to compute if not cached, so
+ // only call count() if the number is in fact negative.
+ index = resolveBegin(index, index < 0 ? this.count() : this.size);
+ var spliced = this.slice(0, index);
+ return reify(
+ this,
+ numArgs === 1 ?
+ spliced :
+ spliced.concat(arrCopy(arguments, 2), this.slice(index + removeNum))
+ );
+ },
+
+
+ // ### More collection methods
+
+ findLastIndex: function(predicate, context) {
+ var entry = this.findLastEntry(predicate, context);
+ return entry ? entry[0] : -1;
+ },
+
+ first: function() {
+ return this.get(0);
+ },
+
+ flatten: function(depth) {
+ return reify(this, flattenFactory(this, depth, false));
+ },
+
+ get: function(index, notSetValue) {
+ index = wrapIndex(this, index);
+ return (index < 0 || (this.size === Infinity ||
+ (this.size !== undefined && index > this.size))) ?
+ notSetValue :
+ this.find(function(_, key) {return key === index}, undefined, notSetValue);
+ },
+
+ has: function(index) {
+ index = wrapIndex(this, index);
+ return index >= 0 && (this.size !== undefined ?
+ this.size === Infinity || index < this.size :
+ this.indexOf(index) !== -1
+ );
+ },
+
+ interpose: function(separator) {
+ return reify(this, interposeFactory(this, separator));
+ },
+
+ interleave: function(/*...iterables*/) {
+ var iterables = [this].concat(arrCopy(arguments));
+ var zipped = zipWithFactory(this.toSeq(), IndexedSeq.of, iterables);
+ var interleaved = zipped.flatten(true);
+ if (zipped.size) {
+ interleaved.size = zipped.size * iterables.length;
+ }
+ return reify(this, interleaved);
+ },
+
+ keySeq: function() {
+ return Range(0, this.size);
+ },
+
+ last: function() {
+ return this.get(-1);
+ },
+
+ skipWhile: function(predicate, context) {
+ return reify(this, skipWhileFactory(this, predicate, context, false));
+ },
+
+ zip: function(/*, ...iterables */) {
+ var iterables = [this].concat(arrCopy(arguments));
+ return reify(this, zipWithFactory(this, defaultZipper, iterables));
+ },
+
+ zipWith: function(zipper/*, ...iterables */) {
+ var iterables = arrCopy(arguments);
+ iterables[0] = this;
+ return reify(this, zipWithFactory(this, zipper, iterables));
+ }
+
+ });
+
+ IndexedIterable.prototype[IS_INDEXED_SENTINEL] = true;
+ IndexedIterable.prototype[IS_ORDERED_SENTINEL] = true;
+
+
+
+ mixin(SetIterable, {
+
+ // ### ES6 Collection methods (ES6 Array and Map)
+
+ get: function(value, notSetValue) {
+ return this.has(value) ? value : notSetValue;
+ },
+
+ includes: function(value) {
+ return this.has(value);
+ },
+
+
+ // ### More sequential methods
+
+ keySeq: function() {
+ return this.valueSeq();
+ }
+
+ });
+
+ SetIterable.prototype.has = IterablePrototype.includes;
+ SetIterable.prototype.contains = SetIterable.prototype.includes;
+
+
+ // Mixin subclasses
+
+ mixin(KeyedSeq, KeyedIterable.prototype);
+ mixin(IndexedSeq, IndexedIterable.prototype);
+ mixin(SetSeq, SetIterable.prototype);
+
+ mixin(KeyedCollection, KeyedIterable.prototype);
+ mixin(IndexedCollection, IndexedIterable.prototype);
+ mixin(SetCollection, SetIterable.prototype);
+
+
+ // #pragma Helper functions
+
+ function keyMapper(v, k) {
+ return k;
+ }
+
+ function entryMapper(v, k) {
+ return [k, v];
+ }
+
+ function not(predicate) {
+ return function() {
+ return !predicate.apply(this, arguments);
+ }
+ }
+
+ function neg(predicate) {
+ return function() {
+ return -predicate.apply(this, arguments);
+ }
+ }
+
+ function quoteString(value) {
+ return typeof value === 'string' ? JSON.stringify(value) : String(value);
+ }
+
+ function defaultZipper() {
+ return arrCopy(arguments);
+ }
+
+ function defaultNegComparator(a, b) {
+ return a < b ? 1 : a > b ? -1 : 0;
+ }
+
+ function hashIterable(iterable) {
+ if (iterable.size === Infinity) {
+ return 0;
+ }
+ var ordered = isOrdered(iterable);
+ var keyed = isKeyed(iterable);
+ var h = ordered ? 1 : 0;
+ var size = iterable.__iterate(
+ keyed ?
+ ordered ?
+ function(v, k) { h = 31 * h + hashMerge(hash(v), hash(k)) | 0; } :
+ function(v, k) { h = h + hashMerge(hash(v), hash(k)) | 0; } :
+ ordered ?
+ function(v ) { h = 31 * h + hash(v) | 0; } :
+ function(v ) { h = h + hash(v) | 0; }
+ );
+ return murmurHashOfSize(size, h);
+ }
+
+ function murmurHashOfSize(size, h) {
+ h = imul(h, 0xCC9E2D51);
+ h = imul(h << 15 | h >>> -15, 0x1B873593);
+ h = imul(h << 13 | h >>> -13, 5);
+ h = (h + 0xE6546B64 | 0) ^ size;
+ h = imul(h ^ h >>> 16, 0x85EBCA6B);
+ h = imul(h ^ h >>> 13, 0xC2B2AE35);
+ h = smi(h ^ h >>> 16);
+ return h;
+ }
+
+ function hashMerge(a, b) {
+ return a ^ b + 0x9E3779B9 + (a << 6) + (a >> 2) | 0; // int
+ }
+
+ var Immutable = {
+
+ Iterable: Iterable,
+
+ Seq: Seq,
+ Collection: Collection,
+ Map: Map,
+ OrderedMap: OrderedMap,
+ List: List,
+ Stack: Stack,
+ Set: Set,
+ OrderedSet: OrderedSet,
+
+ Record: Record,
+ Range: Range,
+ Repeat: Repeat,
+
+ is: is,
+ fromJS: fromJS
+
+ };
+
+ return Immutable;
+
+ }));
+
+/***/ },
+/* 230 */
+/***/ function(module, exports, __webpack_require__) {
+
+ var Immutable = __webpack_require__(229);
+
+ // When our app state is fully types, we should be able to get rid of
+ // this function. This is only temporarily necessary to support
+ // converting typed objects to immutable.js, which usually happens in
+ // reducers.
+ function fromJS(value) {
+ if (Array.isArray(value)) {
+ return Immutable.Seq(value).map(fromJS).toList();
+ }
+ if (value && value.constructor.meta) {
+ // This adds support for tcomb objects which are native JS objects
+ // but are not "plain", so the above checks fail. Since they
+ // behave the same we can use the same constructors, but we need
+ // special checks for them.
+ var kind = value.constructor.meta.kind;
+ if (kind === "struct") {
+ return Immutable.Seq(value).map(fromJS).toMap();
+ } else if (kind === "list") {
+ return Immutable.Seq(value).map(fromJS).toList();
+ }
+ }
+
+ // If it's a primitive type, just return the value. Note `==` check
+ // for null, which is intentionally used to match either `null` or
+ // `undefined`.
+ if (value == null || typeof value !== "object") {
+ return value;
+ }
+
+ // Otherwise, treat it like an object. We can't reliably detect if
+ // it's a plain object because we might be objects from other JS
+ // contexts so `Object !== Object`.
+ return Immutable.Seq(value).map(fromJS).toMap();
+ }
+
+ module.exports = fromJS;
+
+/***/ },
+/* 231 */
+/***/ function(module, exports, __webpack_require__) {
+
+ var React = __webpack_require__(2);
+
+ var _require = __webpack_require__(19);
+
+ var connect = _require.connect;
+
+ var classnames = __webpack_require__(211);
+
+ var _require2 = __webpack_require__(232);
+
+ var getTabs = _require2.getTabs;
+
+
+ __webpack_require__(233);
+ var dom = React.DOM;
+
+ var ImPropTypes = __webpack_require__(235);
+
+ var githubUrl = "https://github.com/devtools-html/debugger.html/blob/master";
+
+ function getTabsByBrowser(tabs, browser) {
+ return tabs.valueSeq().filter(tab => tab.get("browser") == browser);
+ }
+
+ function firstTimeMessage(title, urlPart) {
+ return dom.div({ className: "footer-note" }, `First time connecting to ${ title }? Checkout out the `, dom.a({ href: `${ githubUrl }/CONTRIBUTING.md#${ urlPart }` }, "docs"), ".");
+ }
+
+ var LandingPage = React.createClass({
+ propTypes: {
+ tabs: ImPropTypes.map.isRequired
+ },
+
+ displayName: "LandingPage",
+
+ getInitialState() {
+ return {
+ selectedPane: "Firefox"
+ };
+ },
+
+ renderTabs(tabTitle, tabs, paramName) {
+ if (!tabs || tabs.count() == 0) {
+ return dom.div({}, "");
+ }
+
+ return dom.div({ className: "tab-group" }, dom.ul({ className: "tab-list" }, tabs.valueSeq().map(tab => dom.li({ "className": "tab",
+ "key": tab.get("id"),
+ "onClick": () => {
+ window.location = "/?" + paramName + "=" + tab.get("id");
+ } }, dom.div({ className: "tab-title" }, tab.get("title")), dom.div({ className: "tab-url" }, tab.get("url"))))));
+ },
+
+ renderFirefoxPanel() {
+ var targets = getTabsByBrowser(this.props.tabs, "firefox");
+ return dom.div({ className: "center" }, this.renderTabs("", targets, "firefox-tab"), firstTimeMessage("Firefox", "firefox"));
+ },
+
+ renderChromePanel() {
+ var targets = getTabsByBrowser(this.props.tabs, "chrome");
+ return dom.div({ className: "center" }, this.renderTabs("", targets, "chrome-tab"), firstTimeMessage("Chrome", "chrome"));
+ },
+
+ renderNodePanel() {
+ return dom.div({ className: "center" }, dom.div({ className: "center-message" }, dom.a({
+ href: `/?ws=${ document.location.hostname }:9229/node`
+ }, "Connect to Node")), firstTimeMessage("Node", "nodejs"));
+ },
+
+ renderPanel() {
+ var panels = {
+ Firefox: this.renderFirefoxPanel,
+ Chrome: this.renderChromePanel,
+ Node: this.renderNodePanel
+ };
+
+ return dom.div({
+ className: "panel"
+ }, dom.div({ className: "title" }, dom.h2({}, this.state.selectedPane)), panels[this.state.selectedPane]());
+ },
+
+ renderSidebar() {
+ return dom.div({
+ className: "sidebar"
+ }, dom.h1({}, "Debugger"), dom.ul({}, ["Firefox", "Chrome", "Node"].map(title => dom.li({
+ className: classnames({
+ selected: title == this.state.selectedPane
+ }),
+ key: title,
+
+ onClick: () => this.setState({ selectedPane: title })
+ }, dom.a({}, title)))));
+ },
+
+ render() {
+ return dom.div({
+ className: "landing-page"
+ }, this.renderSidebar(), this.renderPanel());
+ }
+ });
+
+ module.exports = connect(state => ({ tabs: getTabs(state) }))(LandingPage);
+
+/***/ },
+/* 232 */
+/***/ function(module, exports) {
+
+ function getTabs(state) {
+ return state.tabs.get("tabs");
+ }
+
+ function getSelectedTab(state) {
+ return state.tabs.get("selectedTab");
+ }
+
+ module.exports = {
+ getTabs,
+ getSelectedTab
+ };
+
+/***/ },
+/* 233 */
+/***/ function(module, exports) {
+
+ // removed by extract-text-webpack-plugin
+
+/***/ },
+/* 234 */,
+/* 235 */
+/***/ function(module, exports, __webpack_require__) {
+
+ /**
+ * This is a straight rip-off of the React.js ReactPropTypes.js proptype validators,
+ * modified to make it possible to validate Immutable.js data.
+ * ImmutableTypes.listOf is patterned after React.PropTypes.arrayOf, but for Immutable.List
+ * ImmutableTypes.shape is based on React.PropTypes.shape, but for any Immutable.Iterable
+ */
+ "use strict";
+
+ var Immutable = __webpack_require__(229);
+
+ var ANONYMOUS = "<<anonymous>>";
+
+ var ImmutablePropTypes = {
+ listOf: createListOfTypeChecker,
+ mapOf: createMapOfTypeChecker,
+ orderedMapOf: createOrderedMapOfTypeChecker,
+ setOf: createSetOfTypeChecker,
+ orderedSetOf: createOrderedSetOfTypeChecker,
+ stackOf: createStackOfTypeChecker,
+ iterableOf: createIterableOfTypeChecker,
+ recordOf: createRecordOfTypeChecker,
+ shape: createShapeChecker,
+ contains: createShapeChecker,
+ mapContains: createMapContainsChecker,
+ // Primitive Types
+ list: createImmutableTypeChecker("List", Immutable.List.isList),
+ map: createImmutableTypeChecker("Map", Immutable.Map.isMap),
+ orderedMap: createImmutableTypeChecker("OrderedMap", Immutable.OrderedMap.isOrderedMap),
+ set: createImmutableTypeChecker("Set", Immutable.Set.isSet),
+ orderedSet: createImmutableTypeChecker("OrderedSet", Immutable.OrderedSet.isOrderedSet),
+ stack: createImmutableTypeChecker("Stack", Immutable.Stack.isStack),
+ seq: createImmutableTypeChecker("Seq", Immutable.Seq.isSeq),
+ record: createImmutableTypeChecker("Record", function (isRecord) {
+ return isRecord instanceof Immutable.Record;
+ }),
+ iterable: createImmutableTypeChecker("Iterable", Immutable.Iterable.isIterable)
+ };
+
+ function getPropType(propValue) {
+ var propType = typeof propValue;
+ if (Array.isArray(propValue)) {
+ return "array";
+ }
+ if (propValue instanceof RegExp) {
+ // Old webkits (at least until Android 4.0) return 'function' rather than
+ // 'object' for typeof a RegExp. We'll normalize this here so that /bla/
+ // passes PropTypes.object.
+ return "object";
+ }
+ if (propValue instanceof Immutable.Iterable) {
+ return "Immutable." + propValue.toSource().split(" ")[0];
+ }
+ return propType;
+ }
+
+ function createChainableTypeChecker(validate) {
+ function checkType(isRequired, props, propName, componentName, location, propFullName) {
+ propFullName = propFullName || propName;
+ componentName = componentName || ANONYMOUS;
+ if (props[propName] == null) {
+ var locationName = location;
+ if (isRequired) {
+ return new Error("Required " + locationName + " `" + propFullName + "` was not specified in " + ("`" + componentName + "`."));
+ }
+ } else {
+ return validate(props, propName, componentName, location, propFullName);
+ }
+ }
+
+ var chainedCheckType = checkType.bind(null, false);
+ chainedCheckType.isRequired = checkType.bind(null, true);
+
+ return chainedCheckType;
+ }
+
+ function createImmutableTypeChecker(immutableClassName, immutableClassTypeValidator) {
+ function validate(props, propName, componentName, location, propFullName) {
+ var propValue = props[propName];
+ if (!immutableClassTypeValidator(propValue)) {
+ var propType = getPropType(propValue);
+ return new Error("Invalid " + location + " `" + propFullName + "` of type `" + propType + "` " + ("supplied to `" + componentName + "`, expected `" + immutableClassName + "`."));
+ }
+ return null;
+ }
+ return createChainableTypeChecker(validate);
+ }
+
+ function createIterableTypeChecker(typeChecker, immutableClassName, immutableClassTypeValidator) {
+
+ function validate(props, propName, componentName, location, propFullName) {
+ var propValue = props[propName];
+ if (!immutableClassTypeValidator(propValue)) {
+ var locationName = location;
+ var propType = getPropType(propValue);
+ return new Error("Invalid " + locationName + " `" + propFullName + "` of type " + ("`" + propType + "` supplied to `" + componentName + "`, expected an Immutable.js " + immutableClassName + "."));
+ }
+
+ if (typeof typeChecker !== "function") {
+ return new Error("Invalid typeChecker supplied to `" + componentName + "` " + ("for propType `" + propFullName + "`, expected a function."));
+ }
+
+ var propValues = propValue.toArray();
+ for (var i = 0, len = propValues.length; i < len; i++) {
+ var error = typeChecker(propValues, i, componentName, location, "" + propFullName + "[" + i + "]");
+ if (error instanceof Error) {
+ return error;
+ }
+ }
+ }
+ return createChainableTypeChecker(validate);
+ }
+
+ function createListOfTypeChecker(typeChecker) {
+ return createIterableTypeChecker(typeChecker, "List", Immutable.List.isList);
+ }
+
+ function createMapOfTypeChecker(typeChecker) {
+ return createIterableTypeChecker(typeChecker, "Map", Immutable.Map.isMap);
+ }
+
+ function createOrderedMapOfTypeChecker(typeChecker) {
+ return createIterableTypeChecker(typeChecker, "OrderedMap", Immutable.OrderedMap.isOrderedMap);
+ }
+
+ function createSetOfTypeChecker(typeChecker) {
+ return createIterableTypeChecker(typeChecker, "Set", Immutable.Set.isSet);
+ }
+
+ function createOrderedSetOfTypeChecker(typeChecker) {
+ return createIterableTypeChecker(typeChecker, "OrderedSet", Immutable.OrderedSet.isOrderedSet);
+ }
+
+ function createStackOfTypeChecker(typeChecker) {
+ return createIterableTypeChecker(typeChecker, "Stack", Immutable.Stack.isStack);
+ }
+
+ function createIterableOfTypeChecker(typeChecker) {
+ return createIterableTypeChecker(typeChecker, "Iterable", Immutable.Iterable.isIterable);
+ }
+
+ function createRecordOfTypeChecker(recordKeys) {
+ function validate(props, propName, componentName, location, propFullName) {
+ var propValue = props[propName];
+ if (!(propValue instanceof Immutable.Record)) {
+ var propType = getPropType(propValue);
+ var locationName = location;
+ return new Error("Invalid " + locationName + " `" + propFullName + "` of type `" + propType + "` " + ("supplied to `" + componentName + "`, expected an Immutable.js Record."));
+ }
+ for (var key in recordKeys) {
+ var checker = recordKeys[key];
+ if (!checker) {
+ continue;
+ }
+ var mutablePropValue = propValue.toObject();
+ var error = checker(mutablePropValue, key, componentName, location, "" + propFullName + "." + key);
+ if (error) {
+ return error;
+ }
+ }
+ }
+ return createChainableTypeChecker(validate);
+ }
+
+ // there is some irony in the fact that shapeTypes is a standard hash and not an immutable collection
+ function createShapeTypeChecker(shapeTypes) {
+ var immutableClassName = arguments[1] === undefined ? "Iterable" : arguments[1];
+ var immutableClassTypeValidator = arguments[2] === undefined ? Immutable.Iterable.isIterable : arguments[2];
+
+ function validate(props, propName, componentName, location, propFullName) {
+ var propValue = props[propName];
+ if (!immutableClassTypeValidator(propValue)) {
+ var propType = getPropType(propValue);
+ var locationName = location;
+ return new Error("Invalid " + locationName + " `" + propFullName + "` of type `" + propType + "` " + ("supplied to `" + componentName + "`, expected an Immutable.js " + immutableClassName + "."));
+ }
+ var mutablePropValue = propValue.toObject();
+ for (var key in shapeTypes) {
+ var checker = shapeTypes[key];
+ if (!checker) {
+ continue;
+ }
+ var error = checker(mutablePropValue, key, componentName, location, "" + propFullName + "." + key);
+ if (error) {
+ return error;
+ }
+ }
+ }
+ return createChainableTypeChecker(validate);
+ }
+
+ function createShapeChecker(shapeTypes) {
+ return createShapeTypeChecker(shapeTypes);
+ }
+
+ function createMapContainsChecker(shapeTypes) {
+ return createShapeTypeChecker(shapeTypes, "Map", Immutable.Map.isMap);
+ }
+
+ module.exports = ImmutablePropTypes;
+
+/***/ },
+/* 236 */
+/***/ function(module, exports, __webpack_require__) {
+
+
+ var tabs = __webpack_require__(237);
+
+ module.exports = Object.assign({}, tabs);
+
+/***/ },
+/* 237 */
+/***/ function(module, exports, __webpack_require__) {
+
+ /* 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/. */
+ /* global window */
+
+ /**
+ * Redux actions for the pause state
+ * @module actions/tabs
+ */
+
+ var constants = __webpack_require__(228);
+
+ /**
+ * @typedef {Object} TabAction
+ * @memberof actions/tabs
+ * @static
+ * @property {number} type The type of Action
+ * @property {number} value The payload of the Action
+ */
+
+ /**
+ * @memberof actions/tabs
+ * @static
+ * @param {Array} tabs
+ * @returns {TabAction} with type constants.ADD_TABS and tabs as value
+ */
+ function newTabs(tabs) {
+ return {
+ type: constants.ADD_TABS,
+ value: tabs
+ };
+ }
+
+ /**
+ * @memberof actions/tabs
+ * @static
+ * @param {String} $0.id Unique ID of the tab to select
+ * @returns {TabAction}
+ */
+ function selectTab(_ref) {
+ var id = _ref.id;
+
+ return {
+ type: constants.SELECT_TAB,
+ id: id
+ };
+ }
+
+ module.exports = {
+ newTabs,
+ selectTab
+ };
+
+/***/ },
+/* 238 */
+/***/ function(module, exports, __webpack_require__) {
+
+
+
+ /* 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/. */
+ /* global window */
+
+ /**
+ * Redux store utils
+ * @module utils/create-store
+ */
+
+ var _require = __webpack_require__(3);
+
+ var createStore = _require.createStore;
+ var applyMiddleware = _require.applyMiddleware;
+
+ var _require2 = __webpack_require__(239);
+
+ var waitUntilService = _require2.waitUntilService;
+
+ var _require3 = __webpack_require__(240);
+
+ var log = _require3.log;
+
+ var _require4 = __webpack_require__(241);
+
+ var history = _require4.history;
+
+ var _require5 = __webpack_require__(242);
+
+ var promise = _require5.promise;
+
+ var _require6 = __webpack_require__(248);
+
+ var thunk = _require6.thunk;
+
+ /**
+ * @memberof utils/create-store
+ * @static
+ */
+
+ /**
+ * This creates a dispatcher with all the standard middleware in place
+ * that all code requires. It can also be optionally configured in
+ * various ways, such as logging and recording.
+ *
+ * @param {object} opts:
+ * - log: log all dispatched actions to console
+ * - history: an array to store every action in. Should only be
+ * used in tests.
+ * - middleware: array of middleware to be included in the redux store
+ * @memberof utils/create-store
+ * @static
+ */
+ var configureStore = function () {
+ var opts = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
+
+ var middleware = [thunk(opts.makeThunkArgs), promise,
+
+ // Order is important: services must go last as they always
+ // operate on "already transformed" actions. Actions going through
+ // them shouldn't have any special fields like promises, they
+ // should just be normal JSON objects.
+ waitUntilService];
+
+ if (opts.history) {
+ middleware.push(history(opts.history));
+ }
+
+ if (opts.middleware) {
+ opts.middleware.forEach(fn => middleware.push(fn));
+ }
+
+ if (opts.log) {
+ middleware.push(log);
+ }
+
+ // Hook in the redux devtools browser extension if it exists
+ var devtoolsExt = typeof window === "object" && window.devToolsExtension ? window.devToolsExtension() : f => f;
+
+ return applyMiddleware.apply(undefined, middleware)(devtoolsExt(createStore));
+ };
+
+ module.exports = configureStore;
+
+/***/ },
+/* 239 */
+/***/ function(module, exports) {
+
+ /* 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/. */
+
+ /**
+ * A middleware which acts like a service, because it is stateful
+ * and "long-running" in the background. It provides the ability
+ * for actions to install a function to be run once when a specific
+ * condition is met by an action coming through the system. Think of
+ * it as a thunk that blocks until the condition is met. Example:
+ *
+ * ```js
+ * const services = { WAIT_UNTIL: require('wait-service').NAME };
+ *
+ * { type: services.WAIT_UNTIL,
+ * predicate: action => action.type === constants.ADD_ITEM,
+ * run: (dispatch, getState, action) => {
+ * // Do anything here. You only need to accept the arguments
+ * // if you need them. `action` is the action that satisfied
+ * // the predicate.
+ * }
+ * }
+ * ```
+ */
+ var NAME = exports.NAME = "@@service/waitUntil";
+
+ function waitUntilService(_ref) {
+ var dispatch = _ref.dispatch;
+ var getState = _ref.getState;
+
+ var pending = [];
+
+ function checkPending(action) {
+ var readyRequests = [];
+ var stillPending = [];
+
+ // Find the pending requests whose predicates are satisfied with
+ // this action. Wait to run the requests until after we update the
+ // pending queue because the request handler may synchronously
+ // dispatch again and run this service (that use case is
+ // completely valid).
+ for (var request of pending) {
+ if (request.predicate(action)) {
+ readyRequests.push(request);
+ } else {
+ stillPending.push(request);
+ }
+ }
+
+ pending = stillPending;
+ for (var _request of readyRequests) {
+ _request.run(dispatch, getState, action);
+ }
+ }
+
+ return next => action => {
+ if (action.type === NAME) {
+ pending.push(action);
+ return null;
+ }
+ var result = next(action);
+ checkPending(action);
+ return result;
+ };
+ }
+ exports.waitUntilService = waitUntilService;
+
+/***/ },
+/* 240 */
+/***/ function(module, exports) {
+
+ /**
+ * A middleware that logs all actions coming through the system
+ * to the console.
+ */
+ function log(_ref) {
+ var dispatch = _ref.dispatch;
+ var getState = _ref.getState;
+
+ return next => action => {
+ var actionText = JSON.stringify(action, null, 2);
+ var truncatedActionText = actionText.slice(0, 1000) + "...";
+ console.log(`[DISPATCH ${ action.type }]`, action, truncatedActionText);
+ next(action);
+ };
+ }
+
+ exports.log = log;
+
+/***/ },
+/* 241 */
+/***/ function(module, exports, __webpack_require__) {
+
+ /* 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/. */
+
+ var _require = __webpack_require__(89);
+
+ var isDevelopment = _require.isDevelopment;
+
+ /**
+ * A middleware that stores every action coming through the store in the passed
+ * in logging object. Should only be used for tests, as it collects all
+ * action information, which will cause memory bloat.
+ */
+
+ exports.history = function () {
+ var log = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : [];
+ return (_ref) => {
+ var dispatch = _ref.dispatch;
+ var getState = _ref.getState;
+
+ if (isDevelopment()) {
+ console.warn("Using history middleware stores all actions in state for " + "testing and devtools is not currently running in test " + "mode. Be sure this is intentional.");
+ }
+ return next => action => {
+ log.push(action);
+ next(action);
+ };
+ };
+ };
+
+/***/ },
+/* 242 */
+/***/ function(module, exports, __webpack_require__) {
+
+ /* 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/. */
+
+ var defer = __webpack_require__(243);
+
+ var _require = __webpack_require__(244);
+
+ var entries = _require.entries;
+ var toObject = _require.toObject;
+
+ var _require2 = __webpack_require__(246);
+
+ var executeSoon = _require2.executeSoon;
+
+
+ var PROMISE = exports.PROMISE = "@@dispatch/promise";
+ var seqIdVal = 1;
+
+ function seqIdGen() {
+ return seqIdVal++;
+ }
+
+ function promiseMiddleware(_ref) {
+ var dispatch = _ref.dispatch;
+ var getState = _ref.getState;
+
+ return next => action => {
+ if (!(PROMISE in action)) {
+ return next(action);
+ }
+
+ var promiseInst = action[PROMISE];
+ var seqId = seqIdGen().toString();
+
+ // Create a new action that doesn't have the promise field and has
+ // the `seqId` field that represents the sequence id
+ action = Object.assign(toObject(entries(action).filter(pair => pair[0] !== PROMISE)), { seqId });
+
+ dispatch(Object.assign({}, action, { status: "start" }));
+
+ // Return the promise so action creators can still compose if they
+ // want to.
+ var deferred = defer();
+ promiseInst.then(value => {
+ executeSoon(() => {
+ dispatch(Object.assign({}, action, {
+ status: "done",
+ value: value
+ }));
+ deferred.resolve(value);
+ });
+ }, error => {
+ executeSoon(() => {
+ dispatch(Object.assign({}, action, {
+ status: "error",
+ error: error.message || error
+ }));
+ deferred.reject(error);
+ });
+ });
+ return deferred.promise;
+ };
+ }
+
+ exports.promise = promiseMiddleware;
+
+/***/ },
+/* 243 */
+/***/ function(module, exports) {
+
+
+
+ function defer() {
+ var resolve = void 0;
+ var reject = void 0;
+ var promise = new Promise(function (innerResolve, innerReject) {
+ resolve = innerResolve;
+ reject = innerReject;
+ });
+ return {
+ resolve: resolve,
+ reject: reject,
+ promise: promise
+ };
+ } /* flow */
+
+ module.exports = defer;
+
+/***/ },
+/* 244 */
+/***/ function(module, exports, __webpack_require__) {
+
+ var _slicedToArray = function () { function sliceIterator(arr, i) { var _arr = []; var _n = true; var _d = false; var _e = undefined; try { for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i["return"]) _i["return"](); } finally { if (_d) throw _e; } } return _arr; } return function (arr, i) { if (Array.isArray(arr)) { return arr; } else if (Symbol.iterator in Object(arr)) { return sliceIterator(arr, i); } else { throw new TypeError("Invalid attempt to destructure non-iterable instance"); } }; }();
+
+ function _toConsumableArray(arr) { if (Array.isArray(arr)) { for (var i = 0, arr2 = Array(arr.length); i < arr.length; i++) { arr2[i] = arr[i]; } return arr2; } else { return Array.from(arr); } }
+
+ /* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+ /* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+ /* 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/. */
+
+ /**
+ * Utils for utils, by utils
+ * @module utils/utils
+ */
+
+ var co = __webpack_require__(245);
+
+ /**
+ * @memberof utils/utils
+ * @static
+ */
+ function asPaused(client, func) {
+ if (client.state != "paused") {
+ return co(function* () {
+ yield client.interrupt();
+ var result = void 0;
+
+ try {
+ result = yield func();
+ } catch (e) {
+ // Try to put the debugger back in a working state by resuming
+ // it
+ yield client.resume();
+ throw e;
+ }
+
+ yield client.resume();
+ return result;
+ });
+ }
+ return func();
+ }
+
+ /**
+ * @memberof utils/utils
+ * @static
+ */
+ function handleError(err) {
+ console.log("ERROR: ", err);
+ }
+
+ /**
+ * @memberof utils/utils
+ * @static
+ */
+ function promisify(context, method) {
+ for (var _len = arguments.length, args = Array(_len > 2 ? _len - 2 : 0), _key = 2; _key < _len; _key++) {
+ args[_key - 2] = arguments[_key];
+ }
+
+ return new Promise((resolve, reject) => {
+ args.push(response => {
+ if (response.error) {
+ reject(response);
+ } else {
+ resolve(response);
+ }
+ });
+ method.apply(context, args);
+ });
+ }
+
+ /**
+ * @memberof utils/utils
+ * @static
+ */
+ function truncateStr(str, size) {
+ if (str.length > size) {
+ return str.slice(0, size) + "...";
+ }
+ return str;
+ }
+
+ /**
+ * @memberof utils/utils
+ * @static
+ */
+ function endTruncateStr(str, size) {
+ if (str.length > size) {
+ return "..." + str.slice(str.length - size);
+ }
+ return str;
+ }
+
+ var msgId = 1;
+ /**
+ * @memberof utils/utils
+ * @static
+ */
+ function workerTask(worker, method) {
+ return function () {
+ for (var _len2 = arguments.length, args = Array(_len2), _key2 = 0; _key2 < _len2; _key2++) {
+ args[_key2] = arguments[_key2];
+ }
+
+ return new Promise((resolve, reject) => {
+ var id = msgId++;
+ worker.postMessage({ id, method, args });
+
+ var listener = (_ref) => {
+ var result = _ref.data;
+
+ if (result.id !== id) {
+ return;
+ }
+
+ worker.removeEventListener("message", listener);
+ if (result.error) {
+ reject(result.error);
+ } else {
+ resolve(result.response);
+ }
+ };
+
+ worker.addEventListener("message", listener);
+ });
+ };
+ }
+
+ /**
+ * Interleaves two arrays element by element, returning the combined array, like
+ * a zip. In the case of arrays with different sizes, undefined values will be
+ * interleaved at the end along with the extra values of the larger array.
+ *
+ * @param Array a
+ * @param Array b
+ * @returns Array
+ * The combined array, in the form [a1, b1, a2, b2, ...]
+ * @memberof utils/utils
+ * @static
+ */
+ function zip(a, b) {
+ if (!b) {
+ return a;
+ }
+ if (!a) {
+ return b;
+ }
+ var pairs = [];
+ for (var i = 0, aLength = a.length, bLength = b.length; i < aLength || i < bLength; i++) {
+ pairs.push([a[i], b[i]]);
+ }
+ return pairs;
+ }
+
+ /**
+ * Converts an object into an array with 2-element arrays as key/value
+ * pairs of the object. `{ foo: 1, bar: 2}` would become
+ * `[[foo, 1], [bar 2]]` (order not guaranteed);
+ *
+ * @returns array
+ * @memberof utils/utils
+ * @static
+ */
+ function entries(obj) {
+ return Object.keys(obj).map(k => [k, obj[k]]);
+ }
+
+ /**
+ * @memberof utils/utils
+ * @static
+ */
+ function mapObject(obj, iteratee) {
+ return toObject(entries(obj).map((_ref2) => {
+ var _ref3 = _slicedToArray(_ref2, 2);
+
+ var key = _ref3[0];
+ var value = _ref3[1];
+
+ return [key, iteratee(key, value)];
+ }));
+ }
+
+ /**
+ * Takes an array of 2-element arrays as key/values pairs and
+ * constructs an object using them.
+ * @memberof utils/utils
+ * @static
+ */
+ function toObject(arr) {
+ var obj = {};
+ for (var pair of arr) {
+ obj[pair[0]] = pair[1];
+ }
+ return obj;
+ }
+
+ /**
+ * Composes the given functions into a single function, which will
+ * apply the results of each function right-to-left, starting with
+ * applying the given arguments to the right-most function.
+ * `compose(foo, bar, baz)` === `args => foo(bar(baz(args)`
+ *
+ * @param ...function funcs
+ * @returns function
+ * @memberof utils/utils
+ * @static
+ */
+ function compose() {
+ for (var _len3 = arguments.length, funcs = Array(_len3), _key3 = 0; _key3 < _len3; _key3++) {
+ funcs[_key3] = arguments[_key3];
+ }
+
+ return function () {
+ for (var _len4 = arguments.length, args = Array(_len4), _key4 = 0; _key4 < _len4; _key4++) {
+ args[_key4] = arguments[_key4];
+ }
+
+ var initialValue = funcs[funcs.length - 1].apply(null, args);
+ var leftFuncs = funcs.slice(0, -1);
+ return leftFuncs.reduceRight((composed, f) => f(composed), initialValue);
+ };
+ }
+
+ /**
+ * @memberof utils/utils
+ * @static
+ */
+ function updateObj(obj, fields) {
+ return Object.assign({}, obj, fields);
+ }
+
+ /**
+ * @memberof utils/utils
+ * @static
+ */
+ function throttle(func, ms) {
+ var timeout = void 0,
+ _this = void 0;
+ return function () {
+ for (var _len5 = arguments.length, args = Array(_len5), _key5 = 0; _key5 < _len5; _key5++) {
+ args[_key5] = arguments[_key5];
+ }
+
+ _this = this;
+ if (!timeout) {
+ timeout = setTimeout(() => {
+ func.apply.apply(func, [_this].concat(_toConsumableArray(args)));
+ timeout = null;
+ }, ms);
+ }
+ };
+ }
+
+ module.exports = {
+ asPaused,
+ handleError,
+ promisify,
+ truncateStr,
+ endTruncateStr,
+ workerTask,
+ zip,
+ entries,
+ toObject,
+ mapObject,
+ compose,
+ updateObj,
+ throttle
+ };
+
+/***/ },
+/* 245 */
+/***/ function(module, exports) {
+
+
+ /**
+ * slice() reference.
+ */
+
+ var slice = Array.prototype.slice;
+
+ /**
+ * Expose `co`.
+ */
+
+ module.exports = co['default'] = co.co = co;
+
+ /**
+ * Wrap the given generator `fn` into a
+ * function that returns a promise.
+ * This is a separate function so that
+ * every `co()` call doesn't create a new,
+ * unnecessary closure.
+ *
+ * @param {GeneratorFunction} fn
+ * @return {Function}
+ * @api public
+ */
+
+ co.wrap = function (fn) {
+ createPromise.__generatorFunction__ = fn;
+ return createPromise;
+ function createPromise() {
+ return co.call(this, fn.apply(this, arguments));
+ }
+ };
+
+ /**
+ * Execute the generator function or a generator
+ * and return a promise.
+ *
+ * @param {Function} fn
+ * @return {Promise}
+ * @api public
+ */
+
+ function co(gen) {
+ var ctx = this;
+ var args = slice.call(arguments, 1)
+
+ // we wrap everything in a promise to avoid promise chaining,
+ // which leads to memory leak errors.
+ // see https://github.com/tj/co/issues/180
+ return new Promise(function(resolve, reject) {
+ if (typeof gen === 'function') gen = gen.apply(ctx, args);
+ if (!gen || typeof gen.next !== 'function') return resolve(gen);
+
+ onFulfilled();
+
+ /**
+ * @param {Mixed} res
+ * @return {Promise}
+ * @api private
+ */
+
+ function onFulfilled(res) {
+ var ret;
+ try {
+ ret = gen.next(res);
+ } catch (e) {
+ return reject(e);
+ }
+ next(ret);
+ }
+
+ /**
+ * @param {Error} err
+ * @return {Promise}
+ * @api private
+ */
+
+ function onRejected(err) {
+ var ret;
+ try {
+ ret = gen.throw(err);
+ } catch (e) {
+ return reject(e);
+ }
+ next(ret);
+ }
+
+ /**
+ * Get the next value in the generator,
+ * return a promise.
+ *
+ * @param {Object} ret
+ * @return {Promise}
+ * @api private
+ */
+
+ function next(ret) {
+ if (ret.done) return resolve(ret.value);
+ var value = toPromise.call(ctx, ret.value);
+ if (value && isPromise(value)) return value.then(onFulfilled, onRejected);
+ return onRejected(new TypeError('You may only yield a function, promise, generator, array, or object, '
+ + 'but the following object was passed: "' + String(ret.value) + '"'));
+ }
+ });
+ }
+
+ /**
+ * Convert a `yield`ed value into a promise.
+ *
+ * @param {Mixed} obj
+ * @return {Promise}
+ * @api private
+ */
+
+ function toPromise(obj) {
+ if (!obj) return obj;
+ if (isPromise(obj)) return obj;
+ if (isGeneratorFunction(obj) || isGenerator(obj)) return co.call(this, obj);
+ if ('function' == typeof obj) return thunkToPromise.call(this, obj);
+ if (Array.isArray(obj)) return arrayToPromise.call(this, obj);
+ if (isObject(obj)) return objectToPromise.call(this, obj);
+ return obj;
+ }
+
+ /**
+ * Convert a thunk to a promise.
+ *
+ * @param {Function}
+ * @return {Promise}
+ * @api private
+ */
+
+ function thunkToPromise(fn) {
+ var ctx = this;
+ return new Promise(function (resolve, reject) {
+ fn.call(ctx, function (err, res) {
+ if (err) return reject(err);
+ if (arguments.length > 2) res = slice.call(arguments, 1);
+ resolve(res);
+ });
+ });
+ }
+
+ /**
+ * Convert an array of "yieldables" to a promise.
+ * Uses `Promise.all()` internally.
+ *
+ * @param {Array} obj
+ * @return {Promise}
+ * @api private
+ */
+
+ function arrayToPromise(obj) {
+ return Promise.all(obj.map(toPromise, this));
+ }
+
+ /**
+ * Convert an object of "yieldables" to a promise.
+ * Uses `Promise.all()` internally.
+ *
+ * @param {Object} obj
+ * @return {Promise}
+ * @api private
+ */
+
+ function objectToPromise(obj){
+ var results = new obj.constructor();
+ var keys = Object.keys(obj);
+ var promises = [];
+ for (var i = 0; i < keys.length; i++) {
+ var key = keys[i];
+ var promise = toPromise.call(this, obj[key]);
+ if (promise && isPromise(promise)) defer(promise, key);
+ else results[key] = obj[key];
+ }
+ return Promise.all(promises).then(function () {
+ return results;
+ });
+
+ function defer(promise, key) {
+ // predefine the key in the result
+ results[key] = undefined;
+ promises.push(promise.then(function (res) {
+ results[key] = res;
+ }));
+ }
+ }
+
+ /**
+ * Check if `obj` is a promise.
+ *
+ * @param {Object} obj
+ * @return {Boolean}
+ * @api private
+ */
+
+ function isPromise(obj) {
+ return 'function' == typeof obj.then;
+ }
+
+ /**
+ * Check if `obj` is a generator.
+ *
+ * @param {Mixed} obj
+ * @return {Boolean}
+ * @api private
+ */
+
+ function isGenerator(obj) {
+ return 'function' == typeof obj.next && 'function' == typeof obj.throw;
+ }
+
+ /**
+ * Check if `obj` is a generator function.
+ *
+ * @param {Mixed} obj
+ * @return {Boolean}
+ * @api private
+ */
+ function isGeneratorFunction(obj) {
+ var constructor = obj.constructor;
+ if (!constructor) return false;
+ if ('GeneratorFunction' === constructor.name || 'GeneratorFunction' === constructor.displayName) return true;
+ return isGenerator(constructor.prototype);
+ }
+
+ /**
+ * Check for plain object.
+ *
+ * @param {Mixed} val
+ * @return {Boolean}
+ * @api private
+ */
+
+ function isObject(val) {
+ return Object == val.constructor;
+ }
+
+
+/***/ },
+/* 246 */
+/***/ function(module, exports, __webpack_require__) {
+
+ var assert = __webpack_require__(247);
+
+ function reportException(who, exception) {
+ var msg = who + " threw an exception: ";
+ console.error(msg, exception);
+ }
+
+ function executeSoon(fn) {
+ setTimeout(fn, 0);
+ }
+
+ module.exports = {
+ reportException,
+ executeSoon,
+ assert
+ };
+
+/***/ },
+/* 247 */
+/***/ function(module, exports) {
+
+ function assert(condition, message) {
+ if (!condition) {
+ throw new Error("Assertion failure: " + message);
+ }
+ }
+
+ module.exports = assert;
+
+/***/ },
+/* 248 */
+/***/ function(module, exports) {
+
+
+ /**
+ * A middleware that allows thunks (functions) to be dispatched. If
+ * it's a thunk, it is called with an argument that contains
+ * `dispatch`, `getState`, and any additional args passed in via the
+ * middleware constructure. This allows the action to create multiple
+ * actions (most likely asynchronously).
+ */
+ function thunk(makeArgs) {
+ return (_ref) => {
+ var dispatch = _ref.dispatch;
+ var getState = _ref.getState;
+
+ var args = { dispatch, getState };
+
+ return next => action => {
+ return typeof action === "function" ? action(makeArgs ? makeArgs(args, getState()) : args) : next(action);
+ };
+ };
+ }
+ exports.thunk = thunk;
+
+/***/ },
+/* 249 */
+/***/ function(module, exports, __webpack_require__) {
+
+ /* 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/. */
+
+ var eventListeners = __webpack_require__(250);
+ var sources = __webpack_require__(252);
+ var breakpoints = __webpack_require__(255);
+ var asyncRequests = __webpack_require__(256);
+ var pause = __webpack_require__(257);
+ var ui = __webpack_require__(258);
+
+ module.exports = {
+ eventListeners,
+ sources: sources.update,
+ breakpoints: breakpoints.update,
+ pause: pause.update,
+ asyncRequests,
+ ui: ui.update
+ };
+
+/***/ },
+/* 250 */
+/***/ function(module, exports, __webpack_require__) {
+
+ /* 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/. */
+
+ var constants = __webpack_require__(251);
+
+ var initialState = {
+ activeEventNames: [],
+ listeners: [],
+ fetchingListeners: false
+ };
+
+ function update() {
+ var state = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : initialState;
+ var action = arguments[1];
+ var emit = arguments[2];
+
+ switch (action.type) {
+ case constants.UPDATE_EVENT_BREAKPOINTS:
+ state.activeEventNames = action.eventNames;
+ emit("activeEventNames", state.activeEventNames);
+ break;
+ case constants.FETCH_EVENT_LISTENERS:
+ if (action.status === "begin") {
+ state.fetchingListeners = true;
+ } else if (action.status === "done") {
+ state.fetchingListeners = false;
+ state.listeners = action.listeners;
+ emit("event-listeners", state.listeners);
+ }
+ break;
+ case constants.NAVIGATE:
+ return initialState;
+ }
+
+ return state;
+ }
+
+ module.exports = update;
+
+/***/ },
+/* 251 */
+/***/ function(module, exports) {
+
+
+
+ /* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+ /* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+ /* 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/. */
+
+ exports.UPDATE_EVENT_BREAKPOINTS = "UPDATE_EVENT_BREAKPOINTS";
+ exports.FETCH_EVENT_LISTENERS = "FETCH_EVENT_LISTENERS";
+
+ exports.TOGGLE_PRETTY_PRINT = "TOGGLE_PRETTY_PRINT";
+ exports.BLACKBOX = "BLACKBOX";
+
+ exports.ADD_BREAKPOINT = "ADD_BREAKPOINT";
+ exports.REMOVE_BREAKPOINT = "REMOVE_BREAKPOINT";
+ exports.ENABLE_BREAKPOINT = "ENABLE_BREAKPOINT";
+ exports.DISABLE_BREAKPOINT = "DISABLE_BREAKPOINT";
+ exports.SET_BREAKPOINT_CONDITION = "SET_BREAKPOINT_CONDITION";
+ exports.TOGGLE_BREAKPOINTS = "TOGGLE_BREAKPOINTS";
+
+ exports.ADD_SOURCE = "ADD_SOURCE";
+ exports.ADD_SOURCES = "ADD_SOURCES";
+ exports.LOAD_SOURCE_TEXT = "LOAD_SOURCE_TEXT";
+ exports.SELECT_SOURCE = "SELECT_SOURCE";
+ exports.SELECT_SOURCE_URL = "SELECT_SOURCE_URL";
+ exports.CLOSE_TAB = "CLOSE_TAB";
+ exports.NAVIGATE = "NAVIGATE";
+ exports.RELOAD = "RELOAD";
+
+ exports.ADD_TABS = "ADD_TABS";
+ exports.SELECT_TAB = "SELECT_TAB";
+
+ exports.BREAK_ON_NEXT = "BREAK_ON_NEXT";
+ exports.RESUME = "RESUME";
+ exports.PAUSED = "PAUSED";
+ exports.PAUSE_ON_EXCEPTIONS = "PAUSE_ON_EXCEPTIONS";
+ exports.COMMAND = "COMMAND";
+ exports.SELECT_FRAME = "SELECT_FRAME";
+ exports.LOAD_OBJECT_PROPERTIES = "LOAD_OBJECT_PROPERTIES";
+ exports.ADD_EXPRESSION = "ADD_EXPRESSION";
+ exports.EVALUATE_EXPRESSION = "EVALUATE_EXPRESSION";
+ exports.UPDATE_EXPRESSION = "UPDATE_EXPRESSION";
+ exports.DELETE_EXPRESSION = "DELETE_EXPRESSION";
+
+ exports.TOGGLE_FILE_SEARCH = "TOGGLE_FILE_SEARCH";
+
+/***/ },
+/* 252 */
+/***/ function(module, exports, __webpack_require__) {
+
+
+ /* 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/. */
+
+ /**
+ * Sources reducer
+ * @module reducers/sources
+ */
+
+ var fromJS = __webpack_require__(253);
+ var I = __webpack_require__(229);
+ var makeRecord = __webpack_require__(254);
+
+ var State = makeRecord({
+ sources: I.Map(),
+ selectedLocation: undefined,
+ pendingSelectedLocation: undefined,
+ sourcesText: I.Map(),
+ tabs: I.List([])
+ });
+
+ function update() {
+ var state = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : State();
+ var action = arguments[1];
+
+ switch (action.type) {
+ case "ADD_SOURCE":
+ {
+ var _source = action.source;
+ return state.mergeIn(["sources", action.source.id], _source);
+ }
+
+ case "SELECT_SOURCE":
+ return state.set("selectedLocation", {
+ sourceId: action.source.id,
+ line: action.line
+ }).set("pendingSelectedLocation", null).merge({
+ tabs: updateTabList(state, fromJS(action.source), action.tabIndex)
+ });
+
+ case "SELECT_SOURCE_URL":
+ return state.set("pendingSelectedLocation", {
+ url: action.url,
+ line: action.line
+ });
+
+ case "CLOSE_TAB":
+ return state.merge({ tabs: removeSourceFromTabList(state, action.id) }).set("selectedLocation", {
+ sourceId: getNewSelectedSourceId(state, action.id)
+ });
+
+ case "LOAD_SOURCE_TEXT":
+ return _updateText(state, action);
+
+ case "BLACKBOX":
+ if (action.status === "done") {
+ return state.setIn(["sources", action.source.id, "isBlackBoxed"], action.value.isBlackBoxed);
+ }
+ break;
+
+ case "TOGGLE_PRETTY_PRINT":
+ return _updateText(state, action);
+
+ case "NAVIGATE":
+ var source = getSelectedSource({ sources: state });
+ var _url = source && source.get("url");
+ return State().set("pendingSelectedLocation", { url: _url });
+ }
+
+ return state;
+ }
+
+ // TODO: Action is coerced to `any` unfortunately because how we type
+ // asynchronous actions is wrong. The `value` may be null for the
+ // "start" and "error" states but we don't type it like that. We need
+ // to rethink how we type async actions.
+ function _updateText(state, action) {
+ var source = action.source;
+ var sourceText = action.value;
+
+ if (action.status === "start") {
+ // Merge this in, don't set it. That way the previous value is
+ // still stored here, and we can retrieve it if whatever we're
+ // doing fails.
+ return state.mergeIn(["sourcesText", source.id], {
+ loading: true
+ });
+ }
+
+ if (action.status === "error") {
+ return state.setIn(["sourcesText", source.id], I.Map({
+ error: action.error
+ }));
+ }
+
+ return state.setIn(["sourcesText", source.id], I.Map({
+ text: sourceText.text,
+ contentType: sourceText.contentType
+ }));
+ }
+
+ function removeSourceFromTabList(state, id) {
+ return state.tabs.filter(tab => tab.get("id") != id);
+ }
+
+ /**
+ * Adds the new source to the tab list if it is not already there
+ * @memberof reducers/sources
+ * @static
+ */
+ function updateTabList(state, source, tabIndex) {
+ var tabs = state.get("tabs");
+ var sourceIndex = tabs.indexOf(source);
+ var includesSource = !!tabs.find(t => t.get("id") == source.get("id"));
+
+ if (includesSource) {
+ if (tabIndex != undefined) {
+ return tabs.delete(sourceIndex).insert(tabIndex, source);
+ }
+
+ return tabs;
+ }
+
+ return tabs.insert(0, source);
+ }
+
+ /**
+ * Gets the next tab to select when a tab closes.
+ * @memberof reducers/sources
+ * @static
+ */
+ function getNewSelectedSourceId(state, id) {
+ var tabs = state.get("tabs");
+ var selectedSource = getSelectedSource({ sources: state });
+
+ if (!selectedSource) {
+ return undefined;
+ } else if (selectedSource.get("id") != id) {
+ // If we're not closing the selected tab return the selected tab
+ return selectedSource.get("id");
+ }
+
+ var tabIndex = tabs.findIndex(tab => tab.get("id") == id);
+ var numTabs = tabs.count();
+
+ if (numTabs == 1) {
+ return undefined;
+ }
+
+ // if we're closing the last tab, select the penultimate tab
+ if (tabIndex + 1 == numTabs) {
+ return tabs.get(tabIndex - 1).get("id");
+ }
+
+ // return the next tab
+ return tabs.get(tabIndex + 1).get("id");
+ }
+
+ // Selectors
+
+ // Unfortunately, it's really hard to make these functions accept just
+ // the state that we care about and still type it with Flow. The
+ // problem is that we want to re-export all selectors from a single
+ // module for the UI, and all of those selectors should take the
+ // top-level app state, so we'd have to "wrap" them to automatically
+ // pick off the piece of state we're interested in. It's impossible
+ // (right now) to type those wrapped functions.
+
+
+ function getSource(state, id) {
+ return state.sources.sources.get(id);
+ }
+
+ function getSourceByURL(state, url) {
+ return state.sources.sources.find(source => source.get("url") == url);
+ }
+
+ function getSourceById(state, id) {
+ return state.sources.sources.find(source => source.get("id") == id);
+ }
+
+ function getSources(state) {
+ return state.sources.sources;
+ }
+
+ function getSourceText(state, id) {
+ return state.sources.sourcesText.get(id);
+ }
+
+ function getSourceTabs(state) {
+ return state.sources.tabs;
+ }
+
+ function getSelectedSource(state) {
+ if (state.sources.selectedLocation) {
+ return getSource(state, state.sources.selectedLocation.sourceId);
+ }
+ return undefined;
+ }
+
+ function getSelectedLocation(state) {
+ return state.sources.selectedLocation;
+ }
+
+ function getPendingSelectedLocation(state) {
+ return state.sources.pendingSelectedLocation;
+ }
+
+ function getPrettySource(state, id) {
+ var source = getSource(state, id);
+ if (!source) {
+ return;
+ }
+
+ return getSourceByURL(state, source.get("url") + ":formatted");
+ }
+
+ module.exports = {
+ State,
+ update,
+ getSource,
+ getSourceByURL,
+ getSourceById,
+ getSources,
+ getSourceText,
+ getSourceTabs,
+ getSelectedSource,
+ getSelectedLocation,
+ getPendingSelectedLocation,
+ getPrettySource
+ };
+
+/***/ },
+/* 253 */
+/***/ function(module, exports, __webpack_require__) {
+
+
+
+ /**
+ * Immutable JS conversion utils
+ * @deprecated
+ * @module utils/fromJS
+ */
+
+ var Immutable = __webpack_require__(229);
+
+ /**
+ * When our app state is fully typed, we should be able to get rid of
+ * this function. This is only temporarily necessary to support
+ * converting typed objects to immutable.js, which usually happens in
+ * reducers.
+ *
+ * @memberof utils/fromJS
+ * @static
+ */
+ function fromJS(value) {
+ if (Array.isArray(value)) {
+ return Immutable.Seq(value).map(fromJS).toList();
+ }
+ if (value && value.constructor.meta) {
+ // This adds support for tcomb objects which are native JS objects
+ // but are not "plain", so the above checks fail. Since they
+ // behave the same we can use the same constructors, but we need
+ // special checks for them.
+ var kind = value.constructor.meta.kind;
+ if (kind === "struct") {
+ return Immutable.Seq(value).map(fromJS).toMap();
+ } else if (kind === "list") {
+ return Immutable.Seq(value).map(fromJS).toList();
+ }
+ }
+
+ // If it's a primitive type, just return the value. Note `==` check
+ // for null, which is intentionally used to match either `null` or
+ // `undefined`.
+ if (value == null || typeof value !== "object") {
+ return value;
+ }
+
+ // Otherwise, treat it like an object. We can't reliably detect if
+ // it's a plain object because we might be objects from other JS
+ // contexts so `Object !== Object`.
+ return Immutable.Seq(value).map(fromJS).toMap();
+ }
+
+ module.exports = fromJS;
+
+/***/ },
+/* 254 */
+/***/ function(module, exports, __webpack_require__) {
+
+
+
+ /**
+ * When Flow 0.29 is released (very soon), we can use this Record type
+ * instead of the builtin immutable.js Record type. This is better
+ * because all the fields are actually typed, unlike the builtin one.
+ * This depends on a performance fix that will go out in 0.29 though;
+ * @module utils/makeRecord
+ */
+
+ var I = __webpack_require__(229);
+
+ /**
+ * @memberof utils/makeRecord
+ * @static
+ */
+
+
+ /**
+ * Make an immutable record type
+ *
+ * @param spec - the keys and their default values
+ * @return a state record factory function
+ * @memberof utils/makeRecord
+ * @static
+ */
+ function makeRecord(spec) {
+ return I.Record(spec);
+ }
+
+ module.exports = makeRecord;
+
+/***/ },
+/* 255 */
+/***/ function(module, exports, __webpack_require__) {
+
+
+ /* 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/. */
+
+ /**
+ * Breakpoints reducer
+ * @module reducers/breakpoints
+ */
+
+ var fromJS = __webpack_require__(253);
+
+ var _require = __webpack_require__(244);
+
+ var updateObj = _require.updateObj;
+
+ var I = __webpack_require__(229);
+ var makeRecord = __webpack_require__(254);
+
+ var State = makeRecord({
+ breakpoints: I.Map(),
+ breakpointsDisabled: false
+ });
+
+ // Return the first argument that is a string, or null if nothing is a
+ // string.
+ function firstString() {
+ for (var _len = arguments.length, args = Array(_len), _key = 0; _key < _len; _key++) {
+ args[_key] = arguments[_key];
+ }
+
+ for (var arg of args) {
+ if (typeof arg === "string") {
+ return arg;
+ }
+ }
+ return null;
+ }
+
+ function locationMoved(location, newLocation) {
+ return location.line !== newLocation.line || location.column != null && location.column !== newLocation.column;
+ }
+
+ function makeLocationId(location) {
+ return location.sourceId + ":" + location.line.toString();
+ }
+
+ function update() {
+ var state = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : State();
+ var action = arguments[1];
+
+ switch (action.type) {
+ case "ADD_BREAKPOINT":
+ {
+ var id = makeLocationId(action.breakpoint.location);
+
+ if (action.status === "start") {
+ var bp = state.breakpoints.get(id) || action.breakpoint;
+
+ return state.setIn(["breakpoints", id], updateObj(bp, {
+ disabled: false,
+ loading: true,
+ // We want to do an OR here, but we can't because we need
+ // empty strings to be truthy, i.e. an empty string is a valid
+ // condition.
+ condition: firstString(action.condition, bp.condition)
+ }));
+ } else if (action.status === "done") {
+ var _action$value = action.value;
+ var breakpointId = _action$value.id;
+ var text = _action$value.text;
+
+ var location = action.breakpoint.location;
+ var actualLocation = action.value.actualLocation;
+
+ // If the breakpoint moved, update the map
+
+ if (locationMoved(location, actualLocation)) {
+ state = state.deleteIn(["breakpoints", id]);
+
+ var movedId = makeLocationId(actualLocation);
+ var currentBp = state.breakpoints.get(movedId) || fromJS(action.breakpoint);
+ var newBp = updateObj(currentBp, { location: actualLocation });
+ state = state.setIn(["breakpoints", movedId], newBp);
+ location = actualLocation;
+ }
+
+ var locationId = makeLocationId(location);
+ var _bp = state.breakpoints.get(locationId);
+ return state.setIn(["breakpoints", locationId], updateObj(_bp, {
+ id: breakpointId,
+ disabled: false,
+ loading: false,
+ text: text
+ }));
+ } else if (action.status === "error") {
+ // Remove the optimistic update
+ return state.deleteIn(["breakpoints", id]);
+ }
+ break;
+ }
+
+ case "REMOVE_BREAKPOINT":
+ {
+ if (action.status === "done") {
+ var _id = makeLocationId(action.breakpoint.location);
+
+ if (action.disabled) {
+ var _bp2 = state.breakpoints.get(_id);
+ return state.setIn(["breakpoints", _id], updateObj(_bp2, {
+ loading: false, disabled: true
+ }));
+ }
+
+ return state.deleteIn(["breakpoints", _id]);
+ }
+ break;
+ }
+
+ case "TOGGLE_BREAKPOINTS":
+ {
+ if (action.status === "start") {
+ return state.set("breakpointsDisabled", action.shouldDisableBreakpoints);
+ }
+ break;
+ }
+
+ case "SET_BREAKPOINT_CONDITION":
+ {
+ var _id2 = makeLocationId(action.breakpoint.location);
+
+ if (action.status === "start") {
+ var _bp3 = state.breakpoints.get(_id2);
+ return state.setIn(["breakpoints", _id2], updateObj(_bp3, {
+ loading: true,
+ condition: action.condition
+ }));
+ } else if (action.status === "done") {
+ var _bp4 = state.breakpoints.get(_id2);
+ return state.setIn(["breakpoints", _id2], updateObj(_bp4, {
+ id: action.value.id,
+ loading: false
+ }));
+ } else if (action.status === "error") {
+ return state.deleteIn(["breakpoints", _id2]);
+ }
+
+ break;
+ }}
+
+ return state;
+ }
+
+ // Selectors
+
+ function getBreakpoint(state, location) {
+ return state.breakpoints.breakpoints.get(makeLocationId(location));
+ }
+
+ function getBreakpoints(state) {
+ return state.breakpoints.breakpoints;
+ }
+
+ function getBreakpointsForSource(state, sourceId) {
+ return state.breakpoints.breakpoints.filter(bp => {
+ return bp.location.sourceId === sourceId;
+ });
+ }
+
+ function getBreakpointsDisabled(state) {
+ return state.breakpoints.get("breakpointsDisabled");
+ }
+
+ function getBreakpointsLoading(state) {
+ var breakpoints = getBreakpoints(state);
+ var isLoading = !!breakpoints.valueSeq().filter(bp => bp.loading).first();
+
+ return breakpoints.size > 0 && isLoading;
+ }
+
+ module.exports = {
+ State,
+ update,
+ makeLocationId,
+ getBreakpoint,
+ getBreakpoints,
+ getBreakpointsForSource,
+ getBreakpointsDisabled,
+ getBreakpointsLoading
+ };
+
+/***/ },
+/* 256 */
+/***/ function(module, exports, __webpack_require__) {
+
+ function _toConsumableArray(arr) { if (Array.isArray(arr)) { for (var i = 0, arr2 = Array(arr.length); i < arr.length; i++) { arr2[i] = arr[i]; } return arr2; } else { return Array.from(arr); } }
+
+ /* 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/. */
+
+ var constants = __webpack_require__(251);
+ var initialState = [];
+
+ function update() {
+ var state = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : initialState;
+ var action = arguments[1];
+ var seqId = action.seqId;
+
+
+ if (action.type === constants.NAVIGATE) {
+ return initialState;
+ } else if (seqId) {
+ var newState = void 0;
+ if (action.status === "start") {
+ newState = [].concat(_toConsumableArray(state), [seqId]);
+ } else if (action.status === "error" || action.status === "done") {
+ newState = state.filter(id => id !== seqId);
+ }
+
+ return newState;
+ }
+
+ return state;
+ }
+
+ module.exports = update;
+
+/***/ },
+/* 257 */
+/***/ function(module, exports, __webpack_require__) {
+
+ /* 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/. */
+
+ var constants = __webpack_require__(251);
+ var fromJS = __webpack_require__(253);
+
+ var initialState = fromJS({
+ pause: null,
+ isWaitingOnBreak: false,
+ frames: null,
+ selectedFrameId: null,
+ loadedObjects: {},
+ shouldPauseOnExceptions: false,
+ shouldIgnoreCaughtExceptions: false,
+ expressions: []
+ });
+
+ function update() {
+ var state = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : initialState;
+ var action = arguments[1];
+ var emit = arguments[2];
+
+ switch (action.type) {
+ case constants.PAUSED:
+ {
+ var selectedFrameId = action.selectedFrameId;
+ var frames = action.frames;
+ var pauseInfo = action.pauseInfo;
+
+ pauseInfo.isInterrupted = pauseInfo.why.type === "interrupted";
+
+ return state.merge({
+ isWaitingOnBreak: false,
+ pause: fromJS(pauseInfo),
+ selectedFrameId,
+ frames
+ });
+ }
+
+ case constants.RESUME:
+ return state.merge({
+ pause: null,
+ frames: null,
+ selectedFrameId: null,
+ loadedObjects: {}
+ });
+
+ case constants.TOGGLE_PRETTY_PRINT:
+ if (action.status == "done") {
+ var _frames = action.value.frames;
+ var pause = state.get("pause");
+ if (pause) {
+ pause = pause.set("frame", fromJS(_frames[0]));
+ }
+
+ return state.merge({ pause, frames: _frames });
+ }
+
+ break;
+ case constants.BREAK_ON_NEXT:
+ return state.set("isWaitingOnBreak", true);
+
+ case constants.LOADED_FRAMES:
+ if (action.status == "done") {
+ return state.set("frames", action.value.frames);
+ }
+
+ break;
+ case constants.SELECT_FRAME:
+ return state.set("selectedFrameId", action.frame.id);
+
+ case constants.LOAD_OBJECT_PROPERTIES:
+ if (action.status === "done") {
+ var ownProperties = action.value.ownProperties;
+ var prototype = action.value.prototype;
+
+ return state.setIn(["loadedObjects", action.objectId], { ownProperties, prototype });
+ }
+ break;
+
+ case constants.NAVIGATE:
+ return initialState;
+
+ case constants.PAUSE_ON_EXCEPTIONS:
+ var shouldPauseOnExceptions = action.shouldPauseOnExceptions;
+ var shouldIgnoreCaughtExceptions = action.shouldIgnoreCaughtExceptions;
+
+ return state.merge({
+ shouldPauseOnExceptions,
+ shouldIgnoreCaughtExceptions
+ });
+
+ case constants.ADD_EXPRESSION:
+ return state.setIn(["expressions", action.id], { id: action.id,
+ input: action.input,
+ value: action.value,
+ updating: false });
+
+ case constants.EVALUATE_EXPRESSION:
+ if (action.status === "done") {
+ return state.mergeIn(["expressions", action.id], { id: action.id,
+ input: action.input,
+ value: action.value,
+ updating: false });
+ }
+ break;
+
+ case constants.UPDATE_EXPRESSION:
+ return state.mergeIn(["expressions", action.id], { id: action.id,
+ input: action.input,
+ updating: true });
+
+ case constants.DELETE_EXPRESSION:
+ return state.deleteIn(["expressions", action.id]);
+ }
+
+ return state;
+ }
+
+ function getPause(state) {
+ return state.pause.get("pause");
+ }
+
+ function getLoadedObjects(state) {
+ return state.pause.get("loadedObjects");
+ }
+
+ function getExpressions(state) {
+ return state.pause.get("expressions");
+ }
+
+ function getIsWaitingOnBreak(state) {
+ return state.pause.get("isWaitingOnBreak");
+ }
+
+ function getShouldPauseOnExceptions(state) {
+ return state.pause.get("shouldPauseOnExceptions");
+ }
+
+ function getShouldIgnoreCaughtExceptions(state) {
+ return state.pause.get("shouldIgnoreCaughtExceptions");
+ }
+
+ function getFrames(state) {
+ return state.pause.get("frames");
+ }
+
+ function getSelectedFrame(state) {
+ var selectedFrameId = state.pause.get("selectedFrameId");
+ var frames = state.pause.get("frames");
+ return frames && frames.find(frame => frame.id == selectedFrameId);
+ }
+
+ module.exports = {
+ initialState,
+ update,
+ getPause,
+ getLoadedObjects,
+ getExpressions,
+ getIsWaitingOnBreak,
+ getShouldPauseOnExceptions,
+ getShouldIgnoreCaughtExceptions,
+ getFrames,
+ getSelectedFrame
+ };
+
+/***/ },
+/* 258 */
+/***/ function(module, exports, __webpack_require__) {
+
+
+
+ /**
+ * UI reducer
+ * @module reducers/ui
+ */
+
+ var constants = __webpack_require__(251);
+ var makeRecord = __webpack_require__(254);
+
+ var State = makeRecord({
+ searchOn: false
+ });
+
+ function update() {
+ var state = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : State();
+ var action = arguments[1];
+
+ switch (action.type) {
+ case constants.TOGGLE_FILE_SEARCH:
+ {
+ return state.set("searchOn", action.searchOn);
+ }
+ default:
+ {
+ return state;
+ }
+ }
+ }
+
+ // NOTE: we'd like to have the app state fully typed
+ // https://github.com/devtools-html/debugger.html/blob/master/public/js/reducers/sources.js#L179-L185
+
+
+ function getFileSearchState(state) {
+ return state.ui.get("searchOn");
+ }
+
+ module.exports = {
+ State,
+ update,
+ getFileSearchState
+ };
+
+/***/ },
+/* 259 */
+/***/ function(module, exports, __webpack_require__) {
+
+ var sources = __webpack_require__(252);
+ var pause = __webpack_require__(257);
+ var breakpoints = __webpack_require__(255);
+ var ui = __webpack_require__(258);
+
+ /**
+ * @param object - location
+ */
+
+ module.exports = {
+ getSource: sources.getSource,
+ getSourceByURL: sources.getSourceByURL,
+ getSourceById: sources.getSourceById,
+ getSources: sources.getSources,
+ getSourceText: sources.getSourceText,
+ getSourceTabs: sources.getSourceTabs,
+ getSelectedSource: sources.getSelectedSource,
+ getSelectedLocation: sources.getSelectedLocation,
+ getPendingSelectedLocation: sources.getPendingSelectedLocation,
+ getPrettySource: sources.getPrettySource,
+
+ getBreakpoint: breakpoints.getBreakpoint,
+ getBreakpoints: breakpoints.getBreakpoints,
+ getBreakpointsForSource: breakpoints.getBreakpointsForSource,
+ getBreakpointsDisabled: breakpoints.getBreakpointsDisabled,
+ getBreakpointsLoading: breakpoints.getBreakpointsLoading,
+
+ getPause: pause.getPause,
+ getLoadedObjects: pause.getLoadedObjects,
+ getExpressions: pause.getExpressions,
+ getExpressionInputVisibility: pause.getExpressionInputVisibility,
+ getIsWaitingOnBreak: pause.getIsWaitingOnBreak,
+ getShouldPauseOnExceptions: pause.getShouldPauseOnExceptions,
+ getShouldIgnoreCaughtExceptions: pause.getShouldIgnoreCaughtExceptions,
+ getFrames: pause.getFrames,
+ getSelectedFrame: pause.getSelectedFrame,
+
+ getFileSearchState: ui.getFileSearchState
+ };
+
+/***/ },
+/* 260 */
+/***/ function(module, exports, __webpack_require__) {
+
+ var React = __webpack_require__(2);
+ var dom = React.DOM;
+ var PropTypes = React.PropTypes;
+ var createFactory = React.createFactory;
+
+ var _require = __webpack_require__(19);
+
+ var connect = _require.connect;
+
+ var _require2 = __webpack_require__(3);
+
+ var bindActionCreators = _require2.bindActionCreators;
+
+ var _require3 = __webpack_require__(261);
+
+ var cmdString = _require3.cmdString;
+
+ var actions = __webpack_require__(262);
+
+ var _require4 = __webpack_require__(259);
+
+ var getSources = _require4.getSources;
+ var getSelectedSource = _require4.getSelectedSource;
+
+ var _require5 = __webpack_require__(28);
+
+ var KeyShortcuts = _require5.KeyShortcuts;
+
+ var shortcuts = new KeyShortcuts({ window });
+
+ __webpack_require__(283);
+ __webpack_require__(286);
+ __webpack_require__(288);
+ __webpack_require__(290);
+
+ var _require6 = __webpack_require__(30);
+
+ var SplitBox = _require6.SplitBox;
+
+ SplitBox = createFactory(SplitBox);
+
+ var SourceSearch = createFactory(__webpack_require__(292));
+ var Sources = createFactory(__webpack_require__(339));
+ var Editor = createFactory(__webpack_require__(400));
+ var RightSidebar = createFactory(__webpack_require__(416));
+ var SourceTabs = createFactory(__webpack_require__(452));
+
+ var App = React.createClass({
+ propTypes: {
+ sources: PropTypes.object,
+ selectSource: PropTypes.func,
+ selectedSource: PropTypes.object
+ },
+
+ displayName: "App",
+
+ getChildContext() {
+ return { shortcuts };
+ },
+
+ renderWelcomeBox() {
+ return dom.div({ className: "welcomebox" }, L10N.getFormatStr("welcome.search", cmdString() + "+P"));
+ },
+
+ renderCenterPane() {
+ return dom.div({ className: "center-pane" }, dom.div({ className: "editor-container" }, SourceTabs(), Editor(), !this.props.selectedSource ? this.renderWelcomeBox() : null, SourceSearch()));
+ },
+
+ render: function () {
+ return dom.div({ className: "debugger" }, SplitBox({
+ style: { width: "100vw" },
+ initialSize: "300px",
+ minSize: 10,
+ maxSize: "50%",
+ splitterSize: 1,
+ startPanel: Sources({ sources: this.props.sources }),
+ endPanel: SplitBox({
+ initialSize: "300px",
+ minSize: 10,
+ maxSize: "80%",
+ splitterSize: 1,
+ endPanelControl: true,
+ startPanel: this.renderCenterPane(this.props),
+ endPanel: RightSidebar()
+ })
+ }));
+ }
+ });
+
+ App.childContextTypes = {
+ shortcuts: PropTypes.object
+ };
+
+ module.exports = connect(state => ({ sources: getSources(state),
+ selectedSource: getSelectedSource(state)
+ }), dispatch => bindActionCreators(actions, dispatch))(App);
+
+/***/ },
+/* 261 */
+/***/ function(module, exports, __webpack_require__) {
+
+
+
+ /**
+ * Utils for keyboard command strings
+ * @module utils/text
+ */
+
+ var _require = __webpack_require__(30);
+
+ var appinfo = _require.Services.appinfo;
+
+ /**
+ * @memberof utils/text
+ * @static
+ */
+
+ function cmdString() {
+ return appinfo.OS === "Darwin" ? "⌘" : "Ctrl";
+ }
+
+ module.exports = {
+ cmdString
+ };
+
+/***/ },
+/* 262 */
+/***/ function(module, exports, __webpack_require__) {
+
+ var breakpoints = __webpack_require__(263);
+ var eventListeners = __webpack_require__(271);
+ var sources = __webpack_require__(273);
+ var pause = __webpack_require__(280);
+ var navigation = __webpack_require__(281);
+ var ui = __webpack_require__(282);
+
+ module.exports = Object.assign(navigation, breakpoints, eventListeners, sources, pause, ui);
+
+/***/ },
+/* 263 */
+/***/ function(module, exports, __webpack_require__) {
+
+ var _slicedToArray = function () { function sliceIterator(arr, i) { var _arr = []; var _n = true; var _d = false; var _e = undefined; try { for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i["return"]) _i["return"](); } finally { if (_d) throw _e; } } return _arr; } return function (arr, i) { if (Array.isArray(arr)) { return arr; } else if (Symbol.iterator in Object(arr)) { return sliceIterator(arr, i); } else { throw new TypeError("Invalid attempt to destructure non-iterable instance"); } }; }();
+
+ function _asyncToGenerator(fn) { return function () { var gen = fn.apply(this, arguments); return new Promise(function (resolve, reject) { function step(key, arg) { try { var info = gen[key](arg); var value = info.value; } catch (error) { reject(error); return; } if (info.done) { resolve(value); } else { return Promise.resolve(value).then(function (value) { step("next", value); }, function (err) { step("throw", err); }); } } return step("next"); }); }; }
+
+ /* 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/. */
+
+ /**
+ * Redux actions for breakpoints
+ * @module actions/breakpoints
+ */
+
+ var constants = __webpack_require__(251);
+
+ var _require = __webpack_require__(242);
+
+ var PROMISE = _require.PROMISE;
+
+ var _require2 = __webpack_require__(259);
+
+ var getBreakpoint = _require2.getBreakpoint;
+ var getBreakpoints = _require2.getBreakpoints;
+ var getSource = _require2.getSource;
+
+ var _require3 = __webpack_require__(264);
+
+ var getOriginalLocation = _require3.getOriginalLocation;
+ var getGeneratedLocation = _require3.getGeneratedLocation;
+ var isOriginalId = _require3.isOriginalId;
+
+
+ function _breakpointExists(state, location) {
+ var currentBp = getBreakpoint(state, location);
+ return currentBp && !currentBp.disabled;
+ }
+
+ function _getOrCreateBreakpoint(state, location, condition) {
+ return getBreakpoint(state, location) || { location, condition, text: "" };
+ }
+
+ /**
+ * Enabling a breakpoint calls {@link addBreakpoint}
+ * which will reuse the existing breakpoint information that is stored.
+ *
+ * @memberof actions/breakpoints
+ * @static
+ */
+ function enableBreakpoint(location) {
+ return addBreakpoint(location);
+ }
+
+ /**
+ * Add a new or enable an existing breakpoint
+ *
+ * @memberof actions/breakpoints
+ * @static
+ * @param {String} $1.condition Conditional breakpoint condition value
+ * @param {Function} $1.getTextForLine Get the text to represent the line
+ */
+ function addBreakpoint(location) {
+ var _ref = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
+
+ var condition = _ref.condition;
+ var getTextForLine = _ref.getTextForLine;
+
+ return (_ref2) => {
+ var dispatch = _ref2.dispatch;
+ var getState = _ref2.getState;
+ var client = _ref2.client;
+
+ if (_breakpointExists(getState(), location)) {
+ return Promise.resolve();
+ }
+
+ var bp = _getOrCreateBreakpoint(getState(), location, condition);
+
+ return dispatch({
+ type: constants.ADD_BREAKPOINT,
+ breakpoint: bp,
+ condition: condition,
+ [PROMISE]: _asyncToGenerator(function* () {
+ if (isOriginalId(bp.location.sourceId)) {
+ var source = getSource(getState(), bp.location.sourceId);
+ location = yield getGeneratedLocation(bp.location, source.toJS());
+ }
+
+ var _ref4 = yield client.setBreakpoint(location, bp.condition, isOriginalId(bp.location.sourceId));
+
+ var id = _ref4.id;
+ var actualLocation = _ref4.actualLocation;
+
+
+ actualLocation = yield getOriginalLocation(actualLocation);
+
+ // If this breakpoint is being re-enabled, it already has a
+ // text snippet.
+ var text = bp.text;
+ if (!text) {
+ text = getTextForLine ? getTextForLine(actualLocation.line) : "";
+ }
+
+ return { id, actualLocation, text };
+ })()
+ });
+ };
+ }
+
+ /**
+ * Disable a single breakpoint
+ *
+ * @memberof actions/breakpoints
+ * @static
+ */
+ function disableBreakpoint(location) {
+ return _removeOrDisableBreakpoint(location, true);
+ }
+
+ /**
+ * Remove a single breakpoint
+ *
+ * @memberof actions/breakpoints
+ * @static
+ */
+ function removeBreakpoint(location) {
+ return _removeOrDisableBreakpoint(location);
+ }
+
+ function _removeOrDisableBreakpoint(location, isDisabled) {
+ return (_ref5) => {
+ var dispatch = _ref5.dispatch;
+ var getState = _ref5.getState;
+ var client = _ref5.client;
+
+ var bp = getBreakpoint(getState(), location);
+ if (!bp) {
+ throw new Error("attempt to remove breakpoint that does not exist");
+ }
+ if (bp.loading) {
+ // TODO(jwl): make this wait until the breakpoint is saved if it
+ // is still loading
+ throw new Error("attempt to remove unsaved breakpoint");
+ }
+
+ var action = {
+ type: constants.REMOVE_BREAKPOINT,
+ breakpoint: bp,
+ disabled: isDisabled
+ };
+
+ // If the breakpoint is already disabled, we don't need to remove
+ // it from the server. We just need to dispatch an action
+ // simulating a successful server request to remove it, and it
+ // will be removed completely from the state.
+ if (!bp.disabled) {
+ return dispatch(Object.assign({}, action, {
+ [PROMISE]: client.removeBreakpoint(bp.id)
+ }));
+ }
+ return dispatch(Object.assign({}, action, { status: "done" }));
+ };
+ }
+
+ /**
+ * Toggle All Breakpoints
+ *
+ * @memberof actions/breakpoints
+ * @static
+ */
+ function toggleAllBreakpoints(shouldDisableBreakpoints) {
+ return (_ref6) => {
+ var dispatch = _ref6.dispatch;
+ var getState = _ref6.getState;
+
+ var breakpoints = getBreakpoints(getState());
+ return dispatch({
+ type: constants.TOGGLE_BREAKPOINTS,
+ shouldDisableBreakpoints,
+ [PROMISE]: _asyncToGenerator(function* () {
+ for (var _ref8 of breakpoints) {
+ var _ref9 = _slicedToArray(_ref8, 2);
+
+ var breakpoint = _ref9[1];
+
+ if (shouldDisableBreakpoints) {
+ yield dispatch(disableBreakpoint(breakpoint.location));
+ } else {
+ yield dispatch(enableBreakpoint(breakpoint.location));
+ }
+ }
+ })()
+ });
+ };
+ }
+
+ /**
+ * Update the condition of a breakpoint.
+ *
+ * @throws {Error} "not implemented"
+ * @memberof actions/breakpoints
+ * @static
+ * @param {Location} location
+ * @see DebuggerController.Breakpoints.addBreakpoint
+ * @param {string} condition
+ * The condition to set on the breakpoint
+ */
+ function setBreakpointCondition(location) {
+ var _ref10 = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
+
+ var condition = _ref10.condition;
+ var getTextForLine = _ref10.getTextForLine;
+
+ // location: Location, condition: string, { getTextForLine }) {
+ return (_ref11) => {
+ var dispatch = _ref11.dispatch;
+ var getState = _ref11.getState;
+ var client = _ref11.client;
+
+ var bp = getBreakpoint(getState(), location);
+ if (!bp) {
+ return dispatch(addBreakpoint(location, { condition, getTextForLine }));
+ }
+
+ if (bp.loading) {
+ // TODO(jwl): when this function is called, make sure the action
+ // creator waits for the breakpoint to exist
+ throw new Error("breakpoint must be saved");
+ }
+
+ return dispatch({
+ type: constants.SET_BREAKPOINT_CONDITION,
+ breakpoint: bp,
+ condition: condition,
+ [PROMISE]: client.setBreakpointCondition(bp.id, location, condition, isOriginalId(bp.location.sourceId))
+ });
+ };
+ }
+
+ module.exports = {
+ enableBreakpoint,
+ addBreakpoint,
+ disableBreakpoint,
+ removeBreakpoint,
+ toggleAllBreakpoints,
+ setBreakpointCondition
+ };
+
+/***/ },
+/* 264 */
+/***/ function(module, exports, __webpack_require__) {
+
+ var _require = __webpack_require__(89);
+
+ var getValue = _require.getValue;
+ var isEnabled = _require.isEnabled;
+
+ var _require2 = __webpack_require__(244);
+
+ var workerTask = _require2.workerTask;
+
+ var _require3 = __webpack_require__(265);
+
+ var originalToGeneratedId = _require3.originalToGeneratedId;
+ var generatedToOriginalId = _require3.generatedToOriginalId;
+ var isGeneratedId = _require3.isGeneratedId;
+ var isOriginalId = _require3.isOriginalId;
+
+ var _require4 = __webpack_require__(270);
+
+ var prefs = _require4.prefs;
+
+
+ var sourceMapWorker = void 0;
+ function restartWorker() {
+ if (sourceMapWorker) {
+ sourceMapWorker.terminate();
+ }
+ sourceMapWorker = new Worker(getValue("baseWorkerURL") + "source-map-worker.js");
+
+ if (isEnabled("sourceMaps")) {
+ sourceMapWorker.postMessage({ id: 0, method: "enableSourceMaps" });
+ }
+ }
+ restartWorker();
+
+ function destroyWorker() {
+ if (sourceMapWorker) {
+ sourceMapWorker.terminate();
+ sourceMapWorker = null;
+ }
+ }
+
+ function shouldSourceMap() {
+ return isEnabled("sourceMaps") && prefs.clientSourceMapsEnabled;
+ }
+
+ var getOriginalURLs = workerTask(sourceMapWorker, "getOriginalURLs");
+ var getGeneratedLocation = workerTask(sourceMapWorker, "getGeneratedLocation");
+ var getOriginalLocation = workerTask(sourceMapWorker, "getOriginalLocation");
+ var getOriginalSourceText = workerTask(sourceMapWorker, "getOriginalSourceText");
+ var applySourceMap = workerTask(sourceMapWorker, "applySourceMap");
+ var clearSourceMaps = workerTask(sourceMapWorker, "clearSourceMaps");
+
+ module.exports = {
+ originalToGeneratedId,
+ generatedToOriginalId,
+ isGeneratedId,
+ isOriginalId,
+
+ getOriginalURLs,
+ getGeneratedLocation,
+ getOriginalLocation,
+ getOriginalSourceText,
+ applySourceMap,
+ clearSourceMaps,
+ destroyWorker,
+ shouldSourceMap
+ };
+
+/***/ },
+/* 265 */
+/***/ function(module, exports, __webpack_require__) {
+
+ var md5 = __webpack_require__(266);
+
+ function originalToGeneratedId(originalId) {
+ var match = originalId.match(/(.*)\/originalSource/);
+ return match ? match[1] : "";
+ }
+
+ function generatedToOriginalId(generatedId, url) {
+ return generatedId + "/originalSource-" + md5(url);
+ }
+
+ function isOriginalId(id) {
+ return !!id.match(/\/originalSource/);
+ }
+
+ function isGeneratedId(id) {
+ return !isOriginalId(id);
+ }
+
+ module.exports = {
+ originalToGeneratedId, generatedToOriginalId, isOriginalId, isGeneratedId
+ };
+
+/***/ },
+/* 266 */
+/***/ function(module, exports, __webpack_require__) {
+
+ (function(){
+ var crypt = __webpack_require__(267),
+ utf8 = __webpack_require__(268).utf8,
+ isBuffer = __webpack_require__(269),
+ bin = __webpack_require__(268).bin,
+
+ // The core
+ md5 = function (message, options) {
+ // Convert to byte array
+ if (message.constructor == String)
+ if (options && options.encoding === 'binary')
+ message = bin.stringToBytes(message);
+ else
+ message = utf8.stringToBytes(message);
+ else if (isBuffer(message))
+ message = Array.prototype.slice.call(message, 0);
+ else if (!Array.isArray(message))
+ message = message.toString();
+ // else, assume byte array already
+
+ var m = crypt.bytesToWords(message),
+ l = message.length * 8,
+ a = 1732584193,
+ b = -271733879,
+ c = -1732584194,
+ d = 271733878;
+
+ // Swap endian
+ for (var i = 0; i < m.length; i++) {
+ m[i] = ((m[i] << 8) | (m[i] >>> 24)) & 0x00FF00FF |
+ ((m[i] << 24) | (m[i] >>> 8)) & 0xFF00FF00;
+ }
+
+ // Padding
+ m[l >>> 5] |= 0x80 << (l % 32);
+ m[(((l + 64) >>> 9) << 4) + 14] = l;
+
+ // Method shortcuts
+ var FF = md5._ff,
+ GG = md5._gg,
+ HH = md5._hh,
+ II = md5._ii;
+
+ for (var i = 0; i < m.length; i += 16) {
+
+ var aa = a,
+ bb = b,
+ cc = c,
+ dd = d;
+
+ a = FF(a, b, c, d, m[i+ 0], 7, -680876936);
+ d = FF(d, a, b, c, m[i+ 1], 12, -389564586);
+ c = FF(c, d, a, b, m[i+ 2], 17, 606105819);
+ b = FF(b, c, d, a, m[i+ 3], 22, -1044525330);
+ a = FF(a, b, c, d, m[i+ 4], 7, -176418897);
+ d = FF(d, a, b, c, m[i+ 5], 12, 1200080426);
+ c = FF(c, d, a, b, m[i+ 6], 17, -1473231341);
+ b = FF(b, c, d, a, m[i+ 7], 22, -45705983);
+ a = FF(a, b, c, d, m[i+ 8], 7, 1770035416);
+ d = FF(d, a, b, c, m[i+ 9], 12, -1958414417);
+ c = FF(c, d, a, b, m[i+10], 17, -42063);
+ b = FF(b, c, d, a, m[i+11], 22, -1990404162);
+ a = FF(a, b, c, d, m[i+12], 7, 1804603682);
+ d = FF(d, a, b, c, m[i+13], 12, -40341101);
+ c = FF(c, d, a, b, m[i+14], 17, -1502002290);
+ b = FF(b, c, d, a, m[i+15], 22, 1236535329);
+
+ a = GG(a, b, c, d, m[i+ 1], 5, -165796510);
+ d = GG(d, a, b, c, m[i+ 6], 9, -1069501632);
+ c = GG(c, d, a, b, m[i+11], 14, 643717713);
+ b = GG(b, c, d, a, m[i+ 0], 20, -373897302);
+ a = GG(a, b, c, d, m[i+ 5], 5, -701558691);
+ d = GG(d, a, b, c, m[i+10], 9, 38016083);
+ c = GG(c, d, a, b, m[i+15], 14, -660478335);
+ b = GG(b, c, d, a, m[i+ 4], 20, -405537848);
+ a = GG(a, b, c, d, m[i+ 9], 5, 568446438);
+ d = GG(d, a, b, c, m[i+14], 9, -1019803690);
+ c = GG(c, d, a, b, m[i+ 3], 14, -187363961);
+ b = GG(b, c, d, a, m[i+ 8], 20, 1163531501);
+ a = GG(a, b, c, d, m[i+13], 5, -1444681467);
+ d = GG(d, a, b, c, m[i+ 2], 9, -51403784);
+ c = GG(c, d, a, b, m[i+ 7], 14, 1735328473);
+ b = GG(b, c, d, a, m[i+12], 20, -1926607734);
+
+ a = HH(a, b, c, d, m[i+ 5], 4, -378558);
+ d = HH(d, a, b, c, m[i+ 8], 11, -2022574463);
+ c = HH(c, d, a, b, m[i+11], 16, 1839030562);
+ b = HH(b, c, d, a, m[i+14], 23, -35309556);
+ a = HH(a, b, c, d, m[i+ 1], 4, -1530992060);
+ d = HH(d, a, b, c, m[i+ 4], 11, 1272893353);
+ c = HH(c, d, a, b, m[i+ 7], 16, -155497632);
+ b = HH(b, c, d, a, m[i+10], 23, -1094730640);
+ a = HH(a, b, c, d, m[i+13], 4, 681279174);
+ d = HH(d, a, b, c, m[i+ 0], 11, -358537222);
+ c = HH(c, d, a, b, m[i+ 3], 16, -722521979);
+ b = HH(b, c, d, a, m[i+ 6], 23, 76029189);
+ a = HH(a, b, c, d, m[i+ 9], 4, -640364487);
+ d = HH(d, a, b, c, m[i+12], 11, -421815835);
+ c = HH(c, d, a, b, m[i+15], 16, 530742520);
+ b = HH(b, c, d, a, m[i+ 2], 23, -995338651);
+
+ a = II(a, b, c, d, m[i+ 0], 6, -198630844);
+ d = II(d, a, b, c, m[i+ 7], 10, 1126891415);
+ c = II(c, d, a, b, m[i+14], 15, -1416354905);
+ b = II(b, c, d, a, m[i+ 5], 21, -57434055);
+ a = II(a, b, c, d, m[i+12], 6, 1700485571);
+ d = II(d, a, b, c, m[i+ 3], 10, -1894986606);
+ c = II(c, d, a, b, m[i+10], 15, -1051523);
+ b = II(b, c, d, a, m[i+ 1], 21, -2054922799);
+ a = II(a, b, c, d, m[i+ 8], 6, 1873313359);
+ d = II(d, a, b, c, m[i+15], 10, -30611744);
+ c = II(c, d, a, b, m[i+ 6], 15, -1560198380);
+ b = II(b, c, d, a, m[i+13], 21, 1309151649);
+ a = II(a, b, c, d, m[i+ 4], 6, -145523070);
+ d = II(d, a, b, c, m[i+11], 10, -1120210379);
+ c = II(c, d, a, b, m[i+ 2], 15, 718787259);
+ b = II(b, c, d, a, m[i+ 9], 21, -343485551);
+
+ a = (a + aa) >>> 0;
+ b = (b + bb) >>> 0;
+ c = (c + cc) >>> 0;
+ d = (d + dd) >>> 0;
+ }
+
+ return crypt.endian([a, b, c, d]);
+ };
+
+ // Auxiliary functions
+ md5._ff = function (a, b, c, d, x, s, t) {
+ var n = a + (b & c | ~b & d) + (x >>> 0) + t;
+ return ((n << s) | (n >>> (32 - s))) + b;
+ };
+ md5._gg = function (a, b, c, d, x, s, t) {
+ var n = a + (b & d | c & ~d) + (x >>> 0) + t;
+ return ((n << s) | (n >>> (32 - s))) + b;
+ };
+ md5._hh = function (a, b, c, d, x, s, t) {
+ var n = a + (b ^ c ^ d) + (x >>> 0) + t;
+ return ((n << s) | (n >>> (32 - s))) + b;
+ };
+ md5._ii = function (a, b, c, d, x, s, t) {
+ var n = a + (c ^ (b | ~d)) + (x >>> 0) + t;
+ return ((n << s) | (n >>> (32 - s))) + b;
+ };
+
+ // Package private blocksize
+ md5._blocksize = 16;
+ md5._digestsize = 16;
+
+ module.exports = function (message, options) {
+ if (message === undefined || message === null)
+ throw new Error('Illegal argument ' + message);
+
+ var digestbytes = crypt.wordsToBytes(md5(message, options));
+ return options && options.asBytes ? digestbytes :
+ options && options.asString ? bin.bytesToString(digestbytes) :
+ crypt.bytesToHex(digestbytes);
+ };
+
+ })();
+
+
+/***/ },
+/* 267 */
+/***/ function(module, exports) {
+
+ (function() {
+ var base64map
+ = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/',
+
+ crypt = {
+ // Bit-wise rotation left
+ rotl: function(n, b) {
+ return (n << b) | (n >>> (32 - b));
+ },
+
+ // Bit-wise rotation right
+ rotr: function(n, b) {
+ return (n << (32 - b)) | (n >>> b);
+ },
+
+ // Swap big-endian to little-endian and vice versa
+ endian: function(n) {
+ // If number given, swap endian
+ if (n.constructor == Number) {
+ return crypt.rotl(n, 8) & 0x00FF00FF | crypt.rotl(n, 24) & 0xFF00FF00;
+ }
+
+ // Else, assume array and swap all items
+ for (var i = 0; i < n.length; i++)
+ n[i] = crypt.endian(n[i]);
+ return n;
+ },
+
+ // Generate an array of any length of random bytes
+ randomBytes: function(n) {
+ for (var bytes = []; n > 0; n--)
+ bytes.push(Math.floor(Math.random() * 256));
+ return bytes;
+ },
+
+ // Convert a byte array to big-endian 32-bit words
+ bytesToWords: function(bytes) {
+ for (var words = [], i = 0, b = 0; i < bytes.length; i++, b += 8)
+ words[b >>> 5] |= bytes[i] << (24 - b % 32);
+ return words;
+ },
+
+ // Convert big-endian 32-bit words to a byte array
+ wordsToBytes: function(words) {
+ for (var bytes = [], b = 0; b < words.length * 32; b += 8)
+ bytes.push((words[b >>> 5] >>> (24 - b % 32)) & 0xFF);
+ return bytes;
+ },
+
+ // Convert a byte array to a hex string
+ bytesToHex: function(bytes) {
+ for (var hex = [], i = 0; i < bytes.length; i++) {
+ hex.push((bytes[i] >>> 4).toString(16));
+ hex.push((bytes[i] & 0xF).toString(16));
+ }
+ return hex.join('');
+ },
+
+ // Convert a hex string to a byte array
+ hexToBytes: function(hex) {
+ for (var bytes = [], c = 0; c < hex.length; c += 2)
+ bytes.push(parseInt(hex.substr(c, 2), 16));
+ return bytes;
+ },
+
+ // Convert a byte array to a base-64 string
+ bytesToBase64: function(bytes) {
+ for (var base64 = [], i = 0; i < bytes.length; i += 3) {
+ var triplet = (bytes[i] << 16) | (bytes[i + 1] << 8) | bytes[i + 2];
+ for (var j = 0; j < 4; j++)
+ if (i * 8 + j * 6 <= bytes.length * 8)
+ base64.push(base64map.charAt((triplet >>> 6 * (3 - j)) & 0x3F));
+ else
+ base64.push('=');
+ }
+ return base64.join('');
+ },
+
+ // Convert a base-64 string to a byte array
+ base64ToBytes: function(base64) {
+ // Remove non-base-64 characters
+ base64 = base64.replace(/[^A-Z0-9+\/]/ig, '');
+
+ for (var bytes = [], i = 0, imod4 = 0; i < base64.length;
+ imod4 = ++i % 4) {
+ if (imod4 == 0) continue;
+ bytes.push(((base64map.indexOf(base64.charAt(i - 1))
+ & (Math.pow(2, -2 * imod4 + 8) - 1)) << (imod4 * 2))
+ | (base64map.indexOf(base64.charAt(i)) >>> (6 - imod4 * 2)));
+ }
+ return bytes;
+ }
+ };
+
+ module.exports = crypt;
+ })();
+
+
+/***/ },
+/* 268 */
+/***/ function(module, exports) {
+
+ var charenc = {
+ // UTF-8 encoding
+ utf8: {
+ // Convert a string to a byte array
+ stringToBytes: function(str) {
+ return charenc.bin.stringToBytes(unescape(encodeURIComponent(str)));
+ },
+
+ // Convert a byte array to a string
+ bytesToString: function(bytes) {
+ return decodeURIComponent(escape(charenc.bin.bytesToString(bytes)));
+ }
+ },
+
+ // Binary encoding
+ bin: {
+ // Convert a string to a byte array
+ stringToBytes: function(str) {
+ for (var bytes = [], i = 0; i < str.length; i++)
+ bytes.push(str.charCodeAt(i) & 0xFF);
+ return bytes;
+ },
+
+ // Convert a byte array to a string
+ bytesToString: function(bytes) {
+ for (var str = [], i = 0; i < bytes.length; i++)
+ str.push(String.fromCharCode(bytes[i]));
+ return str.join('');
+ }
+ }
+ };
+
+ module.exports = charenc;
+
+
+/***/ },
+/* 269 */
+/***/ function(module, exports) {
+
+ /*!
+ * Determine if an object is a Buffer
+ *
+ * @author Feross Aboukhadijeh <feross@feross.org> <http://feross.org>
+ * @license MIT
+ */
+
+ // The _isBuffer check is for Safari 5-7 support, because it's missing
+ // Object.prototype.constructor. Remove this eventually
+ module.exports = function (obj) {
+ return obj != null && (isBuffer(obj) || isSlowBuffer(obj) || !!obj._isBuffer)
+ }
+
+ function isBuffer (obj) {
+ return !!obj.constructor && typeof obj.constructor.isBuffer === 'function' && obj.constructor.isBuffer(obj)
+ }
+
+ // For Node v0.10 support. Remove this eventually.
+ function isSlowBuffer (obj) {
+ return typeof obj.readFloatLE === 'function' && typeof obj.slice === 'function' && isBuffer(obj.slice(0, 0))
+ }
+
+
+/***/ },
+/* 270 */
+/***/ function(module, exports, __webpack_require__) {
+
+ var { PrefsHelper } = __webpack_require__(28);
+ const { Services: { pref }} = __webpack_require__(30);
+ const { isDevelopment } = __webpack_require__(89);
+
+ if (isDevelopment()) {
+ pref("devtools.debugger.client-source-maps-enabled", true);
+ }
+
+ const prefs = new PrefsHelper("devtools", {
+ clientSourceMapsEnabled: ["Bool", "debugger.client-source-maps-enabled"],
+ });
+
+ module.exports = { prefs };
+
+
+/***/ },
+/* 271 */
+/***/ function(module, exports, __webpack_require__) {
+
+ /* 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/. */
+ /* global window gThreadClient setNamedTimeout services EVENTS */
+ /* eslint no-shadow: 0 */
+
+ /**
+ * Redux actions for the event listeners state
+ * @module actions/event-listeners
+ */
+
+ var constants = __webpack_require__(251);
+
+ var _require = __webpack_require__(244);
+
+ var asPaused = _require.asPaused;
+
+ var _require2 = __webpack_require__(246);
+
+ var reportException = _require2.reportException;
+
+ var _require3 = __webpack_require__(272);
+
+ var Task = _require3.Task;
+
+ // delay is in ms
+
+ var FETCH_EVENT_LISTENERS_DELAY = 200;
+
+ /**
+ * @memberof actions/event-listeners
+ * @static
+ */
+ function fetchEventListeners() {
+ return (dispatch, getState) => {
+ // Make sure we"re not sending a batch of closely repeated requests.
+ // This can easily happen whenever new sources are fetched.
+ setNamedTimeout("event-listeners-fetch", FETCH_EVENT_LISTENERS_DELAY, () => {
+ // In case there is still a request of listeners going on (it
+ // takes several RDP round trips right now), make sure we wait
+ // on a currently running request
+ if (getState().eventListeners.fetchingListeners) {
+ dispatch({
+ type: services.WAIT_UNTIL,
+ predicate: action => action.type === constants.FETCH_EVENT_LISTENERS && action.status === "done",
+ run: dispatch => dispatch(fetchEventListeners())
+ });
+ return;
+ }
+
+ dispatch({
+ type: constants.FETCH_EVENT_LISTENERS,
+ status: "begin"
+ });
+
+ asPaused(gThreadClient, _getListeners).then(listeners => {
+ // Notify that event listeners were fetched and shown in the view,
+ // and callback to resume the active thread if necessary.
+ window.emit(EVENTS.EVENT_LISTENERS_FETCHED);
+
+ dispatch({
+ type: constants.FETCH_EVENT_LISTENERS,
+ status: "done",
+ listeners: listeners
+ });
+ });
+ });
+ };
+ }
+
+ var _getListeners = Task.async(function* () {
+ var response = yield gThreadClient.eventListeners();
+
+ // Make sure all the listeners are sorted by the event type, since
+ // they"re not guaranteed to be clustered together.
+ response.listeners.sort((a, b) => a.type > b.type ? 1 : -1);
+
+ // Add all the listeners in the debugger view event linsteners container.
+ var fetchedDefinitions = new Map();
+ var listeners = [];
+ for (var listener of response.listeners) {
+ var definitionSite = void 0;
+ if (fetchedDefinitions.has(listener.function.actor)) {
+ definitionSite = fetchedDefinitions.get(listener.function.actor);
+ } else if (listener.function.class == "Function") {
+ definitionSite = yield _getDefinitionSite(listener.function);
+ if (!definitionSite) {
+ // We don"t know where this listener comes from so don"t show it in
+ // the UI as breaking on it doesn"t work (bug 942899).
+ continue;
+ }
+
+ fetchedDefinitions.set(listener.function.actor, definitionSite);
+ }
+ listener.function.url = definitionSite;
+ listeners.push(listener);
+ }
+ fetchedDefinitions.clear();
+
+ return listeners;
+ });
+
+ var _getDefinitionSite = Task.async(function* (func) {
+ var grip = gThreadClient.pauseGrip(func);
+ var response = void 0;
+
+ try {
+ response = yield grip.getDefinitionSite();
+ } catch (e) {
+ // Don't make this error fatal, it would break the entire events pane.
+ reportException("_getDefinitionSite", e);
+ return null;
+ }
+
+ return response.source.url;
+ });
+
+ /**
+ * @memberof actions/event-listeners
+ * @static
+ * @param {string} eventNames
+ */
+ function updateEventBreakpoints(eventNames) {
+ return dispatch => {
+ setNamedTimeout("event-breakpoints-update", 0, () => {
+ gThreadClient.pauseOnDOMEvents(eventNames, function () {
+ // Notify that event breakpoints were added/removed on the server.
+ window.emit(EVENTS.EVENT_BREAKPOINTS_UPDATED);
+
+ dispatch({
+ type: constants.UPDATE_EVENT_BREAKPOINTS,
+ eventNames: eventNames
+ });
+ });
+ });
+ };
+ }
+
+ module.exports = { updateEventBreakpoints, fetchEventListeners };
+
+/***/ },
+/* 272 */
+/***/ function(module, exports) {
+
+ /* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+ /* vim: set ts=2 et sw=2 tw=80 filetype=javascript: */
+ /* 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/. */
+
+ /**
+ * This object provides the public module functions.
+ */
+ var Task = {
+ // XXX: Not sure if this works in all cases...
+ async: function (task) {
+ return function () {
+ return Task.spawn(task, this, arguments);
+ };
+ },
+
+ /**
+ * Creates and starts a new task.
+ * @param task A generator function
+ * @return A promise, resolved when the task terminates
+ */
+ spawn: function (task, scope, args) {
+ return new Promise(function (resolve, reject) {
+ var iterator = task.apply(scope, args);
+
+ var callNext = lastValue => {
+ var iteration = iterator.next(lastValue);
+ Promise.resolve(iteration.value).then(value => {
+ if (iteration.done) {
+ resolve(value);
+ } else {
+ callNext(value);
+ }
+ }).catch(error => {
+ reject(error);
+ iterator.throw(error);
+ });
+ };
+
+ callNext(undefined);
+ });
+ }
+ };
+
+ module.exports = { Task };
+
+/***/ },
+/* 273 */
+/***/ function(module, exports, __webpack_require__) {
+
+ var _slicedToArray = function () { function sliceIterator(arr, i) { var _arr = []; var _n = true; var _d = false; var _e = undefined; try { for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i["return"]) _i["return"](); } finally { if (_d) throw _e; } } return _arr; } return function (arr, i) { if (Array.isArray(arr)) { return arr; } else if (Symbol.iterator in Object(arr)) { return sliceIterator(arr, i); } else { throw new TypeError("Invalid attempt to destructure non-iterable instance"); } }; }();
+
+ function _asyncToGenerator(fn) { return function () { var gen = fn.apply(this, arguments); return new Promise(function (resolve, reject) { function step(key, arg) { try { var info = gen[key](arg); var value = info.value; } catch (error) { reject(error); return; } if (info.done) { resolve(value); } else { return Promise.resolve(value).then(function (value) { step("next", value); }, function (err) { step("throw", err); }); } } return step("next"); }); }; }
+
+ /* 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/. */
+
+ /**
+ * Redux actions for the sources state
+ * @module actions/sources
+ */
+
+ var defer = __webpack_require__(243);
+
+ var _require = __webpack_require__(242);
+
+ var PROMISE = _require.PROMISE;
+
+ var assert = __webpack_require__(247);
+
+ var _require2 = __webpack_require__(274);
+
+ var updateFrameLocations = _require2.updateFrameLocations;
+
+ var _require3 = __webpack_require__(264);
+
+ var getOriginalURLs = _require3.getOriginalURLs;
+ var getOriginalSourceText = _require3.getOriginalSourceText;
+ var generatedToOriginalId = _require3.generatedToOriginalId;
+ var isOriginalId = _require3.isOriginalId;
+ var isGeneratedId = _require3.isGeneratedId;
+ var applySourceMap = _require3.applySourceMap;
+ var shouldSourceMap = _require3.shouldSourceMap;
+
+ var _require4 = __webpack_require__(276);
+
+ var prettyPrint = _require4.prettyPrint;
+
+
+ var constants = __webpack_require__(251);
+
+ var _require5 = __webpack_require__(89);
+
+ var isEnabled = _require5.isEnabled;
+
+ var _require6 = __webpack_require__(279);
+
+ var removeDocument = _require6.removeDocument;
+
+ var _require7 = __webpack_require__(259);
+
+ var getSource = _require7.getSource;
+ var getSourceByURL = _require7.getSourceByURL;
+ var getSourceText = _require7.getSourceText;
+ var getPendingSelectedLocation = _require7.getPendingSelectedLocation;
+ var getFrames = _require7.getFrames;
+
+
+ /**
+ * Handler for the debugger client's unsolicited newSource notification.
+ * @memberof actions/sources
+ * @static
+ */
+ function newSource(source) {
+ return (_ref) => {
+ var dispatch = _ref.dispatch;
+ var getState = _ref.getState;
+
+ if (shouldSourceMap()) {
+ dispatch(loadSourceMap(source));
+ }
+
+ dispatch({
+ type: constants.ADD_SOURCE,
+ source
+ });
+
+ // If a request has been made to show this source, go ahead and
+ // select it.
+ var pendingLocation = getPendingSelectedLocation(getState());
+ if (pendingLocation && pendingLocation.url === source.url) {
+ dispatch(selectSource(source.id, { line: pendingLocation.line }));
+ }
+ };
+ }
+
+ function newSources(sources) {
+ return (_ref2) => {
+ var dispatch = _ref2.dispatch;
+ var getState = _ref2.getState;
+
+ sources.filter(source => !getSource(getState(), source.id)).forEach(source => dispatch(newSource(source)));
+ };
+ }
+
+ /**
+ * @memberof actions/sources
+ * @static
+ */
+ function loadSourceMap(generatedSource) {
+ return (() => {
+ var _ref3 = _asyncToGenerator(function* (_ref4) {
+ var dispatch = _ref4.dispatch;
+ var getState = _ref4.getState;
+
+ var urls = yield getOriginalURLs(generatedSource);
+ if (!urls) {
+ // If this source doesn't have a sourcemap, do nothing.
+ return;
+ }
+
+ var originalSources = urls.map(function (originalUrl) {
+ return {
+ url: originalUrl,
+ id: generatedToOriginalId(generatedSource.id, originalUrl),
+ isPrettyPrinted: false
+ };
+ });
+
+ originalSources.forEach(function (s) {
+ return dispatch(newSource(s));
+ });
+ });
+
+ return function (_x) {
+ return _ref3.apply(this, arguments);
+ };
+ })();
+ }
+
+ /**
+ * Deterministically select a source that has a given URL. This will
+ * work regardless of the connection status or if the source exists
+ * yet. This exists mostly for external things to interact with the
+ * debugger.
+ *
+ * @memberof actions/sources
+ * @static
+ */
+ function selectSourceURL(url) {
+ var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
+
+ return (_ref5) => {
+ var dispatch = _ref5.dispatch;
+ var getState = _ref5.getState;
+
+ var source = getSourceByURL(getState(), url);
+ if (source) {
+ dispatch(selectSource(source.get("id"), options));
+ } else {
+ dispatch({
+ type: constants.SELECT_SOURCE_URL,
+ url: url,
+ tabIndex: options.tabIndex,
+ line: options.line
+ });
+ }
+ };
+ }
+
+ /**
+ * @memberof actions/sources
+ * @static
+ */
+ function selectSource(id) {
+ var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
+
+ return (_ref6) => {
+ var dispatch = _ref6.dispatch;
+ var getState = _ref6.getState;
+ var client = _ref6.client;
+
+ if (!client) {
+ // No connection, do nothing. This happens when the debugger is
+ // shut down too fast and it tries to display a default source.
+ return;
+ }
+
+ var source = getSource(getState(), id).toJS();
+
+ // Make sure to start a request to load the source text.
+ dispatch(loadSourceText(source));
+
+ dispatch({ type: constants.TOGGLE_FILE_SEARCH, searchOn: false });
+
+ dispatch({
+ type: constants.SELECT_SOURCE,
+ source: source,
+ tabIndex: options.tabIndex,
+ line: options.line
+ });
+ };
+ }
+
+ /**
+ * @memberof actions/sources
+ * @static
+ */
+ function closeTab(id) {
+ removeDocument(id);
+ return {
+ type: constants.CLOSE_TAB,
+ id: id
+ };
+ }
+
+ /**
+ * Toggle the pretty printing of a source's text. All subsequent calls to
+ * |getText| will return the pretty-toggled text. Nothing will happen for
+ * non-javascript files.
+ *
+ * @memberof actions/sources
+ * @static
+ * @param string id The source form from the RDP.
+ * @returns Promise
+ * A promise that resolves to [aSource, prettyText] or rejects to
+ * [aSource, error].
+ */
+ function togglePrettyPrint(sourceId) {
+ return (_ref7) => {
+ var dispatch = _ref7.dispatch;
+ var getState = _ref7.getState;
+ var client = _ref7.client;
+
+ var source = getSource(getState(), sourceId).toJS();
+ var sourceText = getSourceText(getState(), sourceId).toJS();
+
+ if (!isEnabled("prettyPrint") || sourceText.loading) {
+ return {};
+ }
+
+ assert(isGeneratedId(sourceId), "Pretty-printing only allowed on generated sources");
+
+ var url = source.url + ":formatted";
+ var id = generatedToOriginalId(source.id, url);
+ var originalSource = { url, id, isPrettyPrinted: false };
+ dispatch({
+ type: constants.ADD_SOURCE,
+ source: originalSource
+ });
+
+ return dispatch({
+ type: constants.TOGGLE_PRETTY_PRINT,
+ source: originalSource,
+ [PROMISE]: _asyncToGenerator(function* () {
+ var _ref9 = yield prettyPrint({
+ source, sourceText, url
+ });
+
+ var code = _ref9.code;
+ var mappings = _ref9.mappings;
+
+ yield applySourceMap(source.id, url, code, mappings);
+
+ var frames = yield updateFrameLocations(getFrames(getState()));
+ dispatch(selectSource(originalSource.id));
+
+ return {
+ text: code,
+ contentType: "text/javascript",
+ frames
+ };
+ })()
+ });
+ };
+ }
+
+ /**
+ * @memberof actions/sources
+ * @static
+ */
+ function loadSourceText(source) {
+ return (_ref10) => {
+ var dispatch = _ref10.dispatch;
+ var getState = _ref10.getState;
+ var client = _ref10.client;
+
+ // Fetch the source text only once.
+ var textInfo = getSourceText(getState(), source.id);
+ if (textInfo) {
+ // It's already loaded or is loading
+ return Promise.resolve(textInfo);
+ }
+
+ return dispatch({
+ type: constants.LOAD_SOURCE_TEXT,
+ source: source,
+ [PROMISE]: _asyncToGenerator(function* () {
+ if (isOriginalId(source.id)) {
+ return yield getOriginalSourceText(source);
+ }
+
+ var response = yield client.sourceContents(source.id);
+ return {
+ text: response.source,
+ contentType: response.contentType || "text/javascript"
+ };
+
+ // Automatically pretty print if enabled and the test is
+ // detected to be "minified"
+ // if (Prefs.autoPrettyPrint &&
+ // !source.isPrettyPrinted &&
+ // SourceUtils.isMinified(source.id, response.source)) {
+ // dispatch(togglePrettyPrint(source));
+ // }
+ })()
+ });
+ };
+ }
+
+ // delay is in ms
+ var FETCH_SOURCE_RESPONSE_DELAY = 200;
+
+ /**
+ * Starts fetching all the sources, silently.
+ *
+ * @memberof actions/sources
+ * @static
+ * @param array actors
+ * The urls for the sources to fetch. If fetching a source's text
+ * takes too long, it will be discarded.
+ * @returns {Promise}
+ * A promise that is resolved after source texts have been fetched.
+ */
+ function getTextForSources(actors) {
+ return (_ref12) => {
+ var dispatch = _ref12.dispatch;
+ var getState = _ref12.getState;
+
+ var deferred = defer();
+ var pending = new Set(actors);
+
+ var fetched = [];
+
+ // Can't use promise.all, because if one fetch operation is rejected, then
+ // everything is considered rejected, thus no other subsequent source will
+ // be getting fetched. We don't want that. Something like Q's allSettled
+ // would work like a charm here.
+
+ // Try to fetch as many sources as possible.
+
+ var _loop = function (actor) {
+ var source = getSource(getState(), actor);
+ dispatch(loadSourceText(source)).then((_ref21) => {
+ var text = _ref21.text;
+ var contentType = _ref21.contentType;
+
+ onFetch([source, text, contentType]);
+ }, err => {
+ onError(source, err);
+ });
+ };
+
+ for (var actor of actors) {
+ _loop(actor);
+ }
+
+ setTimeout(onTimeout, FETCH_SOURCE_RESPONSE_DELAY);
+
+ /* Called if fetching a source takes too long. */
+ function onTimeout() {
+ pending = new Set();
+ maybeFinish();
+ }
+
+ /* Called if fetching a source finishes successfully. */
+ function onFetch(_ref13) {
+ var _ref14 = _slicedToArray(_ref13, 3);
+
+ var aSource = _ref14[0];
+ var aText = _ref14[1];
+ var aContentType = _ref14[2];
+
+ // If fetching the source has previously timed out, discard it this time.
+ if (!pending.has(aSource.actor)) {
+ return;
+ }
+ pending.delete(aSource.actor);
+ fetched.push([aSource.actor, aText, aContentType]);
+ maybeFinish();
+ }
+
+ /* Called if fetching a source failed because of an error. */
+ function onError(_ref15) {
+ var _ref16 = _slicedToArray(_ref15, 2);
+
+ var aSource = _ref16[0];
+ var aError = _ref16[1];
+
+ pending.delete(aSource.actor);
+ maybeFinish();
+ }
+
+ /* Called every time something interesting
+ * happens while fetching sources.
+ */
+ function maybeFinish() {
+ if (pending.size == 0) {
+ // Sort the fetched sources alphabetically by their url.
+ if (deferred) {
+ deferred.resolve(fetched.sort((_ref17, _ref18) => {
+ var _ref20 = _slicedToArray(_ref17, 1);
+
+ var aFirst = _ref20[0];
+
+ var _ref19 = _slicedToArray(_ref18, 1);
+
+ var aSecond = _ref19[0];
+ return aFirst > aSecond ? -1 : 1;
+ }));
+ }
+ }
+ }
+
+ return deferred.promise;
+ };
+ }
+
+ module.exports = {
+ newSource,
+ newSources,
+ selectSource,
+ selectSourceURL,
+ closeTab,
+ togglePrettyPrint,
+ loadSourceText,
+ getTextForSources
+ };
+
+/***/ },
+/* 274 */
+/***/ function(module, exports, __webpack_require__) {
+
+ var _require = __webpack_require__(275);
+
+ var Frame = _require.Frame;
+
+ var _require2 = __webpack_require__(264);
+
+ var getOriginalLocation = _require2.getOriginalLocation;
+
+
+ function updateFrameLocations(frames) {
+ if (!frames) {
+ return Promise.resolve(frames);
+ }
+ return Promise.all(frames.map(frame => {
+ return getOriginalLocation(frame.location).then(loc => {
+ return Frame.update(frame, {
+ $merge: { location: loc }
+ });
+ });
+ }));
+ }
+
+ module.exports = {
+ updateFrameLocations
+ };
+
+/***/ },
+/* 275 */
+/***/ function(module, exports, __webpack_require__) {
+
+ var t = __webpack_require__(146);
+
+ var Location = t.struct({
+ sourceId: t.String,
+ line: t.Number,
+ column: t.union([t.Number, t.Nil])
+ }, "Location");
+
+ var Frame = t.struct({
+ id: t.String,
+ displayName: t.String,
+ location: Location,
+ this: t.union([t.Object, t.Nil]),
+ scope: t.union([t.Object, t.Nil])
+ }, "Frame");
+
+ module.exports = {
+ Frame
+ };
+
+/***/ },
+/* 276 */
+/***/ function(module, exports, __webpack_require__) {
+
+ var prettyPrint = (() => {
+ var _ref = _asyncToGenerator(function* (_ref2) {
+ var source = _ref2.source;
+ var sourceText = _ref2.sourceText;
+ var url = _ref2.url;
+
+ var contentType = sourceText ? sourceText.contentType : null;
+ var indent = 2;
+
+ assert(isJavaScript(source.url, contentType), "Can't prettify non-javascript files.");
+
+ return yield _prettyPrint({
+ url,
+ indent,
+ source: sourceText.text
+ });
+ });
+
+ return function prettyPrint(_x) {
+ return _ref.apply(this, arguments);
+ };
+ })();
+
+ function _asyncToGenerator(fn) { return function () { var gen = fn.apply(this, arguments); return new Promise(function (resolve, reject) { function step(key, arg) { try { var info = gen[key](arg); var value = info.value; } catch (error) { reject(error); return; } if (info.done) { resolve(value); } else { return Promise.resolve(value).then(function (value) { step("next", value); }, function (err) { step("throw", err); }); } } return step("next"); }); }; }
+
+ var _require = __webpack_require__(89);
+
+ var getValue = _require.getValue;
+
+ var _require2 = __webpack_require__(244);
+
+ var workerTask = _require2.workerTask;
+
+ var _require3 = __webpack_require__(277);
+
+ var isJavaScript = _require3.isJavaScript;
+
+ var assert = __webpack_require__(247);
+
+ var prettyPrintWorker = new Worker(getValue("baseWorkerURL") + "pretty-print-worker.js");
+
+ function destroyWorker() {
+ prettyPrintWorker.terminate();
+ prettyPrintWorker = null;
+ }
+
+ var _prettyPrint = workerTask(prettyPrintWorker, "prettyPrint");
+
+ module.exports = {
+ prettyPrint,
+ destroyWorker
+ };
+
+/***/ },
+/* 277 */
+/***/ function(module, exports, __webpack_require__) {
+
+
+
+ /**
+ * Utils for working with Source URLs
+ * @module utils/source
+ */
+
+ var _require = __webpack_require__(244);
+
+ var endTruncateStr = _require.endTruncateStr;
+
+ var _require2 = __webpack_require__(278);
+
+ var basename = _require2.basename;
+
+
+ /**
+ * Trims the query part or reference identifier of a url string, if necessary.
+ *
+ * @memberof utils/source
+ * @static
+ */
+ function trimUrlQuery(url) {
+ var length = url.length;
+ var q1 = url.indexOf("?");
+ var q2 = url.indexOf("&");
+ var q3 = url.indexOf("#");
+ var q = Math.min(q1 != -1 ? q1 : length, q2 != -1 ? q2 : length, q3 != -1 ? q3 : length);
+
+ return url.slice(0, q);
+ }
+
+ /**
+ * Returns true if the specified url and/or content type are specific to
+ * javascript files.
+ *
+ * @return boolean
+ * True if the source is likely javascript.
+ *
+ * @memberof utils/source
+ * @static
+ */
+ function isJavaScript(url) {
+ var contentType = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : "";
+
+ return url && /\.(jsm|js)?$/.test(trimUrlQuery(url)) || contentType.includes("javascript");
+ }
+
+ /**
+ * @memberof utils/source
+ * @static
+ */
+ function isPretty(source) {
+ return source.url ? /formatted$/.test(source.url) : false;
+ }
+
+ /**
+ * Show a source url's filename.
+ * If the source does not have a url, use the source id.
+ *
+ * @memberof utils/source
+ * @static
+ */
+ function getFilename(source) {
+ var url = source.url;
+ var id = source.id;
+
+ if (!url) {
+ var sourceId = id.split("/")[1];
+ return `SOURCE${ sourceId }`;
+ }
+
+ var name = basename(source.url || "") || "(index)";
+ return endTruncateStr(name, 50);
+ }
+
+ module.exports = {
+ isJavaScript,
+ isPretty,
+ getFilename
+ };
+
+/***/ },
+/* 278 */
+/***/ function(module, exports) {
+
+ function basename(path) {
+ return path.split("/").pop();
+ }
+
+ function dirname(path) {
+ var idx = path.lastIndexOf("/");
+ return path.slice(0, idx);
+ }
+
+ function isURL(str) {
+ return str.indexOf("://") !== -1;
+ }
+
+ function isAbsolute(str) {
+ return str[0] === "/";
+ }
+
+ function join(base, dir) {
+ return base + "/" + dir;
+ }
+
+ module.exports = {
+ basename, dirname, isURL, isAbsolute, join
+ };
+
+/***/ },
+/* 279 */
+/***/ function(module, exports) {
+
+ var sourceDocs = {};
+
+ function getDocument(key) {
+ return sourceDocs[key];
+ }
+
+ function setDocument(key, doc) {
+ sourceDocs[key] = doc;
+ }
+
+ function removeDocument(key) {
+ delete sourceDocs[key];
+ }
+
+ function clearDocuments() {
+ sourceDocs = {};
+ }
+
+ module.exports = {
+ getDocument,
+ setDocument,
+ removeDocument,
+ clearDocuments
+ };
+
+/***/ },
+/* 280 */
+/***/ function(module, exports, __webpack_require__) {
+
+ function _asyncToGenerator(fn) { return function () { var gen = fn.apply(this, arguments); return new Promise(function (resolve, reject) { function step(key, arg) { try { var info = gen[key](arg); var value = info.value; } catch (error) { reject(error); return; } if (info.done) { resolve(value); } else { return Promise.resolve(value).then(function (value) { step("next", value); }, function (err) { step("throw", err); }); } } return step("next"); }); }; }
+
+ var constants = __webpack_require__(251);
+
+ var _require = __webpack_require__(273);
+
+ var selectSource = _require.selectSource;
+
+ var _require2 = __webpack_require__(242);
+
+ var PROMISE = _require2.PROMISE;
+
+ var _require3 = __webpack_require__(259);
+
+ var getExpressions = _require3.getExpressions;
+ var getSelectedFrame = _require3.getSelectedFrame;
+
+ var _require4 = __webpack_require__(274);
+
+ var updateFrameLocations = _require4.updateFrameLocations;
+
+ /**
+ * Redux actions for the pause state
+ * @module actions/pause
+ */
+
+ /**
+ * Debugger has just resumed
+ *
+ * @memberof actions/pause
+ * @static
+ */
+
+ function resumed() {
+ return (_ref) => {
+ var dispatch = _ref.dispatch;
+ var client = _ref.client;
+
+ return dispatch({
+ type: constants.RESUME,
+ value: undefined
+ });
+ };
+ }
+
+ /**
+ * Debugger has just paused
+ *
+ * @param {object} pauseInfo
+ * @memberof actions/pause
+ * @static
+ */
+ function paused(pauseInfo) {
+ return (() => {
+ var _ref2 = _asyncToGenerator(function* (_ref3) {
+ var dispatch = _ref3.dispatch;
+ var getState = _ref3.getState;
+ var client = _ref3.client;
+ var frames = pauseInfo.frames;
+ var why = pauseInfo.why;
+
+ frames = yield updateFrameLocations(frames);
+ var frame = frames[0];
+
+ dispatch({
+ type: constants.PAUSED,
+ pauseInfo: { why, frame },
+ frames: frames,
+ selectedFrameId: frame.id
+ });
+
+ dispatch(evaluateExpressions());
+
+ dispatch(selectSource(frame.location.sourceId, { line: frame.location.line }));
+ });
+
+ return function (_x) {
+ return _ref2.apply(this, arguments);
+ };
+ })();
+ }
+
+ /**
+ *
+ * @memberof actions/pause
+ * @static
+ */
+ function pauseOnExceptions(shouldPauseOnExceptions, shouldIgnoreCaughtExceptions) {
+ return (_ref4) => {
+ var dispatch = _ref4.dispatch;
+ var client = _ref4.client;
+
+ dispatch({
+ type: constants.PAUSE_ON_EXCEPTIONS,
+ shouldPauseOnExceptions,
+ shouldIgnoreCaughtExceptions,
+ [PROMISE]: client.pauseOnExceptions(shouldPauseOnExceptions, shouldIgnoreCaughtExceptions)
+ });
+ };
+ }
+
+ /**
+ * Debugger commands like stepOver, stepIn, stepUp
+ *
+ * @param string $0.type
+ * @memberof actions/pause
+ * @static
+ */
+ function command(_ref5) {
+ var type = _ref5.type;
+
+ return (_ref6) => {
+ var dispatch = _ref6.dispatch;
+ var client = _ref6.client;
+
+ // execute debugger thread command e.g. stepIn, stepOver
+ client[type]();
+
+ return dispatch({
+ type: constants.COMMAND,
+ value: undefined
+ });
+ };
+ }
+
+ /**
+ * StepIn
+ * @memberof actions/pause
+ * @static
+ * @returns {Function} {@link command}
+ */
+ function stepIn() {
+ return command({ type: "stepIn" });
+ }
+
+ /**
+ * stepOver
+ * @memberof actions/pause
+ * @static
+ * @returns {Function} {@link command}
+ */
+ function stepOver() {
+ return command({ type: "stepOver" });
+ }
+
+ /**
+ * stepOut
+ * @memberof actions/pause
+ * @static
+ * @returns {Function} {@link command}
+ */
+ function stepOut() {
+ return command({ type: "stepOut" });
+ }
+
+ /**
+ * resume
+ * @memberof actions/pause
+ * @static
+ * @returns {Function} {@link command}
+ */
+ function resume() {
+ return command({ type: "resume" });
+ }
+
+ /**
+ * Debugger breakOnNext command.
+ * It's different from the comand action because we also want to
+ * highlight the pause icon.
+ *
+ * @memberof actions/pause
+ * @static
+ */
+ function breakOnNext() {
+ return (_ref7) => {
+ var dispatch = _ref7.dispatch;
+ var client = _ref7.client;
+
+ client.breakOnNext();
+
+ return dispatch({
+ type: constants.BREAK_ON_NEXT,
+ value: true
+ });
+ };
+ }
+
+ /**
+ * Select a frame
+ *
+ * @param frame
+ * @memberof actions/pause
+ * @static
+ */
+ function selectFrame(frame) {
+ return (_ref8) => {
+ var dispatch = _ref8.dispatch;
+
+ dispatch(selectSource(frame.location.sourceId, { line: frame.location.line }));
+ dispatch({
+ type: constants.SELECT_FRAME,
+ frame
+ });
+ };
+ }
+
+ /**
+ * Load an object.
+ *
+ * @param grip
+ * TODO: Right now this if Firefox specific and is not implemented
+ * for Chrome, which is why it takes a grip.
+ * @memberof actions/pause
+ * @static
+ */
+ function loadObjectProperties(grip) {
+ return (_ref9) => {
+ var dispatch = _ref9.dispatch;
+ var client = _ref9.client;
+
+ dispatch({
+ type: constants.LOAD_OBJECT_PROPERTIES,
+ objectId: grip.actor,
+ [PROMISE]: client.getProperties(grip)
+ });
+ };
+ }
+
+ /**
+ * Add expression for debugger to watch
+ *
+ * @param {object} expression
+ * @param {number} expression.id
+ * @memberof actions/pause
+ * @static
+ */
+ function addExpression(expression) {
+ return (_ref10) => {
+ var dispatch = _ref10.dispatch;
+ var getState = _ref10.getState;
+
+ var id = expression.id !== undefined ? parseInt(expression.id, 10) : getExpressions(getState()).toSeq().size++;
+ dispatch({
+ type: constants.ADD_EXPRESSION,
+ id: id,
+ input: expression.input
+ });
+ dispatch(evaluateExpressions());
+ };
+ }
+
+ /**
+ *
+ * @param {object} expression
+ * @param {number} expression.id
+ * @memberof actions/pause
+ * @static
+ */
+ function updateExpression(expression) {
+ return (_ref11) => {
+ var dispatch = _ref11.dispatch;
+
+ dispatch({
+ type: constants.UPDATE_EXPRESSION,
+ id: expression.id,
+ input: expression.input
+ });
+ };
+ }
+
+ /**
+ *
+ * @param {object} expression
+ * @param {number} expression.id
+ * @memberof actions/pause
+ * @static
+ */
+ function deleteExpression(expression) {
+ return (_ref12) => {
+ var dispatch = _ref12.dispatch;
+
+ dispatch({
+ type: constants.DELETE_EXPRESSION,
+ id: expression.id
+ });
+ };
+ }
+
+ /**
+ *
+ * @memberof actions/pause
+ * @static
+ */
+ function evaluateExpressions() {
+ return (() => {
+ var _ref13 = _asyncToGenerator(function* (_ref14) {
+ var dispatch = _ref14.dispatch;
+ var getState = _ref14.getState;
+ var client = _ref14.client;
+
+ var selectedFrame = getSelectedFrame(getState());
+ if (!selectedFrame) {
+ return;
+ }
+
+ var frameId = selectedFrame.id;
+
+ for (var expression of getExpressions(getState())) {
+ yield dispatch({
+ type: constants.EVALUATE_EXPRESSION,
+ id: expression.id,
+ input: expression.input,
+ [PROMISE]: client.evaluate(expression.input, { frameId })
+ });
+ }
+ });
+
+ return function (_x2) {
+ return _ref13.apply(this, arguments);
+ };
+ })();
+ }
+
+ module.exports = {
+ addExpression,
+ updateExpression,
+ deleteExpression,
+ evaluateExpressions,
+ resumed,
+ paused,
+ pauseOnExceptions,
+ command,
+ stepIn,
+ stepOut,
+ stepOver,
+ resume,
+ breakOnNext,
+ selectFrame,
+ loadObjectProperties
+ };
+
+/***/ },
+/* 281 */
+/***/ function(module, exports, __webpack_require__) {
+
+ var constants = __webpack_require__(251);
+
+ var _require = __webpack_require__(264);
+
+ var clearSourceMaps = _require.clearSourceMaps;
+
+ var _require2 = __webpack_require__(279);
+
+ var clearDocuments = _require2.clearDocuments;
+
+ /**
+ * Redux actions for the navigation state
+ * @module actions/navigation
+ */
+
+ /**
+ * @memberof actions/navigation
+ * @static
+ */
+
+ function willNavigate() {
+ clearSourceMaps();
+ clearDocuments();
+
+ return { type: constants.NAVIGATE };
+ }
+
+ /**
+ * @memberof actions/navigation
+ * @static
+ */
+ function navigated() {
+ return (_ref) => {
+ // We need to load all the sources again because they might have
+ // come from bfcache, so we won't get a `newSource` notification.
+ //
+ // TODO: This seems to be buggy on the debugger server side. When
+ // the page is loaded from bfcache, we still get sources from the
+ // *previous* page as well. For now, emulate the current debugger
+ // behavior by not showing sources loaded by bfcache.
+ // return dispatch(sources.loadSources());
+
+ var dispatch = _ref.dispatch;
+ };
+ }
+
+ module.exports = {
+ willNavigate,
+ navigated
+ };
+
+/***/ },
+/* 282 */
+/***/ function(module, exports, __webpack_require__) {
+
+ var constants = __webpack_require__(251);
+
+ function toggleFileSearch(searchOn) {
+ return {
+ type: constants.TOGGLE_FILE_SEARCH,
+ searchOn
+ };
+ }
+
+ module.exports = {
+ toggleFileSearch
+ };
+
+/***/ },
+/* 283 */
+/***/ function(module, exports) {
+
+ // removed by extract-text-webpack-plugin
+
+/***/ },
+/* 284 */,
+/* 285 */,
+/* 286 */
+/***/ function(module, exports) {
+
+ // removed by extract-text-webpack-plugin
+
+/***/ },
+/* 287 */,
+/* 288 */
+/***/ function(module, exports) {
+
+ // removed by extract-text-webpack-plugin
+
+/***/ },
+/* 289 */,
+/* 290 */
+/***/ function(module, exports) {
+
+ // removed by extract-text-webpack-plugin
+
+/***/ },
+/* 291 */,
+/* 292 */
+/***/ function(module, exports, __webpack_require__) {
+
+ var React = __webpack_require__(2);
+ var dom = React.DOM;
+ var PropTypes = React.PropTypes;
+ var createFactory = React.createFactory;
+
+ var _require = __webpack_require__(19);
+
+ var connect = _require.connect;
+
+ var _require2 = __webpack_require__(3);
+
+ var bindActionCreators = _require2.bindActionCreators;
+
+ var actions = __webpack_require__(262);
+
+ var _require3 = __webpack_require__(259);
+
+ var getSources = _require3.getSources;
+ var getSelectedSource = _require3.getSelectedSource;
+ var getFileSearchState = _require3.getFileSearchState;
+
+ var _require4 = __webpack_require__(244);
+
+ var endTruncateStr = _require4.endTruncateStr;
+
+ var _require5 = __webpack_require__(293);
+
+ var parseURL = _require5.parse;
+
+ var _require6 = __webpack_require__(277);
+
+ var isPretty = _require6.isPretty;
+
+
+ __webpack_require__(298);
+
+ var Autocomplete = createFactory(__webpack_require__(300));
+
+ function searchResults(sources) {
+ function getSourcePath(source) {
+ var _parseURL = parseURL(source.get("url"));
+
+ var path = _parseURL.path;
+ var href = _parseURL.href;
+ // for URLs like "about:home" the path is null so we pass the full href
+
+ return endTruncateStr(path || href, 50);
+ }
+
+ return sources.valueSeq().filter(source => !isPretty(source.toJS()) && source.get("url")).map(source => ({
+ value: getSourcePath(source),
+ title: getSourcePath(source).split("/").pop(),
+ subtitle: getSourcePath(source),
+ id: source.get("id")
+ })).toJS();
+ }
+
+ var Search = React.createClass({
+ propTypes: {
+ sources: PropTypes.object,
+ selectSource: PropTypes.func,
+ selectedSource: PropTypes.object,
+ toggleFileSearch: PropTypes.func,
+ searchOn: PropTypes.bool
+ },
+
+ contextTypes: {
+ shortcuts: PropTypes.object
+ },
+
+ displayName: "Search",
+
+ getInitialState() {
+ return {
+ inputValue: ""
+ };
+ },
+
+ componentWillUnmount() {
+ var shortcuts = this.context.shortcuts;
+ shortcuts.off("CmdOrCtrl+P", this.toggle);
+ shortcuts.off("Escape", this.onEscape);
+ },
+
+ componentDidMount() {
+ var shortcuts = this.context.shortcuts;
+ shortcuts.on("CmdOrCtrl+P", this.toggle);
+ shortcuts.on("Escape", this.onEscape);
+ },
+
+ toggle(key, e) {
+ e.preventDefault();
+ this.props.toggleFileSearch(!this.props.searchOn);
+ },
+
+ onEscape(shortcut, e) {
+ if (this.props.searchOn) {
+ e.preventDefault();
+ this.setState({ inputValue: "" });
+ this.props.toggleFileSearch(false);
+ }
+ },
+
+ close() {
+ var inputValue = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : "";
+
+ this.setState({ inputValue });
+ this.props.toggleFileSearch(false);
+ },
+
+ render() {
+ return this.props.searchOn ? dom.div({ className: "search-container" }, Autocomplete({
+ selectItem: result => {
+ this.props.selectSource(result.id);
+ this.setState({ inputValue: "" });
+ this.props.toggleFileSearch(false);
+ },
+ handleClose: this.close,
+ items: searchResults(this.props.sources),
+ inputValue: this.state.inputValue
+ })) : null;
+ }
+
+ });
+
+ module.exports = connect(state => ({
+ sources: getSources(state),
+ selectedSource: getSelectedSource(state),
+ searchOn: getFileSearchState(state)
+ }), dispatch => bindActionCreators(actions, dispatch))(Search);
+
+/***/ },
+/* 293 */
+/***/ function(module, exports, __webpack_require__) {
+
+ // Copyright Joyent, Inc. and other Node contributors.
+ //
+ // Permission is hereby granted, free of charge, to any person obtaining a
+ // copy of this software and associated documentation files (the
+ // "Software"), to deal in the Software without restriction, including
+ // without limitation the rights to use, copy, modify, merge, publish,
+ // distribute, sublicense, and/or sell copies of the Software, and to permit
+ // persons to whom the Software is furnished to do so, subject to the
+ // following conditions:
+ //
+ // The above copyright notice and this permission notice shall be included
+ // in all copies or substantial portions of the Software.
+ //
+ // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+ // OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
+ // NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
+ // DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
+ // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
+ // USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+ var punycode = __webpack_require__(294);
+
+ exports.parse = urlParse;
+ exports.resolve = urlResolve;
+ exports.resolveObject = urlResolveObject;
+ exports.format = urlFormat;
+
+ exports.Url = Url;
+
+ function Url() {
+ this.protocol = null;
+ this.slashes = null;
+ this.auth = null;
+ this.host = null;
+ this.port = null;
+ this.hostname = null;
+ this.hash = null;
+ this.search = null;
+ this.query = null;
+ this.pathname = null;
+ this.path = null;
+ this.href = null;
+ }
+
+ // Reference: RFC 3986, RFC 1808, RFC 2396
+
+ // define these here so at least they only have to be
+ // compiled once on the first module load.
+ var protocolPattern = /^([a-z0-9.+-]+:)/i,
+ portPattern = /:[0-9]*$/,
+
+ // RFC 2396: characters reserved for delimiting URLs.
+ // We actually just auto-escape these.
+ delims = ['<', '>', '"', '`', ' ', '\r', '\n', '\t'],
+
+ // RFC 2396: characters not allowed for various reasons.
+ unwise = ['{', '}', '|', '\\', '^', '`'].concat(delims),
+
+ // Allowed by RFCs, but cause of XSS attacks. Always escape these.
+ autoEscape = ['\''].concat(unwise),
+ // Characters that are never ever allowed in a hostname.
+ // Note that any invalid chars are also handled, but these
+ // are the ones that are *expected* to be seen, so we fast-path
+ // them.
+ nonHostChars = ['%', '/', '?', ';', '#'].concat(autoEscape),
+ hostEndingChars = ['/', '?', '#'],
+ hostnameMaxLen = 255,
+ hostnamePartPattern = /^[a-z0-9A-Z_-]{0,63}$/,
+ hostnamePartStart = /^([a-z0-9A-Z_-]{0,63})(.*)$/,
+ // protocols that can allow "unsafe" and "unwise" chars.
+ unsafeProtocol = {
+ 'javascript': true,
+ 'javascript:': true
+ },
+ // protocols that never have a hostname.
+ hostlessProtocol = {
+ 'javascript': true,
+ 'javascript:': true
+ },
+ // protocols that always contain a // bit.
+ slashedProtocol = {
+ 'http': true,
+ 'https': true,
+ 'ftp': true,
+ 'gopher': true,
+ 'file': true,
+ 'http:': true,
+ 'https:': true,
+ 'ftp:': true,
+ 'gopher:': true,
+ 'file:': true
+ },
+ querystring = __webpack_require__(295);
+
+ function urlParse(url, parseQueryString, slashesDenoteHost) {
+ if (url && isObject(url) && url instanceof Url) return url;
+
+ var u = new Url;
+ u.parse(url, parseQueryString, slashesDenoteHost);
+ return u;
+ }
+
+ Url.prototype.parse = function(url, parseQueryString, slashesDenoteHost) {
+ if (!isString(url)) {
+ throw new TypeError("Parameter 'url' must be a string, not " + typeof url);
+ }
+
+ var rest = url;
+
+ // trim before proceeding.
+ // This is to support parse stuff like " http://foo.com \n"
+ rest = rest.trim();
+
+ var proto = protocolPattern.exec(rest);
+ if (proto) {
+ proto = proto[0];
+ var lowerProto = proto.toLowerCase();
+ this.protocol = lowerProto;
+ rest = rest.substr(proto.length);
+ }
+
+ // figure out if it's got a host
+ // user@server is *always* interpreted as a hostname, and url
+ // resolution will treat //foo/bar as host=foo,path=bar because that's
+ // how the browser resolves relative URLs.
+ if (slashesDenoteHost || proto || rest.match(/^\/\/[^@\/]+@[^@\/]+/)) {
+ var slashes = rest.substr(0, 2) === '//';
+ if (slashes && !(proto && hostlessProtocol[proto])) {
+ rest = rest.substr(2);
+ this.slashes = true;
+ }
+ }
+
+ if (!hostlessProtocol[proto] &&
+ (slashes || (proto && !slashedProtocol[proto]))) {
+
+ // there's a hostname.
+ // the first instance of /, ?, ;, or # ends the host.
+ //
+ // If there is an @ in the hostname, then non-host chars *are* allowed
+ // to the left of the last @ sign, unless some host-ending character
+ // comes *before* the @-sign.
+ // URLs are obnoxious.
+ //
+ // ex:
+ // http://a@b@c/ => user:a@b host:c
+ // http://a@b?@c => user:a host:c path:/?@c
+
+ // v0.12 TODO(isaacs): This is not quite how Chrome does things.
+ // Review our test case against browsers more comprehensively.
+
+ // find the first instance of any hostEndingChars
+ var hostEnd = -1;
+ for (var i = 0; i < hostEndingChars.length; i++) {
+ var hec = rest.indexOf(hostEndingChars[i]);
+ if (hec !== -1 && (hostEnd === -1 || hec < hostEnd))
+ hostEnd = hec;
+ }
+
+ // at this point, either we have an explicit point where the
+ // auth portion cannot go past, or the last @ char is the decider.
+ var auth, atSign;
+ if (hostEnd === -1) {
+ // atSign can be anywhere.
+ atSign = rest.lastIndexOf('@');
+ } else {
+ // atSign must be in auth portion.
+ // http://a@b/c@d => host:b auth:a path:/c@d
+ atSign = rest.lastIndexOf('@', hostEnd);
+ }
+
+ // Now we have a portion which is definitely the auth.
+ // Pull that off.
+ if (atSign !== -1) {
+ auth = rest.slice(0, atSign);
+ rest = rest.slice(atSign + 1);
+ this.auth = decodeURIComponent(auth);
+ }
+
+ // the host is the remaining to the left of the first non-host char
+ hostEnd = -1;
+ for (var i = 0; i < nonHostChars.length; i++) {
+ var hec = rest.indexOf(nonHostChars[i]);
+ if (hec !== -1 && (hostEnd === -1 || hec < hostEnd))
+ hostEnd = hec;
+ }
+ // if we still have not hit it, then the entire thing is a host.
+ if (hostEnd === -1)
+ hostEnd = rest.length;
+
+ this.host = rest.slice(0, hostEnd);
+ rest = rest.slice(hostEnd);
+
+ // pull out port.
+ this.parseHost();
+
+ // we've indicated that there is a hostname,
+ // so even if it's empty, it has to be present.
+ this.hostname = this.hostname || '';
+
+ // if hostname begins with [ and ends with ]
+ // assume that it's an IPv6 address.
+ var ipv6Hostname = this.hostname[0] === '[' &&
+ this.hostname[this.hostname.length - 1] === ']';
+
+ // validate a little.
+ if (!ipv6Hostname) {
+ var hostparts = this.hostname.split(/\./);
+ for (var i = 0, l = hostparts.length; i < l; i++) {
+ var part = hostparts[i];
+ if (!part) continue;
+ if (!part.match(hostnamePartPattern)) {
+ var newpart = '';
+ for (var j = 0, k = part.length; j < k; j++) {
+ if (part.charCodeAt(j) > 127) {
+ // we replace non-ASCII char with a temporary placeholder
+ // we need this to make sure size of hostname is not
+ // broken by replacing non-ASCII by nothing
+ newpart += 'x';
+ } else {
+ newpart += part[j];
+ }
+ }
+ // we test again with ASCII char only
+ if (!newpart.match(hostnamePartPattern)) {
+ var validParts = hostparts.slice(0, i);
+ var notHost = hostparts.slice(i + 1);
+ var bit = part.match(hostnamePartStart);
+ if (bit) {
+ validParts.push(bit[1]);
+ notHost.unshift(bit[2]);
+ }
+ if (notHost.length) {
+ rest = '/' + notHost.join('.') + rest;
+ }
+ this.hostname = validParts.join('.');
+ break;
+ }
+ }
+ }
+ }
+
+ if (this.hostname.length > hostnameMaxLen) {
+ this.hostname = '';
+ } else {
+ // hostnames are always lower case.
+ this.hostname = this.hostname.toLowerCase();
+ }
+
+ if (!ipv6Hostname) {
+ // IDNA Support: Returns a puny coded representation of "domain".
+ // It only converts the part of the domain name that
+ // has non ASCII characters. I.e. it dosent matter if
+ // you call it with a domain that already is in ASCII.
+ var domainArray = this.hostname.split('.');
+ var newOut = [];
+ for (var i = 0; i < domainArray.length; ++i) {
+ var s = domainArray[i];
+ newOut.push(s.match(/[^A-Za-z0-9_-]/) ?
+ 'xn--' + punycode.encode(s) : s);
+ }
+ this.hostname = newOut.join('.');
+ }
+
+ var p = this.port ? ':' + this.port : '';
+ var h = this.hostname || '';
+ this.host = h + p;
+ this.href += this.host;
+
+ // strip [ and ] from the hostname
+ // the host field still retains them, though
+ if (ipv6Hostname) {
+ this.hostname = this.hostname.substr(1, this.hostname.length - 2);
+ if (rest[0] !== '/') {
+ rest = '/' + rest;
+ }
+ }
+ }
+
+ // now rest is set to the post-host stuff.
+ // chop off any delim chars.
+ if (!unsafeProtocol[lowerProto]) {
+
+ // First, make 100% sure that any "autoEscape" chars get
+ // escaped, even if encodeURIComponent doesn't think they
+ // need to be.
+ for (var i = 0, l = autoEscape.length; i < l; i++) {
+ var ae = autoEscape[i];
+ var esc = encodeURIComponent(ae);
+ if (esc === ae) {
+ esc = escape(ae);
+ }
+ rest = rest.split(ae).join(esc);
+ }
+ }
+
+
+ // chop off from the tail first.
+ var hash = rest.indexOf('#');
+ if (hash !== -1) {
+ // got a fragment string.
+ this.hash = rest.substr(hash);
+ rest = rest.slice(0, hash);
+ }
+ var qm = rest.indexOf('?');
+ if (qm !== -1) {
+ this.search = rest.substr(qm);
+ this.query = rest.substr(qm + 1);
+ if (parseQueryString) {
+ this.query = querystring.parse(this.query);
+ }
+ rest = rest.slice(0, qm);
+ } else if (parseQueryString) {
+ // no query string, but parseQueryString still requested
+ this.search = '';
+ this.query = {};
+ }
+ if (rest) this.pathname = rest;
+ if (slashedProtocol[lowerProto] &&
+ this.hostname && !this.pathname) {
+ this.pathname = '/';
+ }
+
+ //to support http.request
+ if (this.pathname || this.search) {
+ var p = this.pathname || '';
+ var s = this.search || '';
+ this.path = p + s;
+ }
+
+ // finally, reconstruct the href based on what has been validated.
+ this.href = this.format();
+ return this;
+ };
+
+ // format a parsed object into a url string
+ function urlFormat(obj) {
+ // ensure it's an object, and not a string url.
+ // If it's an obj, this is a no-op.
+ // this way, you can call url_format() on strings
+ // to clean up potentially wonky urls.
+ if (isString(obj)) obj = urlParse(obj);
+ if (!(obj instanceof Url)) return Url.prototype.format.call(obj);
+ return obj.format();
+ }
+
+ Url.prototype.format = function() {
+ var auth = this.auth || '';
+ if (auth) {
+ auth = encodeURIComponent(auth);
+ auth = auth.replace(/%3A/i, ':');
+ auth += '@';
+ }
+
+ var protocol = this.protocol || '',
+ pathname = this.pathname || '',
+ hash = this.hash || '',
+ host = false,
+ query = '';
+
+ if (this.host) {
+ host = auth + this.host;
+ } else if (this.hostname) {
+ host = auth + (this.hostname.indexOf(':') === -1 ?
+ this.hostname :
+ '[' + this.hostname + ']');
+ if (this.port) {
+ host += ':' + this.port;
+ }
+ }
+
+ if (this.query &&
+ isObject(this.query) &&
+ Object.keys(this.query).length) {
+ query = querystring.stringify(this.query);
+ }
+
+ var search = this.search || (query && ('?' + query)) || '';
+
+ if (protocol && protocol.substr(-1) !== ':') protocol += ':';
+
+ // only the slashedProtocols get the //. Not mailto:, xmpp:, etc.
+ // unless they had them to begin with.
+ if (this.slashes ||
+ (!protocol || slashedProtocol[protocol]) && host !== false) {
+ host = '//' + (host || '');
+ if (pathname && pathname.charAt(0) !== '/') pathname = '/' + pathname;
+ } else if (!host) {
+ host = '';
+ }
+
+ if (hash && hash.charAt(0) !== '#') hash = '#' + hash;
+ if (search && search.charAt(0) !== '?') search = '?' + search;
+
+ pathname = pathname.replace(/[?#]/g, function(match) {
+ return encodeURIComponent(match);
+ });
+ search = search.replace('#', '%23');
+
+ return protocol + host + pathname + search + hash;
+ };
+
+ function urlResolve(source, relative) {
+ return urlParse(source, false, true).resolve(relative);
+ }
+
+ Url.prototype.resolve = function(relative) {
+ return this.resolveObject(urlParse(relative, false, true)).format();
+ };
+
+ function urlResolveObject(source, relative) {
+ if (!source) return relative;
+ return urlParse(source, false, true).resolveObject(relative);
+ }
+
+ Url.prototype.resolveObject = function(relative) {
+ if (isString(relative)) {
+ var rel = new Url();
+ rel.parse(relative, false, true);
+ relative = rel;
+ }
+
+ var result = new Url();
+ Object.keys(this).forEach(function(k) {
+ result[k] = this[k];
+ }, this);
+
+ // hash is always overridden, no matter what.
+ // even href="" will remove it.
+ result.hash = relative.hash;
+
+ // if the relative url is empty, then there's nothing left to do here.
+ if (relative.href === '') {
+ result.href = result.format();
+ return result;
+ }
+
+ // hrefs like //foo/bar always cut to the protocol.
+ if (relative.slashes && !relative.protocol) {
+ // take everything except the protocol from relative
+ Object.keys(relative).forEach(function(k) {
+ if (k !== 'protocol')
+ result[k] = relative[k];
+ });
+
+ //urlParse appends trailing / to urls like http://www.example.com
+ if (slashedProtocol[result.protocol] &&
+ result.hostname && !result.pathname) {
+ result.path = result.pathname = '/';
+ }
+
+ result.href = result.format();
+ return result;
+ }
+
+ if (relative.protocol && relative.protocol !== result.protocol) {
+ // if it's a known url protocol, then changing
+ // the protocol does weird things
+ // first, if it's not file:, then we MUST have a host,
+ // and if there was a path
+ // to begin with, then we MUST have a path.
+ // if it is file:, then the host is dropped,
+ // because that's known to be hostless.
+ // anything else is assumed to be absolute.
+ if (!slashedProtocol[relative.protocol]) {
+ Object.keys(relative).forEach(function(k) {
+ result[k] = relative[k];
+ });
+ result.href = result.format();
+ return result;
+ }
+
+ result.protocol = relative.protocol;
+ if (!relative.host && !hostlessProtocol[relative.protocol]) {
+ var relPath = (relative.pathname || '').split('/');
+ while (relPath.length && !(relative.host = relPath.shift()));
+ if (!relative.host) relative.host = '';
+ if (!relative.hostname) relative.hostname = '';
+ if (relPath[0] !== '') relPath.unshift('');
+ if (relPath.length < 2) relPath.unshift('');
+ result.pathname = relPath.join('/');
+ } else {
+ result.pathname = relative.pathname;
+ }
+ result.search = relative.search;
+ result.query = relative.query;
+ result.host = relative.host || '';
+ result.auth = relative.auth;
+ result.hostname = relative.hostname || relative.host;
+ result.port = relative.port;
+ // to support http.request
+ if (result.pathname || result.search) {
+ var p = result.pathname || '';
+ var s = result.search || '';
+ result.path = p + s;
+ }
+ result.slashes = result.slashes || relative.slashes;
+ result.href = result.format();
+ return result;
+ }
+
+ var isSourceAbs = (result.pathname && result.pathname.charAt(0) === '/'),
+ isRelAbs = (
+ relative.host ||
+ relative.pathname && relative.pathname.charAt(0) === '/'
+ ),
+ mustEndAbs = (isRelAbs || isSourceAbs ||
+ (result.host && relative.pathname)),
+ removeAllDots = mustEndAbs,
+ srcPath = result.pathname && result.pathname.split('/') || [],
+ relPath = relative.pathname && relative.pathname.split('/') || [],
+ psychotic = result.protocol && !slashedProtocol[result.protocol];
+
+ // if the url is a non-slashed url, then relative
+ // links like ../.. should be able
+ // to crawl up to the hostname, as well. This is strange.
+ // result.protocol has already been set by now.
+ // Later on, put the first path part into the host field.
+ if (psychotic) {
+ result.hostname = '';
+ result.port = null;
+ if (result.host) {
+ if (srcPath[0] === '') srcPath[0] = result.host;
+ else srcPath.unshift(result.host);
+ }
+ result.host = '';
+ if (relative.protocol) {
+ relative.hostname = null;
+ relative.port = null;
+ if (relative.host) {
+ if (relPath[0] === '') relPath[0] = relative.host;
+ else relPath.unshift(relative.host);
+ }
+ relative.host = null;
+ }
+ mustEndAbs = mustEndAbs && (relPath[0] === '' || srcPath[0] === '');
+ }
+
+ if (isRelAbs) {
+ // it's absolute.
+ result.host = (relative.host || relative.host === '') ?
+ relative.host : result.host;
+ result.hostname = (relative.hostname || relative.hostname === '') ?
+ relative.hostname : result.hostname;
+ result.search = relative.search;
+ result.query = relative.query;
+ srcPath = relPath;
+ // fall through to the dot-handling below.
+ } else if (relPath.length) {
+ // it's relative
+ // throw away the existing file, and take the new path instead.
+ if (!srcPath) srcPath = [];
+ srcPath.pop();
+ srcPath = srcPath.concat(relPath);
+ result.search = relative.search;
+ result.query = relative.query;
+ } else if (!isNullOrUndefined(relative.search)) {
+ // just pull out the search.
+ // like href='?foo'.
+ // Put this after the other two cases because it simplifies the booleans
+ if (psychotic) {
+ result.hostname = result.host = srcPath.shift();
+ //occationaly the auth can get stuck only in host
+ //this especialy happens in cases like
+ //url.resolveObject('mailto:local1@domain1', 'local2@domain2')
+ var authInHost = result.host && result.host.indexOf('@') > 0 ?
+ result.host.split('@') : false;
+ if (authInHost) {
+ result.auth = authInHost.shift();
+ result.host = result.hostname = authInHost.shift();
+ }
+ }
+ result.search = relative.search;
+ result.query = relative.query;
+ //to support http.request
+ if (!isNull(result.pathname) || !isNull(result.search)) {
+ result.path = (result.pathname ? result.pathname : '') +
+ (result.search ? result.search : '');
+ }
+ result.href = result.format();
+ return result;
+ }
+
+ if (!srcPath.length) {
+ // no path at all. easy.
+ // we've already handled the other stuff above.
+ result.pathname = null;
+ //to support http.request
+ if (result.search) {
+ result.path = '/' + result.search;
+ } else {
+ result.path = null;
+ }
+ result.href = result.format();
+ return result;
+ }
+
+ // if a url ENDs in . or .., then it must get a trailing slash.
+ // however, if it ends in anything else non-slashy,
+ // then it must NOT get a trailing slash.
+ var last = srcPath.slice(-1)[0];
+ var hasTrailingSlash = (
+ (result.host || relative.host) && (last === '.' || last === '..') ||
+ last === '');
+
+ // strip single dots, resolve double dots to parent dir
+ // if the path tries to go above the root, `up` ends up > 0
+ var up = 0;
+ for (var i = srcPath.length; i >= 0; i--) {
+ last = srcPath[i];
+ if (last == '.') {
+ srcPath.splice(i, 1);
+ } else if (last === '..') {
+ srcPath.splice(i, 1);
+ up++;
+ } else if (up) {
+ srcPath.splice(i, 1);
+ up--;
+ }
+ }
+
+ // if the path is allowed to go above the root, restore leading ..s
+ if (!mustEndAbs && !removeAllDots) {
+ for (; up--; up) {
+ srcPath.unshift('..');
+ }
+ }
+
+ if (mustEndAbs && srcPath[0] !== '' &&
+ (!srcPath[0] || srcPath[0].charAt(0) !== '/')) {
+ srcPath.unshift('');
+ }
+
+ if (hasTrailingSlash && (srcPath.join('/').substr(-1) !== '/')) {
+ srcPath.push('');
+ }
+
+ var isAbsolute = srcPath[0] === '' ||
+ (srcPath[0] && srcPath[0].charAt(0) === '/');
+
+ // put the host back
+ if (psychotic) {
+ result.hostname = result.host = isAbsolute ? '' :
+ srcPath.length ? srcPath.shift() : '';
+ //occationaly the auth can get stuck only in host
+ //this especialy happens in cases like
+ //url.resolveObject('mailto:local1@domain1', 'local2@domain2')
+ var authInHost = result.host && result.host.indexOf('@') > 0 ?
+ result.host.split('@') : false;
+ if (authInHost) {
+ result.auth = authInHost.shift();
+ result.host = result.hostname = authInHost.shift();
+ }
+ }
+
+ mustEndAbs = mustEndAbs || (result.host && srcPath.length);
+
+ if (mustEndAbs && !isAbsolute) {
+ srcPath.unshift('');
+ }
+
+ if (!srcPath.length) {
+ result.pathname = null;
+ result.path = null;
+ } else {
+ result.pathname = srcPath.join('/');
+ }
+
+ //to support request.http
+ if (!isNull(result.pathname) || !isNull(result.search)) {
+ result.path = (result.pathname ? result.pathname : '') +
+ (result.search ? result.search : '');
+ }
+ result.auth = relative.auth || result.auth;
+ result.slashes = result.slashes || relative.slashes;
+ result.href = result.format();
+ return result;
+ };
+
+ Url.prototype.parseHost = function() {
+ var host = this.host;
+ var port = portPattern.exec(host);
+ if (port) {
+ port = port[0];
+ if (port !== ':') {
+ this.port = port.substr(1);
+ }
+ host = host.substr(0, host.length - port.length);
+ }
+ if (host) this.hostname = host;
+ };
+
+ function isString(arg) {
+ return typeof arg === "string";
+ }
+
+ function isObject(arg) {
+ return typeof arg === 'object' && arg !== null;
+ }
+
+ function isNull(arg) {
+ return arg === null;
+ }
+ function isNullOrUndefined(arg) {
+ return arg == null;
+ }
+
+
+/***/ },
+/* 294 */
+/***/ function(module, exports, __webpack_require__) {
+
+ var __WEBPACK_AMD_DEFINE_RESULT__;/* WEBPACK VAR INJECTION */(function(module, global) {/*! https://mths.be/punycode v1.3.2 by @mathias */
+ ;(function(root) {
+
+ /** Detect free variables */
+ var freeExports = typeof exports == 'object' && exports &&
+ !exports.nodeType && exports;
+ var freeModule = typeof module == 'object' && module &&
+ !module.nodeType && module;
+ var freeGlobal = typeof global == 'object' && global;
+ if (
+ freeGlobal.global === freeGlobal ||
+ freeGlobal.window === freeGlobal ||
+ freeGlobal.self === freeGlobal
+ ) {
+ root = freeGlobal;
+ }
+
+ /**
+ * The `punycode` object.
+ * @name punycode
+ * @type Object
+ */
+ var punycode,
+
+ /** Highest positive signed 32-bit float value */
+ maxInt = 2147483647, // aka. 0x7FFFFFFF or 2^31-1
+
+ /** Bootstring parameters */
+ base = 36,
+ tMin = 1,
+ tMax = 26,
+ skew = 38,
+ damp = 700,
+ initialBias = 72,
+ initialN = 128, // 0x80
+ delimiter = '-', // '\x2D'
+
+ /** Regular expressions */
+ regexPunycode = /^xn--/,
+ regexNonASCII = /[^\x20-\x7E]/, // unprintable ASCII chars + non-ASCII chars
+ regexSeparators = /[\x2E\u3002\uFF0E\uFF61]/g, // RFC 3490 separators
+
+ /** Error messages */
+ errors = {
+ 'overflow': 'Overflow: input needs wider integers to process',
+ 'not-basic': 'Illegal input >= 0x80 (not a basic code point)',
+ 'invalid-input': 'Invalid input'
+ },
+
+ /** Convenience shortcuts */
+ baseMinusTMin = base - tMin,
+ floor = Math.floor,
+ stringFromCharCode = String.fromCharCode,
+
+ /** Temporary variable */
+ key;
+
+ /*--------------------------------------------------------------------------*/
+
+ /**
+ * A generic error utility function.
+ * @private
+ * @param {String} type The error type.
+ * @returns {Error} Throws a `RangeError` with the applicable error message.
+ */
+ function error(type) {
+ throw RangeError(errors[type]);
+ }
+
+ /**
+ * A generic `Array#map` utility function.
+ * @private
+ * @param {Array} array The array to iterate over.
+ * @param {Function} callback The function that gets called for every array
+ * item.
+ * @returns {Array} A new array of values returned by the callback function.
+ */
+ function map(array, fn) {
+ var length = array.length;
+ var result = [];
+ while (length--) {
+ result[length] = fn(array[length]);
+ }
+ return result;
+ }
+
+ /**
+ * A simple `Array#map`-like wrapper to work with domain name strings or email
+ * addresses.
+ * @private
+ * @param {String} domain The domain name or email address.
+ * @param {Function} callback The function that gets called for every
+ * character.
+ * @returns {Array} A new string of characters returned by the callback
+ * function.
+ */
+ function mapDomain(string, fn) {
+ var parts = string.split('@');
+ var result = '';
+ if (parts.length > 1) {
+ // In email addresses, only the domain name should be punycoded. Leave
+ // the local part (i.e. everything up to `@`) intact.
+ result = parts[0] + '@';
+ string = parts[1];
+ }
+ // Avoid `split(regex)` for IE8 compatibility. See #17.
+ string = string.replace(regexSeparators, '\x2E');
+ var labels = string.split('.');
+ var encoded = map(labels, fn).join('.');
+ return result + encoded;
+ }
+
+ /**
+ * Creates an array containing the numeric code points of each Unicode
+ * character in the string. While JavaScript uses UCS-2 internally,
+ * this function will convert a pair of surrogate halves (each of which
+ * UCS-2 exposes as separate characters) into a single code point,
+ * matching UTF-16.
+ * @see `punycode.ucs2.encode`
+ * @see <https://mathiasbynens.be/notes/javascript-encoding>
+ * @memberOf punycode.ucs2
+ * @name decode
+ * @param {String} string The Unicode input string (UCS-2).
+ * @returns {Array} The new array of code points.
+ */
+ function ucs2decode(string) {
+ var output = [],
+ counter = 0,
+ length = string.length,
+ value,
+ extra;
+ while (counter < length) {
+ value = string.charCodeAt(counter++);
+ if (value >= 0xD800 && value <= 0xDBFF && counter < length) {
+ // high surrogate, and there is a next character
+ extra = string.charCodeAt(counter++);
+ if ((extra & 0xFC00) == 0xDC00) { // low surrogate
+ output.push(((value & 0x3FF) << 10) + (extra & 0x3FF) + 0x10000);
+ } else {
+ // unmatched surrogate; only append this code unit, in case the next
+ // code unit is the high surrogate of a surrogate pair
+ output.push(value);
+ counter--;
+ }
+ } else {
+ output.push(value);
+ }
+ }
+ return output;
+ }
+
+ /**
+ * Creates a string based on an array of numeric code points.
+ * @see `punycode.ucs2.decode`
+ * @memberOf punycode.ucs2
+ * @name encode
+ * @param {Array} codePoints The array of numeric code points.
+ * @returns {String} The new Unicode string (UCS-2).
+ */
+ function ucs2encode(array) {
+ return map(array, function(value) {
+ var output = '';
+ if (value > 0xFFFF) {
+ value -= 0x10000;
+ output += stringFromCharCode(value >>> 10 & 0x3FF | 0xD800);
+ value = 0xDC00 | value & 0x3FF;
+ }
+ output += stringFromCharCode(value);
+ return output;
+ }).join('');
+ }
+
+ /**
+ * Converts a basic code point into a digit/integer.
+ * @see `digitToBasic()`
+ * @private
+ * @param {Number} codePoint The basic numeric code point value.
+ * @returns {Number} The numeric value of a basic code point (for use in
+ * representing integers) in the range `0` to `base - 1`, or `base` if
+ * the code point does not represent a value.
+ */
+ function basicToDigit(codePoint) {
+ if (codePoint - 48 < 10) {
+ return codePoint - 22;
+ }
+ if (codePoint - 65 < 26) {
+ return codePoint - 65;
+ }
+ if (codePoint - 97 < 26) {
+ return codePoint - 97;
+ }
+ return base;
+ }
+
+ /**
+ * Converts a digit/integer into a basic code point.
+ * @see `basicToDigit()`
+ * @private
+ * @param {Number} digit The numeric value of a basic code point.
+ * @returns {Number} The basic code point whose value (when used for
+ * representing integers) is `digit`, which needs to be in the range
+ * `0` to `base - 1`. If `flag` is non-zero, the uppercase form is
+ * used; else, the lowercase form is used. The behavior is undefined
+ * if `flag` is non-zero and `digit` has no uppercase form.
+ */
+ function digitToBasic(digit, flag) {
+ // 0..25 map to ASCII a..z or A..Z
+ // 26..35 map to ASCII 0..9
+ return digit + 22 + 75 * (digit < 26) - ((flag != 0) << 5);
+ }
+
+ /**
+ * Bias adaptation function as per section 3.4 of RFC 3492.
+ * http://tools.ietf.org/html/rfc3492#section-3.4
+ * @private
+ */
+ function adapt(delta, numPoints, firstTime) {
+ var k = 0;
+ delta = firstTime ? floor(delta / damp) : delta >> 1;
+ delta += floor(delta / numPoints);
+ for (/* no initialization */; delta > baseMinusTMin * tMax >> 1; k += base) {
+ delta = floor(delta / baseMinusTMin);
+ }
+ return floor(k + (baseMinusTMin + 1) * delta / (delta + skew));
+ }
+
+ /**
+ * Converts a Punycode string of ASCII-only symbols to a string of Unicode
+ * symbols.
+ * @memberOf punycode
+ * @param {String} input The Punycode string of ASCII-only symbols.
+ * @returns {String} The resulting string of Unicode symbols.
+ */
+ function decode(input) {
+ // Don't use UCS-2
+ var output = [],
+ inputLength = input.length,
+ out,
+ i = 0,
+ n = initialN,
+ bias = initialBias,
+ basic,
+ j,
+ index,
+ oldi,
+ w,
+ k,
+ digit,
+ t,
+ /** Cached calculation results */
+ baseMinusT;
+
+ // Handle the basic code points: let `basic` be the number of input code
+ // points before the last delimiter, or `0` if there is none, then copy
+ // the first basic code points to the output.
+
+ basic = input.lastIndexOf(delimiter);
+ if (basic < 0) {
+ basic = 0;
+ }
+
+ for (j = 0; j < basic; ++j) {
+ // if it's not a basic code point
+ if (input.charCodeAt(j) >= 0x80) {
+ error('not-basic');
+ }
+ output.push(input.charCodeAt(j));
+ }
+
+ // Main decoding loop: start just after the last delimiter if any basic code
+ // points were copied; start at the beginning otherwise.
+
+ for (index = basic > 0 ? basic + 1 : 0; index < inputLength; /* no final expression */) {
+
+ // `index` is the index of the next character to be consumed.
+ // Decode a generalized variable-length integer into `delta`,
+ // which gets added to `i`. The overflow checking is easier
+ // if we increase `i` as we go, then subtract off its starting
+ // value at the end to obtain `delta`.
+ for (oldi = i, w = 1, k = base; /* no condition */; k += base) {
+
+ if (index >= inputLength) {
+ error('invalid-input');
+ }
+
+ digit = basicToDigit(input.charCodeAt(index++));
+
+ if (digit >= base || digit > floor((maxInt - i) / w)) {
+ error('overflow');
+ }
+
+ i += digit * w;
+ t = k <= bias ? tMin : (k >= bias + tMax ? tMax : k - bias);
+
+ if (digit < t) {
+ break;
+ }
+
+ baseMinusT = base - t;
+ if (w > floor(maxInt / baseMinusT)) {
+ error('overflow');
+ }
+
+ w *= baseMinusT;
+
+ }
+
+ out = output.length + 1;
+ bias = adapt(i - oldi, out, oldi == 0);
+
+ // `i` was supposed to wrap around from `out` to `0`,
+ // incrementing `n` each time, so we'll fix that now:
+ if (floor(i / out) > maxInt - n) {
+ error('overflow');
+ }
+
+ n += floor(i / out);
+ i %= out;
+
+ // Insert `n` at position `i` of the output
+ output.splice(i++, 0, n);
+
+ }
+
+ return ucs2encode(output);
+ }
+
+ /**
+ * Converts a string of Unicode symbols (e.g. a domain name label) to a
+ * Punycode string of ASCII-only symbols.
+ * @memberOf punycode
+ * @param {String} input The string of Unicode symbols.
+ * @returns {String} The resulting Punycode string of ASCII-only symbols.
+ */
+ function encode(input) {
+ var n,
+ delta,
+ handledCPCount,
+ basicLength,
+ bias,
+ j,
+ m,
+ q,
+ k,
+ t,
+ currentValue,
+ output = [],
+ /** `inputLength` will hold the number of code points in `input`. */
+ inputLength,
+ /** Cached calculation results */
+ handledCPCountPlusOne,
+ baseMinusT,
+ qMinusT;
+
+ // Convert the input in UCS-2 to Unicode
+ input = ucs2decode(input);
+
+ // Cache the length
+ inputLength = input.length;
+
+ // Initialize the state
+ n = initialN;
+ delta = 0;
+ bias = initialBias;
+
+ // Handle the basic code points
+ for (j = 0; j < inputLength; ++j) {
+ currentValue = input[j];
+ if (currentValue < 0x80) {
+ output.push(stringFromCharCode(currentValue));
+ }
+ }
+
+ handledCPCount = basicLength = output.length;
+
+ // `handledCPCount` is the number of code points that have been handled;
+ // `basicLength` is the number of basic code points.
+
+ // Finish the basic string - if it is not empty - with a delimiter
+ if (basicLength) {
+ output.push(delimiter);
+ }
+
+ // Main encoding loop:
+ while (handledCPCount < inputLength) {
+
+ // All non-basic code points < n have been handled already. Find the next
+ // larger one:
+ for (m = maxInt, j = 0; j < inputLength; ++j) {
+ currentValue = input[j];
+ if (currentValue >= n && currentValue < m) {
+ m = currentValue;
+ }
+ }
+
+ // Increase `delta` enough to advance the decoder's <n,i> state to <m,0>,
+ // but guard against overflow
+ handledCPCountPlusOne = handledCPCount + 1;
+ if (m - n > floor((maxInt - delta) / handledCPCountPlusOne)) {
+ error('overflow');
+ }
+
+ delta += (m - n) * handledCPCountPlusOne;
+ n = m;
+
+ for (j = 0; j < inputLength; ++j) {
+ currentValue = input[j];
+
+ if (currentValue < n && ++delta > maxInt) {
+ error('overflow');
+ }
+
+ if (currentValue == n) {
+ // Represent delta as a generalized variable-length integer
+ for (q = delta, k = base; /* no condition */; k += base) {
+ t = k <= bias ? tMin : (k >= bias + tMax ? tMax : k - bias);
+ if (q < t) {
+ break;
+ }
+ qMinusT = q - t;
+ baseMinusT = base - t;
+ output.push(
+ stringFromCharCode(digitToBasic(t + qMinusT % baseMinusT, 0))
+ );
+ q = floor(qMinusT / baseMinusT);
+ }
+
+ output.push(stringFromCharCode(digitToBasic(q, 0)));
+ bias = adapt(delta, handledCPCountPlusOne, handledCPCount == basicLength);
+ delta = 0;
+ ++handledCPCount;
+ }
+ }
+
+ ++delta;
+ ++n;
+
+ }
+ return output.join('');
+ }
+
+ /**
+ * Converts a Punycode string representing a domain name or an email address
+ * to Unicode. Only the Punycoded parts of the input will be converted, i.e.
+ * it doesn't matter if you call it on a string that has already been
+ * converted to Unicode.
+ * @memberOf punycode
+ * @param {String} input The Punycoded domain name or email address to
+ * convert to Unicode.
+ * @returns {String} The Unicode representation of the given Punycode
+ * string.
+ */
+ function toUnicode(input) {
+ return mapDomain(input, function(string) {
+ return regexPunycode.test(string)
+ ? decode(string.slice(4).toLowerCase())
+ : string;
+ });
+ }
+
+ /**
+ * Converts a Unicode string representing a domain name or an email address to
+ * Punycode. Only the non-ASCII parts of the domain name will be converted,
+ * i.e. it doesn't matter if you call it with a domain that's already in
+ * ASCII.
+ * @memberOf punycode
+ * @param {String} input The domain name or email address to convert, as a
+ * Unicode string.
+ * @returns {String} The Punycode representation of the given domain name or
+ * email address.
+ */
+ function toASCII(input) {
+ return mapDomain(input, function(string) {
+ return regexNonASCII.test(string)
+ ? 'xn--' + encode(string)
+ : string;
+ });
+ }
+
+ /*--------------------------------------------------------------------------*/
+
+ /** Define the public API */
+ punycode = {
+ /**
+ * A string representing the current Punycode.js version number.
+ * @memberOf punycode
+ * @type String
+ */
+ 'version': '1.3.2',
+ /**
+ * An object of methods to convert from JavaScript's internal character
+ * representation (UCS-2) to Unicode code points, and back.
+ * @see <https://mathiasbynens.be/notes/javascript-encoding>
+ * @memberOf punycode
+ * @type Object
+ */
+ 'ucs2': {
+ 'decode': ucs2decode,
+ 'encode': ucs2encode
+ },
+ 'decode': decode,
+ 'encode': encode,
+ 'toASCII': toASCII,
+ 'toUnicode': toUnicode
+ };
+
+ /** Expose `punycode` */
+ // Some AMD build optimizers, like r.js, check for specific condition patterns
+ // like the following:
+ if (
+ true
+ ) {
+ !(__WEBPACK_AMD_DEFINE_RESULT__ = function() {
+ return punycode;
+ }.call(exports, __webpack_require__, exports, module), __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__));
+ } else if (freeExports && freeModule) {
+ if (module.exports == freeExports) { // in Node.js or RingoJS v0.8.0+
+ freeModule.exports = punycode;
+ } else { // in Narwhal or RingoJS v0.7.0-
+ for (key in punycode) {
+ punycode.hasOwnProperty(key) && (freeExports[key] = punycode[key]);
+ }
+ }
+ } else { // in Rhino or a web browser
+ root.punycode = punycode;
+ }
+
+ }(this));
+
+ /* WEBPACK VAR INJECTION */}.call(exports, __webpack_require__(77)(module), (function() { return this; }())))
+
+/***/ },
+/* 295 */
+/***/ function(module, exports, __webpack_require__) {
+
+ 'use strict';
+
+ exports.decode = exports.parse = __webpack_require__(296);
+ exports.encode = exports.stringify = __webpack_require__(297);
+
+
+/***/ },
+/* 296 */
+/***/ function(module, exports) {
+
+ // Copyright Joyent, Inc. and other Node contributors.
+ //
+ // Permission is hereby granted, free of charge, to any person obtaining a
+ // copy of this software and associated documentation files (the
+ // "Software"), to deal in the Software without restriction, including
+ // without limitation the rights to use, copy, modify, merge, publish,
+ // distribute, sublicense, and/or sell copies of the Software, and to permit
+ // persons to whom the Software is furnished to do so, subject to the
+ // following conditions:
+ //
+ // The above copyright notice and this permission notice shall be included
+ // in all copies or substantial portions of the Software.
+ //
+ // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+ // OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
+ // NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
+ // DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
+ // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
+ // USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+ 'use strict';
+
+ // If obj.hasOwnProperty has been overridden, then calling
+ // obj.hasOwnProperty(prop) will break.
+ // See: https://github.com/joyent/node/issues/1707
+ function hasOwnProperty(obj, prop) {
+ return Object.prototype.hasOwnProperty.call(obj, prop);
+ }
+
+ module.exports = function(qs, sep, eq, options) {
+ sep = sep || '&';
+ eq = eq || '=';
+ var obj = {};
+
+ if (typeof qs !== 'string' || qs.length === 0) {
+ return obj;
+ }
+
+ var regexp = /\+/g;
+ qs = qs.split(sep);
+
+ var maxKeys = 1000;
+ if (options && typeof options.maxKeys === 'number') {
+ maxKeys = options.maxKeys;
+ }
+
+ var len = qs.length;
+ // maxKeys <= 0 means that we should not limit keys count
+ if (maxKeys > 0 && len > maxKeys) {
+ len = maxKeys;
+ }
+
+ for (var i = 0; i < len; ++i) {
+ var x = qs[i].replace(regexp, '%20'),
+ idx = x.indexOf(eq),
+ kstr, vstr, k, v;
+
+ if (idx >= 0) {
+ kstr = x.substr(0, idx);
+ vstr = x.substr(idx + 1);
+ } else {
+ kstr = x;
+ vstr = '';
+ }
+
+ k = decodeURIComponent(kstr);
+ v = decodeURIComponent(vstr);
+
+ if (!hasOwnProperty(obj, k)) {
+ obj[k] = v;
+ } else if (Array.isArray(obj[k])) {
+ obj[k].push(v);
+ } else {
+ obj[k] = [obj[k], v];
+ }
+ }
+
+ return obj;
+ };
+
+
+/***/ },
+/* 297 */
+/***/ function(module, exports) {
+
+ // Copyright Joyent, Inc. and other Node contributors.
+ //
+ // Permission is hereby granted, free of charge, to any person obtaining a
+ // copy of this software and associated documentation files (the
+ // "Software"), to deal in the Software without restriction, including
+ // without limitation the rights to use, copy, modify, merge, publish,
+ // distribute, sublicense, and/or sell copies of the Software, and to permit
+ // persons to whom the Software is furnished to do so, subject to the
+ // following conditions:
+ //
+ // The above copyright notice and this permission notice shall be included
+ // in all copies or substantial portions of the Software.
+ //
+ // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+ // OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
+ // NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
+ // DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
+ // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
+ // USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+ 'use strict';
+
+ var stringifyPrimitive = function(v) {
+ switch (typeof v) {
+ case 'string':
+ return v;
+
+ case 'boolean':
+ return v ? 'true' : 'false';
+
+ case 'number':
+ return isFinite(v) ? v : '';
+
+ default:
+ return '';
+ }
+ };
+
+ module.exports = function(obj, sep, eq, name) {
+ sep = sep || '&';
+ eq = eq || '=';
+ if (obj === null) {
+ obj = undefined;
+ }
+
+ if (typeof obj === 'object') {
+ return Object.keys(obj).map(function(k) {
+ var ks = encodeURIComponent(stringifyPrimitive(k)) + eq;
+ if (Array.isArray(obj[k])) {
+ return obj[k].map(function(v) {
+ return ks + encodeURIComponent(stringifyPrimitive(v));
+ }).join(sep);
+ } else {
+ return ks + encodeURIComponent(stringifyPrimitive(obj[k]));
+ }
+ }).join(sep);
+
+ }
+
+ if (!name) return '';
+ return encodeURIComponent(stringifyPrimitive(name)) + eq +
+ encodeURIComponent(stringifyPrimitive(obj));
+ };
+
+
+/***/ },
+/* 298 */
+/***/ function(module, exports) {
+
+ // removed by extract-text-webpack-plugin
+
+/***/ },
+/* 299 */,
+/* 300 */
+/***/ function(module, exports, __webpack_require__) {
+
+ var React = __webpack_require__(2);
+ var dom = React.DOM;
+ var PropTypes = React.PropTypes;
+
+ var _require = __webpack_require__(301);
+
+ var filter = _require.filter;
+
+ var classnames = __webpack_require__(211);
+ __webpack_require__(308);
+ var Svg = __webpack_require__(310);
+ var CloseButton = __webpack_require__(336);
+
+ var INITIAL_SELECTED_INDEX = 0;
+
+ var Autocomplete = React.createClass({
+ propTypes: {
+ selectItem: PropTypes.func,
+ items: PropTypes.array,
+ handleClose: PropTypes.func,
+ inputValue: PropTypes.string
+ },
+
+ displayName: "Autocomplete",
+
+ getInitialState() {
+ return {
+ inputValue: this.props.inputValue,
+ selectedIndex: INITIAL_SELECTED_INDEX
+ };
+ },
+
+ componentDidMount() {
+ var endOfInput = this.state.inputValue.length;
+ this.refs.searchInput.focus();
+ this.refs.searchInput.setSelectionRange(endOfInput, endOfInput);
+ },
+
+ componentDidUpdate() {
+ this.scrollList();
+ },
+
+ scrollList() {
+ var resultsEl = this.refs.results;
+ if (!resultsEl || resultsEl.children.length === 0) {
+ return;
+ }
+
+ var resultsHeight = resultsEl.clientHeight;
+ var itemHeight = resultsEl.children[0].clientHeight;
+ var numVisible = resultsHeight / itemHeight;
+ var positionsToScroll = this.state.selectedIndex - numVisible + 1;
+ var itemOffset = resultsHeight % itemHeight;
+ var scroll = positionsToScroll * (itemHeight + 2) + itemOffset;
+
+ resultsEl.scrollTop = Math.max(0, scroll);
+ },
+
+ getSearchResults() {
+ var inputValue = this.state.inputValue;
+
+ if (inputValue == "") {
+ return [];
+ }
+ return filter(this.props.items, this.state.inputValue, {
+ key: "value"
+ });
+ },
+
+ onKeyDown(e) {
+ var searchResults = this.getSearchResults(),
+ resultCount = searchResults.length;
+
+ if (e.key === "ArrowUp") {
+ this.setState({
+ selectedIndex: Math.max(0, this.state.selectedIndex - 1)
+ });
+ e.preventDefault();
+ } else if (e.key === "ArrowDown") {
+ this.setState({
+ selectedIndex: Math.min(resultCount - 1, this.state.selectedIndex + 1)
+ });
+ e.preventDefault();
+ } else if (e.key === "Enter") {
+ if (searchResults.length) {
+ this.props.selectItem(searchResults[this.state.selectedIndex]);
+ } else {
+ this.props.handleClose(this.state.inputValue);
+ }
+ e.preventDefault();
+ } else if (e.key === "Tab") {
+ this.props.handleClose(this.state.inputValue);
+ e.preventDefault();
+ }
+ },
+
+ renderSearchItem(result, index) {
+ return dom.li({
+ onClick: () => this.props.selectItem(result),
+ key: result.value,
+ className: classnames({
+ selected: index === this.state.selectedIndex
+ })
+ }, dom.div({ className: "title" }, result.title), dom.div({ className: "subtitle" }, result.subtitle));
+ },
+
+ renderInput() {
+ return dom.input({
+ ref: "searchInput",
+ value: this.state.inputValue,
+ onChange: e => this.setState({
+ inputValue: e.target.value,
+ selectedIndex: INITIAL_SELECTED_INDEX
+ }),
+ onFocus: e => this.setState({ focused: true }),
+ onBlur: e => this.setState({ focused: false }),
+ onKeyDown: this.onKeyDown,
+ placeholder: L10N.getStr("sourceSearch.search")
+ });
+ },
+
+ renderResults(results) {
+ if (results.length) {
+ return dom.ul({ className: "results", ref: "results" }, results.map(this.renderSearchItem));
+ } else if (this.state.inputValue && !results.length) {
+ return dom.div({ className: "no-result-msg" }, Svg("sad-face"), L10N.getFormatStr("sourceSearch.noResults", this.state.inputValue));
+ }
+ },
+
+ renderSummary(searchResults) {
+ if (searchResults && searchResults.length === 0) {
+ return;
+ }
+
+ var resultCountSummary = "";
+ if (this.state.inputValue) {
+ resultCountSummary = L10N.getFormatStr("sourceSearch.resultsSummary", searchResults.length, this.state.inputValue);
+ }
+ return dom.div({ className: "results-summary" }, resultCountSummary);
+ },
+
+ render() {
+ var searchResults = this.getSearchResults();
+ return dom.div({ className: classnames({
+ autocomplete: true,
+ focused: this.state.focused
+ })
+ }, dom.div({ className: "searchinput-container" }, Svg("magnifying-glass"), this.renderInput(), CloseButton({
+ buttonClass: "big",
+ handleClick: this.props.handleClose
+ })), this.renderSummary(searchResults), this.renderResults(searchResults));
+ }
+ });
+
+ module.exports = Autocomplete;
+
+/***/ },
+/* 301 */
+/***/ function(module, exports, __webpack_require__) {
+
+ (function() {
+ var PathSeparator, filter, legacy_scorer, matcher, prepQueryCache, scorer;
+
+ scorer = __webpack_require__(302);
+
+ legacy_scorer = __webpack_require__(305);
+
+ filter = __webpack_require__(306);
+
+ matcher = __webpack_require__(307);
+
+ PathSeparator = __webpack_require__(303).sep;
+
+ prepQueryCache = null;
+
+ module.exports = {
+ filter: function(candidates, query, options) {
+ if (!((query != null ? query.length : void 0) && (candidates != null ? candidates.length : void 0))) {
+ return [];
+ }
+ return filter(candidates, query, options);
+ },
+ prepQuery: function(query) {
+ return scorer.prepQuery(query);
+ },
+ score: function(string, query, prepQuery, _arg) {
+ var allowErrors, coreQuery, legacy, queryHasSlashes, score, _ref;
+ _ref = _arg != null ? _arg : {}, allowErrors = _ref.allowErrors, legacy = _ref.legacy;
+ if (!((string != null ? string.length : void 0) && (query != null ? query.length : void 0))) {
+ return 0;
+ }
+ if (prepQuery == null) {
+ prepQuery = prepQueryCache && prepQueryCache.query === query ? prepQueryCache : (prepQueryCache = scorer.prepQuery(query));
+ }
+ if (!legacy) {
+ score = scorer.score(string, query, prepQuery, !!allowErrors);
+ } else {
+ queryHasSlashes = prepQuery.depth > 0;
+ coreQuery = prepQuery.core;
+ score = legacy_scorer.score(string, coreQuery, queryHasSlashes);
+ if (!queryHasSlashes) {
+ score = legacy_scorer.basenameScore(string, coreQuery, score);
+ }
+ }
+ return score;
+ },
+ match: function(string, query, prepQuery, _arg) {
+ var allowErrors, baseMatches, matches, query_lw, string_lw, _i, _ref, _results;
+ allowErrors = (_arg != null ? _arg : {}).allowErrors;
+ if (!string) {
+ return [];
+ }
+ if (!query) {
+ return [];
+ }
+ if (string === query) {
+ return (function() {
+ _results = [];
+ for (var _i = 0, _ref = string.length; 0 <= _ref ? _i < _ref : _i > _ref; 0 <= _ref ? _i++ : _i--){ _results.push(_i); }
+ return _results;
+ }).apply(this);
+ }
+ if (prepQuery == null) {
+ prepQuery = prepQueryCache && prepQueryCache.query === query ? prepQueryCache : (prepQueryCache = scorer.prepQuery(query));
+ }
+ if (!(allowErrors || scorer.isMatch(string, prepQuery.core_lw, prepQuery.core_up))) {
+ return [];
+ }
+ string_lw = string.toLowerCase();
+ query_lw = prepQuery.query_lw;
+ matches = matcher.match(string, string_lw, prepQuery);
+ if (matches.length === 0) {
+ return matches;
+ }
+ if (string.indexOf(PathSeparator) > -1) {
+ baseMatches = matcher.basenameMatch(string, string_lw, prepQuery);
+ matches = matcher.mergeMatches(matches, baseMatches);
+ }
+ return matches;
+ }
+ };
+
+ }).call(this);
+
+
+/***/ },
+/* 302 */
+/***/ function(module, exports, __webpack_require__) {
+
+ (function() {
+ var AcronymResult, PathSeparator, Query, basenameScore, coreChars, countDir, doScore, emptyAcronymResult, file_coeff, isMatch, isSeparator, isWordEnd, isWordStart, miss_coeff, opt_char_re, pos_bonus, scoreAcronyms, scoreCharacter, scoreConsecutives, scoreExact, scoreExactMatch, scorePattern, scorePosition, scoreSize, tau_depth, tau_size, truncatedUpperCase, wm;
+
+ PathSeparator = __webpack_require__(303).sep;
+
+ wm = 150;
+
+ pos_bonus = 20;
+
+ tau_depth = 13;
+
+ tau_size = 85;
+
+ file_coeff = 1.2;
+
+ miss_coeff = 0.75;
+
+ opt_char_re = /[ _\-:\/\\]/g;
+
+ exports.coreChars = coreChars = function(query) {
+ return query.replace(opt_char_re, '');
+ };
+
+ exports.score = function(string, query, prepQuery, allowErrors) {
+ var score, string_lw;
+ if (prepQuery == null) {
+ prepQuery = new Query(query);
+ }
+ if (allowErrors == null) {
+ allowErrors = false;
+ }
+ if (!(allowErrors || isMatch(string, prepQuery.core_lw, prepQuery.core_up))) {
+ return 0;
+ }
+ string_lw = string.toLowerCase();
+ score = doScore(string, string_lw, prepQuery);
+ return Math.ceil(basenameScore(string, string_lw, prepQuery, score));
+ };
+
+ Query = (function() {
+ function Query(query) {
+ if (!(query != null ? query.length : void 0)) {
+ return null;
+ }
+ this.query = query;
+ this.query_lw = query.toLowerCase();
+ this.core = coreChars(query);
+ this.core_lw = this.core.toLowerCase();
+ this.core_up = truncatedUpperCase(this.core);
+ this.depth = countDir(query, query.length);
+ }
+
+ return Query;
+
+ })();
+
+ exports.prepQuery = function(query) {
+ return new Query(query);
+ };
+
+ exports.isMatch = isMatch = function(subject, query_lw, query_up) {
+ var i, j, m, n, qj_lw, qj_up, si;
+ m = subject.length;
+ n = query_lw.length;
+ if (!m || n > m) {
+ return false;
+ }
+ i = -1;
+ j = -1;
+ while (++j < n) {
+ qj_lw = query_lw[j];
+ qj_up = query_up[j];
+ while (++i < m) {
+ si = subject[i];
+ if (si === qj_lw || si === qj_up) {
+ break;
+ }
+ }
+ if (i === m) {
+ return false;
+ }
+ }
+ return true;
+ };
+
+ doScore = function(subject, subject_lw, prepQuery) {
+ var acro, acro_score, align, csc_diag, csc_row, csc_score, i, j, m, miss_budget, miss_left, mm, n, pos, query, query_lw, record_miss, score, score_diag, score_row, score_up, si_lw, start, sz;
+ query = prepQuery.query;
+ query_lw = prepQuery.query_lw;
+ m = subject.length;
+ n = query.length;
+ acro = scoreAcronyms(subject, subject_lw, query, query_lw);
+ acro_score = acro.score;
+ if (acro.count === n) {
+ return scoreExact(n, m, acro_score, acro.pos);
+ }
+ pos = subject_lw.indexOf(query_lw);
+ if (pos > -1) {
+ return scoreExactMatch(subject, subject_lw, query, query_lw, pos, n, m);
+ }
+ score_row = new Array(n);
+ csc_row = new Array(n);
+ sz = scoreSize(n, m);
+ miss_budget = Math.ceil(miss_coeff * n) + 5;
+ miss_left = miss_budget;
+ j = -1;
+ while (++j < n) {
+ score_row[j] = 0;
+ csc_row[j] = 0;
+ }
+ i = subject_lw.indexOf(query_lw[0]);
+ if (i > -1) {
+ i--;
+ }
+ mm = subject_lw.lastIndexOf(query_lw[n - 1], m);
+ if (mm > i) {
+ m = mm + 1;
+ }
+ while (++i < m) {
+ score = 0;
+ score_diag = 0;
+ csc_diag = 0;
+ si_lw = subject_lw[i];
+ record_miss = true;
+ j = -1;
+ while (++j < n) {
+ score_up = score_row[j];
+ if (score_up > score) {
+ score = score_up;
+ }
+ csc_score = 0;
+ if (query_lw[j] === si_lw) {
+ start = isWordStart(i, subject, subject_lw);
+ csc_score = csc_diag > 0 ? csc_diag : scoreConsecutives(subject, subject_lw, query, query_lw, i, j, start);
+ align = score_diag + scoreCharacter(i, j, start, acro_score, csc_score);
+ if (align > score) {
+ score = align;
+ miss_left = miss_budget;
+ } else {
+ if (record_miss && --miss_left <= 0) {
+ return score_row[n - 1] * sz;
+ }
+ record_miss = false;
+ }
+ }
+ score_diag = score_up;
+ csc_diag = csc_row[j];
+ csc_row[j] = csc_score;
+ score_row[j] = score;
+ }
+ }
+ return score * sz;
+ };
+
+ exports.isWordStart = isWordStart = function(pos, subject, subject_lw) {
+ var curr_s, prev_s;
+ if (pos === 0) {
+ return true;
+ }
+ curr_s = subject[pos];
+ prev_s = subject[pos - 1];
+ return isSeparator(curr_s) || isSeparator(prev_s) || (curr_s !== subject_lw[pos] && prev_s === subject_lw[pos - 1]);
+ };
+
+ exports.isWordEnd = isWordEnd = function(pos, subject, subject_lw, len) {
+ var curr_s, next_s;
+ if (pos === len - 1) {
+ return true;
+ }
+ curr_s = subject[pos];
+ next_s = subject[pos + 1];
+ return isSeparator(curr_s) || isSeparator(next_s) || (curr_s === subject_lw[pos] && next_s !== subject_lw[pos + 1]);
+ };
+
+ isSeparator = function(c) {
+ return c === ' ' || c === '.' || c === '-' || c === '_' || c === '/' || c === '\\';
+ };
+
+ scorePosition = function(pos) {
+ var sc;
+ if (pos < pos_bonus) {
+ sc = pos_bonus - pos;
+ return 100 + sc * sc;
+ } else {
+ return Math.max(100 + pos_bonus - pos, 0);
+ }
+ };
+
+ scoreSize = function(n, m) {
+ return tau_size / (tau_size + Math.abs(m - n));
+ };
+
+ scoreExact = function(n, m, quality, pos) {
+ return 2 * n * (wm * quality + scorePosition(pos)) * scoreSize(n, m);
+ };
+
+ exports.scorePattern = scorePattern = function(count, len, sameCase, start, end) {
+ var bonus, sz;
+ sz = count;
+ bonus = 6;
+ if (sameCase === count) {
+ bonus += 2;
+ }
+ if (start) {
+ bonus += 3;
+ }
+ if (end) {
+ bonus += 1;
+ }
+ if (count === len) {
+ if (start) {
+ if (sameCase === len) {
+ sz += 2;
+ } else {
+ sz += 1;
+ }
+ }
+ if (end) {
+ bonus += 1;
+ }
+ }
+ return sameCase + sz * (sz + bonus);
+ };
+
+ exports.scoreCharacter = scoreCharacter = function(i, j, start, acro_score, csc_score) {
+ var posBonus;
+ posBonus = scorePosition(i);
+ if (start) {
+ return posBonus + wm * ((acro_score > csc_score ? acro_score : csc_score) + 10);
+ }
+ return posBonus + wm * csc_score;
+ };
+
+ exports.scoreConsecutives = scoreConsecutives = function(subject, subject_lw, query, query_lw, i, j, start) {
+ var k, m, mi, n, nj, sameCase, startPos, sz;
+ m = subject.length;
+ n = query.length;
+ mi = m - i;
+ nj = n - j;
+ k = mi < nj ? mi : nj;
+ startPos = i;
+ sameCase = 0;
+ sz = 0;
+ if (query[j] === subject[i]) {
+ sameCase++;
+ }
+ while (++sz < k && query_lw[++j] === subject_lw[++i]) {
+ if (query[j] === subject[i]) {
+ sameCase++;
+ }
+ }
+ if (sz === 1) {
+ return 1 + 2 * sameCase;
+ }
+ return scorePattern(sz, n, sameCase, start, isWordEnd(i, subject, subject_lw, m));
+ };
+
+ exports.scoreExactMatch = scoreExactMatch = function(subject, subject_lw, query, query_lw, pos, n, m) {
+ var end, i, pos2, sameCase, start;
+ start = isWordStart(pos, subject, subject_lw);
+ if (!start) {
+ pos2 = subject_lw.indexOf(query_lw, pos + 1);
+ if (pos2 > -1) {
+ start = isWordStart(pos2, subject, subject_lw);
+ if (start) {
+ pos = pos2;
+ }
+ }
+ }
+ i = -1;
+ sameCase = 0;
+ while (++i < n) {
+ if (query[pos + i] === subject[i]) {
+ sameCase++;
+ }
+ }
+ end = isWordEnd(pos + n - 1, subject, subject_lw, m);
+ return scoreExact(n, m, scorePattern(n, n, sameCase, start, end), pos);
+ };
+
+ AcronymResult = (function() {
+ function AcronymResult(score, pos, count) {
+ this.score = score;
+ this.pos = pos;
+ this.count = count;
+ }
+
+ return AcronymResult;
+
+ })();
+
+ emptyAcronymResult = new AcronymResult(0, 0.1, 0);
+
+ exports.scoreAcronyms = scoreAcronyms = function(subject, subject_lw, query, query_lw) {
+ var count, i, j, m, n, pos, qj_lw, sameCase, score;
+ m = subject.length;
+ n = query.length;
+ if (!(m > 1 && n > 1)) {
+ return emptyAcronymResult;
+ }
+ count = 0;
+ pos = 0;
+ sameCase = 0;
+ i = -1;
+ j = -1;
+ while (++j < n) {
+ qj_lw = query_lw[j];
+ while (++i < m) {
+ if (qj_lw === subject_lw[i] && isWordStart(i, subject, subject_lw)) {
+ if (query[j] === subject[i]) {
+ sameCase++;
+ }
+ pos += i;
+ count++;
+ break;
+ }
+ }
+ if (i === m) {
+ break;
+ }
+ }
+ if (count < 2) {
+ return emptyAcronymResult;
+ }
+ score = scorePattern(count, n, sameCase, true, false);
+ return new AcronymResult(score, pos / count, count);
+ };
+
+ basenameScore = function(subject, subject_lw, prepQuery, fullPathScore) {
+ var alpha, basePathScore, basePos, depth, end;
+ if (fullPathScore === 0) {
+ return 0;
+ }
+ end = subject.length - 1;
+ while (subject[end] === PathSeparator) {
+ end--;
+ }
+ basePos = subject.lastIndexOf(PathSeparator, end);
+ if (basePos === -1) {
+ return fullPathScore;
+ }
+ depth = prepQuery.depth;
+ while (depth-- > 0) {
+ basePos = subject.lastIndexOf(PathSeparator, basePos - 1);
+ if (basePos === -1) {
+ return fullPathScore;
+ }
+ }
+ basePos++;
+ end++;
+ basePathScore = doScore(subject.slice(basePos, end), subject_lw.slice(basePos, end), prepQuery);
+ alpha = 0.5 * tau_depth / (tau_depth + countDir(subject, end + 1));
+ return alpha * basePathScore + (1 - alpha) * fullPathScore * scoreSize(0, file_coeff * (end - basePos));
+ };
+
+ exports.countDir = countDir = function(path, end) {
+ var count, i;
+ if (end < 1) {
+ return 0;
+ }
+ count = 0;
+ i = -1;
+ while (++i < end && path[i] === PathSeparator) {
+ continue;
+ }
+ while (++i < end) {
+ if (path[i] === PathSeparator) {
+ count++;
+ while (++i < end && path[i] === PathSeparator) {
+ continue;
+ }
+ }
+ }
+ return count;
+ };
+
+ truncatedUpperCase = function(str) {
+ var char, upper, _i, _len;
+ upper = "";
+ for (_i = 0, _len = str.length; _i < _len; _i++) {
+ char = str[_i];
+ upper += char.toUpperCase()[0];
+ }
+ return upper;
+ };
+
+ }).call(this);
+
+
+/***/ },
+/* 303 */
+/***/ function(module, exports, __webpack_require__) {
+
+ /* WEBPACK VAR INJECTION */(function(process) {// Copyright Joyent, Inc. and other Node contributors.
+ //
+ // Permission is hereby granted, free of charge, to any person obtaining a
+ // copy of this software and associated documentation files (the
+ // "Software"), to deal in the Software without restriction, including
+ // without limitation the rights to use, copy, modify, merge, publish,
+ // distribute, sublicense, and/or sell copies of the Software, and to permit
+ // persons to whom the Software is furnished to do so, subject to the
+ // following conditions:
+ //
+ // The above copyright notice and this permission notice shall be included
+ // in all copies or substantial portions of the Software.
+ //
+ // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+ // OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
+ // NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
+ // DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
+ // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
+ // USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+ // resolves . and .. elements in a path array with directory names there
+ // must be no slashes, empty elements, or device names (c:\) in the array
+ // (so also no leading and trailing slashes - it does not distinguish
+ // relative and absolute paths)
+ function normalizeArray(parts, allowAboveRoot) {
+ // if the path tries to go above the root, `up` ends up > 0
+ var up = 0;
+ for (var i = parts.length - 1; i >= 0; i--) {
+ var last = parts[i];
+ if (last === '.') {
+ parts.splice(i, 1);
+ } else if (last === '..') {
+ parts.splice(i, 1);
+ up++;
+ } else if (up) {
+ parts.splice(i, 1);
+ up--;
+ }
+ }
+
+ // if the path is allowed to go above the root, restore leading ..s
+ if (allowAboveRoot) {
+ for (; up--; up) {
+ parts.unshift('..');
+ }
+ }
+
+ return parts;
+ }
+
+ // Split a filename into [root, dir, basename, ext], unix version
+ // 'root' is just a slash, or nothing.
+ var splitPathRe =
+ /^(\/?|)([\s\S]*?)((?:\.{1,2}|[^\/]+?|)(\.[^.\/]*|))(?:[\/]*)$/;
+ var splitPath = function(filename) {
+ return splitPathRe.exec(filename).slice(1);
+ };
+
+ // path.resolve([from ...], to)
+ // posix version
+ exports.resolve = function() {
+ var resolvedPath = '',
+ resolvedAbsolute = false;
+
+ for (var i = arguments.length - 1; i >= -1 && !resolvedAbsolute; i--) {
+ var path = (i >= 0) ? arguments[i] : process.cwd();
+
+ // Skip empty and invalid entries
+ if (typeof path !== 'string') {
+ throw new TypeError('Arguments to path.resolve must be strings');
+ } else if (!path) {
+ continue;
+ }
+
+ resolvedPath = path + '/' + resolvedPath;
+ resolvedAbsolute = path.charAt(0) === '/';
+ }
+
+ // At this point the path should be resolved to a full absolute path, but
+ // handle relative paths to be safe (might happen when process.cwd() fails)
+
+ // Normalize the path
+ resolvedPath = normalizeArray(filter(resolvedPath.split('/'), function(p) {
+ return !!p;
+ }), !resolvedAbsolute).join('/');
+
+ return ((resolvedAbsolute ? '/' : '') + resolvedPath) || '.';
+ };
+
+ // path.normalize(path)
+ // posix version
+ exports.normalize = function(path) {
+ var isAbsolute = exports.isAbsolute(path),
+ trailingSlash = substr(path, -1) === '/';
+
+ // Normalize the path
+ path = normalizeArray(filter(path.split('/'), function(p) {
+ return !!p;
+ }), !isAbsolute).join('/');
+
+ if (!path && !isAbsolute) {
+ path = '.';
+ }
+ if (path && trailingSlash) {
+ path += '/';
+ }
+
+ return (isAbsolute ? '/' : '') + path;
+ };
+
+ // posix version
+ exports.isAbsolute = function(path) {
+ return path.charAt(0) === '/';
+ };
+
+ // posix version
+ exports.join = function() {
+ var paths = Array.prototype.slice.call(arguments, 0);
+ return exports.normalize(filter(paths, function(p, index) {
+ if (typeof p !== 'string') {
+ throw new TypeError('Arguments to path.join must be strings');
+ }
+ return p;
+ }).join('/'));
+ };
+
+
+ // path.relative(from, to)
+ // posix version
+ exports.relative = function(from, to) {
+ from = exports.resolve(from).substr(1);
+ to = exports.resolve(to).substr(1);
+
+ function trim(arr) {
+ var start = 0;
+ for (; start < arr.length; start++) {
+ if (arr[start] !== '') break;
+ }
+
+ var end = arr.length - 1;
+ for (; end >= 0; end--) {
+ if (arr[end] !== '') break;
+ }
+
+ if (start > end) return [];
+ return arr.slice(start, end - start + 1);
+ }
+
+ var fromParts = trim(from.split('/'));
+ var toParts = trim(to.split('/'));
+
+ var length = Math.min(fromParts.length, toParts.length);
+ var samePartsLength = length;
+ for (var i = 0; i < length; i++) {
+ if (fromParts[i] !== toParts[i]) {
+ samePartsLength = i;
+ break;
+ }
+ }
+
+ var outputParts = [];
+ for (var i = samePartsLength; i < fromParts.length; i++) {
+ outputParts.push('..');
+ }
+
+ outputParts = outputParts.concat(toParts.slice(samePartsLength));
+
+ return outputParts.join('/');
+ };
+
+ exports.sep = '/';
+ exports.delimiter = ':';
+
+ exports.dirname = function(path) {
+ var result = splitPath(path),
+ root = result[0],
+ dir = result[1];
+
+ if (!root && !dir) {
+ // No dirname whatsoever
+ return '.';
+ }
+
+ if (dir) {
+ // It has a dirname, strip trailing slash
+ dir = dir.substr(0, dir.length - 1);
+ }
+
+ return root + dir;
+ };
+
+
+ exports.basename = function(path, ext) {
+ var f = splitPath(path)[2];
+ // TODO: make this comparison case-insensitive on windows?
+ if (ext && f.substr(-1 * ext.length) === ext) {
+ f = f.substr(0, f.length - ext.length);
+ }
+ return f;
+ };
+
+
+ exports.extname = function(path) {
+ return splitPath(path)[3];
+ };
+
+ function filter (xs, f) {
+ if (xs.filter) return xs.filter(f);
+ var res = [];
+ for (var i = 0; i < xs.length; i++) {
+ if (f(xs[i], i, xs)) res.push(xs[i]);
+ }
+ return res;
+ }
+
+ // String.prototype.substr - negative index don't work in IE8
+ var substr = 'ab'.substr(-1) === 'b'
+ ? function (str, start, len) { return str.substr(start, len) }
+ : function (str, start, len) {
+ if (start < 0) start = str.length + start;
+ return str.substr(start, len);
+ }
+ ;
+
+ /* WEBPACK VAR INJECTION */}.call(exports, __webpack_require__(304)))
+
+/***/ },
+/* 304 */
+/***/ function(module, exports) {
+
+ // shim for using process in browser
+ var process = module.exports = {};
+
+ // cached from whatever global is present so that test runners that stub it
+ // don't break things. But we need to wrap it in a try catch in case it is
+ // wrapped in strict mode code which doesn't define any globals. It's inside a
+ // function because try/catches deoptimize in certain engines.
+
+ var cachedSetTimeout;
+ var cachedClearTimeout;
+
+ function defaultSetTimout() {
+ throw new Error('setTimeout has not been defined');
+ }
+ function defaultClearTimeout () {
+ throw new Error('clearTimeout has not been defined');
+ }
+ (function () {
+ try {
+ if (typeof setTimeout === 'function') {
+ cachedSetTimeout = setTimeout;
+ } else {
+ cachedSetTimeout = defaultSetTimout;
+ }
+ } catch (e) {
+ cachedSetTimeout = defaultSetTimout;
+ }
+ try {
+ if (typeof clearTimeout === 'function') {
+ cachedClearTimeout = clearTimeout;
+ } else {
+ cachedClearTimeout = defaultClearTimeout;
+ }
+ } catch (e) {
+ cachedClearTimeout = defaultClearTimeout;
+ }
+ } ())
+ function runTimeout(fun) {
+ if (cachedSetTimeout === setTimeout) {
+ //normal enviroments in sane situations
+ return setTimeout(fun, 0);
+ }
+ // if setTimeout wasn't available but was latter defined
+ if ((cachedSetTimeout === defaultSetTimout || !cachedSetTimeout) && setTimeout) {
+ cachedSetTimeout = setTimeout;
+ return setTimeout(fun, 0);
+ }
+ try {
+ // when when somebody has screwed with setTimeout but no I.E. maddness
+ return cachedSetTimeout(fun, 0);
+ } catch(e){
+ try {
+ // When we are in I.E. but the script has been evaled so I.E. doesn't trust the global object when called normally
+ return cachedSetTimeout.call(null, fun, 0);
+ } catch(e){
+ // same as above but when it's a version of I.E. that must have the global object for 'this', hopfully our context correct otherwise it will throw a global error
+ return cachedSetTimeout.call(this, fun, 0);
+ }
+ }
+
+
+ }
+ function runClearTimeout(marker) {
+ if (cachedClearTimeout === clearTimeout) {
+ //normal enviroments in sane situations
+ return clearTimeout(marker);
+ }
+ // if clearTimeout wasn't available but was latter defined
+ if ((cachedClearTimeout === defaultClearTimeout || !cachedClearTimeout) && clearTimeout) {
+ cachedClearTimeout = clearTimeout;
+ return clearTimeout(marker);
+ }
+ try {
+ // when when somebody has screwed with setTimeout but no I.E. maddness
+ return cachedClearTimeout(marker);
+ } catch (e){
+ try {
+ // When we are in I.E. but the script has been evaled so I.E. doesn't trust the global object when called normally
+ return cachedClearTimeout.call(null, marker);
+ } catch (e){
+ // same as above but when it's a version of I.E. that must have the global object for 'this', hopfully our context correct otherwise it will throw a global error.
+ // Some versions of I.E. have different rules for clearTimeout vs setTimeout
+ return cachedClearTimeout.call(this, marker);
+ }
+ }
+
+
+
+ }
+ var queue = [];
+ var draining = false;
+ var currentQueue;
+ var queueIndex = -1;
+
+ function cleanUpNextTick() {
+ if (!draining || !currentQueue) {
+ return;
+ }
+ draining = false;
+ if (currentQueue.length) {
+ queue = currentQueue.concat(queue);
+ } else {
+ queueIndex = -1;
+ }
+ if (queue.length) {
+ drainQueue();
+ }
+ }
+
+ function drainQueue() {
+ if (draining) {
+ return;
+ }
+ var timeout = runTimeout(cleanUpNextTick);
+ draining = true;
+
+ var len = queue.length;
+ while(len) {
+ currentQueue = queue;
+ queue = [];
+ while (++queueIndex < len) {
+ if (currentQueue) {
+ currentQueue[queueIndex].run();
+ }
+ }
+ queueIndex = -1;
+ len = queue.length;
+ }
+ currentQueue = null;
+ draining = false;
+ runClearTimeout(timeout);
+ }
+
+ process.nextTick = function (fun) {
+ var args = new Array(arguments.length - 1);
+ if (arguments.length > 1) {
+ for (var i = 1; i < arguments.length; i++) {
+ args[i - 1] = arguments[i];
+ }
+ }
+ queue.push(new Item(fun, args));
+ if (queue.length === 1 && !draining) {
+ runTimeout(drainQueue);
+ }
+ };
+
+ // v8 likes predictible objects
+ function Item(fun, array) {
+ this.fun = fun;
+ this.array = array;
+ }
+ Item.prototype.run = function () {
+ this.fun.apply(null, this.array);
+ };
+ process.title = 'browser';
+ process.browser = true;
+ process.env = {};
+ process.argv = [];
+ process.version = ''; // empty string to avoid regexp issues
+ process.versions = {};
+
+ function noop() {}
+
+ process.on = noop;
+ process.addListener = noop;
+ process.once = noop;
+ process.off = noop;
+ process.removeListener = noop;
+ process.removeAllListeners = noop;
+ process.emit = noop;
+
+ process.binding = function (name) {
+ throw new Error('process.binding is not supported');
+ };
+
+ process.cwd = function () { return '/' };
+ process.chdir = function (dir) {
+ throw new Error('process.chdir is not supported');
+ };
+ process.umask = function() { return 0; };
+
+
+/***/ },
+/* 305 */
+/***/ function(module, exports, __webpack_require__) {
+
+ (function() {
+ var PathSeparator, queryIsLastPathSegment;
+
+ PathSeparator = __webpack_require__(303).sep;
+
+ exports.basenameScore = function(string, query, score) {
+ var base, depth, index, lastCharacter, segmentCount, slashCount;
+ index = string.length - 1;
+ while (string[index] === PathSeparator) {
+ index--;
+ }
+ slashCount = 0;
+ lastCharacter = index;
+ base = null;
+ while (index >= 0) {
+ if (string[index] === PathSeparator) {
+ slashCount++;
+ if (base == null) {
+ base = string.substring(index + 1, lastCharacter + 1);
+ }
+ } else if (index === 0) {
+ if (lastCharacter < string.length - 1) {
+ if (base == null) {
+ base = string.substring(0, lastCharacter + 1);
+ }
+ } else {
+ if (base == null) {
+ base = string;
+ }
+ }
+ }
+ index--;
+ }
+ if (base === string) {
+ score *= 2;
+ } else if (base) {
+ score += exports.score(base, query);
+ }
+ segmentCount = slashCount + 1;
+ depth = Math.max(1, 10 - segmentCount);
+ score *= depth * 0.01;
+ return score;
+ };
+
+ exports.score = function(string, query) {
+ var character, characterScore, indexInQuery, indexInString, lowerCaseIndex, minIndex, queryLength, queryScore, stringLength, totalCharacterScore, upperCaseIndex, _ref;
+ if (string === query) {
+ return 1;
+ }
+ if (queryIsLastPathSegment(string, query)) {
+ return 1;
+ }
+ totalCharacterScore = 0;
+ queryLength = query.length;
+ stringLength = string.length;
+ indexInQuery = 0;
+ indexInString = 0;
+ while (indexInQuery < queryLength) {
+ character = query[indexInQuery++];
+ lowerCaseIndex = string.indexOf(character.toLowerCase());
+ upperCaseIndex = string.indexOf(character.toUpperCase());
+ minIndex = Math.min(lowerCaseIndex, upperCaseIndex);
+ if (minIndex === -1) {
+ minIndex = Math.max(lowerCaseIndex, upperCaseIndex);
+ }
+ indexInString = minIndex;
+ if (indexInString === -1) {
+ return 0;
+ }
+ characterScore = 0.1;
+ if (string[indexInString] === character) {
+ characterScore += 0.1;
+ }
+ if (indexInString === 0 || string[indexInString - 1] === PathSeparator) {
+ characterScore += 0.8;
+ } else if ((_ref = string[indexInString - 1]) === '-' || _ref === '_' || _ref === ' ') {
+ characterScore += 0.7;
+ }
+ string = string.substring(indexInString + 1, stringLength);
+ totalCharacterScore += characterScore;
+ }
+ queryScore = totalCharacterScore / queryLength;
+ return ((queryScore * (queryLength / stringLength)) + queryScore) / 2;
+ };
+
+ queryIsLastPathSegment = function(string, query) {
+ if (string[string.length - query.length - 1] === PathSeparator) {
+ return string.lastIndexOf(query) === string.length - query.length;
+ }
+ };
+
+ exports.match = function(string, query, stringOffset) {
+ var character, indexInQuery, indexInString, lowerCaseIndex, matches, minIndex, queryLength, stringLength, upperCaseIndex, _i, _ref, _results;
+ if (stringOffset == null) {
+ stringOffset = 0;
+ }
+ if (string === query) {
+ return (function() {
+ _results = [];
+ for (var _i = stringOffset, _ref = stringOffset + string.length; stringOffset <= _ref ? _i < _ref : _i > _ref; stringOffset <= _ref ? _i++ : _i--){ _results.push(_i); }
+ return _results;
+ }).apply(this);
+ }
+ queryLength = query.length;
+ stringLength = string.length;
+ indexInQuery = 0;
+ indexInString = 0;
+ matches = [];
+ while (indexInQuery < queryLength) {
+ character = query[indexInQuery++];
+ lowerCaseIndex = string.indexOf(character.toLowerCase());
+ upperCaseIndex = string.indexOf(character.toUpperCase());
+ minIndex = Math.min(lowerCaseIndex, upperCaseIndex);
+ if (minIndex === -1) {
+ minIndex = Math.max(lowerCaseIndex, upperCaseIndex);
+ }
+ indexInString = minIndex;
+ if (indexInString === -1) {
+ return [];
+ }
+ matches.push(stringOffset + indexInString);
+ stringOffset += indexInString + 1;
+ string = string.substring(indexInString + 1, stringLength);
+ }
+ return matches;
+ };
+
+ }).call(this);
+
+
+/***/ },
+/* 306 */
+/***/ function(module, exports, __webpack_require__) {
+
+ (function() {
+ var PathSeparator, legacy_scorer, pluckCandidates, scorer, sortCandidates;
+
+ scorer = __webpack_require__(302);
+
+ legacy_scorer = __webpack_require__(305);
+
+ pluckCandidates = function(a) {
+ return a.candidate;
+ };
+
+ sortCandidates = function(a, b) {
+ return b.score - a.score;
+ };
+
+ PathSeparator = __webpack_require__(303).sep;
+
+ module.exports = function(candidates, query, _arg) {
+ var allowErrors, bAllowErrors, bKey, candidate, coreQuery, key, legacy, maxInners, maxResults, prepQuery, queryHasSlashes, score, scoredCandidates, spotLeft, string, _i, _j, _len, _len1, _ref;
+ _ref = _arg != null ? _arg : {}, key = _ref.key, maxResults = _ref.maxResults, maxInners = _ref.maxInners, allowErrors = _ref.allowErrors, legacy = _ref.legacy;
+ scoredCandidates = [];
+ spotLeft = (maxInners != null) && maxInners > 0 ? maxInners : candidates.length;
+ bAllowErrors = !!allowErrors;
+ bKey = key != null;
+ prepQuery = scorer.prepQuery(query);
+ if (!legacy) {
+ for (_i = 0, _len = candidates.length; _i < _len; _i++) {
+ candidate = candidates[_i];
+ string = bKey ? candidate[key] : candidate;
+ if (!string) {
+ continue;
+ }
+ score = scorer.score(string, query, prepQuery, bAllowErrors);
+ if (score > 0) {
+ scoredCandidates.push({
+ candidate: candidate,
+ score: score
+ });
+ if (!--spotLeft) {
+ break;
+ }
+ }
+ }
+ } else {
+ queryHasSlashes = prepQuery.depth > 0;
+ coreQuery = prepQuery.core;
+ for (_j = 0, _len1 = candidates.length; _j < _len1; _j++) {
+ candidate = candidates[_j];
+ string = key != null ? candidate[key] : candidate;
+ if (!string) {
+ continue;
+ }
+ score = legacy_scorer.score(string, coreQuery, queryHasSlashes);
+ if (!queryHasSlashes) {
+ score = legacy_scorer.basenameScore(string, coreQuery, score);
+ }
+ if (score > 0) {
+ scoredCandidates.push({
+ candidate: candidate,
+ score: score
+ });
+ }
+ }
+ }
+ scoredCandidates.sort(sortCandidates);
+ candidates = scoredCandidates.map(pluckCandidates);
+ if (maxResults != null) {
+ candidates = candidates.slice(0, maxResults);
+ }
+ return candidates;
+ };
+
+ }).call(this);
+
+
+/***/ },
+/* 307 */
+/***/ function(module, exports, __webpack_require__) {
+
+ (function() {
+ var PathSeparator, scorer;
+
+ PathSeparator = __webpack_require__(303).sep;
+
+ scorer = __webpack_require__(302);
+
+ exports.basenameMatch = function(subject, subject_lw, prepQuery) {
+ var basePos, depth, end;
+ end = subject.length - 1;
+ while (subject[end] === PathSeparator) {
+ end--;
+ }
+ basePos = subject.lastIndexOf(PathSeparator, end);
+ if (basePos === -1) {
+ return [];
+ }
+ depth = prepQuery.depth;
+ while (depth-- > 0) {
+ basePos = subject.lastIndexOf(PathSeparator, basePos - 1);
+ if (basePos === -1) {
+ return [];
+ }
+ }
+ basePos++;
+ end++;
+ return exports.match(subject.slice(basePos, end), subject_lw.slice(basePos, end), prepQuery, basePos);
+ };
+
+ exports.mergeMatches = function(a, b) {
+ var ai, bj, i, j, m, n, out;
+ m = a.length;
+ n = b.length;
+ if (n === 0) {
+ return a.slice();
+ }
+ if (m === 0) {
+ return b.slice();
+ }
+ i = -1;
+ j = 0;
+ bj = b[j];
+ out = [];
+ while (++i < m) {
+ ai = a[i];
+ while (bj <= ai && ++j < n) {
+ if (bj < ai) {
+ out.push(bj);
+ }
+ bj = b[j];
+ }
+ out.push(ai);
+ }
+ while (j < n) {
+ out.push(b[j++]);
+ }
+ return out;
+ };
+
+ exports.match = function(subject, subject_lw, prepQuery, offset) {
+ var DIAGONAL, LEFT, STOP, UP, acro_score, align, backtrack, csc_diag, csc_row, csc_score, i, j, m, matches, move, n, pos, query, query_lw, score, score_diag, score_row, score_up, si_lw, start, trace;
+ if (offset == null) {
+ offset = 0;
+ }
+ query = prepQuery.query;
+ query_lw = prepQuery.query_lw;
+ m = subject.length;
+ n = query.length;
+ acro_score = scorer.scoreAcronyms(subject, subject_lw, query, query_lw).score;
+ score_row = new Array(n);
+ csc_row = new Array(n);
+ STOP = 0;
+ UP = 1;
+ LEFT = 2;
+ DIAGONAL = 3;
+ trace = new Array(m * n);
+ pos = -1;
+ j = -1;
+ while (++j < n) {
+ score_row[j] = 0;
+ csc_row[j] = 0;
+ }
+ i = -1;
+ while (++i < m) {
+ score = 0;
+ score_up = 0;
+ csc_diag = 0;
+ si_lw = subject_lw[i];
+ j = -1;
+ while (++j < n) {
+ csc_score = 0;
+ align = 0;
+ score_diag = score_up;
+ if (query_lw[j] === si_lw) {
+ start = scorer.isWordStart(i, subject, subject_lw);
+ csc_score = csc_diag > 0 ? csc_diag : scorer.scoreConsecutives(subject, subject_lw, query, query_lw, i, j, start);
+ align = score_diag + scorer.scoreCharacter(i, j, start, acro_score, csc_score);
+ }
+ score_up = score_row[j];
+ csc_diag = csc_row[j];
+ if (score > score_up) {
+ move = LEFT;
+ } else {
+ score = score_up;
+ move = UP;
+ }
+ if (align > score) {
+ score = align;
+ move = DIAGONAL;
+ } else {
+ csc_score = 0;
+ }
+ score_row[j] = score;
+ csc_row[j] = csc_score;
+ trace[++pos] = score > 0 ? move : STOP;
+ }
+ }
+ i = m - 1;
+ j = n - 1;
+ pos = i * n + j;
+ backtrack = true;
+ matches = [];
+ while (backtrack && i >= 0 && j >= 0) {
+ switch (trace[pos]) {
+ case UP:
+ i--;
+ pos -= n;
+ break;
+ case LEFT:
+ j--;
+ pos--;
+ break;
+ case DIAGONAL:
+ matches.push(i + offset);
+ j--;
+ i--;
+ pos -= n + 1;
+ break;
+ default:
+ backtrack = false;
+ }
+ }
+ matches.reverse();
+ return matches;
+ };
+
+ }).call(this);
+
+
+/***/ },
+/* 308 */
+/***/ function(module, exports) {
+
+ // removed by extract-text-webpack-plugin
+
+/***/ },
+/* 309 */,
+/* 310 */
+/***/ function(module, exports, __webpack_require__) {
+
+ /**
+ * This file maps the SVG React Components in the public/images directory.
+ */
+ var Svg = __webpack_require__(311);
+ module.exports = Svg;
+
+/***/ },
+/* 311 */
+/***/ function(module, exports, __webpack_require__) {
+
+ var React = __webpack_require__(2);
+ var InlineSVG = __webpack_require__(312);
+
+ var svg = {
+ "angle-brackets": __webpack_require__(313),
+ "arrow": __webpack_require__(314),
+ "blackBox": __webpack_require__(315),
+ "breakpoint": __webpack_require__(316),
+ "close": __webpack_require__(317),
+ "domain": __webpack_require__(318),
+ "file": __webpack_require__(319),
+ "folder": __webpack_require__(320),
+ "globe": __webpack_require__(321),
+ "magnifying-glass": __webpack_require__(322),
+ "pause": __webpack_require__(323),
+ "pause-exceptions": __webpack_require__(324),
+ "plus": __webpack_require__(325),
+ "prettyPrint": __webpack_require__(326),
+ "resume": __webpack_require__(327),
+ "settings": __webpack_require__(328),
+ "stepIn": __webpack_require__(329),
+ "stepOut": __webpack_require__(330),
+ "stepOver": __webpack_require__(331),
+ "subSettings": __webpack_require__(332),
+ "toggleBreakpoints": __webpack_require__(333),
+ "worker": __webpack_require__(334),
+ "sad-face": __webpack_require__(335)
+ };
+
+ module.exports = function (name, props) {
+ // eslint-disable-line
+ if (!svg[name]) {
+ throw new Error("Unknown SVG: " + name);
+ }
+ var className = name;
+ if (props && props.className) {
+ className = `${ name } ${ props.className }`;
+ }
+ if (name === "subSettings") {
+ className = "";
+ }
+ props = Object.assign({}, props, { className, src: svg[name] });
+ return React.createElement(InlineSVG, props);
+ };
+
+/***/ },
+/* 312 */
+/***/ function(module, exports, __webpack_require__) {
+
+ 'use strict';
+
+ Object.defineProperty(exports, '__esModule', {
+ value: true
+ });
+
+ var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; };
+
+ var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ('value' in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })();
+
+ var _get = function get(_x, _x2, _x3) { var _again = true; _function: while (_again) { var object = _x, property = _x2, receiver = _x3; _again = false; if (object === null) object = Function.prototype; var desc = Object.getOwnPropertyDescriptor(object, property); if (desc === undefined) { var parent = Object.getPrototypeOf(object); if (parent === null) { return undefined; } else { _x = parent; _x2 = property; _x3 = receiver; _again = true; desc = parent = undefined; continue _function; } } else if ('value' in desc) { return desc.value; } else { var getter = desc.get; if (getter === undefined) { return undefined; } return getter.call(receiver); } } };
+
+ function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }
+
+ function _objectWithoutProperties(obj, keys) { var target = {}; for (var i in obj) { if (keys.indexOf(i) >= 0) continue; if (!Object.prototype.hasOwnProperty.call(obj, i)) continue; target[i] = obj[i]; } return target; }
+
+ function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } }
+
+ function _inherits(subClass, superClass) { if (typeof superClass !== 'function' && superClass !== null) { throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }
+
+ var _react = __webpack_require__(2);
+
+ var _react2 = _interopRequireDefault(_react);
+
+ var DOMParser = typeof window !== 'undefined' && window.DOMParser;
+ var process = process || {};
+ process.env = process.env || {};
+ var parserAvailable = typeof DOMParser !== 'undefined' && DOMParser.prototype != null && DOMParser.prototype.parseFromString != null;
+
+ if ("production" !== process.env.NODE_ENV && !parserAvailable) {
+ console.info('<InlineSVG />: `raw` prop works only when `window.DOMParser` exists.');
+ }
+
+ function isParsable(src) {
+ // kinda naive but meh, ain't gonna use full-blown parser for this
+ return parserAvailable && typeof src === 'string' && src.trim().substr(0, 4) === '<svg';
+ }
+
+ // parse SVG string using `DOMParser`
+ function parseFromSVGString(src) {
+ var parser = new DOMParser();
+ return parser.parseFromString(src, "image/svg+xml");
+ }
+
+ // Transform DOM prop/attr names applicable to `<svg>` element but react-limited
+ function switchSVGAttrToReactProp(propName) {
+ switch (propName) {
+ case 'class':
+ return 'className';
+ default:
+ return propName;
+ }
+ }
+
+ var InlineSVG = (function (_React$Component) {
+ _inherits(InlineSVG, _React$Component);
+
+ _createClass(InlineSVG, null, [{
+ key: 'defaultProps',
+ value: {
+ element: 'i',
+ raw: false,
+ src: ''
+ },
+ enumerable: true
+ }, {
+ key: 'propTypes',
+ value: {
+ src: _react2['default'].PropTypes.string.isRequired,
+ element: _react2['default'].PropTypes.string,
+ raw: _react2['default'].PropTypes.bool
+ },
+ enumerable: true
+ }]);
+
+ function InlineSVG(props) {
+ _classCallCheck(this, InlineSVG);
+
+ _get(Object.getPrototypeOf(InlineSVG.prototype), 'constructor', this).call(this, props);
+ this._extractSVGProps = this._extractSVGProps.bind(this);
+ }
+
+ // Serialize `Attr` objects in `NamedNodeMap`
+
+ _createClass(InlineSVG, [{
+ key: '_serializeAttrs',
+ value: function _serializeAttrs(map) {
+ var ret = {};
+ var prop = undefined;
+ for (var i = 0; i < map.length; i++) {
+ prop = switchSVGAttrToReactProp(map[i].name);
+ ret[prop] = map[i].value;
+ }
+ return ret;
+ }
+
+ // get <svg /> element props
+ }, {
+ key: '_extractSVGProps',
+ value: function _extractSVGProps(src) {
+ var map = parseFromSVGString(src).documentElement.attributes;
+ return map.length > 0 ? this._serializeAttrs(map) : null;
+ }
+
+ // get content inside <svg> element.
+ }, {
+ key: '_stripSVG',
+ value: function _stripSVG(src) {
+ return parseFromSVGString(src).documentElement.innerHTML;
+ }
+ }, {
+ key: 'componentWillReceiveProps',
+ value: function componentWillReceiveProps(_ref) {
+ var children = _ref.children;
+
+ if ("production" !== process.env.NODE_ENV && children != null) {
+ console.info('<InlineSVG />: `children` prop will be ignored.');
+ }
+ }
+ }, {
+ key: 'render',
+ value: function render() {
+ var Element = undefined,
+ __html = undefined,
+ svgProps = undefined;
+ var _props = this.props;
+ var element = _props.element;
+ var raw = _props.raw;
+ var src = _props.src;
+
+ var otherProps = _objectWithoutProperties(_props, ['element', 'raw', 'src']);
+
+ if (raw === true && isParsable(src)) {
+ Element = 'svg';
+ svgProps = this._extractSVGProps(src);
+ __html = this._stripSVG(src);
+ }
+ __html = __html || src;
+ Element = Element || element;
+ svgProps = svgProps || {};
+
+ return _react2['default'].createElement(Element, _extends({}, svgProps, otherProps, { src: null, children: null,
+ dangerouslySetInnerHTML: { __html: __html } }));
+ }
+ }]);
+
+ return InlineSVG;
+ })(_react2['default'].Component);
+
+ exports['default'] = InlineSVG;
+ module.exports = exports['default'];
+
+/***/ },
+/* 313 */
+/***/ function(module, exports) {
+
+ module.exports = "<!-- 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/. --><svg viewBox=\"-1 73 16 11\" version=\"1.1\" xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\"><g id=\"Shape-Copy-3-+-Shape-Copy-4\" stroke=\"none\" stroke-width=\"1\" fill=\"none\" fill-rule=\"evenodd\" transform=\"translate(0.000000, 74.000000)\"><path d=\"M0.749321284,4.16081709 L4.43130681,0.242526751 C4.66815444,-0.00952143591 5.06030999,-0.0211407611 5.30721074,0.216574262 C5.55411149,0.454289284 5.56226116,0.851320812 5.32541353,1.103369 L1.95384971,4.69131519 L5.48809879,8.09407556 C5.73499955,8.33179058 5.74314922,8.72882211 5.50630159,8.9808703 C5.26945396,9.23291849 4.87729841,9.24453781 4.63039766,9.00682279 L0.827097345,5.34502101 C0.749816996,5.31670099 0.677016974,5.27216098 0.613753508,5.21125118 C0.427367989,5.03179997 0.377040713,4.7615583 0.465458792,4.53143559 C0.492371834,4.43667624 0.541703274,4.34676528 0.613628034,4.27022448 C0.654709457,4.22650651 0.70046335,4.19002189 0.749321284,4.16081709 Z\" id=\"Shape-Copy-3\" stroke=\"#FFFFFF\" stroke-width=\"0.05\" fill=\"#DDE1E4\"></path><path d=\"M13.7119065,5.44453032 L9.77062746,9.09174784 C9.51677479,9.3266604 9.12476399,9.31089603 8.89504684,9.05653714 C8.66532968,8.80217826 8.68489539,8.40554539 8.93874806,8.17063283 L12.5546008,4.82456128 L9.26827469,1.18571135 C9.03855754,0.931352463 9.05812324,0.534719593 9.31197591,0.299807038 C9.56582858,0.0648944831 9.95783938,0.0806588502 10.1875565,0.335017737 L13.72891,4.25625178 C13.8013755,4.28980469 13.8684335,4.3382578 13.9254821,4.40142604 C14.0883019,4.58171146 14.1258883,4.83347168 14.0435812,5.04846202 C14.0126705,5.15680232 13.9526426,5.2583679 13.8641331,5.34027361 C13.8174417,5.38348136 13.7660763,5.41820853 13.7119065,5.44453032 Z\" id=\"Shape-Copy-4\" stroke=\"#FFFFFF\" stroke-width=\"0.05\" fill=\"#DDE1E4\"></path></g></svg>"
+
+/***/ },
+/* 314 */
+/***/ function(module, exports) {
+
+ module.exports = "<!-- 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/. --><svg xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\" viewBox=\"0 0 16 16\"><path d=\"M8 13.4c-.5 0-.9-.2-1.2-.6L.4 5.2C0 4.7-.1 4.3.2 3.7S1 3 1.6 3h12.8c.6 0 1.2.1 1.4.7.3.6.2 1.1-.2 1.6l-6.4 7.6c-.3.4-.7.5-1.2.5z\"></path></svg>"
+
+/***/ },
+/* 315 */
+/***/ function(module, exports) {
+
+ module.exports = "<!-- 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/. --><svg viewBox=\"0 0 16 16\" xmlns=\"http://www.w3.org/2000/svg\"><g fill-rule=\"evenodd\"><circle cx=\"8\" cy=\"8.5\" r=\"1.5\"></circle><path d=\"M15.498 8.28l-.001-.03v-.002-.004l-.002-.018-.004-.031c0-.002 0-.002 0 0l-.004-.035.006.082c-.037-.296-.133-.501-.28-.661-.4-.522-.915-1.042-1.562-1.604-1.36-1.182-2.74-1.975-4.178-2.309a6.544 6.544 0 0 0-2.755-.042c-.78.153-1.565.462-2.369.91C3.252 5.147 2.207 6 1.252 7.035c-.216.233-.36.398-.499.577-.338.437-.338 1 0 1.437.428.552.941 1.072 1.59 1.635 1.359 1.181 2.739 1.975 4.177 2.308.907.21 1.829.223 2.756.043.78-.153 1.564-.462 2.369-.91 1.097-.612 2.141-1.464 3.097-2.499.217-.235.36-.398.498-.578.12-.128.216-.334.248-.554 0 .01 0 .01-.008.04l.013-.079-.001.011.003-.031.001-.017v.005l.001-.02v.008l.002-.03.001-.05-.001-.044v-.004-.004zm-.954.045v.007l.001.004V8.33v.012l-.001.01v-.005-.005l.002-.015-.001.008c-.002.014-.002.014 0 0l-.007.084c.003-.057-.004-.041-.014-.031-.143.182-.27.327-.468.543-.89.963-1.856 1.752-2.86 2.311-.724.404-1.419.677-2.095.81a5.63 5.63 0 0 1-2.374-.036c-1.273-.295-2.523-1.014-3.774-2.101-.604-.525-1.075-1.001-1.457-1.496-.054-.07-.054-.107 0-.177.117-.152.244-.298.442-.512.89-.963 1.856-1.752 2.86-2.311.724-.404 1.419-.678 2.095-.81a5.631 5.631 0 0 1 2.374.036c1.272.295 2.523 1.014 3.774 2.101.603.524 1.074 1 1.457 1.496.035.041.043.057.046.076 0 .01 0 .01.008.043l-.009-.047.003.02-.002-.013v-.008.016c0-.004 0-.004 0 0v-.004z\"></path></g></svg>"
+
+/***/ },
+/* 316 */
+/***/ function(module, exports) {
+
+ module.exports = "<!-- 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/. --><svg xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\" viewBox=\"0 0 60 12\"><path id=\"base-path\" d=\"M53.9,0H1C0.4,0,0,0.4,0,1v10c0,0.6,0.4,1,1,1h52.9c0.6,0,1.2-0.3,1.5-0.7L60,6l-4.4-5.3C55,0.3,54.5,0,53.9,0z\"></path></svg>"
+
+/***/ },
+/* 317 */
+/***/ function(module, exports) {
+
+ module.exports = "<!-- 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/. --><svg viewBox=\"0 0 6 6\" version=\"1.1\" xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\"><path d=\"M1.35191454,5.27895256 L5.31214367,1.35518468 C5.50830675,1.16082764 5.50977084,0.844248536 5.3154138,0.648085456 C5.12105677,0.451922377 4.80447766,0.450458288 4.60831458,0.644815324 L0.648085456,4.56858321 C0.451922377,4.76294025 0.450458288,5.07951935 0.644815324,5.27568243 C0.83917236,5.47184551 1.15575146,5.4733096 1.35191454,5.27895256 L1.35191454,5.27895256 Z\" id=\"Line\" stroke=\"none\" fill=\"#696969\" fill-rule=\"evenodd\"></path><path d=\"M5.31214367,4.56858321 L1.35191454,0.644815324 C1.15575146,0.450458288 0.83917236,0.451922377 0.644815324,0.648085456 C0.450458288,0.844248536 0.451922377,1.16082764 0.648085456,1.35518468 L4.60831458,5.27895256 C4.80447766,5.4733096 5.12105677,5.47184551 5.3154138,5.27568243 C5.50977084,5.07951935 5.50830675,4.76294025 5.31214367,4.56858321 L5.31214367,4.56858321 Z\" id=\"Line-Copy-2\" stroke=\"none\" fill=\"#696969\" fill-rule=\"evenodd\"></path></svg>"
+
+/***/ },
+/* 318 */
+/***/ function(module, exports) {
+
+ module.exports = "<!-- 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/. --><svg viewBox=\"0 0 16 16\" xmlns=\"http://www.w3.org/2000/svg\"><path d=\"M9.05 4.634l-2.144.003-.116.116v1.445l.92.965.492.034.116-.116v-.617L9.13 5.7l.035-.95M12.482 10.38l-1.505-1.462H9.362l-.564.516-.034 1.108.72.768 1.323.034-.117-.116v1.2l.972 1.02.315.034.116-.116v-1.154l.422-.374.034-.927-.117.117h.26l.408-.36V10.5l-.125-.124-.575-.033\"></path><path d=\"M8.47 15.073c-3.088 0-5.6-2.513-5.6-5.602V9.4v-.003c0-.018 0-.018.002-.034l.182-.088.724.587.49.033.497.543-.034.9.317.383h.47l.114.096-.032 1.9.524.553h.105l.025-.338 1.004-.95.054-.474.53-.462v-.888l-.588-.038-1.118-1.155H4.48l-.154-.09V9.01l.155-.1h1.164v-.273l.12-.115.7.033.494-.443.034-.746-.624-.655h-.724v.28l-.11.07H4.64l-.114-.09.025-.64.48-.43v-.244h-.382c-.102 0-.152-.128-.08-.2 1.04-1.01 2.428-1.59 3.903-1.59 1.374 0 2.672.5 3.688 1.39.08.068.03.198-.075.198l-1.144-.034-.81.803.52.523v.16l-.382.388h-.158l-.176-.177v-.16l.076-.074-.252-.252-.37.362.53.53c.072.072.005.194-.096.194l-.752-.005v.844h.783L9.885 8l.16-.143h.16l.62.61v.267l.58.027.003.002V8.76l.18-.03 1.234 1.24.753-.708h.382l.116.108c0 .02.003.016.003.036v.065c0 3.09-2.515 5.603-5.605 5.603M8.47 3C4.904 3 2 5.903 2 9.47c0 3.57 2.903 6.472 6.47 6.472 3.57 0 6.472-2.903 6.472-6.47C14.942 5.9 12.04 3 8.472 3\"></path></svg>"
+
+/***/ },
+/* 319 */
+/***/ function(module, exports) {
+
+ module.exports = "<!-- 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/. --><svg viewBox=\"0 0 16 16\" xmlns=\"http://www.w3.org/2000/svg\"><path d=\"M4 2v12h9V4.775L9.888 2H4zm0-1h5.888c.246 0 .483.09.666.254l3.112 2.774c.212.19.334.462.334.747V14c0 .552-.448 1-1 1H4c-.552 0-1-.448-1-1V2c0-.552.448-1 1-1z\"></path><path d=\"M9 1.5v4c0 .325.306.564.62.485l4-1c.27-.067.432-.338.365-.606-.067-.27-.338-.432-.606-.365l-4 1L10 5.5v-4c0-.276-.224-.5-.5-.5s-.5.224-.5.5z\"></path></svg>"
+
+/***/ },
+/* 320 */
+/***/ function(module, exports) {
+
+ module.exports = "<!-- 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/. --><svg viewBox=\"0 0 16 16\" xmlns=\"http://www.w3.org/2000/svg\"><path d=\"M2 5.193v7.652c0 .003-.002 0 .007 0H14v-7.69c0-.003.002 0-.007 0h-7.53v-2.15c0-.002-.004-.005-.01-.005H2.01C2 3 2 3 2 3.005V5.193zm-1 0V3.005C1 2.45 1.444 2 2.01 2h4.442c.558 0 1.01.45 1.01 1.005v1.15h6.53c.557 0 1.008.44 1.008 1v7.69c0 .553-.45 1-1.007 1H2.007c-.556 0-1.007-.44-1.007-1V5.193zM6.08 4.15H2v1h4.46v-1h-.38z\" fill-rule=\"evenodd\"></path></svg>"
+
+/***/ },
+/* 321 */
+/***/ function(module, exports) {
+
+ module.exports = "<!-- 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/. --><svg viewBox=\"14 6 13 12\" version=\"1.1\" xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\"><g id=\"world\" stroke=\"none\" stroke-width=\"1\" fill=\"none\" fill-rule=\"evenodd\" transform=\"translate(14.000000, 6.000000)\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M6.35076107,0.354 C3.25095418,0.354 0.729,2.87582735 0.729,5.9758879 C0.729,9.07544113 3.25082735,11.5972685 6.35076107,11.5972685 C9.45044113,11.5972685 11.9723953,9.07544113 11.9723953,5.97576107 C11.9723953,2.87582735 9.45044113,0.354 6.35076107,0.354 L6.35076107,0.354 Z M6.35076107,10.8289121 C3.67445071,10.8289121 1.49722956,8.65181776 1.49722956,5.97576107 C1.49722956,5.9443064 1.49900522,5.91335907 1.49976622,5.88215806 L2.20090094,6.4213266 L2.56313696,6.4213266 L2.97268183,6.8306178 L2.97268183,7.68217686 L3.32324919,8.03287105 L3.73926255,8.03287105 L3.73926255,9.79940584 L4.27386509,10.3361645 L4.4591686,10.3361645 L4.4591686,10.000183 L5.37655417,9.08343163 L5.37655417,8.73400577 L5.85585737,8.25203907 L5.85585737,7.37206934 L5.32518666,7.37206934 L4.28439226,6.33140176 L2.82225748,6.33140176 L2.82225748,5.56938704 L3.96286973,5.56938704 L3.96286973,5.23949352 L4.65068695,5.23949352 L5.11477015,4.77667865 L5.11477015,4.03001076 L4.49087694,3.40662489 L3.75359472,3.40662489 L3.75359472,3.78725175 L2.96228149,3.78725175 L2.96228149,3.28385021 L3.42217919,2.82319151 L3.42217919,2.49786399 L2.97001833,2.49786399 C3.84466106,1.64744643 5.03714814,1.12222956 6.35063424,1.12222956 C7.57292716,1.12222956 8.69020207,1.57730759 9.54442463,2.32587797 L8.46164839,2.32587797 L7.680355,3.10666403 L8.21508437,3.64088607 L7.87238068,3.98257509 L7.7165025,3.82669692 L7.85297518,3.68946324 L7.78930484,3.62566607 L7.78943167,3.62566607 L7.56011699,3.39559038 L7.55986332,3.39571722 L7.49758815,3.33318838 L7.01904595,3.78585658 L7.55910232,4.32654712 L6.8069806,4.32198112 L6.8069806,5.25864535 L7.66716433,5.25864535 L7.6723645,4.72112565 L7.81289584,4.57996014 L8.31819988,5.08653251 L8.31819988,5.41921636 L9.00703176,5.41921636 L9.03366676,5.39321553 L9.03430093,5.39194719 L10.195587,6.55259911 L10.8637451,5.88520206 L11.2018828,5.88520206 C11.2023901,5.9153884 11.2041658,5.94532107 11.2041658,5.97563424 C11.2040389,8.65181776 9.0269446,10.8289121 6.35076107,10.8289121 L6.35076107,10.8289121 Z\" id=\"Shape\" stroke=\"#DDE1E5\" stroke-width=\"0.25\" fill=\"#DDE1E5\"></path><polygon id=\"Shape\" stroke=\"#DDE1E5\" stroke-width=\"0.25\" fill=\"#DDE1E5\" points=\"6.50676608 1.61523076 4.52892694 1.61789426 4.52892694 2.95192735 5.34560683 3.76733891 5.72496536 3.76733891 5.72496536 3.1967157 6.50676608 2.41592965\"></polygon><polygon id=\"Shape\" stroke=\"#DDE1E5\" stroke-width=\"0.25\" fill=\"#DDE1E5\" points=\"9.59959714 6.88718547 8.28623788 5.57268471 8.28623788 5.57002121 6.79607294 5.57002121 6.35101474 6.01469891 6.35101474 6.96201714 6.98429362 7.59466185 8.12909136 7.59466185 8.12909136 8.70343893 8.99434843 9.56882283 9.20971144 9.56882283 9.20971144 8.50329592 9.63029081 8.08271655 9.63029081 7.3026915 9.87025949 7.3026915 10.1711082 7.00082814 10.0558167 6.88718547\"></polygon></g></svg>"
+
+/***/ },
+/* 322 */
+/***/ function(module, exports) {
+
+ module.exports = "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 16 16\"><path class=\"st0\" d=\"M9 9.3l3.6 3.6\"></path><ellipse fill=\"transparent\" cx=\"5.9\" cy=\"6.2\" rx=\"4.5\" ry=\"4.5\"></ellipse></svg>"
+
+/***/ },
+/* 323 */
+/***/ function(module, exports) {
+
+ module.exports = "<!-- 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/. --><svg viewBox=\"0 0 16 16\" xmlns=\"http://www.w3.org/2000/svg\"><g fill-rule=\"evenodd\"><path d=\"M6.5 12.003l.052-9a.5.5 0 1 0-1-.006l-.052 9a.5.5 0 1 0 1 .006zM13 11.997l-.05-9a.488.488 0 0 0-.477-.497.488.488 0 0 0-.473.503l.05 9a.488.488 0 0 0 .477.497.488.488 0 0 0 .473-.503z\"></path></g></svg>"
+
+/***/ },
+/* 324 */
+/***/ function(module, exports) {
+
+ module.exports = "<!-- 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/. --><svg viewBox=\"0 0 16 16\" xmlns=\"http://www.w3.org/2000/svg\"><path d=\"M10.483 13.995H5.517l-3.512-3.512V5.516l3.512-3.512h4.966l3.512 3.512v4.967l-3.512 3.512zm4.37-9.042l-3.807-3.805A.503.503 0 0 0 10.691 1H5.309a.503.503 0 0 0-.356.148L1.147 4.953A.502.502 0 0 0 1 5.308v5.383c0 .134.053.262.147.356l3.806 3.806a.503.503 0 0 0 .356.147h5.382a.503.503 0 0 0 .355-.147l3.806-3.806A.502.502 0 0 0 15 10.69V5.308a.502.502 0 0 0-.147-.355z\"></path><path d=\"M10 10.5a.5.5 0 1 0 1 0v-5a.5.5 0 1 0-1 0v5zM5 10.5a.5.5 0 1 0 1 0v-5a.5.5 0 0 0-1 0v5z\"></path></svg>"
+
+/***/ },
+/* 325 */
+/***/ function(module, exports) {
+
+ module.exports = "<!-- 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/. --><svg viewBox=\"0 0 16 16\" xmlns=\"http://www.w3.org/2000/svg\"><path d=\"M8.5 8.5V14a.5.5 0 1 1-1 0V8.5H2a.5.5 0 0 1 0-1h5.5V2a.5.5 0 0 1 1 0v5.5H14a.5.5 0 1 1 0 1H8.5z\" fill-rule=\"evenodd\"></path></svg>"
+
+/***/ },
+/* 326 */
+/***/ function(module, exports) {
+
+ module.exports = "<!-- 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/. --><svg viewBox=\"0 0 16 16\" xmlns=\"http://www.w3.org/2000/svg\"><path d=\"M4.525 13.21h-.472c-.574 0-.987-.154-1.24-.463-.253-.31-.38-.882-.38-1.719v-.573c0-.746-.097-1.265-.292-1.557-.196-.293-.51-.44-.945-.44v-.974c.435 0 .75-.146.945-.44.195-.292.293-.811.293-1.556v-.58c0-.833.126-1.404.379-1.712.253-.31.666-.464 1.24-.464h.472v.783h-.179c-.37 0-.628.08-.774.24-.145.159-.218.54-.218 1.141v.383c0 .824-.096 1.432-.287 1.823-.191.39-.516.679-.974.866.458.191.783.482.974.873.191.39.287.998.287 1.823v.382c0 .602.073.982.218 1.142.146.16.404.239.774.239h.18v.783zm9.502-4.752c-.43 0-.744.147-.942.44-.197.292-.296.811-.296 1.557v.573c0 .837-.125 1.41-.376 1.719-.251.309-.664.463-1.237.463h-.478v-.783h.185c.37 0 .628-.08.774-.24.145-.159.218-.539.218-1.14v-.383c0-.825.096-1.433.287-1.823.191-.39.516-.682.974-.873-.458-.187-.783-.476-.974-.866-.191-.391-.287-.999-.287-1.823v-.383c0-.602-.073-.982-.218-1.142-.146-.159-.404-.239-.774-.239h-.185v-.783h.478c.573 0 .986.155 1.237.464.25.308.376.88.376 1.712v.58c0 .673.088 1.174.263 1.503.176.329.5.493.975.493v.974z\" fill-rule=\"evenodd\"></path></svg>"
+
+/***/ },
+/* 327 */
+/***/ function(module, exports) {
+
+ module.exports = "<!-- 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/. --><svg viewBox=\"0 0 16 16\" xmlns=\"http://www.w3.org/2000/svg\"><path d=\"M6.925 12.5l7.4-5-7.4-5v10zM6 12.5v-10c0-.785.8-1.264 1.415-.848l7.4 5c.58.392.58 1.304 0 1.696l-7.4 5C6.8 13.764 6 13.285 6 12.5z\" fill-rule=\"evenodd\"></path></svg>"
+
+/***/ },
+/* 328 */
+/***/ function(module, exports) {
+
+ module.exports = "<!-- 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/. --><svg xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\" viewBox=\"0 0 33 12\"><path id=\"base-path\" d=\"M27.1,0H1C0.4,0,0,0.4,0,1v10c0,0.6,0.4,1,1,1h26.1 c0.6,0,1.2-0.3,1.5-0.7L33,6l-4.4-5.3C28.2,0.3,27.7,0,27.1,0z\"></path></svg>"
+
+/***/ },
+/* 329 */
+/***/ function(module, exports) {
+
+ module.exports = "<!-- 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/. --><svg viewBox=\"0 0 16 16\" xmlns=\"http://www.w3.org/2000/svg\"><g fill-rule=\"evenodd\"><path d=\"M1.5 14.042h4.095a.5.5 0 0 0 0-1H1.5a.5.5 0 1 0 0 1zM7.983 2a.5.5 0 0 1 .517.5v7.483l3.136-3.326a.5.5 0 1 1 .728.686l-4 4.243a.499.499 0 0 1-.73-.004L3.635 7.343a.5.5 0 0 1 .728-.686L7.5 9.983V3H1.536C1.24 3 1 2.776 1 2.5s.24-.5.536-.5h6.447zM10.5 14.042h4.095a.5.5 0 0 0 0-1H10.5a.5.5 0 1 0 0 1z\"></path></g></svg>"
+
+/***/ },
+/* 330 */
+/***/ function(module, exports) {
+
+ module.exports = "<!-- 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/. --><svg viewBox=\"0 0 16 16\" xmlns=\"http://www.w3.org/2000/svg\"><g fill-rule=\"evenodd\"><path d=\"M5 13.5H1a.5.5 0 1 0 0 1h4a.5.5 0 1 0 0-1zM12 13.5H8a.5.5 0 1 0 0 1h4a.5.5 0 1 0 0-1zM6.11 5.012A.427.427 0 0 1 6.21 5h7.083L9.646 1.354a.5.5 0 1 1 .708-.708l4.5 4.5a.498.498 0 0 1 0 .708l-4.5 4.5a.5.5 0 0 1-.708-.708L13.293 6H6.5v5.5a.5.5 0 1 1-1 0v-6a.5.5 0 0 1 .61-.488z\"></path></g></svg>"
+
+/***/ },
+/* 331 */
+/***/ function(module, exports) {
+
+ module.exports = "<!-- 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/. --><svg viewBox=\"0 0 16 16\" xmlns=\"http://www.w3.org/2000/svg\"><g fill-rule=\"evenodd\"><path d=\"M13.297 6.912C12.595 4.39 10.167 2.5 7.398 2.5A5.898 5.898 0 0 0 1.5 8.398a.5.5 0 0 0 1 0A4.898 4.898 0 0 1 7.398 3.5c2.75 0 5.102 2.236 5.102 4.898v.004L8.669 7.029a.5.5 0 0 0-.338.942l4.462 1.598a.5.5 0 0 0 .651-.34.506.506 0 0 0 .02-.043l2-5a.5.5 0 1 0-.928-.372l-1.24 3.098z\"></path><circle cx=\"7\" cy=\"12\" r=\"1\"></circle></g></svg>"
+
+/***/ },
+/* 332 */
+/***/ function(module, exports) {
+
+ module.exports = "<!-- 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/. --><svg viewBox=\"0 0 16 16\" xmlns=\"http://www.w3.org/2000/svg\"><path d=\"M12.219 7c.345 0 .635.117.869.352.234.234.351.524.351.869 0 .351-.118.652-.356.903-.238.25-.526.376-.864.376-.332 0-.615-.125-.85-.376a1.276 1.276 0 0 1-.351-.903A1.185 1.185 0 0 1 12.218 7zM8.234 7c.345 0 .635.117.87.352.234.234.351.524.351.869 0 .351-.119.652-.356.903-.238.25-.526.376-.865.376-.332 0-.613-.125-.844-.376a1.286 1.286 0 0 1-.347-.903c0-.352.114-.643.342-.874.228-.231.51-.347.85-.347zM4.201 7c.339 0 .627.117.864.352.238.234.357.524.357.869 0 .351-.119.652-.357.903-.237.25-.525.376-.864.376-.338 0-.623-.125-.854-.376A1.286 1.286 0 0 1 3 8.221 1.185 1.185 0 0 1 4.201 7z\" fill-rule=\"evenodd\"></path></svg>"
+
+/***/ },
+/* 333 */
+/***/ function(module, exports) {
+
+ module.exports = "<!-- 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/. --><svg viewBox=\"0 0 16 16\" xmlns=\"http://www.w3.org/2000/svg\"><g fill-rule=\"evenodd\"><path d=\"M3.233 11.25l-.417 1H1.712C.763 12.25 0 11.574 0 10.747V6.503C0 5.675.755 5 1.712 5h4.127l-.417 1H1.597C1.257 6 1 6.225 1 6.503v4.244c0 .277.267.503.597.503h1.636zM7.405 11.27L7 12.306c.865.01 2.212-.024 2.315-.04.112-.016.112-.016.185-.035.075-.02.156-.046.251-.082.152-.056.349-.138.592-.244.415-.182.962-.435 1.612-.744l.138-.066a179.35 179.35 0 0 0 2.255-1.094c1.191-.546 1.191-2.074-.025-2.632l-.737-.34a3547.554 3547.554 0 0 0-3.854-1.78c-.029.11-.065.222-.11.336l-.232.596c.894.408 4.56 2.107 4.56 2.107.458.21.458.596 0 .806L9.197 11.27H7.405zM4.462 14.692l5-12a.5.5 0 1 0-.924-.384l-5 12a.5.5 0 1 0 .924.384z\"></path></g></svg>"
+
+/***/ },
+/* 334 */
+/***/ function(module, exports) {
+
+ module.exports = "<!-- 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/. --><svg viewBox=\"0 0 16 16\" xmlns=\"http://www.w3.org/2000/svg\"><path fill-rule=\"evenodd\" d=\"M8.5 8.793L5.854 6.146l-.04-.035L7.5 4.426c.2-.2.3-.4.3-.6 0-.2-.1-.4-.2-.6l-1-1c-.4-.3-.9-.3-1.2 0l-4.1 4.1c-.2.2-.3.4-.3.6 0 .2.1.4.2.6l1 1c.3.3.9.3 1.2 0l1.71-1.71.036.04L7.793 9.5l-3.647 3.646c-.195.196-.195.512 0 .708.196.195.512.195.708 0L8.5 10.207l3.646 3.647c.196.195.512.195.708 0 .195-.196.195-.512 0-.708L9.207 9.5l2.565-2.565L13.3 8.5c.1.1 2.3 1.1 2.7.7.4-.4-.3-2.7-.5-2.9l-1.1-1.1c.1-.1.2-.4.2-.6 0-.2-.1-.4-.2-.6l-.4-.4c-.3-.3-.8-.3-1.1 0l-1.5-1.4c-.2-.2-.3-.2-.5-.2s-.3.1-.5.2L9.2 3.4c-.2.1-.2.2-.2.4s.1.4.2.5l1.874 1.92L8.5 8.792z\"></path></svg>"
+
+/***/ },
+/* 335 */
+/***/ function(module, exports) {
+
+ module.exports = "<!-- 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/. --><svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 16 16\" fill=\"#D92215\"><path d=\"M8 14.5c-3.6 0-6.5-2.9-6.5-6.5S4.4 1.5 8 1.5s6.5 2.9 6.5 6.5-2.9 6.5-6.5 6.5zm0-12C5 2.5 2.5 5 2.5 8S5 13.5 8 13.5 13.5 11 13.5 8 11 2.5 8 2.5z\"></path><circle cx=\"5\" cy=\"6\" r=\"1\" transform=\"translate(1 1)\"></circle><circle cx=\"9\" cy=\"6\" r=\"1\" transform=\"translate(1 1)\"></circle><path d=\"M5.5 11c-.1 0-.2 0-.3-.1-.2-.1-.3-.4-.1-.7C6 9 7 8.5 8.1 8.5c1.7.1 2.8 1.7 2.8 1.8.2.2.1.5-.1.7-.2.1-.6 0-.7-.2 0 0-.9-1.3-2-1.3-.7 0-1.4.4-2.1 1.3-.2.2-.4.2-.5.2z\"></path></svg>"
+
+/***/ },
+/* 336 */
+/***/ function(module, exports, __webpack_require__) {
+
+ var React = __webpack_require__(2);
+ var dom = React.DOM;
+ var PropTypes = React.PropTypes;
+
+ var Svg = __webpack_require__(310);
+
+ __webpack_require__(337);
+
+ function CloseButton(_ref) {
+ var handleClick = _ref.handleClick;
+ var buttonClass = _ref.buttonClass;
+
+ return dom.div({
+ className: buttonClass ? "close-btn-" + buttonClass : "close-btn",
+ onClick: handleClick
+ }, Svg("close"));
+ }
+
+ CloseButton.propTypes = {
+ handleClick: PropTypes.func.isRequired
+ };
+
+ module.exports = CloseButton;
+
+/***/ },
+/* 337 */
+/***/ function(module, exports) {
+
+ // removed by extract-text-webpack-plugin
+
+/***/ },
+/* 338 */,
+/* 339 */
+/***/ function(module, exports, __webpack_require__) {
+
+ var React = __webpack_require__(2);
+ var dom = React.DOM;
+ var PropTypes = React.PropTypes;
+
+ var ImPropTypes = __webpack_require__(235);
+
+ var _require = __webpack_require__(3);
+
+ var bindActionCreators = _require.bindActionCreators;
+
+ var _require2 = __webpack_require__(19);
+
+ var connect = _require2.connect;
+
+ var _require3 = __webpack_require__(261);
+
+ var cmdString = _require3.cmdString;
+
+ var SourcesTree = React.createFactory(__webpack_require__(340));
+ var actions = __webpack_require__(262);
+
+ var _require4 = __webpack_require__(259);
+
+ var getSelectedSource = _require4.getSelectedSource;
+ var getSources = _require4.getSources;
+
+
+ __webpack_require__(398);
+
+ var Sources = React.createClass({
+ propTypes: {
+ sources: ImPropTypes.map.isRequired,
+ selectSource: PropTypes.func.isRequired
+ },
+
+ displayName: "Sources",
+
+ render() {
+ var _props = this.props;
+ var sources = _props.sources;
+ var selectSource = _props.selectSource;
+
+
+ return dom.div({ className: "sources-panel" }, dom.div({ className: "sources-header" }, L10N.getStr("sources.header"), dom.span({ className: "sources-header-info" }, L10N.getFormatStr("sources.search", cmdString() + "+P"))), SourcesTree({ sources, selectSource }));
+ }
+ });
+
+ module.exports = connect(state => ({ selectedSource: getSelectedSource(state),
+ sources: getSources(state) }), dispatch => bindActionCreators(actions, dispatch))(Sources);
+
+/***/ },
+/* 340 */
+/***/ function(module, exports, __webpack_require__) {
+
+ var React = __webpack_require__(2);
+ var dom = React.DOM;
+ var PropTypes = React.PropTypes;
+
+ var classnames = __webpack_require__(211);
+ var ImPropTypes = __webpack_require__(235);
+
+ var _require = __webpack_require__(229);
+
+ var Set = _require.Set;
+
+ var _require2 = __webpack_require__(341);
+
+ var nodeHasChildren = _require2.nodeHasChildren;
+ var createParentMap = _require2.createParentMap;
+ var addToTree = _require2.addToTree;
+ var collapseTree = _require2.collapseTree;
+ var createTree = _require2.createTree;
+
+ var ManagedTree = React.createFactory(__webpack_require__(394));
+ var Svg = __webpack_require__(310);
+
+ var _require3 = __webpack_require__(244);
+
+ var throttle = _require3.throttle;
+
+
+ var SourcesTree = React.createClass({
+ propTypes: {
+ sources: ImPropTypes.map.isRequired,
+ selectSource: PropTypes.func.isRequired
+ },
+
+ displayName: "SourcesTree",
+
+ getInitialState() {
+ return createTree(this.props.sources);
+ },
+
+ queueUpdate: throttle(function () {
+ if (!this.isMounted()) {
+ return;
+ }
+
+ this.forceUpdate();
+ }, 50),
+
+ shouldComponentUpdate() {
+ this.queueUpdate();
+ return false;
+ },
+
+ componentWillReceiveProps(nextProps) {
+ if (nextProps.sources === this.props.sources) {
+ return;
+ }
+
+ if (nextProps.sources.size === 0) {
+ this.setState(createTree(nextProps.sources));
+ return;
+ }
+
+ var next = Set(nextProps.sources.valueSeq());
+ var prev = Set(this.props.sources.valueSeq());
+ var newSet = next.subtract(prev);
+
+ var uncollapsedTree = this.state.uncollapsedTree;
+ for (var source of newSet) {
+ addToTree(uncollapsedTree, source);
+ }
+
+ // TODO: recreating the tree every time messes with the expanded
+ // state of ManagedTree, because it depends on item instances
+ // being the same. The result is that if a source is added at a
+ // later time, all expanded state is lost.
+ var sourceTree = newSet.size > 0 ? collapseTree(uncollapsedTree) : this.state.sourceTree;
+
+ this.setState({ uncollapsedTree,
+ sourceTree,
+ parentMap: createParentMap(sourceTree) });
+ },
+
+ focusItem(item) {
+ this.setState({ focusedItem: item });
+ },
+
+ selectItem(item) {
+ if (!nodeHasChildren(item)) {
+ this.props.selectSource(item.contents.get("id"));
+ }
+ },
+
+ getIcon(item, depth) {
+ if (depth === 0) {
+ return Svg("domain");
+ }
+
+ if (!nodeHasChildren(item)) {
+ return Svg("file");
+ }
+
+ return Svg("folder");
+ },
+
+ renderItem(item, depth, focused, _, expanded, _ref) {
+ var setExpanded = _ref.setExpanded;
+
+ var arrow = Svg("arrow", {
+ className: classnames({ expanded: expanded,
+ hidden: !nodeHasChildren(item) }),
+ onClick: e => {
+ e.stopPropagation();
+ setExpanded(item, !expanded);
+ }
+ });
+
+ var icon = this.getIcon(item, depth);
+
+ return dom.div({
+ className: classnames("node", { focused }),
+ style: { paddingLeft: depth * 15 + "px" },
+ key: item.path,
+ onClick: () => this.selectItem(item),
+ onDoubleClick: e => setExpanded(item, !expanded)
+ }, dom.div(null, arrow, icon, item.name));
+ },
+
+ render: function () {
+ var _state = this.state;
+ var focusedItem = _state.focusedItem;
+ var sourceTree = _state.sourceTree;
+ var parentMap = _state.parentMap;
+
+ const isEmpty = sourceTree.contents.length === 0;
+
+ var tree = ManagedTree({
+ key: isEmpty ? "empty" : "full",
+ getParent: item => {
+ return parentMap.get(item);
+ },
+ getChildren: item => {
+ if (nodeHasChildren(item)) {
+ return item.contents;
+ }
+ return [];
+ },
+ getRoots: () => sourceTree.contents,
+ getKey: (item, i) => item.path,
+ itemHeight: 30,
+ autoExpandDepth: 1,
+ onFocus: this.focusItem,
+ renderItem: this.renderItem
+ });
+
+ return dom.div({
+ className: "sources-list",
+ onKeyDown: e => {
+ if (e.keyCode === 13 && focusedItem) {
+ this.selectItem(focusedItem);
+ }
+ }
+ }, tree);
+ }
+ });
+
+ module.exports = SourcesTree;
+
+/***/ },
+/* 341 */
+/***/ function(module, exports, __webpack_require__) {
+
+
+
+ /**
+ * Utils for Sources Tree Component
+ * @module utils/sources-tree
+ */
+
+ var _require = __webpack_require__(293);
+
+ var parse = _require.parse;
+
+ var _require2 = __webpack_require__(246);
+
+ var assert = _require2.assert;
+
+ var _require3 = __webpack_require__(277);
+
+ var isPretty = _require3.isPretty;
+
+ var merge = __webpack_require__(342);
+
+ var IGNORED_URLS = ["debugger eval code", "XStringBundle"];
+
+ /**
+ * Temporary Source type to be used only within this module
+ * TODO: Replace with real Source type definition when refactoring types
+ * @memberof utils/sources-tree
+ * @static
+ */
+
+
+ /**
+ * TODO: createNode is exported so this type could be useful to other modules
+ * @memberof utils/sources-tree
+ * @static
+ */
+
+
+ /**
+ * @memberof utils/sources-tree
+ * @static
+ */
+ function nodeHasChildren(item) {
+ return Array.isArray(item.contents);
+ }
+
+ /**
+ * @memberof utils/sources-tree
+ * @static
+ */
+ function createNode(name, path, contents) {
+ return {
+ name,
+ path,
+ contents: contents || null
+ };
+ }
+
+ /**
+ * @memberof utils/sources-tree
+ * @static
+ */
+ function createParentMap(tree) {
+ var map = new WeakMap();
+
+ function _traverse(subtree) {
+ if (nodeHasChildren(subtree)) {
+ for (var child of subtree.contents) {
+ map.set(child, subtree);
+ _traverse(child);
+ }
+ }
+ }
+
+ // Don't link each top-level path to the "root" node because the
+ // user never sees the root
+ tree.contents.forEach(_traverse);
+ return map;
+ }
+
+ /**
+ * @memberof utils/sources-tree
+ * @static
+ */
+ function getURL(source) {
+ var url = source.get("url");
+ var def = { path: "", group: "" };
+ if (!url) {
+ return def;
+ }
+
+ var _parse = parse(url);
+
+ var pathname = _parse.pathname;
+ var protocol = _parse.protocol;
+ var host = _parse.host;
+ var path = _parse.path;
+
+
+ switch (protocol) {
+ case "javascript:":
+ // Ignore `javascript:` URLs for now
+ return def;
+
+ case "about:":
+ // An about page is a special case
+ return merge(def, {
+ path: "/",
+ group: url
+ });
+
+ case null:
+ if (pathname && pathname.startsWith("/")) {
+ // If it's just a URL like "/foo/bar.js", resolve it to the file
+ // protocol
+ return merge(def, {
+ path: path,
+ group: "file://"
+ });
+ } else if (host === null) {
+ // We don't know what group to put this under, and it's a script
+ // with a weird URL. Just group them all under an anonymous group.
+ return merge(def, {
+ path: url,
+ group: "(no domain)"
+ });
+ }
+ break;
+
+ case "http:":
+ case "https:":
+ return merge(def, {
+ path: pathname,
+ group: host
+ });
+ }
+
+ return merge(def, {
+ path: path,
+ group: protocol ? protocol + "//" : ""
+ });
+ }
+
+ /**
+ * @memberof utils/sources-tree
+ * @static
+ */
+ function addToTree(tree, source) {
+ var url = getURL(source);
+
+ if (IGNORED_URLS.indexOf(url) != -1 || !source.get("url") || isPretty(source.toJS())) {
+ return;
+ }
+
+ url.path = decodeURIComponent(url.path);
+
+ var parts = url.path.split("/").filter(p => p !== "");
+ var isDir = parts.length === 0 || parts[parts.length - 1].indexOf(".") === -1;
+ parts.unshift(url.group);
+
+ var path = "";
+ var subtree = tree;
+
+ for (var i = 0; i < parts.length; i++) {
+ var part = parts[i];
+ var isLastPart = i === parts.length - 1;
+
+ // Currently we assume that we are descending into a node with
+ // children. This will fail if a path has a directory named the
+ // same as another file, like `foo/bar.js/file.js`.
+ //
+ // TODO: Be smarter about this, which we'll probably do when we
+ // are smarter about folders and collapsing empty ones.
+ assert(nodeHasChildren(subtree), `${ subtree.name } should have children`);
+ var children = subtree.contents;
+
+ var index = determineFileSortOrder(children, part, isLastPart);
+
+ if (index >= 0 && children[index].name === part) {
+ // A node with the same name already exists, simply traverse
+ // into it.
+ subtree = children[index];
+ } else {
+ // No node with this name exists, so insert a new one in the
+ // place that is alphabetically sorted.
+ var node = createNode(part, path + "/" + part, []);
+ var where = index === -1 ? children.length : index;
+ children.splice(where, 0, node);
+ subtree = children[where];
+ }
+
+ // Keep track of the children so we can tag each node with them.
+ path = path + "/" + part;
+ }
+
+ // Overwrite the contents of the final node to store the source
+ // there.
+ if (isDir) {
+ subtree.contents.unshift(createNode("(index)", source.get("url"), source));
+ } else {
+ subtree.contents = source;
+ }
+ }
+
+ /**
+ * Look at the nodes in the source tree, and determine the index of where to
+ * insert a new node. The ordering is index -> folder -> file.
+ * @memberof utils/sources-tree
+ * @static
+ */
+ function determineFileSortOrder(nodes, pathPart, isLastPart) {
+ var partIsDir = !isLastPart || pathPart.indexOf(".") === -1;
+
+ return nodes.findIndex(node => {
+ var nodeIsDir = nodeHasChildren(node);
+
+ // The index will always be the first thing, so this pathPart will be
+ // after it.
+ if (node.name === "(index)") {
+ return false;
+ }
+
+ // If both the pathPart and node are the same type, then compare them
+ // alphabetically.
+ if (partIsDir === nodeIsDir) {
+ return node.name.localeCompare(pathPart) >= 0;
+ }
+
+ // If the pathPart and node differ, then stop here if the pathPart is a
+ // directory. Keep on searching if the part is a file, as it needs to be
+ // placed after the directories.
+ return partIsDir;
+ });
+ }
+
+ /**
+ * Take an existing source tree, and return a new one with collapsed nodes.
+ * @memberof utils/sources-tree
+ * @static
+ */
+ function collapseTree(node) {
+ var depth = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 0;
+
+ // Node is a folder.
+ if (nodeHasChildren(node)) {
+ // Node is not a root/domain node, and only contains 1 item.
+ if (depth > 1 && node.contents.length === 1) {
+ var next = node.contents[0];
+ // Do not collapse if the next node is a leaf node.
+ if (nodeHasChildren(next)) {
+ return collapseTree(createNode(`${ node.name }/${ next.name }`, next.path, next.contents), depth + 1);
+ }
+ }
+ // Map the contents.
+ return createNode(node.name, node.path, node.contents.map(next => collapseTree(next, depth + 1)));
+ }
+ // Node is a leaf, not a folder, do not modify it.
+ return node;
+ }
+
+ /**
+ * @memberof utils/sources-tree
+ * @static
+ */
+ function createTree(sources) {
+ var uncollapsedTree = createNode("root", "", []);
+ for (var source of sources.valueSeq()) {
+ addToTree(uncollapsedTree, source);
+ }
+ var sourceTree = collapseTree(uncollapsedTree);
+
+ return { uncollapsedTree,
+ sourceTree,
+ parentMap: createParentMap(sourceTree),
+ focusedItem: null };
+ }
+
+ module.exports = {
+ createNode,
+ nodeHasChildren,
+ createParentMap,
+ addToTree,
+ collapseTree,
+ createTree
+ };
+
+/***/ },
+/* 342 */
+/***/ function(module, exports, __webpack_require__) {
+
+ var baseMerge = __webpack_require__(343),
+ createAssigner = __webpack_require__(384);
+
+ /**
+ * This method is like `_.assign` except that it recursively merges own and
+ * inherited enumerable string keyed properties of source objects into the
+ * destination object. Source properties that resolve to `undefined` are
+ * skipped if a destination value exists. Array and plain object properties
+ * are merged recursively. Other objects and value types are overridden by
+ * assignment. Source objects are applied from left to right. Subsequent
+ * sources overwrite property assignments of previous sources.
+ *
+ * **Note:** This method mutates `object`.
+ *
+ * @static
+ * @memberOf _
+ * @since 0.5.0
+ * @category Object
+ * @param {Object} object The destination object.
+ * @param {...Object} [sources] The source objects.
+ * @returns {Object} Returns `object`.
+ * @example
+ *
+ * var object = {
+ * 'a': [{ 'b': 2 }, { 'd': 4 }]
+ * };
+ *
+ * var other = {
+ * 'a': [{ 'c': 3 }, { 'e': 5 }]
+ * };
+ *
+ * _.merge(object, other);
+ * // => { 'a': [{ 'b': 2, 'c': 3 }, { 'd': 4, 'e': 5 }] }
+ */
+ var merge = createAssigner(function(object, source, srcIndex) {
+ baseMerge(object, source, srcIndex);
+ });
+
+ module.exports = merge;
+
+
+/***/ },
+/* 343 */
+/***/ function(module, exports, __webpack_require__) {
+
+ var Stack = __webpack_require__(344),
+ assignMergeValue = __webpack_require__(350),
+ baseFor = __webpack_require__(353),
+ baseMergeDeep = __webpack_require__(355),
+ isObject = __webpack_require__(106),
+ keysIn = __webpack_require__(378);
+
+ /**
+ * The base implementation of `_.merge` without support for multiple sources.
+ *
+ * @private
+ * @param {Object} object The destination object.
+ * @param {Object} source The source object.
+ * @param {number} srcIndex The index of `source`.
+ * @param {Function} [customizer] The function to customize merged values.
+ * @param {Object} [stack] Tracks traversed source values and their merged
+ * counterparts.
+ */
+ function baseMerge(object, source, srcIndex, customizer, stack) {
+ if (object === source) {
+ return;
+ }
+ baseFor(source, function(srcValue, key) {
+ if (isObject(srcValue)) {
+ stack || (stack = new Stack);
+ baseMergeDeep(object, source, key, srcIndex, baseMerge, customizer, stack);
+ }
+ else {
+ var newValue = customizer
+ ? customizer(object[key], srcValue, (key + ''), object, source, stack)
+ : undefined;
+
+ if (newValue === undefined) {
+ newValue = srcValue;
+ }
+ assignMergeValue(object, key, newValue);
+ }
+ }, keysIn);
+ }
+
+ module.exports = baseMerge;
+
+
+/***/ },
+/* 344 */
+/***/ function(module, exports, __webpack_require__) {
+
+ var ListCache = __webpack_require__(117),
+ stackClear = __webpack_require__(345),
+ stackDelete = __webpack_require__(346),
+ stackGet = __webpack_require__(347),
+ stackHas = __webpack_require__(348),
+ stackSet = __webpack_require__(349);
+
+ /**
+ * Creates a stack cache object to store key-value pairs.
+ *
+ * @private
+ * @constructor
+ * @param {Array} [entries] The key-value pairs to cache.
+ */
+ function Stack(entries) {
+ var data = this.__data__ = new ListCache(entries);
+ this.size = data.size;
+ }
+
+ // Add methods to `Stack`.
+ Stack.prototype.clear = stackClear;
+ Stack.prototype['delete'] = stackDelete;
+ Stack.prototype.get = stackGet;
+ Stack.prototype.has = stackHas;
+ Stack.prototype.set = stackSet;
+
+ module.exports = Stack;
+
+
+/***/ },
+/* 345 */
+/***/ function(module, exports, __webpack_require__) {
+
+ var ListCache = __webpack_require__(117);
+
+ /**
+ * Removes all key-value entries from the stack.
+ *
+ * @private
+ * @name clear
+ * @memberOf Stack
+ */
+ function stackClear() {
+ this.__data__ = new ListCache;
+ this.size = 0;
+ }
+
+ module.exports = stackClear;
+
+
+/***/ },
+/* 346 */
+/***/ function(module, exports) {
+
+ /**
+ * Removes `key` and its value from the stack.
+ *
+ * @private
+ * @name delete
+ * @memberOf Stack
+ * @param {string} key The key of the value to remove.
+ * @returns {boolean} Returns `true` if the entry was removed, else `false`.
+ */
+ function stackDelete(key) {
+ var data = this.__data__,
+ result = data['delete'](key);
+
+ this.size = data.size;
+ return result;
+ }
+
+ module.exports = stackDelete;
+
+
+/***/ },
+/* 347 */
+/***/ function(module, exports) {
+
+ /**
+ * Gets the stack value for `key`.
+ *
+ * @private
+ * @name get
+ * @memberOf Stack
+ * @param {string} key The key of the value to get.
+ * @returns {*} Returns the entry value.
+ */
+ function stackGet(key) {
+ return this.__data__.get(key);
+ }
+
+ module.exports = stackGet;
+
+
+/***/ },
+/* 348 */
+/***/ function(module, exports) {
+
+ /**
+ * Checks if a stack value for `key` exists.
+ *
+ * @private
+ * @name has
+ * @memberOf Stack
+ * @param {string} key The key of the entry to check.
+ * @returns {boolean} Returns `true` if an entry for `key` exists, else `false`.
+ */
+ function stackHas(key) {
+ return this.__data__.has(key);
+ }
+
+ module.exports = stackHas;
+
+
+/***/ },
+/* 349 */
+/***/ function(module, exports, __webpack_require__) {
+
+ var ListCache = __webpack_require__(117),
+ Map = __webpack_require__(125),
+ MapCache = __webpack_require__(98);
+
+ /** Used as the size to enable large array optimizations. */
+ var LARGE_ARRAY_SIZE = 200;
+
+ /**
+ * Sets the stack `key` to `value`.
+ *
+ * @private
+ * @name set
+ * @memberOf Stack
+ * @param {string} key The key of the value to set.
+ * @param {*} value The value to set.
+ * @returns {Object} Returns the stack cache instance.
+ */
+ function stackSet(key, value) {
+ var data = this.__data__;
+ if (data instanceof ListCache) {
+ var pairs = data.__data__;
+ if (!Map || (pairs.length < LARGE_ARRAY_SIZE - 1)) {
+ pairs.push([key, value]);
+ this.size = ++data.size;
+ return this;
+ }
+ data = this.__data__ = new MapCache(pairs);
+ }
+ data.set(key, value);
+ this.size = data.size;
+ return this;
+ }
+
+ module.exports = stackSet;
+
+
+/***/ },
+/* 350 */
+/***/ function(module, exports, __webpack_require__) {
+
+ var baseAssignValue = __webpack_require__(351),
+ eq = __webpack_require__(121);
+
+ /**
+ * This function is like `assignValue` except that it doesn't assign
+ * `undefined` values.
+ *
+ * @private
+ * @param {Object} object The object to modify.
+ * @param {string} key The key of the property to assign.
+ * @param {*} value The value to assign.
+ */
+ function assignMergeValue(object, key, value) {
+ if ((value !== undefined && !eq(object[key], value)) ||
+ (value === undefined && !(key in object))) {
+ baseAssignValue(object, key, value);
+ }
+ }
+
+ module.exports = assignMergeValue;
+
+
+/***/ },
+/* 351 */
+/***/ function(module, exports, __webpack_require__) {
+
+ var defineProperty = __webpack_require__(352);
+
+ /**
+ * The base implementation of `assignValue` and `assignMergeValue` without
+ * value checks.
+ *
+ * @private
+ * @param {Object} object The object to modify.
+ * @param {string} key The key of the property to assign.
+ * @param {*} value The value to assign.
+ */
+ function baseAssignValue(object, key, value) {
+ if (key == '__proto__' && defineProperty) {
+ defineProperty(object, key, {
+ 'configurable': true,
+ 'enumerable': true,
+ 'value': value,
+ 'writable': true
+ });
+ } else {
+ object[key] = value;
+ }
+ }
+
+ module.exports = baseAssignValue;
+
+
+/***/ },
+/* 352 */
+/***/ function(module, exports, __webpack_require__) {
+
+ var getNative = __webpack_require__(103);
+
+ var defineProperty = (function() {
+ try {
+ var func = getNative(Object, 'defineProperty');
+ func({}, '', {});
+ return func;
+ } catch (e) {}
+ }());
+
+ module.exports = defineProperty;
+
+
+/***/ },
+/* 353 */
+/***/ function(module, exports, __webpack_require__) {
+
+ var createBaseFor = __webpack_require__(354);
+
+ /**
+ * The base implementation of `baseForOwn` which iterates over `object`
+ * properties returned by `keysFunc` and invokes `iteratee` for each property.
+ * Iteratee functions may exit iteration early by explicitly returning `false`.
+ *
+ * @private
+ * @param {Object} object The object to iterate over.
+ * @param {Function} iteratee The function invoked per iteration.
+ * @param {Function} keysFunc The function to get the keys of `object`.
+ * @returns {Object} Returns `object`.
+ */
+ var baseFor = createBaseFor();
+
+ module.exports = baseFor;
+
+
+/***/ },
+/* 354 */
+/***/ function(module, exports) {
+
+ /**
+ * Creates a base function for methods like `_.forIn` and `_.forOwn`.
+ *
+ * @private
+ * @param {boolean} [fromRight] Specify iterating from right to left.
+ * @returns {Function} Returns the new base function.
+ */
+ function createBaseFor(fromRight) {
+ return function(object, iteratee, keysFunc) {
+ var index = -1,
+ iterable = Object(object),
+ props = keysFunc(object),
+ length = props.length;
+
+ while (length--) {
+ var key = props[fromRight ? length : ++index];
+ if (iteratee(iterable[key], key, iterable) === false) {
+ break;
+ }
+ }
+ return object;
+ };
+ }
+
+ module.exports = createBaseFor;
+
+
+/***/ },
+/* 355 */
+/***/ function(module, exports, __webpack_require__) {
+
+ var assignMergeValue = __webpack_require__(350),
+ cloneBuffer = __webpack_require__(356),
+ cloneTypedArray = __webpack_require__(357),
+ copyArray = __webpack_require__(360),
+ initCloneObject = __webpack_require__(361),
+ isArguments = __webpack_require__(364),
+ isArray = __webpack_require__(94),
+ isArrayLikeObject = __webpack_require__(366),
+ isBuffer = __webpack_require__(369),
+ isFunction = __webpack_require__(105),
+ isObject = __webpack_require__(106),
+ isPlainObject = __webpack_require__(5),
+ isTypedArray = __webpack_require__(371),
+ toPlainObject = __webpack_require__(375);
+
+ /**
+ * A specialized version of `baseMerge` for arrays and objects which performs
+ * deep merges and tracks traversed objects enabling objects with circular
+ * references to be merged.
+ *
+ * @private
+ * @param {Object} object The destination object.
+ * @param {Object} source The source object.
+ * @param {string} key The key of the value to merge.
+ * @param {number} srcIndex The index of `source`.
+ * @param {Function} mergeFunc The function to merge values.
+ * @param {Function} [customizer] The function to customize assigned values.
+ * @param {Object} [stack] Tracks traversed source values and their merged
+ * counterparts.
+ */
+ function baseMergeDeep(object, source, key, srcIndex, mergeFunc, customizer, stack) {
+ var objValue = object[key],
+ srcValue = source[key],
+ stacked = stack.get(srcValue);
+
+ if (stacked) {
+ assignMergeValue(object, key, stacked);
+ return;
+ }
+ var newValue = customizer
+ ? customizer(objValue, srcValue, (key + ''), object, source, stack)
+ : undefined;
+
+ var isCommon = newValue === undefined;
+
+ if (isCommon) {
+ var isArr = isArray(srcValue),
+ isBuff = !isArr && isBuffer(srcValue),
+ isTyped = !isArr && !isBuff && isTypedArray(srcValue);
+
+ newValue = srcValue;
+ if (isArr || isBuff || isTyped) {
+ if (isArray(objValue)) {
+ newValue = objValue;
+ }
+ else if (isArrayLikeObject(objValue)) {
+ newValue = copyArray(objValue);
+ }
+ else if (isBuff) {
+ isCommon = false;
+ newValue = cloneBuffer(srcValue, true);
+ }
+ else if (isTyped) {
+ isCommon = false;
+ newValue = cloneTypedArray(srcValue, true);
+ }
+ else {
+ newValue = [];
+ }
+ }
+ else if (isPlainObject(srcValue) || isArguments(srcValue)) {
+ newValue = objValue;
+ if (isArguments(objValue)) {
+ newValue = toPlainObject(objValue);
+ }
+ else if (!isObject(objValue) || (srcIndex && isFunction(objValue))) {
+ newValue = initCloneObject(srcValue);
+ }
+ }
+ else {
+ isCommon = false;
+ }
+ }
+ if (isCommon) {
+ // Recursively merge objects and arrays (susceptible to call stack limits).
+ stack.set(srcValue, newValue);
+ mergeFunc(newValue, srcValue, srcIndex, customizer, stack);
+ stack['delete'](srcValue);
+ }
+ assignMergeValue(object, key, newValue);
+ }
+
+ module.exports = baseMergeDeep;
+
+
+/***/ },
+/* 356 */
+/***/ function(module, exports, __webpack_require__) {
+
+ /* WEBPACK VAR INJECTION */(function(module) {var root = __webpack_require__(109);
+
+ /** Detect free variable `exports`. */
+ var freeExports = typeof exports == 'object' && exports && !exports.nodeType && exports;
+
+ /** Detect free variable `module`. */
+ var freeModule = freeExports && typeof module == 'object' && module && !module.nodeType && module;
+
+ /** Detect the popular CommonJS extension `module.exports`. */
+ var moduleExports = freeModule && freeModule.exports === freeExports;
+
+ /** Built-in value references. */
+ var Buffer = moduleExports ? root.Buffer : undefined,
+ allocUnsafe = Buffer ? Buffer.allocUnsafe : undefined;
+
+ /**
+ * Creates a clone of `buffer`.
+ *
+ * @private
+ * @param {Buffer} buffer The buffer to clone.
+ * @param {boolean} [isDeep] Specify a deep clone.
+ * @returns {Buffer} Returns the cloned buffer.
+ */
+ function cloneBuffer(buffer, isDeep) {
+ if (isDeep) {
+ return buffer.slice();
+ }
+ var length = buffer.length,
+ result = allocUnsafe ? allocUnsafe(length) : new buffer.constructor(length);
+
+ buffer.copy(result);
+ return result;
+ }
+
+ module.exports = cloneBuffer;
+
+ /* WEBPACK VAR INJECTION */}.call(exports, __webpack_require__(77)(module)))
+
+/***/ },
+/* 357 */
+/***/ function(module, exports, __webpack_require__) {
+
+ var cloneArrayBuffer = __webpack_require__(358);
+
+ /**
+ * Creates a clone of `typedArray`.
+ *
+ * @private
+ * @param {Object} typedArray The typed array to clone.
+ * @param {boolean} [isDeep] Specify a deep clone.
+ * @returns {Object} Returns the cloned typed array.
+ */
+ function cloneTypedArray(typedArray, isDeep) {
+ var buffer = isDeep ? cloneArrayBuffer(typedArray.buffer) : typedArray.buffer;
+ return new typedArray.constructor(buffer, typedArray.byteOffset, typedArray.length);
+ }
+
+ module.exports = cloneTypedArray;
+
+
+/***/ },
+/* 358 */
+/***/ function(module, exports, __webpack_require__) {
+
+ var Uint8Array = __webpack_require__(359);
+
+ /**
+ * Creates a clone of `arrayBuffer`.
+ *
+ * @private
+ * @param {ArrayBuffer} arrayBuffer The array buffer to clone.
+ * @returns {ArrayBuffer} Returns the cloned array buffer.
+ */
+ function cloneArrayBuffer(arrayBuffer) {
+ var result = new arrayBuffer.constructor(arrayBuffer.byteLength);
+ new Uint8Array(result).set(new Uint8Array(arrayBuffer));
+ return result;
+ }
+
+ module.exports = cloneArrayBuffer;
+
+
+/***/ },
+/* 359 */
+/***/ function(module, exports, __webpack_require__) {
+
+ var root = __webpack_require__(109);
+
+ /** Built-in value references. */
+ var Uint8Array = root.Uint8Array;
+
+ module.exports = Uint8Array;
+
+
+/***/ },
+/* 360 */
+/***/ function(module, exports) {
+
+ /**
+ * Copies the values of `source` to `array`.
+ *
+ * @private
+ * @param {Array} source The array to copy values from.
+ * @param {Array} [array=[]] The array to copy values to.
+ * @returns {Array} Returns `array`.
+ */
+ function copyArray(source, array) {
+ var index = -1,
+ length = source.length;
+
+ array || (array = Array(length));
+ while (++index < length) {
+ array[index] = source[index];
+ }
+ return array;
+ }
+
+ module.exports = copyArray;
+
+
+/***/ },
+/* 361 */
+/***/ function(module, exports, __webpack_require__) {
+
+ var baseCreate = __webpack_require__(362),
+ getPrototype = __webpack_require__(6),
+ isPrototype = __webpack_require__(363);
+
+ /**
+ * Initializes an object clone.
+ *
+ * @private
+ * @param {Object} object The object to clone.
+ * @returns {Object} Returns the initialized clone.
+ */
+ function initCloneObject(object) {
+ return (typeof object.constructor == 'function' && !isPrototype(object))
+ ? baseCreate(getPrototype(object))
+ : {};
+ }
+
+ module.exports = initCloneObject;
+
+
+/***/ },
+/* 362 */
+/***/ function(module, exports, __webpack_require__) {
+
+ var isObject = __webpack_require__(106);
+
+ /** Built-in value references. */
+ var objectCreate = Object.create;
+
+ /**
+ * The base implementation of `_.create` without support for assigning
+ * properties to the created object.
+ *
+ * @private
+ * @param {Object} proto The object to inherit from.
+ * @returns {Object} Returns the new object.
+ */
+ var baseCreate = (function() {
+ function object() {}
+ return function(proto) {
+ if (!isObject(proto)) {
+ return {};
+ }
+ if (objectCreate) {
+ return objectCreate(proto);
+ }
+ object.prototype = proto;
+ var result = new object;
+ object.prototype = undefined;
+ return result;
+ };
+ }());
+
+ module.exports = baseCreate;
+
+
+/***/ },
+/* 363 */
+/***/ function(module, exports) {
+
+ /** Used for built-in method references. */
+ var objectProto = Object.prototype;
+
+ /**
+ * Checks if `value` is likely a prototype object.
+ *
+ * @private
+ * @param {*} value The value to check.
+ * @returns {boolean} Returns `true` if `value` is a prototype, else `false`.
+ */
+ function isPrototype(value) {
+ var Ctor = value && value.constructor,
+ proto = (typeof Ctor == 'function' && Ctor.prototype) || objectProto;
+
+ return value === proto;
+ }
+
+ module.exports = isPrototype;
+
+
+/***/ },
+/* 364 */
+/***/ function(module, exports, __webpack_require__) {
+
+ var baseIsArguments = __webpack_require__(365),
+ isObjectLike = __webpack_require__(8);
+
+ /** Used for built-in method references. */
+ var objectProto = Object.prototype;
+
+ /** Used to check objects for own properties. */
+ var hasOwnProperty = objectProto.hasOwnProperty;
+
+ /** Built-in value references. */
+ var propertyIsEnumerable = objectProto.propertyIsEnumerable;
+
+ /**
+ * Checks if `value` is likely an `arguments` object.
+ *
+ * @static
+ * @memberOf _
+ * @since 0.1.0
+ * @category Lang
+ * @param {*} value The value to check.
+ * @returns {boolean} Returns `true` if `value` is an `arguments` object,
+ * else `false`.
+ * @example
+ *
+ * _.isArguments(function() { return arguments; }());
+ * // => true
+ *
+ * _.isArguments([1, 2, 3]);
+ * // => false
+ */
+ var isArguments = baseIsArguments(function() { return arguments; }()) ? baseIsArguments : function(value) {
+ return isObjectLike(value) && hasOwnProperty.call(value, 'callee') &&
+ !propertyIsEnumerable.call(value, 'callee');
+ };
+
+ module.exports = isArguments;
+
+
+/***/ },
+/* 365 */
+/***/ function(module, exports, __webpack_require__) {
+
+ var isObjectLike = __webpack_require__(8);
+
+ /** `Object#toString` result references. */
+ var argsTag = '[object Arguments]';
+
+ /** Used for built-in method references. */
+ var objectProto = Object.prototype;
+
+ /**
+ * Used to resolve the
+ * [`toStringTag`](http://ecma-international.org/ecma-262/7.0/#sec-object.prototype.tostring)
+ * of values.
+ */
+ var objectToString = objectProto.toString;
+
+ /**
+ * The base implementation of `_.isArguments`.
+ *
+ * @private
+ * @param {*} value The value to check.
+ * @returns {boolean} Returns `true` if `value` is an `arguments` object,
+ */
+ function baseIsArguments(value) {
+ return isObjectLike(value) && objectToString.call(value) == argsTag;
+ }
+
+ module.exports = baseIsArguments;
+
+
+/***/ },
+/* 366 */
+/***/ function(module, exports, __webpack_require__) {
+
+ var isArrayLike = __webpack_require__(367),
+ isObjectLike = __webpack_require__(8);
+
+ /**
+ * This method is like `_.isArrayLike` except that it also checks if `value`
+ * is an object.
+ *
+ * @static
+ * @memberOf _
+ * @since 4.0.0
+ * @category Lang
+ * @param {*} value The value to check.
+ * @returns {boolean} Returns `true` if `value` is an array-like object,
+ * else `false`.
+ * @example
+ *
+ * _.isArrayLikeObject([1, 2, 3]);
+ * // => true
+ *
+ * _.isArrayLikeObject(document.body.children);
+ * // => true
+ *
+ * _.isArrayLikeObject('abc');
+ * // => false
+ *
+ * _.isArrayLikeObject(_.noop);
+ * // => false
+ */
+ function isArrayLikeObject(value) {
+ return isObjectLike(value) && isArrayLike(value);
+ }
+
+ module.exports = isArrayLikeObject;
+
+
+/***/ },
+/* 367 */
+/***/ function(module, exports, __webpack_require__) {
+
+ var isFunction = __webpack_require__(105),
+ isLength = __webpack_require__(368);
+
+ /**
+ * Checks if `value` is array-like. A value is considered array-like if it's
+ * not a function and has a `value.length` that's an integer greater than or
+ * equal to `0` and less than or equal to `Number.MAX_SAFE_INTEGER`.
+ *
+ * @static
+ * @memberOf _
+ * @since 4.0.0
+ * @category Lang
+ * @param {*} value The value to check.
+ * @returns {boolean} Returns `true` if `value` is array-like, else `false`.
+ * @example
+ *
+ * _.isArrayLike([1, 2, 3]);
+ * // => true
+ *
+ * _.isArrayLike(document.body.children);
+ * // => true
+ *
+ * _.isArrayLike('abc');
+ * // => true
+ *
+ * _.isArrayLike(_.noop);
+ * // => false
+ */
+ function isArrayLike(value) {
+ return value != null && isLength(value.length) && !isFunction(value);
+ }
+
+ module.exports = isArrayLike;
+
+
+/***/ },
+/* 368 */
+/***/ function(module, exports) {
+
+ /** Used as references for various `Number` constants. */
+ var MAX_SAFE_INTEGER = 9007199254740991;
+
+ /**
+ * Checks if `value` is a valid array-like length.
+ *
+ * **Note:** This method is loosely based on
+ * [`ToLength`](http://ecma-international.org/ecma-262/7.0/#sec-tolength).
+ *
+ * @static
+ * @memberOf _
+ * @since 4.0.0
+ * @category Lang
+ * @param {*} value The value to check.
+ * @returns {boolean} Returns `true` if `value` is a valid length, else `false`.
+ * @example
+ *
+ * _.isLength(3);
+ * // => true
+ *
+ * _.isLength(Number.MIN_VALUE);
+ * // => false
+ *
+ * _.isLength(Infinity);
+ * // => false
+ *
+ * _.isLength('3');
+ * // => false
+ */
+ function isLength(value) {
+ return typeof value == 'number' &&
+ value > -1 && value % 1 == 0 && value <= MAX_SAFE_INTEGER;
+ }
+
+ module.exports = isLength;
+
+
+/***/ },
+/* 369 */
+/***/ function(module, exports, __webpack_require__) {
+
+ /* WEBPACK VAR INJECTION */(function(module) {var root = __webpack_require__(109),
+ stubFalse = __webpack_require__(370);
+
+ /** Detect free variable `exports`. */
+ var freeExports = typeof exports == 'object' && exports && !exports.nodeType && exports;
+
+ /** Detect free variable `module`. */
+ var freeModule = freeExports && typeof module == 'object' && module && !module.nodeType && module;
+
+ /** Detect the popular CommonJS extension `module.exports`. */
+ var moduleExports = freeModule && freeModule.exports === freeExports;
+
+ /** Built-in value references. */
+ var Buffer = moduleExports ? root.Buffer : undefined;
+
+ /* Built-in method references for those with the same name as other `lodash` methods. */
+ var nativeIsBuffer = Buffer ? Buffer.isBuffer : undefined;
+
+ /**
+ * Checks if `value` is a buffer.
+ *
+ * @static
+ * @memberOf _
+ * @since 4.3.0
+ * @category Lang
+ * @param {*} value The value to check.
+ * @returns {boolean} Returns `true` if `value` is a buffer, else `false`.
+ * @example
+ *
+ * _.isBuffer(new Buffer(2));
+ * // => true
+ *
+ * _.isBuffer(new Uint8Array(2));
+ * // => false
+ */
+ var isBuffer = nativeIsBuffer || stubFalse;
+
+ module.exports = isBuffer;
+
+ /* WEBPACK VAR INJECTION */}.call(exports, __webpack_require__(77)(module)))
+
+/***/ },
+/* 370 */
+/***/ function(module, exports) {
+
+ /**
+ * This method returns `false`.
+ *
+ * @static
+ * @memberOf _
+ * @since 4.13.0
+ * @category Util
+ * @returns {boolean} Returns `false`.
+ * @example
+ *
+ * _.times(2, _.stubFalse);
+ * // => [false, false]
+ */
+ function stubFalse() {
+ return false;
+ }
+
+ module.exports = stubFalse;
+
+
+/***/ },
+/* 371 */
+/***/ function(module, exports, __webpack_require__) {
+
+ var baseIsTypedArray = __webpack_require__(372),
+ baseUnary = __webpack_require__(373),
+ nodeUtil = __webpack_require__(374);
+
+ /* Node.js helper references. */
+ var nodeIsTypedArray = nodeUtil && nodeUtil.isTypedArray;
+
+ /**
+ * Checks if `value` is classified as a typed array.
+ *
+ * @static
+ * @memberOf _
+ * @since 3.0.0
+ * @category Lang
+ * @param {*} value The value to check.
+ * @returns {boolean} Returns `true` if `value` is a typed array, else `false`.
+ * @example
+ *
+ * _.isTypedArray(new Uint8Array);
+ * // => true
+ *
+ * _.isTypedArray([]);
+ * // => false
+ */
+ var isTypedArray = nodeIsTypedArray ? baseUnary(nodeIsTypedArray) : baseIsTypedArray;
+
+ module.exports = isTypedArray;
+
+
+/***/ },
+/* 372 */
+/***/ function(module, exports, __webpack_require__) {
+
+ var isLength = __webpack_require__(368),
+ isObjectLike = __webpack_require__(8);
+
+ /** `Object#toString` result references. */
+ var argsTag = '[object Arguments]',
+ arrayTag = '[object Array]',
+ boolTag = '[object Boolean]',
+ dateTag = '[object Date]',
+ errorTag = '[object Error]',
+ funcTag = '[object Function]',
+ mapTag = '[object Map]',
+ numberTag = '[object Number]',
+ objectTag = '[object Object]',
+ regexpTag = '[object RegExp]',
+ setTag = '[object Set]',
+ stringTag = '[object String]',
+ weakMapTag = '[object WeakMap]';
+
+ var arrayBufferTag = '[object ArrayBuffer]',
+ dataViewTag = '[object DataView]',
+ float32Tag = '[object Float32Array]',
+ float64Tag = '[object Float64Array]',
+ int8Tag = '[object Int8Array]',
+ int16Tag = '[object Int16Array]',
+ int32Tag = '[object Int32Array]',
+ uint8Tag = '[object Uint8Array]',
+ uint8ClampedTag = '[object Uint8ClampedArray]',
+ uint16Tag = '[object Uint16Array]',
+ uint32Tag = '[object Uint32Array]';
+
+ /** Used to identify `toStringTag` values of typed arrays. */
+ var typedArrayTags = {};
+ typedArrayTags[float32Tag] = typedArrayTags[float64Tag] =
+ typedArrayTags[int8Tag] = typedArrayTags[int16Tag] =
+ typedArrayTags[int32Tag] = typedArrayTags[uint8Tag] =
+ typedArrayTags[uint8ClampedTag] = typedArrayTags[uint16Tag] =
+ typedArrayTags[uint32Tag] = true;
+ typedArrayTags[argsTag] = typedArrayTags[arrayTag] =
+ typedArrayTags[arrayBufferTag] = typedArrayTags[boolTag] =
+ typedArrayTags[dataViewTag] = typedArrayTags[dateTag] =
+ typedArrayTags[errorTag] = typedArrayTags[funcTag] =
+ typedArrayTags[mapTag] = typedArrayTags[numberTag] =
+ typedArrayTags[objectTag] = typedArrayTags[regexpTag] =
+ typedArrayTags[setTag] = typedArrayTags[stringTag] =
+ typedArrayTags[weakMapTag] = false;
+
+ /** Used for built-in method references. */
+ var objectProto = Object.prototype;
+
+ /**
+ * Used to resolve the
+ * [`toStringTag`](http://ecma-international.org/ecma-262/7.0/#sec-object.prototype.tostring)
+ * of values.
+ */
+ var objectToString = objectProto.toString;
+
+ /**
+ * The base implementation of `_.isTypedArray` without Node.js optimizations.
+ *
+ * @private
+ * @param {*} value The value to check.
+ * @returns {boolean} Returns `true` if `value` is a typed array, else `false`.
+ */
+ function baseIsTypedArray(value) {
+ return isObjectLike(value) &&
+ isLength(value.length) && !!typedArrayTags[objectToString.call(value)];
+ }
+
+ module.exports = baseIsTypedArray;
+
+
+/***/ },
+/* 373 */
+/***/ function(module, exports) {
+
+ /**
+ * The base implementation of `_.unary` without support for storing metadata.
+ *
+ * @private
+ * @param {Function} func The function to cap arguments for.
+ * @returns {Function} Returns the new capped function.
+ */
+ function baseUnary(func) {
+ return function(value) {
+ return func(value);
+ };
+ }
+
+ module.exports = baseUnary;
+
+
+/***/ },
+/* 374 */
+/***/ function(module, exports, __webpack_require__) {
+
+ /* WEBPACK VAR INJECTION */(function(module) {var freeGlobal = __webpack_require__(110);
+
+ /** Detect free variable `exports`. */
+ var freeExports = typeof exports == 'object' && exports && !exports.nodeType && exports;
+
+ /** Detect free variable `module`. */
+ var freeModule = freeExports && typeof module == 'object' && module && !module.nodeType && module;
+
+ /** Detect the popular CommonJS extension `module.exports`. */
+ var moduleExports = freeModule && freeModule.exports === freeExports;
+
+ /** Detect free variable `process` from Node.js. */
+ var freeProcess = moduleExports && freeGlobal.process;
+
+ /** Used to access faster Node.js helpers. */
+ var nodeUtil = (function() {
+ try {
+ return freeProcess && freeProcess.binding('util');
+ } catch (e) {}
+ }());
+
+ module.exports = nodeUtil;
+
+ /* WEBPACK VAR INJECTION */}.call(exports, __webpack_require__(77)(module)))
+
+/***/ },
+/* 375 */
+/***/ function(module, exports, __webpack_require__) {
+
+ var copyObject = __webpack_require__(376),
+ keysIn = __webpack_require__(378);
+
+ /**
+ * Converts `value` to a plain object flattening inherited enumerable string
+ * keyed properties of `value` to own properties of the plain object.
+ *
+ * @static
+ * @memberOf _
+ * @since 3.0.0
+ * @category Lang
+ * @param {*} value The value to convert.
+ * @returns {Object} Returns the converted plain object.
+ * @example
+ *
+ * function Foo() {
+ * this.b = 2;
+ * }
+ *
+ * Foo.prototype.c = 3;
+ *
+ * _.assign({ 'a': 1 }, new Foo);
+ * // => { 'a': 1, 'b': 2 }
+ *
+ * _.assign({ 'a': 1 }, _.toPlainObject(new Foo));
+ * // => { 'a': 1, 'b': 2, 'c': 3 }
+ */
+ function toPlainObject(value) {
+ return copyObject(value, keysIn(value));
+ }
+
+ module.exports = toPlainObject;
+
+
+/***/ },
+/* 376 */
+/***/ function(module, exports, __webpack_require__) {
+
+ var assignValue = __webpack_require__(377),
+ baseAssignValue = __webpack_require__(351);
+
+ /**
+ * Copies properties of `source` to `object`.
+ *
+ * @private
+ * @param {Object} source The object to copy properties from.
+ * @param {Array} props The property identifiers to copy.
+ * @param {Object} [object={}] The object to copy properties to.
+ * @param {Function} [customizer] The function to customize copied values.
+ * @returns {Object} Returns `object`.
+ */
+ function copyObject(source, props, object, customizer) {
+ var isNew = !object;
+ object || (object = {});
+
+ var index = -1,
+ length = props.length;
+
+ while (++index < length) {
+ var key = props[index];
+
+ var newValue = customizer
+ ? customizer(object[key], source[key], key, object, source)
+ : undefined;
+
+ if (newValue === undefined) {
+ newValue = source[key];
+ }
+ if (isNew) {
+ baseAssignValue(object, key, newValue);
+ } else {
+ assignValue(object, key, newValue);
+ }
+ }
+ return object;
+ }
+
+ module.exports = copyObject;
+
+
+/***/ },
+/* 377 */
+/***/ function(module, exports, __webpack_require__) {
+
+ var baseAssignValue = __webpack_require__(351),
+ eq = __webpack_require__(121);
+
+ /** Used for built-in method references. */
+ var objectProto = Object.prototype;
+
+ /** Used to check objects for own properties. */
+ var hasOwnProperty = objectProto.hasOwnProperty;
+
+ /**
+ * Assigns `value` to `key` of `object` if the existing value is not equivalent
+ * using [`SameValueZero`](http://ecma-international.org/ecma-262/7.0/#sec-samevaluezero)
+ * for equality comparisons.
+ *
+ * @private
+ * @param {Object} object The object to modify.
+ * @param {string} key The key of the property to assign.
+ * @param {*} value The value to assign.
+ */
+ function assignValue(object, key, value) {
+ var objValue = object[key];
+ if (!(hasOwnProperty.call(object, key) && eq(objValue, value)) ||
+ (value === undefined && !(key in object))) {
+ baseAssignValue(object, key, value);
+ }
+ }
+
+ module.exports = assignValue;
+
+
+/***/ },
+/* 378 */
+/***/ function(module, exports, __webpack_require__) {
+
+ var arrayLikeKeys = __webpack_require__(379),
+ baseKeysIn = __webpack_require__(382),
+ isArrayLike = __webpack_require__(367);
+
+ /**
+ * Creates an array of the own and inherited enumerable property names of `object`.
+ *
+ * **Note:** Non-object values are coerced to objects.
+ *
+ * @static
+ * @memberOf _
+ * @since 3.0.0
+ * @category Object
+ * @param {Object} object The object to query.
+ * @returns {Array} Returns the array of property names.
+ * @example
+ *
+ * function Foo() {
+ * this.a = 1;
+ * this.b = 2;
+ * }
+ *
+ * Foo.prototype.c = 3;
+ *
+ * _.keysIn(new Foo);
+ * // => ['a', 'b', 'c'] (iteration order is not guaranteed)
+ */
+ function keysIn(object) {
+ return isArrayLike(object) ? arrayLikeKeys(object, true) : baseKeysIn(object);
+ }
+
+ module.exports = keysIn;
+
+
+/***/ },
+/* 379 */
+/***/ function(module, exports, __webpack_require__) {
+
+ var baseTimes = __webpack_require__(380),
+ isArguments = __webpack_require__(364),
+ isArray = __webpack_require__(94),
+ isBuffer = __webpack_require__(369),
+ isIndex = __webpack_require__(381),
+ isTypedArray = __webpack_require__(371);
+
+ /** Used for built-in method references. */
+ var objectProto = Object.prototype;
+
+ /** Used to check objects for own properties. */
+ var hasOwnProperty = objectProto.hasOwnProperty;
+
+ /**
+ * Creates an array of the enumerable property names of the array-like `value`.
+ *
+ * @private
+ * @param {*} value The value to query.
+ * @param {boolean} inherited Specify returning inherited property names.
+ * @returns {Array} Returns the array of property names.
+ */
+ function arrayLikeKeys(value, inherited) {
+ var isArr = isArray(value),
+ isArg = !isArr && isArguments(value),
+ isBuff = !isArr && !isArg && isBuffer(value),
+ isType = !isArr && !isArg && !isBuff && isTypedArray(value),
+ skipIndexes = isArr || isArg || isBuff || isType,
+ result = skipIndexes ? baseTimes(value.length, String) : [],
+ length = result.length;
+
+ for (var key in value) {
+ if ((inherited || hasOwnProperty.call(value, key)) &&
+ !(skipIndexes && (
+ // Safari 9 has enumerable `arguments.length` in strict mode.
+ key == 'length' ||
+ // Node.js 0.10 has enumerable non-index properties on buffers.
+ (isBuff && (key == 'offset' || key == 'parent')) ||
+ // PhantomJS 2 has enumerable non-index properties on typed arrays.
+ (isType && (key == 'buffer' || key == 'byteLength' || key == 'byteOffset')) ||
+ // Skip index properties.
+ isIndex(key, length)
+ ))) {
+ result.push(key);
+ }
+ }
+ return result;
+ }
+
+ module.exports = arrayLikeKeys;
+
+
+/***/ },
+/* 380 */
+/***/ function(module, exports) {
+
+ /**
+ * The base implementation of `_.times` without support for iteratee shorthands
+ * or max array length checks.
+ *
+ * @private
+ * @param {number} n The number of times to invoke `iteratee`.
+ * @param {Function} iteratee The function invoked per iteration.
+ * @returns {Array} Returns the array of results.
+ */
+ function baseTimes(n, iteratee) {
+ var index = -1,
+ result = Array(n);
+
+ while (++index < n) {
+ result[index] = iteratee(index);
+ }
+ return result;
+ }
+
+ module.exports = baseTimes;
+
+
+/***/ },
+/* 381 */
+/***/ function(module, exports) {
+
+ /** Used as references for various `Number` constants. */
+ var MAX_SAFE_INTEGER = 9007199254740991;
+
+ /** Used to detect unsigned integer values. */
+ var reIsUint = /^(?:0|[1-9]\d*)$/;
+
+ /**
+ * Checks if `value` is a valid array-like index.
+ *
+ * @private
+ * @param {*} value The value to check.
+ * @param {number} [length=MAX_SAFE_INTEGER] The upper bounds of a valid index.
+ * @returns {boolean} Returns `true` if `value` is a valid index, else `false`.
+ */
+ function isIndex(value, length) {
+ length = length == null ? MAX_SAFE_INTEGER : length;
+ return !!length &&
+ (typeof value == 'number' || reIsUint.test(value)) &&
+ (value > -1 && value % 1 == 0 && value < length);
+ }
+
+ module.exports = isIndex;
+
+
+/***/ },
+/* 382 */
+/***/ function(module, exports, __webpack_require__) {
+
+ var isObject = __webpack_require__(106),
+ isPrototype = __webpack_require__(363),
+ nativeKeysIn = __webpack_require__(383);
+
+ /** Used for built-in method references. */
+ var objectProto = Object.prototype;
+
+ /** Used to check objects for own properties. */
+ var hasOwnProperty = objectProto.hasOwnProperty;
+
+ /**
+ * The base implementation of `_.keysIn` which doesn't treat sparse arrays as dense.
+ *
+ * @private
+ * @param {Object} object The object to query.
+ * @returns {Array} Returns the array of property names.
+ */
+ function baseKeysIn(object) {
+ if (!isObject(object)) {
+ return nativeKeysIn(object);
+ }
+ var isProto = isPrototype(object),
+ result = [];
+
+ for (var key in object) {
+ if (!(key == 'constructor' && (isProto || !hasOwnProperty.call(object, key)))) {
+ result.push(key);
+ }
+ }
+ return result;
+ }
+
+ module.exports = baseKeysIn;
+
+
+/***/ },
+/* 383 */
+/***/ function(module, exports) {
+
+ /**
+ * This function is like
+ * [`Object.keys`](http://ecma-international.org/ecma-262/7.0/#sec-object.keys)
+ * except that it includes inherited enumerable properties.
+ *
+ * @private
+ * @param {Object} object The object to query.
+ * @returns {Array} Returns the array of property names.
+ */
+ function nativeKeysIn(object) {
+ var result = [];
+ if (object != null) {
+ for (var key in Object(object)) {
+ result.push(key);
+ }
+ }
+ return result;
+ }
+
+ module.exports = nativeKeysIn;
+
+
+/***/ },
+/* 384 */
+/***/ function(module, exports, __webpack_require__) {
+
+ var baseRest = __webpack_require__(385),
+ isIterateeCall = __webpack_require__(393);
+
+ /**
+ * Creates a function like `_.assign`.
+ *
+ * @private
+ * @param {Function} assigner The function to assign values.
+ * @returns {Function} Returns the new assigner function.
+ */
+ function createAssigner(assigner) {
+ return baseRest(function(object, sources) {
+ var index = -1,
+ length = sources.length,
+ customizer = length > 1 ? sources[length - 1] : undefined,
+ guard = length > 2 ? sources[2] : undefined;
+
+ customizer = (assigner.length > 3 && typeof customizer == 'function')
+ ? (length--, customizer)
+ : undefined;
+
+ if (guard && isIterateeCall(sources[0], sources[1], guard)) {
+ customizer = length < 3 ? undefined : customizer;
+ length = 1;
+ }
+ object = Object(object);
+ while (++index < length) {
+ var source = sources[index];
+ if (source) {
+ assigner(object, source, index, customizer);
+ }
+ }
+ return object;
+ });
+ }
+
+ module.exports = createAssigner;
+
+
+/***/ },
+/* 385 */
+/***/ function(module, exports, __webpack_require__) {
+
+ var identity = __webpack_require__(386),
+ overRest = __webpack_require__(387),
+ setToString = __webpack_require__(389);
+
+ /**
+ * The base implementation of `_.rest` which doesn't validate or coerce arguments.
+ *
+ * @private
+ * @param {Function} func The function to apply a rest parameter to.
+ * @param {number} [start=func.length-1] The start position of the rest parameter.
+ * @returns {Function} Returns the new function.
+ */
+ function baseRest(func, start) {
+ return setToString(overRest(func, start, identity), func + '');
+ }
+
+ module.exports = baseRest;
+
+
+/***/ },
+/* 386 */
+/***/ function(module, exports) {
+
+ /**
+ * This method returns the first argument it receives.
+ *
+ * @static
+ * @since 0.1.0
+ * @memberOf _
+ * @category Util
+ * @param {*} value Any value.
+ * @returns {*} Returns `value`.
+ * @example
+ *
+ * var object = { 'a': 1 };
+ *
+ * console.log(_.identity(object) === object);
+ * // => true
+ */
+ function identity(value) {
+ return value;
+ }
+
+ module.exports = identity;
+
+
+/***/ },
+/* 387 */
+/***/ function(module, exports, __webpack_require__) {
+
+ var apply = __webpack_require__(388);
+
+ /* Built-in method references for those with the same name as other `lodash` methods. */
+ var nativeMax = Math.max;
+
+ /**
+ * A specialized version of `baseRest` which transforms the rest array.
+ *
+ * @private
+ * @param {Function} func The function to apply a rest parameter to.
+ * @param {number} [start=func.length-1] The start position of the rest parameter.
+ * @param {Function} transform The rest array transform.
+ * @returns {Function} Returns the new function.
+ */
+ function overRest(func, start, transform) {
+ start = nativeMax(start === undefined ? (func.length - 1) : start, 0);
+ return function() {
+ var args = arguments,
+ index = -1,
+ length = nativeMax(args.length - start, 0),
+ array = Array(length);
+
+ while (++index < length) {
+ array[index] = args[start + index];
+ }
+ index = -1;
+ var otherArgs = Array(start + 1);
+ while (++index < start) {
+ otherArgs[index] = args[index];
+ }
+ otherArgs[start] = transform(array);
+ return apply(func, this, otherArgs);
+ };
+ }
+
+ module.exports = overRest;
+
+
+/***/ },
+/* 388 */
+/***/ function(module, exports) {
+
+ /**
+ * A faster alternative to `Function#apply`, this function invokes `func`
+ * with the `this` binding of `thisArg` and the arguments of `args`.
+ *
+ * @private
+ * @param {Function} func The function to invoke.
+ * @param {*} thisArg The `this` binding of `func`.
+ * @param {Array} args The arguments to invoke `func` with.
+ * @returns {*} Returns the result of `func`.
+ */
+ function apply(func, thisArg, args) {
+ switch (args.length) {
+ case 0: return func.call(thisArg);
+ case 1: return func.call(thisArg, args[0]);
+ case 2: return func.call(thisArg, args[0], args[1]);
+ case 3: return func.call(thisArg, args[0], args[1], args[2]);
+ }
+ return func.apply(thisArg, args);
+ }
+
+ module.exports = apply;
+
+
+/***/ },
+/* 389 */
+/***/ function(module, exports, __webpack_require__) {
+
+ var baseSetToString = __webpack_require__(390),
+ shortOut = __webpack_require__(392);
+
+ /**
+ * Sets the `toString` method of `func` to return `string`.
+ *
+ * @private
+ * @param {Function} func The function to modify.
+ * @param {Function} string The `toString` result.
+ * @returns {Function} Returns `func`.
+ */
+ var setToString = shortOut(baseSetToString);
+
+ module.exports = setToString;
+
+
+/***/ },
+/* 390 */
+/***/ function(module, exports, __webpack_require__) {
+
+ var constant = __webpack_require__(391),
+ defineProperty = __webpack_require__(352),
+ identity = __webpack_require__(386);
+
+ /**
+ * The base implementation of `setToString` without support for hot loop shorting.
+ *
+ * @private
+ * @param {Function} func The function to modify.
+ * @param {Function} string The `toString` result.
+ * @returns {Function} Returns `func`.
+ */
+ var baseSetToString = !defineProperty ? identity : function(func, string) {
+ return defineProperty(func, 'toString', {
+ 'configurable': true,
+ 'enumerable': false,
+ 'value': constant(string),
+ 'writable': true
+ });
+ };
+
+ module.exports = baseSetToString;
+
+
+/***/ },
+/* 391 */
+/***/ function(module, exports) {
+
+ /**
+ * Creates a function that returns `value`.
+ *
+ * @static
+ * @memberOf _
+ * @since 2.4.0
+ * @category Util
+ * @param {*} value The value to return from the new function.
+ * @returns {Function} Returns the new constant function.
+ * @example
+ *
+ * var objects = _.times(2, _.constant({ 'a': 1 }));
+ *
+ * console.log(objects);
+ * // => [{ 'a': 1 }, { 'a': 1 }]
+ *
+ * console.log(objects[0] === objects[1]);
+ * // => true
+ */
+ function constant(value) {
+ return function() {
+ return value;
+ };
+ }
+
+ module.exports = constant;
+
+
+/***/ },
+/* 392 */
+/***/ function(module, exports) {
+
+ /** Used to detect hot functions by number of calls within a span of milliseconds. */
+ var HOT_COUNT = 500,
+ HOT_SPAN = 16;
+
+ /* Built-in method references for those with the same name as other `lodash` methods. */
+ var nativeNow = Date.now;
+
+ /**
+ * Creates a function that'll short out and invoke `identity` instead
+ * of `func` when it's called `HOT_COUNT` or more times in `HOT_SPAN`
+ * milliseconds.
+ *
+ * @private
+ * @param {Function} func The function to restrict.
+ * @returns {Function} Returns the new shortable function.
+ */
+ function shortOut(func) {
+ var count = 0,
+ lastCalled = 0;
+
+ return function() {
+ var stamp = nativeNow(),
+ remaining = HOT_SPAN - (stamp - lastCalled);
+
+ lastCalled = stamp;
+ if (remaining > 0) {
+ if (++count >= HOT_COUNT) {
+ return arguments[0];
+ }
+ } else {
+ count = 0;
+ }
+ return func.apply(undefined, arguments);
+ };
+ }
+
+ module.exports = shortOut;
+
+
+/***/ },
+/* 393 */
+/***/ function(module, exports, __webpack_require__) {
+
+ var eq = __webpack_require__(121),
+ isArrayLike = __webpack_require__(367),
+ isIndex = __webpack_require__(381),
+ isObject = __webpack_require__(106);
+
+ /**
+ * Checks if the given arguments are from an iteratee call.
+ *
+ * @private
+ * @param {*} value The potential iteratee value argument.
+ * @param {*} index The potential iteratee index or key argument.
+ * @param {*} object The potential iteratee object argument.
+ * @returns {boolean} Returns `true` if the arguments are from an iteratee call,
+ * else `false`.
+ */
+ function isIterateeCall(value, index, object) {
+ if (!isObject(object)) {
+ return false;
+ }
+ var type = typeof index;
+ if (type == 'number'
+ ? (isArrayLike(object) && isIndex(index, object.length))
+ : (type == 'string' && index in object)
+ ) {
+ return eq(object[index], value);
+ }
+ return false;
+ }
+
+ module.exports = isIterateeCall;
+
+
+/***/ },
+/* 394 */
+/***/ function(module, exports, __webpack_require__) {
+
+ var React = __webpack_require__(2);
+ var Tree = React.createFactory(__webpack_require__(395));
+ __webpack_require__(396);
+
+ var ManagedTree = React.createClass({
+ propTypes: Tree.propTypes,
+
+ displayName: "ManagedTree",
+
+ getInitialState() {
+ return {
+ expanded: new Set(),
+ focusedItem: null
+ };
+ },
+
+ setExpanded(item, isExpanded) {
+ var expanded = this.state.expanded;
+ var key = this.props.getKey(item);
+ if (isExpanded) {
+ expanded.add(key);
+ } else {
+ expanded.delete(key);
+ }
+ this.setState({ expanded });
+
+ if (isExpanded && this.props.onExpand) {
+ this.props.onExpand(item);
+ } else if (!expanded && this.props.onCollapse) {
+ this.props.onCollapse(item);
+ }
+ },
+
+ focusItem(item) {
+ if (!this.props.disabledFocus && this.state.focusedItem !== item) {
+ this.setState({ focusedItem: item });
+
+ if (this.props.onFocus) {
+ this.props.onFocus(item);
+ }
+ }
+ },
+
+ render() {
+ var _this = this;
+
+ var _state = this.state;
+ var expanded = _state.expanded;
+ var focusedItem = _state.focusedItem;
+
+
+ var props = Object.assign({}, this.props, {
+ isExpanded: item => expanded.has(this.props.getKey(item)),
+ focused: focusedItem,
+
+ onExpand: item => this.setExpanded(item, true),
+ onCollapse: item => this.setExpanded(item, false),
+ onFocus: this.focusItem,
+
+ renderItem: function () {
+ var _props;
+
+ for (var _len = arguments.length, args = Array(_len), _key = 0; _key < _len; _key++) {
+ args[_key] = arguments[_key];
+ }
+
+ return (_props = _this.props).renderItem.apply(_props, args.concat([{
+ setExpanded: _this.setExpanded
+ }]));
+ }
+ });
+
+ return Tree(props);
+ }
+ });
+
+ module.exports = ManagedTree;
+
+/***/ },
+/* 395 */
+/***/ function(module, exports, __webpack_require__) {
+
+ /* 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/. */
+
+ const { DOM: dom, createClass, createFactory, PropTypes } = __webpack_require__(2);
+ // const { ViewHelpers } =
+ // require("resource://devtools/client/shared/widgets/ViewHelpers.jsm");
+ // let { VirtualScroll } = require("react-virtualized");
+ // VirtualScroll = createFactory(VirtualScroll);
+
+ const AUTO_EXPAND_DEPTH = 0; // depth
+
+ /**
+ * An arrow that displays whether its node is expanded (▼) or collapsed
+ * (▶). When its node has no children, it is hidden.
+ */
+ const ArrowExpander = createFactory(createClass({
+ displayName: "ArrowExpander",
+
+ shouldComponentUpdate(nextProps, nextState) {
+ return this.props.item !== nextProps.item
+ || this.props.visible !== nextProps.visible
+ || this.props.expanded !== nextProps.expanded;
+ },
+
+ render() {
+ const attrs = {
+ className: "arrow theme-twisty",
+ onClick: this.props.expanded
+ ? () => this.props.onCollapse(this.props.item)
+ : e => this.props.onExpand(this.props.item, e.altKey)
+ };
+
+ if (this.props.expanded) {
+ attrs.className += " open";
+ }
+
+ if (!this.props.visible) {
+ attrs.style = Object.assign({}, this.props.style || {}, {
+ visibility: "hidden"
+ });
+ }
+
+ return dom.div(attrs, this.props.children);
+ }
+ }));
+
+ const TreeNode = createFactory(createClass({
+ displayName: "TreeNode",
+
+ componentDidMount() {
+ if (this.props.focused) {
+ this.refs.button.focus();
+ }
+ },
+
+ componentDidUpdate() {
+ if (this.props.focused) {
+ this.refs.button.focus();
+ }
+ },
+
+ shouldComponentUpdate(nextProps) {
+ return this.props.item !== nextProps.item ||
+ this.props.focused !== nextProps.focused ||
+ this.props.expanded !== nextProps.expanded;
+ },
+
+ render() {
+ const arrow = ArrowExpander({
+ item: this.props.item,
+ expanded: this.props.expanded,
+ visible: this.props.hasChildren,
+ onExpand: this.props.onExpand,
+ onCollapse: this.props.onCollapse,
+ });
+
+ let isOddRow = this.props.index % 2;
+ return dom.div(
+ {
+ className: `tree-node div ${isOddRow ? "tree-node-odd" : ""}`,
+ onFocus: this.props.onFocus,
+ onClick: this.props.onFocus,
+ onBlur: this.props.onBlur,
+ style: {
+ padding: 0,
+ margin: 0
+ }
+ },
+
+ this.props.renderItem(this.props.item,
+ this.props.depth,
+ this.props.focused,
+ arrow,
+ this.props.expanded),
+
+ // XXX: OSX won't focus/blur regular elements even if you set tabindex
+ // unless there is an input/button child.
+ dom.button(this._buttonAttrs)
+ );
+ },
+
+ _buttonAttrs: {
+ ref: "button",
+ style: {
+ opacity: 0,
+ width: "0 !important",
+ height: "0 !important",
+ padding: "0 !important",
+ outline: "none",
+ MozAppearance: "none",
+ // XXX: Despite resetting all of the above properties (and margin), the
+ // button still ends up with ~79px width, so we set a large negative
+ // margin to completely hide it.
+ MozMarginStart: "-1000px !important",
+ }
+ }
+ }));
+
+ /**
+ * Create a function that calls the given function `fn` only once per animation
+ * frame.
+ *
+ * @param {Function} fn
+ * @returns {Function}
+ */
+ function oncePerAnimationFrame(fn) {
+ let animationId = null;
+ let argsToPass = null;
+ return function(...args) {
+ argsToPass = args;
+ if (animationId !== null) {
+ return;
+ }
+
+ animationId = requestAnimationFrame(() => {
+ fn.call(this, ...argsToPass);
+ animationId = null;
+ argsToPass = null;
+ });
+ };
+ }
+
+ const NUMBER_OF_OFFSCREEN_ITEMS = 1;
+
+ /**
+ * A generic tree component. See propTypes for the public API.
+ *
+ * @see `devtools/client/memory/components/test/mochitest/head.js` for usage
+ * @see `devtools/client/memory/components/heap.js` for usage
+ */
+ const Tree = module.exports = createClass({
+ displayName: "Tree",
+
+ propTypes: {
+ // Required props
+
+ // A function to get an item's parent, or null if it is a root.
+ getParent: PropTypes.func.isRequired,
+ // A function to get an item's children.
+ getChildren: PropTypes.func.isRequired,
+ // A function which takes an item and ArrowExpander and returns a
+ // component.
+ renderItem: PropTypes.func.isRequired,
+ // A function which returns the roots of the tree (forest).
+ getRoots: PropTypes.func.isRequired,
+ // A function to get a unique key for the given item.
+ getKey: PropTypes.func.isRequired,
+ // A function to get whether an item is expanded or not. If an item is not
+ // expanded, then it must be collapsed.
+ isExpanded: PropTypes.func.isRequired,
+ // The height of an item in the tree including margin and padding, in
+ // pixels.
+ itemHeight: PropTypes.number.isRequired,
+
+ // Optional props
+
+ // The currently focused item, if any such item exists.
+ focused: PropTypes.any,
+ // Handle when a new item is focused.
+ onFocus: PropTypes.func,
+ // The depth to which we should automatically expand new items.
+ autoExpandDepth: PropTypes.number,
+ // Optional event handlers for when items are expanded or collapsed.
+ onExpand: PropTypes.func,
+ onCollapse: PropTypes.func,
+ },
+
+ getDefaultProps() {
+ return {
+ autoExpandDepth: AUTO_EXPAND_DEPTH,
+ };
+ },
+
+ getInitialState() {
+ return {
+ scroll: 0,
+ height: window.innerHeight,
+ seen: new Set(),
+ };
+ },
+
+ componentDidMount() {
+ window.addEventListener("resize", this._updateHeight);
+ this._autoExpand(this.props);
+ this._updateHeight();
+ },
+
+ componentWillUnmount() {
+ window.removeEventListener("resize", this._updateHeight);
+ },
+
+ componentWillReceiveProps(nextProps) {
+ this._autoExpand(nextProps);
+ this._updateHeight();
+ },
+
+ _autoExpand(props) {
+ if (!props.autoExpandDepth) {
+ return;
+ }
+
+ // Automatically expand the first autoExpandDepth levels for new items. Do
+ // not use the usual DFS infrastructure because we don't want to ignore
+ // collapsed nodes.
+ const autoExpand = (item, currentDepth) => {
+ if (currentDepth >= props.autoExpandDepth ||
+ this.state.seen.has(item)) {
+ return;
+ }
+
+ props.onExpand(item);
+ this.state.seen.add(item);
+
+ const children = props.getChildren(item);
+ const length = children.length;
+ for (let i = 0; i < length; i++) {
+ autoExpand(children[i], currentDepth + 1);
+ }
+ };
+
+ const roots = props.getRoots();
+ const length = roots.length;
+ for (let i = 0; i < length; i++) {
+ autoExpand(roots[i], 0);
+ }
+ },
+
+ render() {
+ const traversal = this._dfsFromRoots();
+
+ // Remove `NUMBER_OF_OFFSCREEN_ITEMS` from `begin` and add `2 *
+ // NUMBER_OF_OFFSCREEN_ITEMS` to `end` so that the top and bottom of the
+ // page are filled with the `NUMBER_OF_OFFSCREEN_ITEMS` previous and next
+ // items respectively, rather than whitespace if the item is not in full
+ // view.
+ // const begin = Math.max(((this.state.scroll / this.props.itemHeight) | 0) - NUMBER_OF_OFFSCREEN_ITEMS, 0);
+ // const end = begin + (2 * NUMBER_OF_OFFSCREEN_ITEMS) + ((this.state.height / this.props.itemHeight) | 0);
+ // const toRender = traversal;
+
+ // const nodes = [
+ // dom.div({
+ // key: "top-spacer",
+ // style: {
+ // padding: 0,
+ // margin: 0,
+ // height: begin * this.props.itemHeight + "px"
+ // }
+ // })
+ // ];
+
+ const renderItem = i => {
+ let { item, depth } = traversal[i];
+ return TreeNode({
+ key: this.props.getKey(item, i),
+ index: i,
+ item: item,
+ depth: depth,
+ renderItem: this.props.renderItem,
+ focused: this.props.focused === item,
+ expanded: this.props.isExpanded(item),
+ hasChildren: !!this.props.getChildren(item).length,
+ onExpand: this._onExpand,
+ onCollapse: this._onCollapse,
+ onFocus: () => this._focus(i, item),
+ });
+ };
+
+ // nodes.push(dom.div({
+ // key: "bottom-spacer",
+ // style: {
+ // padding: 0,
+ // margin: 0,
+ // height: (traversal.length - 1 - end) * this.props.itemHeight + "px"
+ // }
+ // }));
+
+ const style = Object.assign({}, this.props.style || {}, {
+ padding: 0,
+ margin: 0
+ });
+
+ return dom.div(
+ {
+ className: "tree",
+ ref: "tree",
+ onKeyDown: this._onKeyDown,
+ onKeyPress: this._preventArrowKeyScrolling,
+ onKeyUp: this._preventArrowKeyScrolling,
+ // onScroll: this._onScroll,
+ style
+ },
+ // VirtualScroll({
+ // width: this.props.width,
+ // height: this.props.height,
+ // rowsCount: traversal.length,
+ // rowHeight: this.props.itemHeight,
+ // rowRenderer: renderItem
+ // })
+ traversal.map((v, i) => renderItem(i))
+ );
+ },
+
+ _preventArrowKeyScrolling(e) {
+ switch (e.key) {
+ case "ArrowUp":
+ case "ArrowDown":
+ case "ArrowLeft":
+ case "ArrowRight":
+ e.preventDefault();
+ e.stopPropagation();
+ if (e.nativeEvent) {
+ if (e.nativeEvent.preventDefault) {
+ e.nativeEvent.preventDefault();
+ }
+ if (e.nativeEvent.stopPropagation) {
+ e.nativeEvent.stopPropagation();
+ }
+ }
+ }
+ },
+
+ /**
+ * Updates the state's height based on clientHeight.
+ */
+ _updateHeight() {
+ this.setState({
+ height: this.refs.tree.clientHeight
+ });
+ },
+
+ /**
+ * Perform a pre-order depth-first search from item.
+ */
+ _dfs(item, maxDepth = Infinity, traversal = [], _depth = 0) {
+ traversal.push({ item, depth: _depth });
+
+ if (!this.props.isExpanded(item)) {
+ return traversal;
+ }
+
+ const nextDepth = _depth + 1;
+
+ if (nextDepth > maxDepth) {
+ return traversal;
+ }
+
+ const children = this.props.getChildren(item);
+ const length = children.length;
+ for (let i = 0; i < length; i++) {
+ this._dfs(children[i], maxDepth, traversal, nextDepth);
+ }
+
+ return traversal;
+ },
+
+ /**
+ * Perform a pre-order depth-first search over the whole forest.
+ */
+ _dfsFromRoots(maxDepth = Infinity) {
+ const traversal = [];
+
+ const roots = this.props.getRoots();
+ const length = roots.length;
+ for (let i = 0; i < length; i++) {
+ this._dfs(roots[i], maxDepth, traversal);
+ }
+
+ return traversal;
+ },
+
+ /**
+ * Expands current row.
+ *
+ * @param {Object} item
+ * @param {Boolean} expandAllChildren
+ */
+ _onExpand: oncePerAnimationFrame(function(item, expandAllChildren) {
+ if (this.props.onExpand) {
+ this.props.onExpand(item);
+
+ if (expandAllChildren) {
+ const children = this._dfs(item);
+ const length = children.length;
+ for (let i = 0; i < length; i++) {
+ this.props.onExpand(children[i].item);
+ }
+ }
+ }
+ }),
+
+ /**
+ * Collapses current row.
+ *
+ * @param {Object} item
+ */
+ _onCollapse: oncePerAnimationFrame(function(item) {
+ if (this.props.onCollapse) {
+ this.props.onCollapse(item);
+ }
+ }),
+
+ /**
+ * Sets the passed in item to be the focused item.
+ *
+ * @param {Number} index
+ * The index of the item in a full DFS traversal (ignoring collapsed
+ * nodes). Ignored if `item` is undefined.
+ *
+ * @param {Object|undefined} item
+ * The item to be focused, or undefined to focus no item.
+ */
+ _focus(index, item) {
+ if (item !== undefined) {
+ const itemStartPosition = index * this.props.itemHeight;
+ const itemEndPosition = (index + 1) * this.props.itemHeight;
+
+ // Note that if the height of the viewport (this.state.height) is less than
+ // `this.props.itemHeight`, we could accidentally try and scroll both up and
+ // down in a futile attempt to make both the item's start and end positions
+ // visible. Instead, give priority to the start of the item by checking its
+ // position first, and then using an "else if", rather than a separate "if",
+ // for the end position.
+ if (this.state.scroll > itemStartPosition) {
+ this.refs.tree.scrollTop = itemStartPosition;
+ } else if ((this.state.scroll + this.state.height) < itemEndPosition) {
+ this.refs.tree.scrollTop = itemEndPosition - this.state.height;
+ }
+ }
+
+ if (this.props.onFocus) {
+ this.props.onFocus(item);
+ }
+ },
+
+ /**
+ * Sets the state to have no focused item.
+ */
+ _onBlur() {
+ this._focus(0, undefined);
+ },
+
+ /**
+ * Fired on a scroll within the tree's container, updates
+ * the stored position of the view port to handle virtual view rendering.
+ *
+ * @param {Event} e
+ */
+ _onScroll: oncePerAnimationFrame(function(e) {
+ this.setState({
+ scroll: Math.max(this.refs.tree.scrollTop, 0),
+ height: this.refs.tree.clientHeight
+ });
+ }),
+
+ /**
+ * Handles key down events in the tree's container.
+ *
+ * @param {Event} e
+ */
+ _onKeyDown(e) {
+ if (this.props.focused == null) {
+ return;
+ }
+
+ // Allow parent nodes to use navigation arrows with modifiers.
+ if (e.altKey || e.ctrlKey || e.shiftKey || e.metaKey) {
+ return;
+ }
+
+ this._preventArrowKeyScrolling(e);
+
+ switch (e.key) {
+ case "ArrowUp":
+ this._focusPrevNode();
+ return;
+
+ case "ArrowDown":
+ this._focusNextNode();
+ return;
+
+ case "ArrowLeft":
+ if (this.props.isExpanded(this.props.focused)
+ && this.props.getChildren(this.props.focused).length) {
+ this._onCollapse(this.props.focused);
+ } else {
+ this._focusParentNode();
+ }
+ return;
+
+ case "ArrowRight":
+ if (!this.props.isExpanded(this.props.focused)) {
+ this._onExpand(this.props.focused);
+ } else {
+ this._focusNextNode();
+ }
+ return;
+ }
+ },
+
+ /**
+ * Sets the previous node relative to the currently focused item, to focused.
+ */
+ _focusPrevNode: oncePerAnimationFrame(function() {
+ // Start a depth first search and keep going until we reach the currently
+ // focused node. Focus the previous node in the DFS, if it exists. If it
+ // doesn't exist, we're at the first node already.
+
+ let prev;
+ let prevIndex;
+
+ const traversal = this._dfsFromRoots();
+ const length = traversal.length;
+ for (let i = 0; i < length; i++) {
+ const item = traversal[i].item;
+ if (item === this.props.focused) {
+ break;
+ }
+ prev = item;
+ prevIndex = i;
+ }
+
+ if (prev === undefined) {
+ return;
+ }
+
+ this._focus(prevIndex, prev);
+ }),
+
+ /**
+ * Handles the down arrow key which will focus either the next child
+ * or sibling row.
+ */
+ _focusNextNode: oncePerAnimationFrame(function() {
+ // Start a depth first search and keep going until we reach the currently
+ // focused node. Focus the next node in the DFS, if it exists. If it
+ // doesn't exist, we're at the last node already.
+
+ const traversal = this._dfsFromRoots();
+ const length = traversal.length;
+ let i = 0;
+
+ while (i < length) {
+ if (traversal[i].item === this.props.focused) {
+ break;
+ }
+ i++;
+ }
+
+ if (i + 1 < traversal.length) {
+ this._focus(i + 1, traversal[i + 1].item);
+ }
+ }),
+
+ /**
+ * Handles the left arrow key, going back up to the current rows'
+ * parent row.
+ */
+ _focusParentNode: oncePerAnimationFrame(function() {
+ const parent = this.props.getParent(this.props.focused);
+ if (!parent) {
+ return;
+ }
+
+ const traversal = this._dfsFromRoots();
+ const length = traversal.length;
+ let parentIndex = 0;
+ for (; parentIndex < length; parentIndex++) {
+ if (traversal[parentIndex].item === parent) {
+ break;
+ }
+ }
+
+ this._focus(parentIndex, parent);
+ }),
+ });
+
+
+/***/ },
+/* 396 */
+/***/ function(module, exports) {
+
+ // removed by extract-text-webpack-plugin
+
+/***/ },
+/* 397 */,
+/* 398 */
+/***/ function(module, exports) {
+
+ // removed by extract-text-webpack-plugin
+
+/***/ },
+/* 399 */,
+/* 400 */
+/***/ function(module, exports, __webpack_require__) {
+
+ var React = __webpack_require__(2);
+ var dom = React.DOM;
+ var PropTypes = React.PropTypes;
+ var createFactory = React.createFactory;
+
+
+ var ReactDOM = __webpack_require__(16);
+ var ImPropTypes = __webpack_require__(235);
+
+ var _require = __webpack_require__(3);
+
+ var bindActionCreators = _require.bindActionCreators;
+
+ var _require2 = __webpack_require__(19);
+
+ var connect = _require2.connect;
+
+ var SourceEditor = __webpack_require__(401);
+ var SourceFooter = createFactory(__webpack_require__(402));
+ var EditorSearchBar = createFactory(__webpack_require__(406));
+
+ var _require3 = __webpack_require__(411);
+
+ var renderConditionalPanel = _require3.renderConditionalPanel;
+
+ var _require4 = __webpack_require__(18);
+
+ var debugGlobal = _require4.debugGlobal;
+
+ var _require5 = __webpack_require__(259);
+
+ var getSourceText = _require5.getSourceText;
+ var getBreakpointsForSource = _require5.getBreakpointsForSource;
+ var getSelectedLocation = _require5.getSelectedLocation;
+ var getSelectedFrame = _require5.getSelectedFrame;
+ var getSelectedSource = _require5.getSelectedSource;
+
+ var _require6 = __webpack_require__(255);
+
+ var makeLocationId = _require6.makeLocationId;
+
+ var actions = __webpack_require__(262);
+ var Breakpoint = React.createFactory(__webpack_require__(412));
+
+ var _require7 = __webpack_require__(279);
+
+ var getDocument = _require7.getDocument;
+ var setDocument = _require7.setDocument;
+
+ var _require8 = __webpack_require__(403);
+
+ var shouldShowFooter = _require8.shouldShowFooter;
+
+ var _require9 = __webpack_require__(89);
+
+ var isFirefox = _require9.isFirefox;
+
+ var _require10 = __webpack_require__(413);
+
+ var showMenu = _require10.showMenu;
+
+ var _require11 = __webpack_require__(89);
+
+ var isEnabled = _require11.isEnabled;
+
+
+ __webpack_require__(414);
+
+ function isTextForSource(sourceText) {
+ return !sourceText.get("loading") && !sourceText.get("error");
+ }
+
+ function breakpointAtLine(breakpoints, line) {
+ return breakpoints.find(b => {
+ return b.location.line === line + 1;
+ });
+ }
+
+ function getTextForLine(codeMirror, line) {
+ return codeMirror.getLine(line - 1).trim();
+ }
+
+ /**
+ * Forces the breakpoint gutter to be the same size as the line
+ * numbers gutter. Editor CSS will absolutely position the gutter
+ * beneath the line numbers. This makes it easy to be flexible with
+ * how we overlay breakpoints.
+ */
+ function resizeBreakpointGutter(editor) {
+ var gutters = editor.display.gutters;
+ var lineNumbers = gutters.querySelector(".CodeMirror-linenumbers");
+ var breakpoints = gutters.querySelector(".breakpoints");
+ breakpoints.style.width = lineNumbers.clientWidth + "px";
+ }
+
+ var Editor = React.createClass({
+ propTypes: {
+ breakpoints: ImPropTypes.map.isRequired,
+ selectedLocation: PropTypes.object,
+ selectedSource: ImPropTypes.map,
+ sourceText: PropTypes.object,
+ addBreakpoint: PropTypes.func,
+ removeBreakpoint: PropTypes.func,
+ setBreakpointCondition: PropTypes.func,
+ selectedFrame: PropTypes.object
+ },
+
+ displayName: "Editor",
+
+ onGutterClick(cm, line, gutter, ev) {
+ // ignore right clicks in the gutter
+ if (ev.which === 3) {
+ return;
+ }
+
+ if (this.isCbPanelOpen()) {
+ return this.closeConditionalPanel(line);
+ }
+
+ this.toggleBreakpoint(line);
+ },
+
+ onGutterContextMenu(event) {
+ event.preventDefault();
+ var line = this.editor.codeMirror.lineAtHeight(event.clientY);
+ var bp = breakpointAtLine(this.props.breakpoints, line);
+ this.showGutterMenu(event, line, bp);
+ },
+
+ showConditionalPanel(line) {
+ if (this.isCbPanelOpen()) {
+ return;
+ }
+
+ var _props = this.props;
+ var sourceId = _props.selectedLocation.sourceId;
+ var setBreakpointCondition = _props.setBreakpointCondition;
+ var breakpoints = _props.breakpoints;
+
+
+ var bp = breakpointAtLine(breakpoints, line);
+ var location = { sourceId, line: line + 1 };
+ var condition = bp ? bp.condition : "";
+
+ var setBreakpoint = value => {
+ setBreakpointCondition(location, {
+ condition: value,
+ getTextForLine: l => getTextForLine(this.editor.codeMirror, l)
+ });
+ };
+
+ var panel = renderConditionalPanel({
+ condition,
+ setBreakpoint,
+ closePanel: this.closeConditionalPanel
+ });
+
+ this.cbPanel = this.editor.codeMirror.addLineWidget(line, panel);
+ this.cbPanel.node.querySelector("input").focus();
+ },
+
+ closeConditionalPanel() {
+ this.cbPanel.clear();
+ this.cbPanel = null;
+ },
+
+ isCbPanelOpen() {
+ return !!this.cbPanel;
+ },
+
+ toggleBreakpoint(line) {
+ var bp = breakpointAtLine(this.props.breakpoints, line);
+
+ if (bp && bp.loading) {
+ return;
+ }
+
+ if (bp) {
+ this.props.removeBreakpoint({
+ sourceId: this.props.selectedLocation.sourceId,
+ line: line + 1
+ });
+ } else {
+ this.props.addBreakpoint({ sourceId: this.props.selectedLocation.sourceId,
+ line: line + 1 },
+ // Pass in a function to get line text because the breakpoint
+ // may slide and it needs to compute the value at the new
+ // line.
+ { getTextForLine: l => getTextForLine(this.editor.codeMirror, l) });
+ }
+ },
+
+ clearDebugLine(selectedFrame) {
+ if (selectedFrame) {
+ var line = selectedFrame.location.line;
+ this.editor.codeMirror.removeLineClass(line - 1, "line", "debug-line");
+ }
+ },
+
+ setDebugLine(selectedFrame, selectedLocation) {
+ if (selectedFrame && selectedLocation && selectedFrame.location.sourceId === selectedLocation.sourceId) {
+ var line = selectedFrame.location.line;
+ this.editor.codeMirror.addLineClass(line - 1, "line", "debug-line");
+ }
+ },
+
+ highlightLine() {
+ if (!this.pendingJumpLine) {
+ return;
+ }
+
+ // If the location has changed and a specific line is requested,
+ // move to that line and flash it.
+ var codeMirror = this.editor.codeMirror;
+
+ // Make sure to clean up after ourselves. Not only does this
+ // cancel any existing animation, but it avoids it from
+ // happening ever again (in case CodeMirror re-applies the
+ // class, etc).
+ if (this.lastJumpLine) {
+ codeMirror.removeLineClass(this.lastJumpLine - 1, "line", "highlight-line");
+ }
+
+ var line = this.pendingJumpLine;
+ this.editor.alignLine(line);
+
+ // We only want to do the flashing animation if it's not a debug
+ // line, which has it's own styling.
+ if (!this.props.selectedFrame || this.props.selectedFrame.location.line !== line) {
+ this.editor.codeMirror.addLineClass(line - 1, "line", "highlight-line");
+ }
+
+ this.lastJumpLine = line;
+ this.pendingJumpLine = null;
+ },
+
+ setText(text) {
+ if (!text || !this.editor) {
+ return;
+ }
+
+ this.editor.setText(text);
+ },
+
+ setMode(sourceText) {
+ var contentType = sourceText.get("contentType");
+
+ if (contentType.includes("javascript")) {
+ this.editor.setMode({ name: "javascript" });
+ } else if (contentType === "text/wasm") {
+ this.editor.setMode({ name: "text" });
+ } else if (sourceText.get("text").match(/^\s*</)) {
+ // Use HTML mode for files in which the first non whitespace
+ // character is `<` regardless of extension.
+ this.editor.setMode({ name: "htmlmixed" });
+ } else {
+ this.editor.setMode({ name: "text" });
+ }
+ },
+
+ showGutterMenu(e, line, bp) {
+ var bpLabel = void 0;
+ var cbLabel = void 0;
+ if (!bp) {
+ bpLabel = L10N.getStr("editor.addBreakpoint");
+ cbLabel = L10N.getStr("editor.addConditionalBreakpoint");
+ } else {
+ bpLabel = L10N.getStr("editor.removeBreakpoint");
+ cbLabel = L10N.getStr("editor.editBreakpoint");
+ }
+
+ var toggleBreakpoint = {
+ id: "node-menu-breakpoint",
+ label: bpLabel,
+ accesskey: "B",
+ disabled: false,
+ click: () => {
+ this.toggleBreakpoint(line);
+ if (this.isCbPanelOpen()) {
+ this.closeConditionalPanel();
+ }
+ }
+ };
+
+ var conditionalBreakpoint = {
+ id: "node-menu-conditional-breakpoint",
+ label: cbLabel,
+ accesskey: "C",
+ disabled: false,
+ click: () => this.showConditionalPanel(line)
+ };
+
+ showMenu(e, [toggleBreakpoint, conditionalBreakpoint]);
+ },
+
+ componentDidMount() {
+ this.cbPanel = null;
+
+ this.editor = new SourceEditor({
+ mode: "javascript",
+ readOnly: true,
+ lineNumbers: true,
+ theme: "mozilla",
+ lineWrapping: false,
+ matchBrackets: true,
+ showAnnotationRuler: true,
+ enableCodeFolding: false,
+ gutters: ["breakpoints"],
+ value: " ",
+ extraKeys: {}
+ });
+
+ // disables the default search shortcuts
+ if (isEnabled("editorSearch")) {
+ this.editor._initShortcuts = () => {};
+ }
+
+ this.editor.appendToLocalElement(ReactDOM.findDOMNode(this).querySelector(".editor-mount"));
+
+ this.editor.codeMirror.on("gutterClick", this.onGutterClick);
+
+ if (!isFirefox()) {
+ this.editor.codeMirror.on("gutterContextMenu", (cm, line, eventName, event) => this.onGutterContextMenu(event));
+ } else {
+ this.editor.codeMirror.getWrapperElement().addEventListener("contextmenu", event => this.onGutterContextMenu(event), false);
+ }
+
+ resizeBreakpointGutter(this.editor.codeMirror);
+ debugGlobal("cm", this.editor.codeMirror);
+
+ if (this.props.sourceText) {
+ this.setText(this.props.sourceText.get("text"));
+ }
+ },
+
+ componentWillUnmount() {
+ this.editor.destroy();
+ this.editor = null;
+ },
+
+ componentWillReceiveProps(nextProps) {
+ // This lifecycle method is responsible for updating the editor
+ // text.
+ var sourceText = nextProps.sourceText;
+ var selectedLocation = nextProps.selectedLocation;
+
+ this.clearDebugLine(this.props.selectedFrame);
+
+ if (!sourceText) {
+ this.showMessage("");
+ } else if (!isTextForSource(sourceText)) {
+ this.showMessage(sourceText.get("error") || "Loading...");
+ } else if (this.props.sourceText !== sourceText) {
+ this.showSourceText(sourceText, selectedLocation);
+ }
+
+ this.setDebugLine(nextProps.selectedFrame, selectedLocation);
+ resizeBreakpointGutter(this.editor.codeMirror);
+ },
+
+ showMessage(msg) {
+ this.editor.replaceDocument(this.editor.createDocument());
+ this.setText(msg);
+ this.editor.setMode({ name: "text" });
+ },
+
+ /**
+ * Handle getting the source document or creating a new
+ * document with the correct mode and text.
+ *
+ */
+ showSourceText(sourceText, selectedLocation) {
+ var doc = getDocument(selectedLocation.sourceId);
+ if (doc) {
+ this.editor.replaceDocument(doc);
+ return doc;
+ }
+
+ doc = this.editor.createDocument();
+ setDocument(selectedLocation.sourceId, doc);
+ this.editor.replaceDocument(doc);
+
+ this.setText(sourceText.get("text"));
+ this.setMode(sourceText);
+ },
+
+ componentDidUpdate(prevProps) {
+ // This is in `componentDidUpdate` so helper functions can expect
+ // `this.props` to be the current props. This lifecycle method is
+ // responsible for updating the editor annotations.
+ var selectedLocation = this.props.selectedLocation;
+
+ // If the location is different and a new line is requested,
+ // update the pending jump line. Note that if jumping to a line in
+ // a source where the text hasn't been loaded yet, we will set the
+ // line here but not jump until rendering the actual source.
+
+ if (prevProps.selectedLocation !== selectedLocation) {
+ if (selectedLocation && selectedLocation.line != undefined) {
+ this.pendingJumpLine = selectedLocation.line;
+ } else {
+ this.pendingJumpLine = null;
+ }
+ }
+
+ // Only update and jump around in real source texts. This will
+ // keep the jump state around until the real source text is
+ // loaded.
+ if (this.props.sourceText && isTextForSource(this.props.sourceText)) {
+ this.highlightLine();
+ }
+ },
+
+ renderBreakpoints() {
+ var _props2 = this.props;
+ var breakpoints = _props2.breakpoints;
+ var sourceText = _props2.sourceText;
+
+ var isLoading = sourceText && sourceText.get("loading");
+
+ if (isLoading) {
+ return;
+ }
+
+ return breakpoints.valueSeq().map(bp => {
+ return Breakpoint({
+ key: makeLocationId(bp.location),
+ breakpoint: bp,
+ editor: this.editor && this.editor.codeMirror
+ });
+ });
+ },
+
+ editorHeight() {
+ var selectedSource = this.props.selectedSource;
+
+
+ if (!selectedSource || !shouldShowFooter(selectedSource.toJS())) {
+ return "100%";
+ }
+
+ return "";
+ },
+
+ render() {
+ var sourceText = this.props.sourceText;
+
+
+ return dom.div({ className: "editor-wrapper devtools-monospace" }, EditorSearchBar({
+ editor: this.editor,
+ sourceText
+ }), dom.div({
+ className: "editor-mount",
+ style: { height: this.editorHeight() }
+ }), this.renderBreakpoints(), SourceFooter({ editor: this.editor }));
+ }
+ });
+
+ module.exports = connect(state => {
+ var selectedLocation = getSelectedLocation(state);
+ var sourceId = selectedLocation && selectedLocation.sourceId;
+ var selectedSource = getSelectedSource(state);
+
+ return {
+ selectedLocation,
+ selectedSource,
+ sourceText: getSourceText(state, sourceId),
+ breakpoints: getBreakpointsForSource(state, sourceId),
+ selectedFrame: getSelectedFrame(state)
+ };
+ }, dispatch => bindActionCreators(actions, dispatch))(Editor);
+
+/***/ },
+/* 401 */
+/***/ function(module, exports) {
+
+ module.exports = devtoolsRequire("devtools/client/sourceeditor/editor");
+
+/***/ },
+/* 402 */
+/***/ function(module, exports, __webpack_require__) {
+
+ var React = __webpack_require__(2);
+ var dom = React.DOM;
+ var PropTypes = React.PropTypes;
+
+ var _require = __webpack_require__(19);
+
+ var connect = _require.connect;
+
+ var _require2 = __webpack_require__(3);
+
+ var bindActionCreators = _require2.bindActionCreators;
+
+ var actions = __webpack_require__(262);
+
+ var _require3 = __webpack_require__(259);
+
+ var getSelectedSource = _require3.getSelectedSource;
+ var getSourceText = _require3.getSourceText;
+ var getPrettySource = _require3.getPrettySource;
+
+ var Svg = __webpack_require__(310);
+ var ImPropTypes = __webpack_require__(235);
+ var classnames = __webpack_require__(211);
+
+ var _require4 = __webpack_require__(277);
+
+ var isPretty = _require4.isPretty;
+
+ var _require5 = __webpack_require__(403);
+
+ var shouldShowFooter = _require5.shouldShowFooter;
+ var shouldShowPrettyPrint = _require5.shouldShowPrettyPrint;
+
+
+ __webpack_require__(404);
+
+ function debugBtn(onClick, type) {
+ var className = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : "active";
+ var tooltip = arguments[3];
+
+ className = `${ type } ${ className }`;
+ return dom.span({ onClick, className, key: type }, Svg(type, { title: tooltip }));
+ }
+
+ var SourceFooter = React.createClass({
+ propTypes: {
+ selectedSource: ImPropTypes.map,
+ togglePrettyPrint: PropTypes.func,
+ sourceText: ImPropTypes.map,
+ selectSource: PropTypes.func,
+ prettySource: ImPropTypes.map,
+ editor: PropTypes.object
+ },
+
+ displayName: "SourceFooter",
+
+ onClickPrettyPrint() {
+ this.props.togglePrettyPrint(this.props.selectedSource.get("id"));
+ },
+
+ prettyPrintButton() {
+ var _props = this.props;
+ var selectedSource = _props.selectedSource;
+ var sourceText = _props.sourceText;
+
+ var sourceLoaded = selectedSource && !sourceText.get("loading");
+
+ if (!shouldShowPrettyPrint(selectedSource.toJS())) {
+ return;
+ }
+
+ return debugBtn(this.onClickPrettyPrint, "prettyPrint", classnames({
+ active: sourceLoaded,
+ pretty: isPretty(selectedSource.toJS())
+ }), "Prettify Source");
+ },
+
+ render() {
+ var selectedSource = this.props.selectedSource;
+
+
+ if (!selectedSource || !shouldShowFooter(selectedSource.toJS())) {
+ return null;
+ }
+
+ return dom.div({ className: "source-footer" }, dom.div({ className: "command-bar" }, this.prettyPrintButton()));
+ }
+ });
+
+ module.exports = connect(state => {
+ var selectedSource = getSelectedSource(state);
+ var selectedId = selectedSource && selectedSource.get("id");
+ return {
+ selectedSource,
+ sourceText: getSourceText(state, selectedId),
+ prettySource: getPrettySource(state, selectedId)
+ };
+ }, dispatch => bindActionCreators(actions, dispatch))(SourceFooter);
+
+/***/ },
+/* 403 */
+/***/ function(module, exports, __webpack_require__) {
+
+ var _require = __webpack_require__(277);
+
+ var isPretty = _require.isPretty;
+
+ var _require2 = __webpack_require__(89);
+
+ var isEnabled = _require2.isEnabled;
+
+ var _require3 = __webpack_require__(264);
+
+ var isOriginalId = _require3.isOriginalId;
+
+
+ function shouldShowPrettyPrint(selectedSource) {
+ if (!isEnabled("prettyPrint")) {
+ return false;
+ }
+
+ var _isPretty = isPretty(selectedSource);
+ var isOriginal = isOriginalId(selectedSource.id);
+ var hasSourceMap = selectedSource.sourceMapURL;
+
+ if (_isPretty || isOriginal || hasSourceMap) {
+ return false;
+ }
+
+ return true;
+ }
+
+ function shouldShowFooter(selectedSource) {
+ return shouldShowPrettyPrint(selectedSource);
+ }
+
+ module.exports = {
+ shouldShowPrettyPrint,
+ shouldShowFooter
+ };
+
+/***/ },
+/* 404 */
+/***/ function(module, exports) {
+
+ // removed by extract-text-webpack-plugin
+
+/***/ },
+/* 405 */,
+/* 406 */
+/***/ function(module, exports, __webpack_require__) {
+
+ var React = __webpack_require__(2);
+ var dom = React.DOM;
+ var PropTypes = React.PropTypes;
+
+ var _require = __webpack_require__(16);
+
+ var findDOMNode = _require.findDOMNode;
+
+ var Svg = __webpack_require__(310);
+
+ var _require2 = __webpack_require__(407);
+
+ var find = _require2.find;
+ var findNext = _require2.findNext;
+ var findPrev = _require2.findPrev;
+
+ var classnames = __webpack_require__(211);
+
+ var _require3 = __webpack_require__(408);
+
+ var debounce = _require3.debounce;
+ var escapeRegExp = _require3.escapeRegExp;
+
+ var CloseButton = __webpack_require__(336);
+
+ var _require4 = __webpack_require__(89);
+
+ var isEnabled = _require4.isEnabled;
+
+
+ __webpack_require__(409);
+
+ function countMatches(query, text) {
+ var re = new RegExp(escapeRegExp(query), "g");
+ var match = text.match(re);
+ return match ? match.length : 0;
+ }
+
+ var EditorSearchBar = React.createClass({
+
+ propTypes: {
+ editor: PropTypes.object,
+ sourceText: PropTypes.object
+ },
+
+ displayName: "EditorSearchBar",
+
+ getInitialState() {
+ return {
+ enabled: false,
+ query: "",
+ count: 0,
+ index: 0
+ };
+ },
+
+ contextTypes: {
+ shortcuts: PropTypes.object
+ },
+
+ componentWillUnmount() {
+ var shortcuts = this.context.shortcuts;
+ if (isEnabled("editorSearch")) {
+ shortcuts.off("CmdOrCtrl+F", this.toggleSearch);
+ shortcuts.off("Escape", this.onEscape);
+ }
+ },
+
+ componentDidMount() {
+ var shortcuts = this.context.shortcuts;
+ if (isEnabled("editorSearch")) {
+ shortcuts.on("CmdOrCtrl+F", this.toggleSearch);
+ shortcuts.on("Escape", this.onEscape);
+ }
+ },
+
+ componentWillReceiveProps() {
+ var shortcuts = this.context.shortcuts;
+ shortcuts.on("CmdOrCtrl+Shift+G", (_, e) => this.traverseResultsPrev(e));
+ shortcuts.on("CmdOrCtrl+G", (_, e) => this.traverseResultsNext(e));
+ },
+
+ componentDidUpdate() {
+ if (this.searchInput()) {
+ this.searchInput().focus();
+ }
+ },
+
+ onEscape(shortcut, e) {
+ this.closeSearch(e);
+ },
+
+ closeSearch(e) {
+ if (this.state.enabled) {
+ this.setState({ enabled: false });
+ e.stopPropagation();
+ e.preventDefault();
+ }
+ },
+
+ toggleSearch(shortcut, e) {
+ e.stopPropagation();
+ e.preventDefault();
+
+ this.setState({ enabled: !this.state.enabled });
+
+ if (this.state.enabled) {
+ var node = this.searchInput();
+ if (node) {
+ node.setSelectionRange(0, node.value.length);
+ }
+ }
+ },
+
+ searchInput() {
+ return findDOMNode(this).querySelector("input");
+ },
+
+ onChange(e) {
+ var query = e.target.value;
+
+ var count = countMatches(query, this.props.sourceText.get("text"));
+ this.setState({ query, count, index: 0 });
+
+ this.search(query);
+ },
+
+ traverseResultsPrev(e) {
+ e.stopPropagation();
+ e.preventDefault();
+
+ var ed = this.props.editor;
+ var ctx = { ed, cm: ed.codeMirror };
+ var _state = this.state;
+ var query = _state.query;
+ var index = _state.index;
+ var count = _state.count;
+
+
+ findPrev(ctx, query);
+ var nextIndex = index == 0 ? count - 1 : index - 1;
+ this.setState({ index: nextIndex });
+ },
+
+ traverseResultsNext(e) {
+ e.stopPropagation();
+ e.preventDefault();
+
+ var ed = this.props.editor;
+ var ctx = { ed, cm: ed.codeMirror };
+ var _state2 = this.state;
+ var query = _state2.query;
+ var index = _state2.index;
+ var count = _state2.count;
+
+
+ findNext(ctx, query);
+ var nextIndex = index == count - 1 ? 0 : index + 1;
+ this.setState({ index: nextIndex });
+ },
+
+ onKeyUp(e) {
+ if (e.key != "Enter") {
+ return;
+ }
+
+ if (e.shiftKey) {
+ this.traverseResultsPrev(e);
+ } else {
+ this.traverseResultsNext(e);
+ }
+ },
+
+ search: debounce(function (query) {
+ var ed = this.props.editor;
+ var ctx = { ed, cm: ed.codeMirror };
+
+ find(ctx, query);
+ }, 100),
+
+ renderSummary() {
+ var _state3 = this.state;
+ var count = _state3.count;
+ var index = _state3.index;
+ var query = _state3.query;
+
+
+ if (query.trim() == "") {
+ return dom.div({});
+ } else if (count == 0) {
+ return dom.div({ className: "summary" }, L10N.getStr("editor.noResults"));
+ }
+
+ return dom.div({ className: "summary" }, L10N.getFormatStr("editor.searchResults", index + 1, count));
+ },
+
+ renderSvg() {
+ var _state4 = this.state;
+ var count = _state4.count;
+ var query = _state4.query;
+
+
+ if (count == 0 && query.trim() != "") {
+ return Svg("sad-face");
+ }
+
+ return Svg("magnifying-glass");
+ },
+
+ render() {
+ if (!this.state.enabled) {
+ return dom.div();
+ }
+
+ var _state5 = this.state;
+ var count = _state5.count;
+ var query = _state5.query;
+
+
+ return dom.div({ className: "search-bar" }, this.renderSvg(), dom.input({
+ className: classnames({
+ empty: count == 0 && query.trim() != ""
+ }),
+ onChange: this.onChange,
+ onKeyUp: this.onKeyUp,
+ placeholder: "Search in file...",
+ value: this.state.query,
+ spellCheck: false
+ }), this.renderSummary(), CloseButton({
+ handleClick: this.closeSearch,
+ buttonClass: "big"
+ }));
+ }
+ });
+
+ module.exports = EditorSearchBar;
+
+/***/ },
+/* 407 */
+/***/ function(module, exports, __webpack_require__) {
+
+ var _require = __webpack_require__(408);
+
+ var escapeRegExp = _require.escapeRegExp;
+ /**
+ * These functions implement search within the debugger. Since
+ * search in the debugger is different from other components,
+ * we can't use search.js CodeMirror addon. This is a slightly
+ * modified version of that addon. Depends on searchcursor.js.
+ * @module utils/source-search
+ */
+
+ /**
+ * @memberof utils/source-search
+ * @static
+ */
+
+ function SearchState() {
+ this.posFrom = this.posTo = this.query = null;
+ this.overlay = null;
+ }
+
+ /**
+ * @memberof utils/source-search
+ * @static
+ */
+ function getSearchState(cm) {
+ return cm.state.search || (cm.state.search = new SearchState());
+ }
+
+ /**
+ * @memberof utils/source-search
+ * @static
+ */
+ function getSearchCursor(cm, query, pos) {
+ // If the query string is all lowercase, do a case insensitive search.
+ return cm.getSearchCursor(query, pos, typeof query == "string" && query == query.toLowerCase());
+ }
+
+ /**
+ * Ignore doing outline matches for less than 3 whitespaces
+ *
+ * @memberof utils/source-search
+ * @static
+ */
+ function ignoreWhiteSpace(str) {
+ return (/^\s{0,2}$/.test(str) ? "(?!\s*.*)" : str
+ );
+ }
+
+ /**
+ * This returns a mode object used by CoeMirror's addOverlay function
+ * to parse and style tokens in the file.
+ * The mode object contains a tokenizer function (token) which takes
+ * a character stream as input, advances it past a token, and returns
+ * a style for that token. For more details see
+ * https://codemirror.net/doc/manual.html#modeapi
+ *
+ * @memberof utils/source-search
+ * @static
+ */
+ function searchOverlay(query) {
+ query = new RegExp(escapeRegExp(ignoreWhiteSpace(query)), "g");
+ return {
+ token: function (stream) {
+ query.lastIndex = stream.pos;
+ var match = query.exec(stream.string);
+ if (match && match.index == stream.pos) {
+ stream.pos += match[0].length || 1;
+ return "selecting";
+ } else if (match) {
+ stream.pos = match.index;
+ } else {
+ stream.skipToEnd();
+ }
+ }
+ };
+ }
+
+ /**
+ * @memberof utils/source-search
+ * @static
+ */
+ function startSearch(cm, state, query) {
+ cm.removeOverlay(state.overlay);
+ state.overlay = searchOverlay(query);
+ cm.addOverlay(state.overlay, { opaque: true });
+ }
+
+ /**
+ * If there's a saved search, selects the next results.
+ * Otherwise, creates a new search and selects the first
+ * result.
+ *
+ * @memberof utils/source-search
+ * @static
+ */
+ function doSearch(ctx, rev, query) {
+ var cm = ctx.cm;
+
+ var state = getSearchState(cm);
+
+ if (state.query) {
+ searchNext(ctx, rev);
+ return;
+ }
+
+ cm.operation(function () {
+ if (state.query) {
+ return;
+ }
+ startSearch(cm, state, query);
+ state.query = query;
+ state.posFrom = state.posTo = { line: 0, ch: 0 };
+ searchNext(ctx, rev);
+ });
+ }
+
+ /**
+ * Selects the next result of a saved search.
+ *
+ * @memberof utils/source-search
+ * @static
+ */
+ function searchNext(ctx, rev) {
+ var cm = ctx.cm;
+ var ed = ctx.ed;
+
+ cm.operation(function () {
+ var state = getSearchState(cm);
+ var cursor = getSearchCursor(cm, state.query, rev ? state.posFrom : state.posTo);
+
+ if (!cursor.find(rev)) {
+ cursor = getSearchCursor(cm, state.query, rev ? { line: cm.lastLine(), ch: null } : { line: cm.firstLine(), ch: 0 });
+ if (!cursor.find(rev)) {
+ return;
+ }
+ }
+
+ ed.alignLine(cursor.from().line, "center");
+ cm.setSelection(cursor.from(), cursor.to());
+ state.posFrom = cursor.from();
+ state.posTo = cursor.to();
+ });
+ }
+
+ /**
+ * Clears the currently saved search.
+ *
+ * @memberof utils/source-search
+ * @static
+ */
+ function clearSearch(cm) {
+ var state = getSearchState(cm);
+
+ if (!state.query) {
+ return;
+ }
+ cm.removeOverlay(state.overlay);
+ state.query = null;
+ }
+
+ /**
+ * Starts a new search.
+ *
+ * @memberof utils/source-search
+ * @static
+ */
+ function find(ctx, query) {
+ clearSearch(ctx.cm);
+ doSearch(ctx, false, query);
+ }
+
+ /**
+ * Finds the next item based on the currently saved search.
+ *
+ * @memberof utils/source-search
+ * @static
+ */
+ function findNext(ctx, query) {
+ doSearch(ctx, false, query);
+ }
+
+ /**
+ * Finds the previous item based on the currently saved search.
+ *
+ * @memberof utils/source-search
+ * @static
+ */
+ function findPrev(ctx, query) {
+ doSearch(ctx, true, query);
+ }
+
+ module.exports = { find, findNext, findPrev };
+
+/***/ },
+/* 408 */
+/***/ function(module, exports, __webpack_require__) {
+
+ var __WEBPACK_AMD_DEFINE_RESULT__;/* WEBPACK VAR INJECTION */(function(global, module) {/**
+ * @license
+ * lodash <https://lodash.com/>
+ * Copyright jQuery Foundation and other contributors <https://jquery.org/>
+ * Released under MIT license <https://lodash.com/license>
+ * Based on Underscore.js 1.8.3 <http://underscorejs.org/LICENSE>
+ * Copyright Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
+ */
+ ;(function() {
+
+ /** Used as a safe reference for `undefined` in pre-ES5 environments. */
+ var undefined;
+
+ /** Used as the semantic version number. */
+ var VERSION = '4.16.4';
+
+ /** Used as the size to enable large array optimizations. */
+ var LARGE_ARRAY_SIZE = 200;
+
+ /** Error message constants. */
+ var CORE_ERROR_TEXT = 'Unsupported core-js use. Try https://github.com/es-shims.',
+ FUNC_ERROR_TEXT = 'Expected a function';
+
+ /** Used to stand-in for `undefined` hash values. */
+ var HASH_UNDEFINED = '__lodash_hash_undefined__';
+
+ /** Used as the maximum memoize cache size. */
+ var MAX_MEMOIZE_SIZE = 500;
+
+ /** Used as the internal argument placeholder. */
+ var PLACEHOLDER = '__lodash_placeholder__';
+
+ /** Used to compose bitmasks for function metadata. */
+ var BIND_FLAG = 1,
+ BIND_KEY_FLAG = 2,
+ CURRY_BOUND_FLAG = 4,
+ CURRY_FLAG = 8,
+ CURRY_RIGHT_FLAG = 16,
+ PARTIAL_FLAG = 32,
+ PARTIAL_RIGHT_FLAG = 64,
+ ARY_FLAG = 128,
+ REARG_FLAG = 256,
+ FLIP_FLAG = 512;
+
+ /** Used to compose bitmasks for comparison styles. */
+ var UNORDERED_COMPARE_FLAG = 1,
+ PARTIAL_COMPARE_FLAG = 2;
+
+ /** Used as default options for `_.truncate`. */
+ var DEFAULT_TRUNC_LENGTH = 30,
+ DEFAULT_TRUNC_OMISSION = '...';
+
+ /** Used to detect hot functions by number of calls within a span of milliseconds. */
+ var HOT_COUNT = 500,
+ HOT_SPAN = 16;
+
+ /** Used to indicate the type of lazy iteratees. */
+ var LAZY_FILTER_FLAG = 1,
+ LAZY_MAP_FLAG = 2,
+ LAZY_WHILE_FLAG = 3;
+
+ /** Used as references for various `Number` constants. */
+ var INFINITY = 1 / 0,
+ MAX_SAFE_INTEGER = 9007199254740991,
+ MAX_INTEGER = 1.7976931348623157e+308,
+ NAN = 0 / 0;
+
+ /** Used as references for the maximum length and index of an array. */
+ var MAX_ARRAY_LENGTH = 4294967295,
+ MAX_ARRAY_INDEX = MAX_ARRAY_LENGTH - 1,
+ HALF_MAX_ARRAY_LENGTH = MAX_ARRAY_LENGTH >>> 1;
+
+ /** Used to associate wrap methods with their bit flags. */
+ var wrapFlags = [
+ ['ary', ARY_FLAG],
+ ['bind', BIND_FLAG],
+ ['bindKey', BIND_KEY_FLAG],
+ ['curry', CURRY_FLAG],
+ ['curryRight', CURRY_RIGHT_FLAG],
+ ['flip', FLIP_FLAG],
+ ['partial', PARTIAL_FLAG],
+ ['partialRight', PARTIAL_RIGHT_FLAG],
+ ['rearg', REARG_FLAG]
+ ];
+
+ /** `Object#toString` result references. */
+ var argsTag = '[object Arguments]',
+ arrayTag = '[object Array]',
+ boolTag = '[object Boolean]',
+ dateTag = '[object Date]',
+ errorTag = '[object Error]',
+ funcTag = '[object Function]',
+ genTag = '[object GeneratorFunction]',
+ mapTag = '[object Map]',
+ numberTag = '[object Number]',
+ objectTag = '[object Object]',
+ promiseTag = '[object Promise]',
+ proxyTag = '[object Proxy]',
+ regexpTag = '[object RegExp]',
+ setTag = '[object Set]',
+ stringTag = '[object String]',
+ symbolTag = '[object Symbol]',
+ weakMapTag = '[object WeakMap]',
+ weakSetTag = '[object WeakSet]';
+
+ var arrayBufferTag = '[object ArrayBuffer]',
+ dataViewTag = '[object DataView]',
+ float32Tag = '[object Float32Array]',
+ float64Tag = '[object Float64Array]',
+ int8Tag = '[object Int8Array]',
+ int16Tag = '[object Int16Array]',
+ int32Tag = '[object Int32Array]',
+ uint8Tag = '[object Uint8Array]',
+ uint8ClampedTag = '[object Uint8ClampedArray]',
+ uint16Tag = '[object Uint16Array]',
+ uint32Tag = '[object Uint32Array]';
+
+ /** Used to match empty string literals in compiled template source. */
+ var reEmptyStringLeading = /\b__p \+= '';/g,
+ reEmptyStringMiddle = /\b(__p \+=) '' \+/g,
+ reEmptyStringTrailing = /(__e\(.*?\)|\b__t\)) \+\n'';/g;
+
+ /** Used to match HTML entities and HTML characters. */
+ var reEscapedHtml = /&(?:amp|lt|gt|quot|#39);/g,
+ reUnescapedHtml = /[&<>"']/g,
+ reHasEscapedHtml = RegExp(reEscapedHtml.source),
+ reHasUnescapedHtml = RegExp(reUnescapedHtml.source);
+
+ /** Used to match template delimiters. */
+ var reEscape = /<%-([\s\S]+?)%>/g,
+ reEvaluate = /<%([\s\S]+?)%>/g,
+ reInterpolate = /<%=([\s\S]+?)%>/g;
+
+ /** Used to match property names within property paths. */
+ var reIsDeepProp = /\.|\[(?:[^[\]]*|(["'])(?:(?!\1)[^\\]|\\.)*?\1)\]/,
+ reIsPlainProp = /^\w*$/,
+ reLeadingDot = /^\./,
+ rePropName = /[^.[\]]+|\[(?:(-?\d+(?:\.\d+)?)|(["'])((?:(?!\2)[^\\]|\\.)*?)\2)\]|(?=(?:\.|\[\])(?:\.|\[\]|$))/g;
+
+ /**
+ * Used to match `RegExp`
+ * [syntax characters](http://ecma-international.org/ecma-262/7.0/#sec-patterns).
+ */
+ var reRegExpChar = /[\\^$.*+?()[\]{}|]/g,
+ reHasRegExpChar = RegExp(reRegExpChar.source);
+
+ /** Used to match leading and trailing whitespace. */
+ var reTrim = /^\s+|\s+$/g,
+ reTrimStart = /^\s+/,
+ reTrimEnd = /\s+$/;
+
+ /** Used to match wrap detail comments. */
+ var reWrapComment = /\{(?:\n\/\* \[wrapped with .+\] \*\/)?\n?/,
+ reWrapDetails = /\{\n\/\* \[wrapped with (.+)\] \*/,
+ reSplitDetails = /,? & /;
+
+ /** Used to match words composed of alphanumeric characters. */
+ var reAsciiWord = /[^\x00-\x2f\x3a-\x40\x5b-\x60\x7b-\x7f]+/g;
+
+ /** Used to match backslashes in property paths. */
+ var reEscapeChar = /\\(\\)?/g;
+
+ /**
+ * Used to match
+ * [ES template delimiters](http://ecma-international.org/ecma-262/7.0/#sec-template-literal-lexical-components).
+ */
+ var reEsTemplate = /\$\{([^\\}]*(?:\\.[^\\}]*)*)\}/g;
+
+ /** Used to match `RegExp` flags from their coerced string values. */
+ var reFlags = /\w*$/;
+
+ /** Used to detect bad signed hexadecimal string values. */
+ var reIsBadHex = /^[-+]0x[0-9a-f]+$/i;
+
+ /** Used to detect binary string values. */
+ var reIsBinary = /^0b[01]+$/i;
+
+ /** Used to detect host constructors (Safari). */
+ var reIsHostCtor = /^\[object .+?Constructor\]$/;
+
+ /** Used to detect octal string values. */
+ var reIsOctal = /^0o[0-7]+$/i;
+
+ /** Used to detect unsigned integer values. */
+ var reIsUint = /^(?:0|[1-9]\d*)$/;
+
+ /** Used to match Latin Unicode letters (excluding mathematical operators). */
+ var reLatin = /[\xc0-\xd6\xd8-\xf6\xf8-\xff\u0100-\u017f]/g;
+
+ /** Used to ensure capturing order of template delimiters. */
+ var reNoMatch = /($^)/;
+
+ /** Used to match unescaped characters in compiled string literals. */
+ var reUnescapedString = /['\n\r\u2028\u2029\\]/g;
+
+ /** Used to compose unicode character classes. */
+ var rsAstralRange = '\\ud800-\\udfff',
+ rsComboMarksRange = '\\u0300-\\u036f\\ufe20-\\ufe23',
+ rsComboSymbolsRange = '\\u20d0-\\u20f0',
+ rsDingbatRange = '\\u2700-\\u27bf',
+ rsLowerRange = 'a-z\\xdf-\\xf6\\xf8-\\xff',
+ rsMathOpRange = '\\xac\\xb1\\xd7\\xf7',
+ rsNonCharRange = '\\x00-\\x2f\\x3a-\\x40\\x5b-\\x60\\x7b-\\xbf',
+ rsPunctuationRange = '\\u2000-\\u206f',
+ rsSpaceRange = ' \\t\\x0b\\f\\xa0\\ufeff\\n\\r\\u2028\\u2029\\u1680\\u180e\\u2000\\u2001\\u2002\\u2003\\u2004\\u2005\\u2006\\u2007\\u2008\\u2009\\u200a\\u202f\\u205f\\u3000',
+ rsUpperRange = 'A-Z\\xc0-\\xd6\\xd8-\\xde',
+ rsVarRange = '\\ufe0e\\ufe0f',
+ rsBreakRange = rsMathOpRange + rsNonCharRange + rsPunctuationRange + rsSpaceRange;
+
+ /** Used to compose unicode capture groups. */
+ var rsApos = "['\u2019]",
+ rsAstral = '[' + rsAstralRange + ']',
+ rsBreak = '[' + rsBreakRange + ']',
+ rsCombo = '[' + rsComboMarksRange + rsComboSymbolsRange + ']',
+ rsDigits = '\\d+',
+ rsDingbat = '[' + rsDingbatRange + ']',
+ rsLower = '[' + rsLowerRange + ']',
+ rsMisc = '[^' + rsAstralRange + rsBreakRange + rsDigits + rsDingbatRange + rsLowerRange + rsUpperRange + ']',
+ rsFitz = '\\ud83c[\\udffb-\\udfff]',
+ rsModifier = '(?:' + rsCombo + '|' + rsFitz + ')',
+ rsNonAstral = '[^' + rsAstralRange + ']',
+ rsRegional = '(?:\\ud83c[\\udde6-\\uddff]){2}',
+ rsSurrPair = '[\\ud800-\\udbff][\\udc00-\\udfff]',
+ rsUpper = '[' + rsUpperRange + ']',
+ rsZWJ = '\\u200d';
+
+ /** Used to compose unicode regexes. */
+ var rsLowerMisc = '(?:' + rsLower + '|' + rsMisc + ')',
+ rsUpperMisc = '(?:' + rsUpper + '|' + rsMisc + ')',
+ rsOptLowerContr = '(?:' + rsApos + '(?:d|ll|m|re|s|t|ve))?',
+ rsOptUpperContr = '(?:' + rsApos + '(?:D|LL|M|RE|S|T|VE))?',
+ reOptMod = rsModifier + '?',
+ rsOptVar = '[' + rsVarRange + ']?',
+ rsOptJoin = '(?:' + rsZWJ + '(?:' + [rsNonAstral, rsRegional, rsSurrPair].join('|') + ')' + rsOptVar + reOptMod + ')*',
+ rsSeq = rsOptVar + reOptMod + rsOptJoin,
+ rsEmoji = '(?:' + [rsDingbat, rsRegional, rsSurrPair].join('|') + ')' + rsSeq,
+ rsSymbol = '(?:' + [rsNonAstral + rsCombo + '?', rsCombo, rsRegional, rsSurrPair, rsAstral].join('|') + ')';
+
+ /** Used to match apostrophes. */
+ var reApos = RegExp(rsApos, 'g');
+
+ /**
+ * Used to match [combining diacritical marks](https://en.wikipedia.org/wiki/Combining_Diacritical_Marks) and
+ * [combining diacritical marks for symbols](https://en.wikipedia.org/wiki/Combining_Diacritical_Marks_for_Symbols).
+ */
+ var reComboMark = RegExp(rsCombo, 'g');
+
+ /** Used to match [string symbols](https://mathiasbynens.be/notes/javascript-unicode). */
+ var reUnicode = RegExp(rsFitz + '(?=' + rsFitz + ')|' + rsSymbol + rsSeq, 'g');
+
+ /** Used to match complex or compound words. */
+ var reUnicodeWord = RegExp([
+ rsUpper + '?' + rsLower + '+' + rsOptLowerContr + '(?=' + [rsBreak, rsUpper, '$'].join('|') + ')',
+ rsUpperMisc + '+' + rsOptUpperContr + '(?=' + [rsBreak, rsUpper + rsLowerMisc, '$'].join('|') + ')',
+ rsUpper + '?' + rsLowerMisc + '+' + rsOptLowerContr,
+ rsUpper + '+' + rsOptUpperContr,
+ rsDigits,
+ rsEmoji
+ ].join('|'), 'g');
+
+ /** Used to detect strings with [zero-width joiners or code points from the astral planes](http://eev.ee/blog/2015/09/12/dark-corners-of-unicode/). */
+ var reHasUnicode = RegExp('[' + rsZWJ + rsAstralRange + rsComboMarksRange + rsComboSymbolsRange + rsVarRange + ']');
+
+ /** Used to detect strings that need a more robust regexp to match words. */
+ var reHasUnicodeWord = /[a-z][A-Z]|[A-Z]{2,}[a-z]|[0-9][a-zA-Z]|[a-zA-Z][0-9]|[^a-zA-Z0-9 ]/;
+
+ /** Used to assign default `context` object properties. */
+ var contextProps = [
+ 'Array', 'Buffer', 'DataView', 'Date', 'Error', 'Float32Array', 'Float64Array',
+ 'Function', 'Int8Array', 'Int16Array', 'Int32Array', 'Map', 'Math', 'Object',
+ 'Promise', 'RegExp', 'Set', 'String', 'Symbol', 'TypeError', 'Uint8Array',
+ 'Uint8ClampedArray', 'Uint16Array', 'Uint32Array', 'WeakMap',
+ '_', 'clearTimeout', 'isFinite', 'parseInt', 'setTimeout'
+ ];
+
+ /** Used to make template sourceURLs easier to identify. */
+ var templateCounter = -1;
+
+ /** Used to identify `toStringTag` values of typed arrays. */
+ var typedArrayTags = {};
+ typedArrayTags[float32Tag] = typedArrayTags[float64Tag] =
+ typedArrayTags[int8Tag] = typedArrayTags[int16Tag] =
+ typedArrayTags[int32Tag] = typedArrayTags[uint8Tag] =
+ typedArrayTags[uint8ClampedTag] = typedArrayTags[uint16Tag] =
+ typedArrayTags[uint32Tag] = true;
+ typedArrayTags[argsTag] = typedArrayTags[arrayTag] =
+ typedArrayTags[arrayBufferTag] = typedArrayTags[boolTag] =
+ typedArrayTags[dataViewTag] = typedArrayTags[dateTag] =
+ typedArrayTags[errorTag] = typedArrayTags[funcTag] =
+ typedArrayTags[mapTag] = typedArrayTags[numberTag] =
+ typedArrayTags[objectTag] = typedArrayTags[regexpTag] =
+ typedArrayTags[setTag] = typedArrayTags[stringTag] =
+ typedArrayTags[weakMapTag] = false;
+
+ /** Used to identify `toStringTag` values supported by `_.clone`. */
+ var cloneableTags = {};
+ cloneableTags[argsTag] = cloneableTags[arrayTag] =
+ cloneableTags[arrayBufferTag] = cloneableTags[dataViewTag] =
+ cloneableTags[boolTag] = cloneableTags[dateTag] =
+ cloneableTags[float32Tag] = cloneableTags[float64Tag] =
+ cloneableTags[int8Tag] = cloneableTags[int16Tag] =
+ cloneableTags[int32Tag] = cloneableTags[mapTag] =
+ cloneableTags[numberTag] = cloneableTags[objectTag] =
+ cloneableTags[regexpTag] = cloneableTags[setTag] =
+ cloneableTags[stringTag] = cloneableTags[symbolTag] =
+ cloneableTags[uint8Tag] = cloneableTags[uint8ClampedTag] =
+ cloneableTags[uint16Tag] = cloneableTags[uint32Tag] = true;
+ cloneableTags[errorTag] = cloneableTags[funcTag] =
+ cloneableTags[weakMapTag] = false;
+
+ /** Used to map Latin Unicode letters to basic Latin letters. */
+ var deburredLetters = {
+ // Latin-1 Supplement block.
+ '\xc0': 'A', '\xc1': 'A', '\xc2': 'A', '\xc3': 'A', '\xc4': 'A', '\xc5': 'A',
+ '\xe0': 'a', '\xe1': 'a', '\xe2': 'a', '\xe3': 'a', '\xe4': 'a', '\xe5': 'a',
+ '\xc7': 'C', '\xe7': 'c',
+ '\xd0': 'D', '\xf0': 'd',
+ '\xc8': 'E', '\xc9': 'E', '\xca': 'E', '\xcb': 'E',
+ '\xe8': 'e', '\xe9': 'e', '\xea': 'e', '\xeb': 'e',
+ '\xcc': 'I', '\xcd': 'I', '\xce': 'I', '\xcf': 'I',
+ '\xec': 'i', '\xed': 'i', '\xee': 'i', '\xef': 'i',
+ '\xd1': 'N', '\xf1': 'n',
+ '\xd2': 'O', '\xd3': 'O', '\xd4': 'O', '\xd5': 'O', '\xd6': 'O', '\xd8': 'O',
+ '\xf2': 'o', '\xf3': 'o', '\xf4': 'o', '\xf5': 'o', '\xf6': 'o', '\xf8': 'o',
+ '\xd9': 'U', '\xda': 'U', '\xdb': 'U', '\xdc': 'U',
+ '\xf9': 'u', '\xfa': 'u', '\xfb': 'u', '\xfc': 'u',
+ '\xdd': 'Y', '\xfd': 'y', '\xff': 'y',
+ '\xc6': 'Ae', '\xe6': 'ae',
+ '\xde': 'Th', '\xfe': 'th',
+ '\xdf': 'ss',
+ // Latin Extended-A block.
+ '\u0100': 'A', '\u0102': 'A', '\u0104': 'A',
+ '\u0101': 'a', '\u0103': 'a', '\u0105': 'a',
+ '\u0106': 'C', '\u0108': 'C', '\u010a': 'C', '\u010c': 'C',
+ '\u0107': 'c', '\u0109': 'c', '\u010b': 'c', '\u010d': 'c',
+ '\u010e': 'D', '\u0110': 'D', '\u010f': 'd', '\u0111': 'd',
+ '\u0112': 'E', '\u0114': 'E', '\u0116': 'E', '\u0118': 'E', '\u011a': 'E',
+ '\u0113': 'e', '\u0115': 'e', '\u0117': 'e', '\u0119': 'e', '\u011b': 'e',
+ '\u011c': 'G', '\u011e': 'G', '\u0120': 'G', '\u0122': 'G',
+ '\u011d': 'g', '\u011f': 'g', '\u0121': 'g', '\u0123': 'g',
+ '\u0124': 'H', '\u0126': 'H', '\u0125': 'h', '\u0127': 'h',
+ '\u0128': 'I', '\u012a': 'I', '\u012c': 'I', '\u012e': 'I', '\u0130': 'I',
+ '\u0129': 'i', '\u012b': 'i', '\u012d': 'i', '\u012f': 'i', '\u0131': 'i',
+ '\u0134': 'J', '\u0135': 'j',
+ '\u0136': 'K', '\u0137': 'k', '\u0138': 'k',
+ '\u0139': 'L', '\u013b': 'L', '\u013d': 'L', '\u013f': 'L', '\u0141': 'L',
+ '\u013a': 'l', '\u013c': 'l', '\u013e': 'l', '\u0140': 'l', '\u0142': 'l',
+ '\u0143': 'N', '\u0145': 'N', '\u0147': 'N', '\u014a': 'N',
+ '\u0144': 'n', '\u0146': 'n', '\u0148': 'n', '\u014b': 'n',
+ '\u014c': 'O', '\u014e': 'O', '\u0150': 'O',
+ '\u014d': 'o', '\u014f': 'o', '\u0151': 'o',
+ '\u0154': 'R', '\u0156': 'R', '\u0158': 'R',
+ '\u0155': 'r', '\u0157': 'r', '\u0159': 'r',
+ '\u015a': 'S', '\u015c': 'S', '\u015e': 'S', '\u0160': 'S',
+ '\u015b': 's', '\u015d': 's', '\u015f': 's', '\u0161': 's',
+ '\u0162': 'T', '\u0164': 'T', '\u0166': 'T',
+ '\u0163': 't', '\u0165': 't', '\u0167': 't',
+ '\u0168': 'U', '\u016a': 'U', '\u016c': 'U', '\u016e': 'U', '\u0170': 'U', '\u0172': 'U',
+ '\u0169': 'u', '\u016b': 'u', '\u016d': 'u', '\u016f': 'u', '\u0171': 'u', '\u0173': 'u',
+ '\u0174': 'W', '\u0175': 'w',
+ '\u0176': 'Y', '\u0177': 'y', '\u0178': 'Y',
+ '\u0179': 'Z', '\u017b': 'Z', '\u017d': 'Z',
+ '\u017a': 'z', '\u017c': 'z', '\u017e': 'z',
+ '\u0132': 'IJ', '\u0133': 'ij',
+ '\u0152': 'Oe', '\u0153': 'oe',
+ '\u0149': "'n", '\u017f': 's'
+ };
+
+ /** Used to map characters to HTML entities. */
+ var htmlEscapes = {
+ '&': '&amp;',
+ '<': '&lt;',
+ '>': '&gt;',
+ '"': '&quot;',
+ "'": '&#39;'
+ };
+
+ /** Used to map HTML entities to characters. */
+ var htmlUnescapes = {
+ '&amp;': '&',
+ '&lt;': '<',
+ '&gt;': '>',
+ '&quot;': '"',
+ '&#39;': "'"
+ };
+
+ /** Used to escape characters for inclusion in compiled string literals. */
+ var stringEscapes = {
+ '\\': '\\',
+ "'": "'",
+ '\n': 'n',
+ '\r': 'r',
+ '\u2028': 'u2028',
+ '\u2029': 'u2029'
+ };
+
+ /** Built-in method references without a dependency on `root`. */
+ var freeParseFloat = parseFloat,
+ freeParseInt = parseInt;
+
+ /** Detect free variable `global` from Node.js. */
+ var freeGlobal = typeof global == 'object' && global && global.Object === Object && global;
+
+ /** Detect free variable `self`. */
+ var freeSelf = typeof self == 'object' && self && self.Object === Object && self;
+
+ /** Used as a reference to the global object. */
+ var root = freeGlobal || freeSelf || Function('return this')();
+
+ /** Detect free variable `exports`. */
+ var freeExports = typeof exports == 'object' && exports && !exports.nodeType && exports;
+
+ /** Detect free variable `module`. */
+ var freeModule = freeExports && typeof module == 'object' && module && !module.nodeType && module;
+
+ /** Detect the popular CommonJS extension `module.exports`. */
+ var moduleExports = freeModule && freeModule.exports === freeExports;
+
+ /** Detect free variable `process` from Node.js. */
+ var freeProcess = moduleExports && freeGlobal.process;
+
+ /** Used to access faster Node.js helpers. */
+ var nodeUtil = (function() {
+ try {
+ return freeProcess && freeProcess.binding('util');
+ } catch (e) {}
+ }());
+
+ /* Node.js helper references. */
+ var nodeIsArrayBuffer = nodeUtil && nodeUtil.isArrayBuffer,
+ nodeIsDate = nodeUtil && nodeUtil.isDate,
+ nodeIsMap = nodeUtil && nodeUtil.isMap,
+ nodeIsRegExp = nodeUtil && nodeUtil.isRegExp,
+ nodeIsSet = nodeUtil && nodeUtil.isSet,
+ nodeIsTypedArray = nodeUtil && nodeUtil.isTypedArray;
+
+ /*--------------------------------------------------------------------------*/
+
+ /**
+ * Adds the key-value `pair` to `map`.
+ *
+ * @private
+ * @param {Object} map The map to modify.
+ * @param {Array} pair The key-value pair to add.
+ * @returns {Object} Returns `map`.
+ */
+ function addMapEntry(map, pair) {
+ // Don't return `map.set` because it's not chainable in IE 11.
+ map.set(pair[0], pair[1]);
+ return map;
+ }
+
+ /**
+ * Adds `value` to `set`.
+ *
+ * @private
+ * @param {Object} set The set to modify.
+ * @param {*} value The value to add.
+ * @returns {Object} Returns `set`.
+ */
+ function addSetEntry(set, value) {
+ // Don't return `set.add` because it's not chainable in IE 11.
+ set.add(value);
+ return set;
+ }
+
+ /**
+ * A faster alternative to `Function#apply`, this function invokes `func`
+ * with the `this` binding of `thisArg` and the arguments of `args`.
+ *
+ * @private
+ * @param {Function} func The function to invoke.
+ * @param {*} thisArg The `this` binding of `func`.
+ * @param {Array} args The arguments to invoke `func` with.
+ * @returns {*} Returns the result of `func`.
+ */
+ function apply(func, thisArg, args) {
+ switch (args.length) {
+ case 0: return func.call(thisArg);
+ case 1: return func.call(thisArg, args[0]);
+ case 2: return func.call(thisArg, args[0], args[1]);
+ case 3: return func.call(thisArg, args[0], args[1], args[2]);
+ }
+ return func.apply(thisArg, args);
+ }
+
+ /**
+ * A specialized version of `baseAggregator` for arrays.
+ *
+ * @private
+ * @param {Array} [array] The array to iterate over.
+ * @param {Function} setter The function to set `accumulator` values.
+ * @param {Function} iteratee The iteratee to transform keys.
+ * @param {Object} accumulator The initial aggregated object.
+ * @returns {Function} Returns `accumulator`.
+ */
+ function arrayAggregator(array, setter, iteratee, accumulator) {
+ var index = -1,
+ length = array ? array.length : 0;
+
+ while (++index < length) {
+ var value = array[index];
+ setter(accumulator, value, iteratee(value), array);
+ }
+ return accumulator;
+ }
+
+ /**
+ * A specialized version of `_.forEach` for arrays without support for
+ * iteratee shorthands.
+ *
+ * @private
+ * @param {Array} [array] The array to iterate over.
+ * @param {Function} iteratee The function invoked per iteration.
+ * @returns {Array} Returns `array`.
+ */
+ function arrayEach(array, iteratee) {
+ var index = -1,
+ length = array ? array.length : 0;
+
+ while (++index < length) {
+ if (iteratee(array[index], index, array) === false) {
+ break;
+ }
+ }
+ return array;
+ }
+
+ /**
+ * A specialized version of `_.forEachRight` for arrays without support for
+ * iteratee shorthands.
+ *
+ * @private
+ * @param {Array} [array] The array to iterate over.
+ * @param {Function} iteratee The function invoked per iteration.
+ * @returns {Array} Returns `array`.
+ */
+ function arrayEachRight(array, iteratee) {
+ var length = array ? array.length : 0;
+
+ while (length--) {
+ if (iteratee(array[length], length, array) === false) {
+ break;
+ }
+ }
+ return array;
+ }
+
+ /**
+ * A specialized version of `_.every` for arrays without support for
+ * iteratee shorthands.
+ *
+ * @private
+ * @param {Array} [array] The array to iterate over.
+ * @param {Function} predicate The function invoked per iteration.
+ * @returns {boolean} Returns `true` if all elements pass the predicate check,
+ * else `false`.
+ */
+ function arrayEvery(array, predicate) {
+ var index = -1,
+ length = array ? array.length : 0;
+
+ while (++index < length) {
+ if (!predicate(array[index], index, array)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * A specialized version of `_.filter` for arrays without support for
+ * iteratee shorthands.
+ *
+ * @private
+ * @param {Array} [array] The array to iterate over.
+ * @param {Function} predicate The function invoked per iteration.
+ * @returns {Array} Returns the new filtered array.
+ */
+ function arrayFilter(array, predicate) {
+ var index = -1,
+ length = array ? array.length : 0,
+ resIndex = 0,
+ result = [];
+
+ while (++index < length) {
+ var value = array[index];
+ if (predicate(value, index, array)) {
+ result[resIndex++] = value;
+ }
+ }
+ return result;
+ }
+
+ /**
+ * A specialized version of `_.includes` for arrays without support for
+ * specifying an index to search from.
+ *
+ * @private
+ * @param {Array} [array] The array to inspect.
+ * @param {*} target The value to search for.
+ * @returns {boolean} Returns `true` if `target` is found, else `false`.
+ */
+ function arrayIncludes(array, value) {
+ var length = array ? array.length : 0;
+ return !!length && baseIndexOf(array, value, 0) > -1;
+ }
+
+ /**
+ * This function is like `arrayIncludes` except that it accepts a comparator.
+ *
+ * @private
+ * @param {Array} [array] The array to inspect.
+ * @param {*} target The value to search for.
+ * @param {Function} comparator The comparator invoked per element.
+ * @returns {boolean} Returns `true` if `target` is found, else `false`.
+ */
+ function arrayIncludesWith(array, value, comparator) {
+ var index = -1,
+ length = array ? array.length : 0;
+
+ while (++index < length) {
+ if (comparator(value, array[index])) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * A specialized version of `_.map` for arrays without support for iteratee
+ * shorthands.
+ *
+ * @private
+ * @param {Array} [array] The array to iterate over.
+ * @param {Function} iteratee The function invoked per iteration.
+ * @returns {Array} Returns the new mapped array.
+ */
+ function arrayMap(array, iteratee) {
+ var index = -1,
+ length = array ? array.length : 0,
+ result = Array(length);
+
+ while (++index < length) {
+ result[index] = iteratee(array[index], index, array);
+ }
+ return result;
+ }
+
+ /**
+ * Appends the elements of `values` to `array`.
+ *
+ * @private
+ * @param {Array} array The array to modify.
+ * @param {Array} values The values to append.
+ * @returns {Array} Returns `array`.
+ */
+ function arrayPush(array, values) {
+ var index = -1,
+ length = values.length,
+ offset = array.length;
+
+ while (++index < length) {
+ array[offset + index] = values[index];
+ }
+ return array;
+ }
+
+ /**
+ * A specialized version of `_.reduce` for arrays without support for
+ * iteratee shorthands.
+ *
+ * @private
+ * @param {Array} [array] The array to iterate over.
+ * @param {Function} iteratee The function invoked per iteration.
+ * @param {*} [accumulator] The initial value.
+ * @param {boolean} [initAccum] Specify using the first element of `array` as
+ * the initial value.
+ * @returns {*} Returns the accumulated value.
+ */
+ function arrayReduce(array, iteratee, accumulator, initAccum) {
+ var index = -1,
+ length = array ? array.length : 0;
+
+ if (initAccum && length) {
+ accumulator = array[++index];
+ }
+ while (++index < length) {
+ accumulator = iteratee(accumulator, array[index], index, array);
+ }
+ return accumulator;
+ }
+
+ /**
+ * A specialized version of `_.reduceRight` for arrays without support for
+ * iteratee shorthands.
+ *
+ * @private
+ * @param {Array} [array] The array to iterate over.
+ * @param {Function} iteratee The function invoked per iteration.
+ * @param {*} [accumulator] The initial value.
+ * @param {boolean} [initAccum] Specify using the last element of `array` as
+ * the initial value.
+ * @returns {*} Returns the accumulated value.
+ */
+ function arrayReduceRight(array, iteratee, accumulator, initAccum) {
+ var length = array ? array.length : 0;
+ if (initAccum && length) {
+ accumulator = array[--length];
+ }
+ while (length--) {
+ accumulator = iteratee(accumulator, array[length], length, array);
+ }
+ return accumulator;
+ }
+
+ /**
+ * A specialized version of `_.some` for arrays without support for iteratee
+ * shorthands.
+ *
+ * @private
+ * @param {Array} [array] The array to iterate over.
+ * @param {Function} predicate The function invoked per iteration.
+ * @returns {boolean} Returns `true` if any element passes the predicate check,
+ * else `false`.
+ */
+ function arraySome(array, predicate) {
+ var index = -1,
+ length = array ? array.length : 0;
+
+ while (++index < length) {
+ if (predicate(array[index], index, array)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Gets the size of an ASCII `string`.
+ *
+ * @private
+ * @param {string} string The string inspect.
+ * @returns {number} Returns the string size.
+ */
+ var asciiSize = baseProperty('length');
+
+ /**
+ * Converts an ASCII `string` to an array.
+ *
+ * @private
+ * @param {string} string The string to convert.
+ * @returns {Array} Returns the converted array.
+ */
+ function asciiToArray(string) {
+ return string.split('');
+ }
+
+ /**
+ * Splits an ASCII `string` into an array of its words.
+ *
+ * @private
+ * @param {string} The string to inspect.
+ * @returns {Array} Returns the words of `string`.
+ */
+ function asciiWords(string) {
+ return string.match(reAsciiWord) || [];
+ }
+
+ /**
+ * The base implementation of methods like `_.findKey` and `_.findLastKey`,
+ * without support for iteratee shorthands, which iterates over `collection`
+ * using `eachFunc`.
+ *
+ * @private
+ * @param {Array|Object} collection The collection to inspect.
+ * @param {Function} predicate The function invoked per iteration.
+ * @param {Function} eachFunc The function to iterate over `collection`.
+ * @returns {*} Returns the found element or its key, else `undefined`.
+ */
+ function baseFindKey(collection, predicate, eachFunc) {
+ var result;
+ eachFunc(collection, function(value, key, collection) {
+ if (predicate(value, key, collection)) {
+ result = key;
+ return false;
+ }
+ });
+ return result;
+ }
+
+ /**
+ * The base implementation of `_.findIndex` and `_.findLastIndex` without
+ * support for iteratee shorthands.
+ *
+ * @private
+ * @param {Array} array The array to inspect.
+ * @param {Function} predicate The function invoked per iteration.
+ * @param {number} fromIndex The index to search from.
+ * @param {boolean} [fromRight] Specify iterating from right to left.
+ * @returns {number} Returns the index of the matched value, else `-1`.
+ */
+ function baseFindIndex(array, predicate, fromIndex, fromRight) {
+ var length = array.length,
+ index = fromIndex + (fromRight ? 1 : -1);
+
+ while ((fromRight ? index-- : ++index < length)) {
+ if (predicate(array[index], index, array)) {
+ return index;
+ }
+ }
+ return -1;
+ }
+
+ /**
+ * The base implementation of `_.indexOf` without `fromIndex` bounds checks.
+ *
+ * @private
+ * @param {Array} array The array to inspect.
+ * @param {*} value The value to search for.
+ * @param {number} fromIndex The index to search from.
+ * @returns {number} Returns the index of the matched value, else `-1`.
+ */
+ function baseIndexOf(array, value, fromIndex) {
+ return value === value
+ ? strictIndexOf(array, value, fromIndex)
+ : baseFindIndex(array, baseIsNaN, fromIndex);
+ }
+
+ /**
+ * This function is like `baseIndexOf` except that it accepts a comparator.
+ *
+ * @private
+ * @param {Array} array The array to inspect.
+ * @param {*} value The value to search for.
+ * @param {number} fromIndex The index to search from.
+ * @param {Function} comparator The comparator invoked per element.
+ * @returns {number} Returns the index of the matched value, else `-1`.
+ */
+ function baseIndexOfWith(array, value, fromIndex, comparator) {
+ var index = fromIndex - 1,
+ length = array.length;
+
+ while (++index < length) {
+ if (comparator(array[index], value)) {
+ return index;
+ }
+ }
+ return -1;
+ }
+
+ /**
+ * The base implementation of `_.isNaN` without support for number objects.
+ *
+ * @private
+ * @param {*} value The value to check.
+ * @returns {boolean} Returns `true` if `value` is `NaN`, else `false`.
+ */
+ function baseIsNaN(value) {
+ return value !== value;
+ }
+
+ /**
+ * The base implementation of `_.mean` and `_.meanBy` without support for
+ * iteratee shorthands.
+ *
+ * @private
+ * @param {Array} array The array to iterate over.
+ * @param {Function} iteratee The function invoked per iteration.
+ * @returns {number} Returns the mean.
+ */
+ function baseMean(array, iteratee) {
+ var length = array ? array.length : 0;
+ return length ? (baseSum(array, iteratee) / length) : NAN;
+ }
+
+ /**
+ * The base implementation of `_.property` without support for deep paths.
+ *
+ * @private
+ * @param {string} key The key of the property to get.
+ * @returns {Function} Returns the new accessor function.
+ */
+ function baseProperty(key) {
+ return function(object) {
+ return object == null ? undefined : object[key];
+ };
+ }
+
+ /**
+ * The base implementation of `_.propertyOf` without support for deep paths.
+ *
+ * @private
+ * @param {Object} object The object to query.
+ * @returns {Function} Returns the new accessor function.
+ */
+ function basePropertyOf(object) {
+ return function(key) {
+ return object == null ? undefined : object[key];
+ };
+ }
+
+ /**
+ * The base implementation of `_.reduce` and `_.reduceRight`, without support
+ * for iteratee shorthands, which iterates over `collection` using `eachFunc`.
+ *
+ * @private
+ * @param {Array|Object} collection The collection to iterate over.
+ * @param {Function} iteratee The function invoked per iteration.
+ * @param {*} accumulator The initial value.
+ * @param {boolean} initAccum Specify using the first or last element of
+ * `collection` as the initial value.
+ * @param {Function} eachFunc The function to iterate over `collection`.
+ * @returns {*} Returns the accumulated value.
+ */
+ function baseReduce(collection, iteratee, accumulator, initAccum, eachFunc) {
+ eachFunc(collection, function(value, index, collection) {
+ accumulator = initAccum
+ ? (initAccum = false, value)
+ : iteratee(accumulator, value, index, collection);
+ });
+ return accumulator;
+ }
+
+ /**
+ * The base implementation of `_.sortBy` which uses `comparer` to define the
+ * sort order of `array` and replaces criteria objects with their corresponding
+ * values.
+ *
+ * @private
+ * @param {Array} array The array to sort.
+ * @param {Function} comparer The function to define sort order.
+ * @returns {Array} Returns `array`.
+ */
+ function baseSortBy(array, comparer) {
+ var length = array.length;
+
+ array.sort(comparer);
+ while (length--) {
+ array[length] = array[length].value;
+ }
+ return array;
+ }
+
+ /**
+ * The base implementation of `_.sum` and `_.sumBy` without support for
+ * iteratee shorthands.
+ *
+ * @private
+ * @param {Array} array The array to iterate over.
+ * @param {Function} iteratee The function invoked per iteration.
+ * @returns {number} Returns the sum.
+ */
+ function baseSum(array, iteratee) {
+ var result,
+ index = -1,
+ length = array.length;
+
+ while (++index < length) {
+ var current = iteratee(array[index]);
+ if (current !== undefined) {
+ result = result === undefined ? current : (result + current);
+ }
+ }
+ return result;
+ }
+
+ /**
+ * The base implementation of `_.times` without support for iteratee shorthands
+ * or max array length checks.
+ *
+ * @private
+ * @param {number} n The number of times to invoke `iteratee`.
+ * @param {Function} iteratee The function invoked per iteration.
+ * @returns {Array} Returns the array of results.
+ */
+ function baseTimes(n, iteratee) {
+ var index = -1,
+ result = Array(n);
+
+ while (++index < n) {
+ result[index] = iteratee(index);
+ }
+ return result;
+ }
+
+ /**
+ * The base implementation of `_.toPairs` and `_.toPairsIn` which creates an array
+ * of key-value pairs for `object` corresponding to the property names of `props`.
+ *
+ * @private
+ * @param {Object} object The object to query.
+ * @param {Array} props The property names to get values for.
+ * @returns {Object} Returns the key-value pairs.
+ */
+ function baseToPairs(object, props) {
+ return arrayMap(props, function(key) {
+ return [key, object[key]];
+ });
+ }
+
+ /**
+ * The base implementation of `_.unary` without support for storing metadata.
+ *
+ * @private
+ * @param {Function} func The function to cap arguments for.
+ * @returns {Function} Returns the new capped function.
+ */
+ function baseUnary(func) {
+ return function(value) {
+ return func(value);
+ };
+ }
+
+ /**
+ * The base implementation of `_.values` and `_.valuesIn` which creates an
+ * array of `object` property values corresponding to the property names
+ * of `props`.
+ *
+ * @private
+ * @param {Object} object The object to query.
+ * @param {Array} props The property names to get values for.
+ * @returns {Object} Returns the array of property values.
+ */
+ function baseValues(object, props) {
+ return arrayMap(props, function(key) {
+ return object[key];
+ });
+ }
+
+ /**
+ * Checks if a `cache` value for `key` exists.
+ *
+ * @private
+ * @param {Object} cache The cache to query.
+ * @param {string} key The key of the entry to check.
+ * @returns {boolean} Returns `true` if an entry for `key` exists, else `false`.
+ */
+ function cacheHas(cache, key) {
+ return cache.has(key);
+ }
+
+ /**
+ * Used by `_.trim` and `_.trimStart` to get the index of the first string symbol
+ * that is not found in the character symbols.
+ *
+ * @private
+ * @param {Array} strSymbols The string symbols to inspect.
+ * @param {Array} chrSymbols The character symbols to find.
+ * @returns {number} Returns the index of the first unmatched string symbol.
+ */
+ function charsStartIndex(strSymbols, chrSymbols) {
+ var index = -1,
+ length = strSymbols.length;
+
+ while (++index < length && baseIndexOf(chrSymbols, strSymbols[index], 0) > -1) {}
+ return index;
+ }
+
+ /**
+ * Used by `_.trim` and `_.trimEnd` to get the index of the last string symbol
+ * that is not found in the character symbols.
+ *
+ * @private
+ * @param {Array} strSymbols The string symbols to inspect.
+ * @param {Array} chrSymbols The character symbols to find.
+ * @returns {number} Returns the index of the last unmatched string symbol.
+ */
+ function charsEndIndex(strSymbols, chrSymbols) {
+ var index = strSymbols.length;
+
+ while (index-- && baseIndexOf(chrSymbols, strSymbols[index], 0) > -1) {}
+ return index;
+ }
+
+ /**
+ * Gets the number of `placeholder` occurrences in `array`.
+ *
+ * @private
+ * @param {Array} array The array to inspect.
+ * @param {*} placeholder The placeholder to search for.
+ * @returns {number} Returns the placeholder count.
+ */
+ function countHolders(array, placeholder) {
+ var length = array.length,
+ result = 0;
+
+ while (length--) {
+ if (array[length] === placeholder) {
+ ++result;
+ }
+ }
+ return result;
+ }
+
+ /**
+ * Used by `_.deburr` to convert Latin-1 Supplement and Latin Extended-A
+ * letters to basic Latin letters.
+ *
+ * @private
+ * @param {string} letter The matched letter to deburr.
+ * @returns {string} Returns the deburred letter.
+ */
+ var deburrLetter = basePropertyOf(deburredLetters);
+
+ /**
+ * Used by `_.escape` to convert characters to HTML entities.
+ *
+ * @private
+ * @param {string} chr The matched character to escape.
+ * @returns {string} Returns the escaped character.
+ */
+ var escapeHtmlChar = basePropertyOf(htmlEscapes);
+
+ /**
+ * Used by `_.template` to escape characters for inclusion in compiled string literals.
+ *
+ * @private
+ * @param {string} chr The matched character to escape.
+ * @returns {string} Returns the escaped character.
+ */
+ function escapeStringChar(chr) {
+ return '\\' + stringEscapes[chr];
+ }
+
+ /**
+ * Gets the value at `key` of `object`.
+ *
+ * @private
+ * @param {Object} [object] The object to query.
+ * @param {string} key The key of the property to get.
+ * @returns {*} Returns the property value.
+ */
+ function getValue(object, key) {
+ return object == null ? undefined : object[key];
+ }
+
+ /**
+ * Checks if `string` contains Unicode symbols.
+ *
+ * @private
+ * @param {string} string The string to inspect.
+ * @returns {boolean} Returns `true` if a symbol is found, else `false`.
+ */
+ function hasUnicode(string) {
+ return reHasUnicode.test(string);
+ }
+
+ /**
+ * Checks if `string` contains a word composed of Unicode symbols.
+ *
+ * @private
+ * @param {string} string The string to inspect.
+ * @returns {boolean} Returns `true` if a word is found, else `false`.
+ */
+ function hasUnicodeWord(string) {
+ return reHasUnicodeWord.test(string);
+ }
+
+ /**
+ * Converts `iterator` to an array.
+ *
+ * @private
+ * @param {Object} iterator The iterator to convert.
+ * @returns {Array} Returns the converted array.
+ */
+ function iteratorToArray(iterator) {
+ var data,
+ result = [];
+
+ while (!(data = iterator.next()).done) {
+ result.push(data.value);
+ }
+ return result;
+ }
+
+ /**
+ * Converts `map` to its key-value pairs.
+ *
+ * @private
+ * @param {Object} map The map to convert.
+ * @returns {Array} Returns the key-value pairs.
+ */
+ function mapToArray(map) {
+ var index = -1,
+ result = Array(map.size);
+
+ map.forEach(function(value, key) {
+ result[++index] = [key, value];
+ });
+ return result;
+ }
+
+ /**
+ * Creates a unary function that invokes `func` with its argument transformed.
+ *
+ * @private
+ * @param {Function} func The function to wrap.
+ * @param {Function} transform The argument transform.
+ * @returns {Function} Returns the new function.
+ */
+ function overArg(func, transform) {
+ return function(arg) {
+ return func(transform(arg));
+ };
+ }
+
+ /**
+ * Replaces all `placeholder` elements in `array` with an internal placeholder
+ * and returns an array of their indexes.
+ *
+ * @private
+ * @param {Array} array The array to modify.
+ * @param {*} placeholder The placeholder to replace.
+ * @returns {Array} Returns the new array of placeholder indexes.
+ */
+ function replaceHolders(array, placeholder) {
+ var index = -1,
+ length = array.length,
+ resIndex = 0,
+ result = [];
+
+ while (++index < length) {
+ var value = array[index];
+ if (value === placeholder || value === PLACEHOLDER) {
+ array[index] = PLACEHOLDER;
+ result[resIndex++] = index;
+ }
+ }
+ return result;
+ }
+
+ /**
+ * Converts `set` to an array of its values.
+ *
+ * @private
+ * @param {Object} set The set to convert.
+ * @returns {Array} Returns the values.
+ */
+ function setToArray(set) {
+ var index = -1,
+ result = Array(set.size);
+
+ set.forEach(function(value) {
+ result[++index] = value;
+ });
+ return result;
+ }
+
+ /**
+ * Converts `set` to its value-value pairs.
+ *
+ * @private
+ * @param {Object} set The set to convert.
+ * @returns {Array} Returns the value-value pairs.
+ */
+ function setToPairs(set) {
+ var index = -1,
+ result = Array(set.size);
+
+ set.forEach(function(value) {
+ result[++index] = [value, value];
+ });
+ return result;
+ }
+
+ /**
+ * A specialized version of `_.indexOf` which performs strict equality
+ * comparisons of values, i.e. `===`.
+ *
+ * @private
+ * @param {Array} array The array to inspect.
+ * @param {*} value The value to search for.
+ * @param {number} fromIndex The index to search from.
+ * @returns {number} Returns the index of the matched value, else `-1`.
+ */
+ function strictIndexOf(array, value, fromIndex) {
+ var index = fromIndex - 1,
+ length = array.length;
+
+ while (++index < length) {
+ if (array[index] === value) {
+ return index;
+ }
+ }
+ return -1;
+ }
+
+ /**
+ * A specialized version of `_.lastIndexOf` which performs strict equality
+ * comparisons of values, i.e. `===`.
+ *
+ * @private
+ * @param {Array} array The array to inspect.
+ * @param {*} value The value to search for.
+ * @param {number} fromIndex The index to search from.
+ * @returns {number} Returns the index of the matched value, else `-1`.
+ */
+ function strictLastIndexOf(array, value, fromIndex) {
+ var index = fromIndex + 1;
+ while (index--) {
+ if (array[index] === value) {
+ return index;
+ }
+ }
+ return index;
+ }
+
+ /**
+ * Gets the number of symbols in `string`.
+ *
+ * @private
+ * @param {string} string The string to inspect.
+ * @returns {number} Returns the string size.
+ */
+ function stringSize(string) {
+ return hasUnicode(string)
+ ? unicodeSize(string)
+ : asciiSize(string);
+ }
+
+ /**
+ * Converts `string` to an array.
+ *
+ * @private
+ * @param {string} string The string to convert.
+ * @returns {Array} Returns the converted array.
+ */
+ function stringToArray(string) {
+ return hasUnicode(string)
+ ? unicodeToArray(string)
+ : asciiToArray(string);
+ }
+
+ /**
+ * Used by `_.unescape` to convert HTML entities to characters.
+ *
+ * @private
+ * @param {string} chr The matched character to unescape.
+ * @returns {string} Returns the unescaped character.
+ */
+ var unescapeHtmlChar = basePropertyOf(htmlUnescapes);
+
+ /**
+ * Gets the size of a Unicode `string`.
+ *
+ * @private
+ * @param {string} string The string inspect.
+ * @returns {number} Returns the string size.
+ */
+ function unicodeSize(string) {
+ var result = reUnicode.lastIndex = 0;
+ while (reUnicode.test(string)) {
+ ++result;
+ }
+ return result;
+ }
+
+ /**
+ * Converts a Unicode `string` to an array.
+ *
+ * @private
+ * @param {string} string The string to convert.
+ * @returns {Array} Returns the converted array.
+ */
+ function unicodeToArray(string) {
+ return string.match(reUnicode) || [];
+ }
+
+ /**
+ * Splits a Unicode `string` into an array of its words.
+ *
+ * @private
+ * @param {string} The string to inspect.
+ * @returns {Array} Returns the words of `string`.
+ */
+ function unicodeWords(string) {
+ return string.match(reUnicodeWord) || [];
+ }
+
+ /*--------------------------------------------------------------------------*/
+
+ /**
+ * Create a new pristine `lodash` function using the `context` object.
+ *
+ * @static
+ * @memberOf _
+ * @since 1.1.0
+ * @category Util
+ * @param {Object} [context=root] The context object.
+ * @returns {Function} Returns a new `lodash` function.
+ * @example
+ *
+ * _.mixin({ 'foo': _.constant('foo') });
+ *
+ * var lodash = _.runInContext();
+ * lodash.mixin({ 'bar': lodash.constant('bar') });
+ *
+ * _.isFunction(_.foo);
+ * // => true
+ * _.isFunction(_.bar);
+ * // => false
+ *
+ * lodash.isFunction(lodash.foo);
+ * // => false
+ * lodash.isFunction(lodash.bar);
+ * // => true
+ *
+ * // Create a suped-up `defer` in Node.js.
+ * var defer = _.runInContext({ 'setTimeout': setImmediate }).defer;
+ */
+ var runInContext = (function runInContext(context) {
+ context = context ? _.defaults(root.Object(), context, _.pick(root, contextProps)) : root;
+
+ /** Built-in constructor references. */
+ var Array = context.Array,
+ Date = context.Date,
+ Error = context.Error,
+ Function = context.Function,
+ Math = context.Math,
+ Object = context.Object,
+ RegExp = context.RegExp,
+ String = context.String,
+ TypeError = context.TypeError;
+
+ /** Used for built-in method references. */
+ var arrayProto = Array.prototype,
+ funcProto = Function.prototype,
+ objectProto = Object.prototype;
+
+ /** Used to detect overreaching core-js shims. */
+ var coreJsData = context['__core-js_shared__'];
+
+ /** Used to detect methods masquerading as native. */
+ var maskSrcKey = (function() {
+ var uid = /[^.]+$/.exec(coreJsData && coreJsData.keys && coreJsData.keys.IE_PROTO || '');
+ return uid ? ('Symbol(src)_1.' + uid) : '';
+ }());
+
+ /** Used to resolve the decompiled source of functions. */
+ var funcToString = funcProto.toString;
+
+ /** Used to check objects for own properties. */
+ var hasOwnProperty = objectProto.hasOwnProperty;
+
+ /** Used to generate unique IDs. */
+ var idCounter = 0;
+
+ /** Used to infer the `Object` constructor. */
+ var objectCtorString = funcToString.call(Object);
+
+ /**
+ * Used to resolve the
+ * [`toStringTag`](http://ecma-international.org/ecma-262/7.0/#sec-object.prototype.tostring)
+ * of values.
+ */
+ var objectToString = objectProto.toString;
+
+ /** Used to restore the original `_` reference in `_.noConflict`. */
+ var oldDash = root._;
+
+ /** Used to detect if a method is native. */
+ var reIsNative = RegExp('^' +
+ funcToString.call(hasOwnProperty).replace(reRegExpChar, '\\$&')
+ .replace(/hasOwnProperty|(function).*?(?=\\\()| for .+?(?=\\\])/g, '$1.*?') + '$'
+ );
+
+ /** Built-in value references. */
+ var Buffer = moduleExports ? context.Buffer : undefined,
+ Symbol = context.Symbol,
+ Uint8Array = context.Uint8Array,
+ allocUnsafe = Buffer ? Buffer.allocUnsafe : undefined,
+ getPrototype = overArg(Object.getPrototypeOf, Object),
+ iteratorSymbol = Symbol ? Symbol.iterator : undefined,
+ objectCreate = Object.create,
+ propertyIsEnumerable = objectProto.propertyIsEnumerable,
+ splice = arrayProto.splice,
+ spreadableSymbol = Symbol ? Symbol.isConcatSpreadable : undefined;
+
+ var defineProperty = (function() {
+ try {
+ var func = getNative(Object, 'defineProperty');
+ func({}, '', {});
+ return func;
+ } catch (e) {}
+ }());
+
+ /** Mocked built-ins. */
+ var ctxClearTimeout = context.clearTimeout !== root.clearTimeout && context.clearTimeout,
+ ctxNow = Date && Date.now !== root.Date.now && Date.now,
+ ctxSetTimeout = context.setTimeout !== root.setTimeout && context.setTimeout;
+
+ /* Built-in method references for those with the same name as other `lodash` methods. */
+ var nativeCeil = Math.ceil,
+ nativeFloor = Math.floor,
+ nativeGetSymbols = Object.getOwnPropertySymbols,
+ nativeIsBuffer = Buffer ? Buffer.isBuffer : undefined,
+ nativeIsFinite = context.isFinite,
+ nativeJoin = arrayProto.join,
+ nativeKeys = overArg(Object.keys, Object),
+ nativeMax = Math.max,
+ nativeMin = Math.min,
+ nativeNow = Date.now,
+ nativeParseInt = context.parseInt,
+ nativeRandom = Math.random,
+ nativeReverse = arrayProto.reverse;
+
+ /* Built-in method references that are verified to be native. */
+ var DataView = getNative(context, 'DataView'),
+ Map = getNative(context, 'Map'),
+ Promise = getNative(context, 'Promise'),
+ Set = getNative(context, 'Set'),
+ WeakMap = getNative(context, 'WeakMap'),
+ nativeCreate = getNative(Object, 'create');
+
+ /** Used to store function metadata. */
+ var metaMap = WeakMap && new WeakMap;
+
+ /** Used to lookup unminified function names. */
+ var realNames = {};
+
+ /** Used to detect maps, sets, and weakmaps. */
+ var dataViewCtorString = toSource(DataView),
+ mapCtorString = toSource(Map),
+ promiseCtorString = toSource(Promise),
+ setCtorString = toSource(Set),
+ weakMapCtorString = toSource(WeakMap);
+
+ /** Used to convert symbols to primitives and strings. */
+ var symbolProto = Symbol ? Symbol.prototype : undefined,
+ symbolValueOf = symbolProto ? symbolProto.valueOf : undefined,
+ symbolToString = symbolProto ? symbolProto.toString : undefined;
+
+ /*------------------------------------------------------------------------*/
+
+ /**
+ * Creates a `lodash` object which wraps `value` to enable implicit method
+ * chain sequences. Methods that operate on and return arrays, collections,
+ * and functions can be chained together. Methods that retrieve a single value
+ * or may return a primitive value will automatically end the chain sequence
+ * and return the unwrapped value. Otherwise, the value must be unwrapped
+ * with `_#value`.
+ *
+ * Explicit chain sequences, which must be unwrapped with `_#value`, may be
+ * enabled using `_.chain`.
+ *
+ * The execution of chained methods is lazy, that is, it's deferred until
+ * `_#value` is implicitly or explicitly called.
+ *
+ * Lazy evaluation allows several methods to support shortcut fusion.
+ * Shortcut fusion is an optimization to merge iteratee calls; this avoids
+ * the creation of intermediate arrays and can greatly reduce the number of
+ * iteratee executions. Sections of a chain sequence qualify for shortcut
+ * fusion if the section is applied to an array of at least `200` elements
+ * and any iteratees accept only one argument. The heuristic for whether a
+ * section qualifies for shortcut fusion is subject to change.
+ *
+ * Chaining is supported in custom builds as long as the `_#value` method is
+ * directly or indirectly included in the build.
+ *
+ * In addition to lodash methods, wrappers have `Array` and `String` methods.
+ *
+ * The wrapper `Array` methods are:
+ * `concat`, `join`, `pop`, `push`, `shift`, `sort`, `splice`, and `unshift`
+ *
+ * The wrapper `String` methods are:
+ * `replace` and `split`
+ *
+ * The wrapper methods that support shortcut fusion are:
+ * `at`, `compact`, `drop`, `dropRight`, `dropWhile`, `filter`, `find`,
+ * `findLast`, `head`, `initial`, `last`, `map`, `reject`, `reverse`, `slice`,
+ * `tail`, `take`, `takeRight`, `takeRightWhile`, `takeWhile`, and `toArray`
+ *
+ * The chainable wrapper methods are:
+ * `after`, `ary`, `assign`, `assignIn`, `assignInWith`, `assignWith`, `at`,
+ * `before`, `bind`, `bindAll`, `bindKey`, `castArray`, `chain`, `chunk`,
+ * `commit`, `compact`, `concat`, `conforms`, `constant`, `countBy`, `create`,
+ * `curry`, `debounce`, `defaults`, `defaultsDeep`, `defer`, `delay`,
+ * `difference`, `differenceBy`, `differenceWith`, `drop`, `dropRight`,
+ * `dropRightWhile`, `dropWhile`, `extend`, `extendWith`, `fill`, `filter`,
+ * `flatMap`, `flatMapDeep`, `flatMapDepth`, `flatten`, `flattenDeep`,
+ * `flattenDepth`, `flip`, `flow`, `flowRight`, `fromPairs`, `functions`,
+ * `functionsIn`, `groupBy`, `initial`, `intersection`, `intersectionBy`,
+ * `intersectionWith`, `invert`, `invertBy`, `invokeMap`, `iteratee`, `keyBy`,
+ * `keys`, `keysIn`, `map`, `mapKeys`, `mapValues`, `matches`, `matchesProperty`,
+ * `memoize`, `merge`, `mergeWith`, `method`, `methodOf`, `mixin`, `negate`,
+ * `nthArg`, `omit`, `omitBy`, `once`, `orderBy`, `over`, `overArgs`,
+ * `overEvery`, `overSome`, `partial`, `partialRight`, `partition`, `pick`,
+ * `pickBy`, `plant`, `property`, `propertyOf`, `pull`, `pullAll`, `pullAllBy`,
+ * `pullAllWith`, `pullAt`, `push`, `range`, `rangeRight`, `rearg`, `reject`,
+ * `remove`, `rest`, `reverse`, `sampleSize`, `set`, `setWith`, `shuffle`,
+ * `slice`, `sort`, `sortBy`, `splice`, `spread`, `tail`, `take`, `takeRight`,
+ * `takeRightWhile`, `takeWhile`, `tap`, `throttle`, `thru`, `toArray`,
+ * `toPairs`, `toPairsIn`, `toPath`, `toPlainObject`, `transform`, `unary`,
+ * `union`, `unionBy`, `unionWith`, `uniq`, `uniqBy`, `uniqWith`, `unset`,
+ * `unshift`, `unzip`, `unzipWith`, `update`, `updateWith`, `values`,
+ * `valuesIn`, `without`, `wrap`, `xor`, `xorBy`, `xorWith`, `zip`,
+ * `zipObject`, `zipObjectDeep`, and `zipWith`
+ *
+ * The wrapper methods that are **not** chainable by default are:
+ * `add`, `attempt`, `camelCase`, `capitalize`, `ceil`, `clamp`, `clone`,
+ * `cloneDeep`, `cloneDeepWith`, `cloneWith`, `conformsTo`, `deburr`,
+ * `defaultTo`, `divide`, `each`, `eachRight`, `endsWith`, `eq`, `escape`,
+ * `escapeRegExp`, `every`, `find`, `findIndex`, `findKey`, `findLast`,
+ * `findLastIndex`, `findLastKey`, `first`, `floor`, `forEach`, `forEachRight`,
+ * `forIn`, `forInRight`, `forOwn`, `forOwnRight`, `get`, `gt`, `gte`, `has`,
+ * `hasIn`, `head`, `identity`, `includes`, `indexOf`, `inRange`, `invoke`,
+ * `isArguments`, `isArray`, `isArrayBuffer`, `isArrayLike`, `isArrayLikeObject`,
+ * `isBoolean`, `isBuffer`, `isDate`, `isElement`, `isEmpty`, `isEqual`,
+ * `isEqualWith`, `isError`, `isFinite`, `isFunction`, `isInteger`, `isLength`,
+ * `isMap`, `isMatch`, `isMatchWith`, `isNaN`, `isNative`, `isNil`, `isNull`,
+ * `isNumber`, `isObject`, `isObjectLike`, `isPlainObject`, `isRegExp`,
+ * `isSafeInteger`, `isSet`, `isString`, `isUndefined`, `isTypedArray`,
+ * `isWeakMap`, `isWeakSet`, `join`, `kebabCase`, `last`, `lastIndexOf`,
+ * `lowerCase`, `lowerFirst`, `lt`, `lte`, `max`, `maxBy`, `mean`, `meanBy`,
+ * `min`, `minBy`, `multiply`, `noConflict`, `noop`, `now`, `nth`, `pad`,
+ * `padEnd`, `padStart`, `parseInt`, `pop`, `random`, `reduce`, `reduceRight`,
+ * `repeat`, `result`, `round`, `runInContext`, `sample`, `shift`, `size`,
+ * `snakeCase`, `some`, `sortedIndex`, `sortedIndexBy`, `sortedLastIndex`,
+ * `sortedLastIndexBy`, `startCase`, `startsWith`, `stubArray`, `stubFalse`,
+ * `stubObject`, `stubString`, `stubTrue`, `subtract`, `sum`, `sumBy`,
+ * `template`, `times`, `toFinite`, `toInteger`, `toJSON`, `toLength`,
+ * `toLower`, `toNumber`, `toSafeInteger`, `toString`, `toUpper`, `trim`,
+ * `trimEnd`, `trimStart`, `truncate`, `unescape`, `uniqueId`, `upperCase`,
+ * `upperFirst`, `value`, and `words`
+ *
+ * @name _
+ * @constructor
+ * @category Seq
+ * @param {*} value The value to wrap in a `lodash` instance.
+ * @returns {Object} Returns the new `lodash` wrapper instance.
+ * @example
+ *
+ * function square(n) {
+ * return n * n;
+ * }
+ *
+ * var wrapped = _([1, 2, 3]);
+ *
+ * // Returns an unwrapped value.
+ * wrapped.reduce(_.add);
+ * // => 6
+ *
+ * // Returns a wrapped value.
+ * var squares = wrapped.map(square);
+ *
+ * _.isArray(squares);
+ * // => false
+ *
+ * _.isArray(squares.value());
+ * // => true
+ */
+ function lodash(value) {
+ if (isObjectLike(value) && !isArray(value) && !(value instanceof LazyWrapper)) {
+ if (value instanceof LodashWrapper) {
+ return value;
+ }
+ if (hasOwnProperty.call(value, '__wrapped__')) {
+ return wrapperClone(value);
+ }
+ }
+ return new LodashWrapper(value);
+ }
+
+ /**
+ * The base implementation of `_.create` without support for assigning
+ * properties to the created object.
+ *
+ * @private
+ * @param {Object} proto The object to inherit from.
+ * @returns {Object} Returns the new object.
+ */
+ var baseCreate = (function() {
+ function object() {}
+ return function(proto) {
+ if (!isObject(proto)) {
+ return {};
+ }
+ if (objectCreate) {
+ return objectCreate(proto);
+ }
+ object.prototype = proto;
+ var result = new object;
+ object.prototype = undefined;
+ return result;
+ };
+ }());
+
+ /**
+ * The function whose prototype chain sequence wrappers inherit from.
+ *
+ * @private
+ */
+ function baseLodash() {
+ // No operation performed.
+ }
+
+ /**
+ * The base constructor for creating `lodash` wrapper objects.
+ *
+ * @private
+ * @param {*} value The value to wrap.
+ * @param {boolean} [chainAll] Enable explicit method chain sequences.
+ */
+ function LodashWrapper(value, chainAll) {
+ this.__wrapped__ = value;
+ this.__actions__ = [];
+ this.__chain__ = !!chainAll;
+ this.__index__ = 0;
+ this.__values__ = undefined;
+ }
+
+ /**
+ * By default, the template delimiters used by lodash are like those in
+ * embedded Ruby (ERB). Change the following template settings to use
+ * alternative delimiters.
+ *
+ * @static
+ * @memberOf _
+ * @type {Object}
+ */
+ lodash.templateSettings = {
+
+ /**
+ * Used to detect `data` property values to be HTML-escaped.
+ *
+ * @memberOf _.templateSettings
+ * @type {RegExp}
+ */
+ 'escape': reEscape,
+
+ /**
+ * Used to detect code to be evaluated.
+ *
+ * @memberOf _.templateSettings
+ * @type {RegExp}
+ */
+ 'evaluate': reEvaluate,
+
+ /**
+ * Used to detect `data` property values to inject.
+ *
+ * @memberOf _.templateSettings
+ * @type {RegExp}
+ */
+ 'interpolate': reInterpolate,
+
+ /**
+ * Used to reference the data object in the template text.
+ *
+ * @memberOf _.templateSettings
+ * @type {string}
+ */
+ 'variable': '',
+
+ /**
+ * Used to import variables into the compiled template.
+ *
+ * @memberOf _.templateSettings
+ * @type {Object}
+ */
+ 'imports': {
+
+ /**
+ * A reference to the `lodash` function.
+ *
+ * @memberOf _.templateSettings.imports
+ * @type {Function}
+ */
+ '_': lodash
+ }
+ };
+
+ // Ensure wrappers are instances of `baseLodash`.
+ lodash.prototype = baseLodash.prototype;
+ lodash.prototype.constructor = lodash;
+
+ LodashWrapper.prototype = baseCreate(baseLodash.prototype);
+ LodashWrapper.prototype.constructor = LodashWrapper;
+
+ /*------------------------------------------------------------------------*/
+
+ /**
+ * Creates a lazy wrapper object which wraps `value` to enable lazy evaluation.
+ *
+ * @private
+ * @constructor
+ * @param {*} value The value to wrap.
+ */
+ function LazyWrapper(value) {
+ this.__wrapped__ = value;
+ this.__actions__ = [];
+ this.__dir__ = 1;
+ this.__filtered__ = false;
+ this.__iteratees__ = [];
+ this.__takeCount__ = MAX_ARRAY_LENGTH;
+ this.__views__ = [];
+ }
+
+ /**
+ * Creates a clone of the lazy wrapper object.
+ *
+ * @private
+ * @name clone
+ * @memberOf LazyWrapper
+ * @returns {Object} Returns the cloned `LazyWrapper` object.
+ */
+ function lazyClone() {
+ var result = new LazyWrapper(this.__wrapped__);
+ result.__actions__ = copyArray(this.__actions__);
+ result.__dir__ = this.__dir__;
+ result.__filtered__ = this.__filtered__;
+ result.__iteratees__ = copyArray(this.__iteratees__);
+ result.__takeCount__ = this.__takeCount__;
+ result.__views__ = copyArray(this.__views__);
+ return result;
+ }
+
+ /**
+ * Reverses the direction of lazy iteration.
+ *
+ * @private
+ * @name reverse
+ * @memberOf LazyWrapper
+ * @returns {Object} Returns the new reversed `LazyWrapper` object.
+ */
+ function lazyReverse() {
+ if (this.__filtered__) {
+ var result = new LazyWrapper(this);
+ result.__dir__ = -1;
+ result.__filtered__ = true;
+ } else {
+ result = this.clone();
+ result.__dir__ *= -1;
+ }
+ return result;
+ }
+
+ /**
+ * Extracts the unwrapped value from its lazy wrapper.
+ *
+ * @private
+ * @name value
+ * @memberOf LazyWrapper
+ * @returns {*} Returns the unwrapped value.
+ */
+ function lazyValue() {
+ var array = this.__wrapped__.value(),
+ dir = this.__dir__,
+ isArr = isArray(array),
+ isRight = dir < 0,
+ arrLength = isArr ? array.length : 0,
+ view = getView(0, arrLength, this.__views__),
+ start = view.start,
+ end = view.end,
+ length = end - start,
+ index = isRight ? end : (start - 1),
+ iteratees = this.__iteratees__,
+ iterLength = iteratees.length,
+ resIndex = 0,
+ takeCount = nativeMin(length, this.__takeCount__);
+
+ if (!isArr || arrLength < LARGE_ARRAY_SIZE ||
+ (arrLength == length && takeCount == length)) {
+ return baseWrapperValue(array, this.__actions__);
+ }
+ var result = [];
+
+ outer:
+ while (length-- && resIndex < takeCount) {
+ index += dir;
+
+ var iterIndex = -1,
+ value = array[index];
+
+ while (++iterIndex < iterLength) {
+ var data = iteratees[iterIndex],
+ iteratee = data.iteratee,
+ type = data.type,
+ computed = iteratee(value);
+
+ if (type == LAZY_MAP_FLAG) {
+ value = computed;
+ } else if (!computed) {
+ if (type == LAZY_FILTER_FLAG) {
+ continue outer;
+ } else {
+ break outer;
+ }
+ }
+ }
+ result[resIndex++] = value;
+ }
+ return result;
+ }
+
+ // Ensure `LazyWrapper` is an instance of `baseLodash`.
+ LazyWrapper.prototype = baseCreate(baseLodash.prototype);
+ LazyWrapper.prototype.constructor = LazyWrapper;
+
+ /*------------------------------------------------------------------------*/
+
+ /**
+ * Creates a hash object.
+ *
+ * @private
+ * @constructor
+ * @param {Array} [entries] The key-value pairs to cache.
+ */
+ function Hash(entries) {
+ var index = -1,
+ length = entries ? entries.length : 0;
+
+ this.clear();
+ while (++index < length) {
+ var entry = entries[index];
+ this.set(entry[0], entry[1]);
+ }
+ }
+
+ /**
+ * Removes all key-value entries from the hash.
+ *
+ * @private
+ * @name clear
+ * @memberOf Hash
+ */
+ function hashClear() {
+ this.__data__ = nativeCreate ? nativeCreate(null) : {};
+ this.size = 0;
+ }
+
+ /**
+ * Removes `key` and its value from the hash.
+ *
+ * @private
+ * @name delete
+ * @memberOf Hash
+ * @param {Object} hash The hash to modify.
+ * @param {string} key The key of the value to remove.
+ * @returns {boolean} Returns `true` if the entry was removed, else `false`.
+ */
+ function hashDelete(key) {
+ var result = this.has(key) && delete this.__data__[key];
+ this.size -= result ? 1 : 0;
+ return result;
+ }
+
+ /**
+ * Gets the hash value for `key`.
+ *
+ * @private
+ * @name get
+ * @memberOf Hash
+ * @param {string} key The key of the value to get.
+ * @returns {*} Returns the entry value.
+ */
+ function hashGet(key) {
+ var data = this.__data__;
+ if (nativeCreate) {
+ var result = data[key];
+ return result === HASH_UNDEFINED ? undefined : result;
+ }
+ return hasOwnProperty.call(data, key) ? data[key] : undefined;
+ }
+
+ /**
+ * Checks if a hash value for `key` exists.
+ *
+ * @private
+ * @name has
+ * @memberOf Hash
+ * @param {string} key The key of the entry to check.
+ * @returns {boolean} Returns `true` if an entry for `key` exists, else `false`.
+ */
+ function hashHas(key) {
+ var data = this.__data__;
+ return nativeCreate ? data[key] !== undefined : hasOwnProperty.call(data, key);
+ }
+
+ /**
+ * Sets the hash `key` to `value`.
+ *
+ * @private
+ * @name set
+ * @memberOf Hash
+ * @param {string} key The key of the value to set.
+ * @param {*} value The value to set.
+ * @returns {Object} Returns the hash instance.
+ */
+ function hashSet(key, value) {
+ var data = this.__data__;
+ this.size += this.has(key) ? 0 : 1;
+ data[key] = (nativeCreate && value === undefined) ? HASH_UNDEFINED : value;
+ return this;
+ }
+
+ // Add methods to `Hash`.
+ Hash.prototype.clear = hashClear;
+ Hash.prototype['delete'] = hashDelete;
+ Hash.prototype.get = hashGet;
+ Hash.prototype.has = hashHas;
+ Hash.prototype.set = hashSet;
+
+ /*------------------------------------------------------------------------*/
+
+ /**
+ * Creates an list cache object.
+ *
+ * @private
+ * @constructor
+ * @param {Array} [entries] The key-value pairs to cache.
+ */
+ function ListCache(entries) {
+ var index = -1,
+ length = entries ? entries.length : 0;
+
+ this.clear();
+ while (++index < length) {
+ var entry = entries[index];
+ this.set(entry[0], entry[1]);
+ }
+ }
+
+ /**
+ * Removes all key-value entries from the list cache.
+ *
+ * @private
+ * @name clear
+ * @memberOf ListCache
+ */
+ function listCacheClear() {
+ this.__data__ = [];
+ this.size = 0;
+ }
+
+ /**
+ * Removes `key` and its value from the list cache.
+ *
+ * @private
+ * @name delete
+ * @memberOf ListCache
+ * @param {string} key The key of the value to remove.
+ * @returns {boolean} Returns `true` if the entry was removed, else `false`.
+ */
+ function listCacheDelete(key) {
+ var data = this.__data__,
+ index = assocIndexOf(data, key);
+
+ if (index < 0) {
+ return false;
+ }
+ var lastIndex = data.length - 1;
+ if (index == lastIndex) {
+ data.pop();
+ } else {
+ splice.call(data, index, 1);
+ }
+ --this.size;
+ return true;
+ }
+
+ /**
+ * Gets the list cache value for `key`.
+ *
+ * @private
+ * @name get
+ * @memberOf ListCache
+ * @param {string} key The key of the value to get.
+ * @returns {*} Returns the entry value.
+ */
+ function listCacheGet(key) {
+ var data = this.__data__,
+ index = assocIndexOf(data, key);
+
+ return index < 0 ? undefined : data[index][1];
+ }
+
+ /**
+ * Checks if a list cache value for `key` exists.
+ *
+ * @private
+ * @name has
+ * @memberOf ListCache
+ * @param {string} key The key of the entry to check.
+ * @returns {boolean} Returns `true` if an entry for `key` exists, else `false`.
+ */
+ function listCacheHas(key) {
+ return assocIndexOf(this.__data__, key) > -1;
+ }
+
+ /**
+ * Sets the list cache `key` to `value`.
+ *
+ * @private
+ * @name set
+ * @memberOf ListCache
+ * @param {string} key The key of the value to set.
+ * @param {*} value The value to set.
+ * @returns {Object} Returns the list cache instance.
+ */
+ function listCacheSet(key, value) {
+ var data = this.__data__,
+ index = assocIndexOf(data, key);
+
+ if (index < 0) {
+ ++this.size;
+ data.push([key, value]);
+ } else {
+ data[index][1] = value;
+ }
+ return this;
+ }
+
+ // Add methods to `ListCache`.
+ ListCache.prototype.clear = listCacheClear;
+ ListCache.prototype['delete'] = listCacheDelete;
+ ListCache.prototype.get = listCacheGet;
+ ListCache.prototype.has = listCacheHas;
+ ListCache.prototype.set = listCacheSet;
+
+ /*------------------------------------------------------------------------*/
+
+ /**
+ * Creates a map cache object to store key-value pairs.
+ *
+ * @private
+ * @constructor
+ * @param {Array} [entries] The key-value pairs to cache.
+ */
+ function MapCache(entries) {
+ var index = -1,
+ length = entries ? entries.length : 0;
+
+ this.clear();
+ while (++index < length) {
+ var entry = entries[index];
+ this.set(entry[0], entry[1]);
+ }
+ }
+
+ /**
+ * Removes all key-value entries from the map.
+ *
+ * @private
+ * @name clear
+ * @memberOf MapCache
+ */
+ function mapCacheClear() {
+ this.size = 0;
+ this.__data__ = {
+ 'hash': new Hash,
+ 'map': new (Map || ListCache),
+ 'string': new Hash
+ };
+ }
+
+ /**
+ * Removes `key` and its value from the map.
+ *
+ * @private
+ * @name delete
+ * @memberOf MapCache
+ * @param {string} key The key of the value to remove.
+ * @returns {boolean} Returns `true` if the entry was removed, else `false`.
+ */
+ function mapCacheDelete(key) {
+ var result = getMapData(this, key)['delete'](key);
+ this.size -= result ? 1 : 0;
+ return result;
+ }
+
+ /**
+ * Gets the map value for `key`.
+ *
+ * @private
+ * @name get
+ * @memberOf MapCache
+ * @param {string} key The key of the value to get.
+ * @returns {*} Returns the entry value.
+ */
+ function mapCacheGet(key) {
+ return getMapData(this, key).get(key);
+ }
+
+ /**
+ * Checks if a map value for `key` exists.
+ *
+ * @private
+ * @name has
+ * @memberOf MapCache
+ * @param {string} key The key of the entry to check.
+ * @returns {boolean} Returns `true` if an entry for `key` exists, else `false`.
+ */
+ function mapCacheHas(key) {
+ return getMapData(this, key).has(key);
+ }
+
+ /**
+ * Sets the map `key` to `value`.
+ *
+ * @private
+ * @name set
+ * @memberOf MapCache
+ * @param {string} key The key of the value to set.
+ * @param {*} value The value to set.
+ * @returns {Object} Returns the map cache instance.
+ */
+ function mapCacheSet(key, value) {
+ var data = getMapData(this, key),
+ size = data.size;
+
+ data.set(key, value);
+ this.size += data.size == size ? 0 : 1;
+ return this;
+ }
+
+ // Add methods to `MapCache`.
+ MapCache.prototype.clear = mapCacheClear;
+ MapCache.prototype['delete'] = mapCacheDelete;
+ MapCache.prototype.get = mapCacheGet;
+ MapCache.prototype.has = mapCacheHas;
+ MapCache.prototype.set = mapCacheSet;
+
+ /*------------------------------------------------------------------------*/
+
+ /**
+ *
+ * Creates an array cache object to store unique values.
+ *
+ * @private
+ * @constructor
+ * @param {Array} [values] The values to cache.
+ */
+ function SetCache(values) {
+ var index = -1,
+ length = values ? values.length : 0;
+
+ this.__data__ = new MapCache;
+ while (++index < length) {
+ this.add(values[index]);
+ }
+ }
+
+ /**
+ * Adds `value` to the array cache.
+ *
+ * @private
+ * @name add
+ * @memberOf SetCache
+ * @alias push
+ * @param {*} value The value to cache.
+ * @returns {Object} Returns the cache instance.
+ */
+ function setCacheAdd(value) {
+ this.__data__.set(value, HASH_UNDEFINED);
+ return this;
+ }
+
+ /**
+ * Checks if `value` is in the array cache.
+ *
+ * @private
+ * @name has
+ * @memberOf SetCache
+ * @param {*} value The value to search for.
+ * @returns {number} Returns `true` if `value` is found, else `false`.
+ */
+ function setCacheHas(value) {
+ return this.__data__.has(value);
+ }
+
+ // Add methods to `SetCache`.
+ SetCache.prototype.add = SetCache.prototype.push = setCacheAdd;
+ SetCache.prototype.has = setCacheHas;
+
+ /*------------------------------------------------------------------------*/
+
+ /**
+ * Creates a stack cache object to store key-value pairs.
+ *
+ * @private
+ * @constructor
+ * @param {Array} [entries] The key-value pairs to cache.
+ */
+ function Stack(entries) {
+ var data = this.__data__ = new ListCache(entries);
+ this.size = data.size;
+ }
+
+ /**
+ * Removes all key-value entries from the stack.
+ *
+ * @private
+ * @name clear
+ * @memberOf Stack
+ */
+ function stackClear() {
+ this.__data__ = new ListCache;
+ this.size = 0;
+ }
+
+ /**
+ * Removes `key` and its value from the stack.
+ *
+ * @private
+ * @name delete
+ * @memberOf Stack
+ * @param {string} key The key of the value to remove.
+ * @returns {boolean} Returns `true` if the entry was removed, else `false`.
+ */
+ function stackDelete(key) {
+ var data = this.__data__,
+ result = data['delete'](key);
+
+ this.size = data.size;
+ return result;
+ }
+
+ /**
+ * Gets the stack value for `key`.
+ *
+ * @private
+ * @name get
+ * @memberOf Stack
+ * @param {string} key The key of the value to get.
+ * @returns {*} Returns the entry value.
+ */
+ function stackGet(key) {
+ return this.__data__.get(key);
+ }
+
+ /**
+ * Checks if a stack value for `key` exists.
+ *
+ * @private
+ * @name has
+ * @memberOf Stack
+ * @param {string} key The key of the entry to check.
+ * @returns {boolean} Returns `true` if an entry for `key` exists, else `false`.
+ */
+ function stackHas(key) {
+ return this.__data__.has(key);
+ }
+
+ /**
+ * Sets the stack `key` to `value`.
+ *
+ * @private
+ * @name set
+ * @memberOf Stack
+ * @param {string} key The key of the value to set.
+ * @param {*} value The value to set.
+ * @returns {Object} Returns the stack cache instance.
+ */
+ function stackSet(key, value) {
+ var data = this.__data__;
+ if (data instanceof ListCache) {
+ var pairs = data.__data__;
+ if (!Map || (pairs.length < LARGE_ARRAY_SIZE - 1)) {
+ pairs.push([key, value]);
+ this.size = ++data.size;
+ return this;
+ }
+ data = this.__data__ = new MapCache(pairs);
+ }
+ data.set(key, value);
+ this.size = data.size;
+ return this;
+ }
+
+ // Add methods to `Stack`.
+ Stack.prototype.clear = stackClear;
+ Stack.prototype['delete'] = stackDelete;
+ Stack.prototype.get = stackGet;
+ Stack.prototype.has = stackHas;
+ Stack.prototype.set = stackSet;
+
+ /*------------------------------------------------------------------------*/
+
+ /**
+ * Creates an array of the enumerable property names of the array-like `value`.
+ *
+ * @private
+ * @param {*} value The value to query.
+ * @param {boolean} inherited Specify returning inherited property names.
+ * @returns {Array} Returns the array of property names.
+ */
+ function arrayLikeKeys(value, inherited) {
+ var isArr = isArray(value),
+ isArg = !isArr && isArguments(value),
+ isBuff = !isArr && !isArg && isBuffer(value),
+ isType = !isArr && !isArg && !isBuff && isTypedArray(value),
+ skipIndexes = isArr || isArg || isBuff || isType,
+ result = skipIndexes ? baseTimes(value.length, String) : [],
+ length = result.length;
+
+ for (var key in value) {
+ if ((inherited || hasOwnProperty.call(value, key)) &&
+ !(skipIndexes && (
+ // Safari 9 has enumerable `arguments.length` in strict mode.
+ key == 'length' ||
+ // Node.js 0.10 has enumerable non-index properties on buffers.
+ (isBuff && (key == 'offset' || key == 'parent')) ||
+ // PhantomJS 2 has enumerable non-index properties on typed arrays.
+ (isType && (key == 'buffer' || key == 'byteLength' || key == 'byteOffset')) ||
+ // Skip index properties.
+ isIndex(key, length)
+ ))) {
+ result.push(key);
+ }
+ }
+ return result;
+ }
+
+ /**
+ * A specialized version of `_.sample` for arrays.
+ *
+ * @private
+ * @param {Array} array The array to sample.
+ * @returns {*} Returns the random element.
+ */
+ function arraySample(array) {
+ var length = array.length;
+ return length ? array[baseRandom(0, length - 1)] : undefined;
+ }
+
+ /**
+ * A specialized version of `_.sampleSize` for arrays.
+ *
+ * @private
+ * @param {Array} array The array to sample.
+ * @param {number} n The number of elements to sample.
+ * @returns {Array} Returns the random elements.
+ */
+ function arraySampleSize(array, n) {
+ return shuffleSelf(copyArray(array), baseClamp(n, 0, array.length));
+ }
+
+ /**
+ * A specialized version of `_.shuffle` for arrays.
+ *
+ * @private
+ * @param {Array} array The array to shuffle.
+ * @returns {Array} Returns the new shuffled array.
+ */
+ function arrayShuffle(array) {
+ return shuffleSelf(copyArray(array));
+ }
+
+ /**
+ * Used by `_.defaults` to customize its `_.assignIn` use.
+ *
+ * @private
+ * @param {*} objValue The destination value.
+ * @param {*} srcValue The source value.
+ * @param {string} key The key of the property to assign.
+ * @param {Object} object The parent object of `objValue`.
+ * @returns {*} Returns the value to assign.
+ */
+ function assignInDefaults(objValue, srcValue, key, object) {
+ if (objValue === undefined ||
+ (eq(objValue, objectProto[key]) && !hasOwnProperty.call(object, key))) {
+ return srcValue;
+ }
+ return objValue;
+ }
+
+ /**
+ * This function is like `assignValue` except that it doesn't assign
+ * `undefined` values.
+ *
+ * @private
+ * @param {Object} object The object to modify.
+ * @param {string} key The key of the property to assign.
+ * @param {*} value The value to assign.
+ */
+ function assignMergeValue(object, key, value) {
+ if ((value !== undefined && !eq(object[key], value)) ||
+ (value === undefined && !(key in object))) {
+ baseAssignValue(object, key, value);
+ }
+ }
+
+ /**
+ * Assigns `value` to `key` of `object` if the existing value is not equivalent
+ * using [`SameValueZero`](http://ecma-international.org/ecma-262/7.0/#sec-samevaluezero)
+ * for equality comparisons.
+ *
+ * @private
+ * @param {Object} object The object to modify.
+ * @param {string} key The key of the property to assign.
+ * @param {*} value The value to assign.
+ */
+ function assignValue(object, key, value) {
+ var objValue = object[key];
+ if (!(hasOwnProperty.call(object, key) && eq(objValue, value)) ||
+ (value === undefined && !(key in object))) {
+ baseAssignValue(object, key, value);
+ }
+ }
+
+ /**
+ * Gets the index at which the `key` is found in `array` of key-value pairs.
+ *
+ * @private
+ * @param {Array} array The array to inspect.
+ * @param {*} key The key to search for.
+ * @returns {number} Returns the index of the matched value, else `-1`.
+ */
+ function assocIndexOf(array, key) {
+ var length = array.length;
+ while (length--) {
+ if (eq(array[length][0], key)) {
+ return length;
+ }
+ }
+ return -1;
+ }
+
+ /**
+ * Aggregates elements of `collection` on `accumulator` with keys transformed
+ * by `iteratee` and values set by `setter`.
+ *
+ * @private
+ * @param {Array|Object} collection The collection to iterate over.
+ * @param {Function} setter The function to set `accumulator` values.
+ * @param {Function} iteratee The iteratee to transform keys.
+ * @param {Object} accumulator The initial aggregated object.
+ * @returns {Function} Returns `accumulator`.
+ */
+ function baseAggregator(collection, setter, iteratee, accumulator) {
+ baseEach(collection, function(value, key, collection) {
+ setter(accumulator, value, iteratee(value), collection);
+ });
+ return accumulator;
+ }
+
+ /**
+ * The base implementation of `_.assign` without support for multiple sources
+ * or `customizer` functions.
+ *
+ * @private
+ * @param {Object} object The destination object.
+ * @param {Object} source The source object.
+ * @returns {Object} Returns `object`.
+ */
+ function baseAssign(object, source) {
+ return object && copyObject(source, keys(source), object);
+ }
+
+ /**
+ * The base implementation of `assignValue` and `assignMergeValue` without
+ * value checks.
+ *
+ * @private
+ * @param {Object} object The object to modify.
+ * @param {string} key The key of the property to assign.
+ * @param {*} value The value to assign.
+ */
+ function baseAssignValue(object, key, value) {
+ if (key == '__proto__' && defineProperty) {
+ defineProperty(object, key, {
+ 'configurable': true,
+ 'enumerable': true,
+ 'value': value,
+ 'writable': true
+ });
+ } else {
+ object[key] = value;
+ }
+ }
+
+ /**
+ * The base implementation of `_.at` without support for individual paths.
+ *
+ * @private
+ * @param {Object} object The object to iterate over.
+ * @param {string[]} paths The property paths of elements to pick.
+ * @returns {Array} Returns the picked elements.
+ */
+ function baseAt(object, paths) {
+ var index = -1,
+ isNil = object == null,
+ length = paths.length,
+ result = Array(length);
+
+ while (++index < length) {
+ result[index] = isNil ? undefined : get(object, paths[index]);
+ }
+ return result;
+ }
+
+ /**
+ * The base implementation of `_.clamp` which doesn't coerce arguments.
+ *
+ * @private
+ * @param {number} number The number to clamp.
+ * @param {number} [lower] The lower bound.
+ * @param {number} upper The upper bound.
+ * @returns {number} Returns the clamped number.
+ */
+ function baseClamp(number, lower, upper) {
+ if (number === number) {
+ if (upper !== undefined) {
+ number = number <= upper ? number : upper;
+ }
+ if (lower !== undefined) {
+ number = number >= lower ? number : lower;
+ }
+ }
+ return number;
+ }
+
+ /**
+ * The base implementation of `_.clone` and `_.cloneDeep` which tracks
+ * traversed objects.
+ *
+ * @private
+ * @param {*} value The value to clone.
+ * @param {boolean} [isDeep] Specify a deep clone.
+ * @param {boolean} [isFull] Specify a clone including symbols.
+ * @param {Function} [customizer] The function to customize cloning.
+ * @param {string} [key] The key of `value`.
+ * @param {Object} [object] The parent object of `value`.
+ * @param {Object} [stack] Tracks traversed objects and their clone counterparts.
+ * @returns {*} Returns the cloned value.
+ */
+ function baseClone(value, isDeep, isFull, customizer, key, object, stack) {
+ var result;
+ if (customizer) {
+ result = object ? customizer(value, key, object, stack) : customizer(value);
+ }
+ if (result !== undefined) {
+ return result;
+ }
+ if (!isObject(value)) {
+ return value;
+ }
+ var isArr = isArray(value);
+ if (isArr) {
+ result = initCloneArray(value);
+ if (!isDeep) {
+ return copyArray(value, result);
+ }
+ } else {
+ var tag = getTag(value),
+ isFunc = tag == funcTag || tag == genTag;
+
+ if (isBuffer(value)) {
+ return cloneBuffer(value, isDeep);
+ }
+ if (tag == objectTag || tag == argsTag || (isFunc && !object)) {
+ result = initCloneObject(isFunc ? {} : value);
+ if (!isDeep) {
+ return copySymbols(value, baseAssign(result, value));
+ }
+ } else {
+ if (!cloneableTags[tag]) {
+ return object ? value : {};
+ }
+ result = initCloneByTag(value, tag, baseClone, isDeep);
+ }
+ }
+ // Check for circular references and return its corresponding clone.
+ stack || (stack = new Stack);
+ var stacked = stack.get(value);
+ if (stacked) {
+ return stacked;
+ }
+ stack.set(value, result);
+
+ var props = isArr ? undefined : (isFull ? getAllKeys : keys)(value);
+ arrayEach(props || value, function(subValue, key) {
+ if (props) {
+ key = subValue;
+ subValue = value[key];
+ }
+ // Recursively populate clone (susceptible to call stack limits).
+ assignValue(result, key, baseClone(subValue, isDeep, isFull, customizer, key, value, stack));
+ });
+ return result;
+ }
+
+ /**
+ * The base implementation of `_.conforms` which doesn't clone `source`.
+ *
+ * @private
+ * @param {Object} source The object of property predicates to conform to.
+ * @returns {Function} Returns the new spec function.
+ */
+ function baseConforms(source) {
+ var props = keys(source);
+ return function(object) {
+ return baseConformsTo(object, source, props);
+ };
+ }
+
+ /**
+ * The base implementation of `_.conformsTo` which accepts `props` to check.
+ *
+ * @private
+ * @param {Object} object The object to inspect.
+ * @param {Object} source The object of property predicates to conform to.
+ * @returns {boolean} Returns `true` if `object` conforms, else `false`.
+ */
+ function baseConformsTo(object, source, props) {
+ var length = props.length;
+ if (object == null) {
+ return !length;
+ }
+ object = Object(object);
+ while (length--) {
+ var key = props[length],
+ predicate = source[key],
+ value = object[key];
+
+ if ((value === undefined && !(key in object)) || !predicate(value)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * The base implementation of `_.delay` and `_.defer` which accepts `args`
+ * to provide to `func`.
+ *
+ * @private
+ * @param {Function} func The function to delay.
+ * @param {number} wait The number of milliseconds to delay invocation.
+ * @param {Array} args The arguments to provide to `func`.
+ * @returns {number|Object} Returns the timer id or timeout object.
+ */
+ function baseDelay(func, wait, args) {
+ if (typeof func != 'function') {
+ throw new TypeError(FUNC_ERROR_TEXT);
+ }
+ return setTimeout(function() { func.apply(undefined, args); }, wait);
+ }
+
+ /**
+ * The base implementation of methods like `_.difference` without support
+ * for excluding multiple arrays or iteratee shorthands.
+ *
+ * @private
+ * @param {Array} array The array to inspect.
+ * @param {Array} values The values to exclude.
+ * @param {Function} [iteratee] The iteratee invoked per element.
+ * @param {Function} [comparator] The comparator invoked per element.
+ * @returns {Array} Returns the new array of filtered values.
+ */
+ function baseDifference(array, values, iteratee, comparator) {
+ var index = -1,
+ includes = arrayIncludes,
+ isCommon = true,
+ length = array.length,
+ result = [],
+ valuesLength = values.length;
+
+ if (!length) {
+ return result;
+ }
+ if (iteratee) {
+ values = arrayMap(values, baseUnary(iteratee));
+ }
+ if (comparator) {
+ includes = arrayIncludesWith;
+ isCommon = false;
+ }
+ else if (values.length >= LARGE_ARRAY_SIZE) {
+ includes = cacheHas;
+ isCommon = false;
+ values = new SetCache(values);
+ }
+ outer:
+ while (++index < length) {
+ var value = array[index],
+ computed = iteratee ? iteratee(value) : value;
+
+ value = (comparator || value !== 0) ? value : 0;
+ if (isCommon && computed === computed) {
+ var valuesIndex = valuesLength;
+ while (valuesIndex--) {
+ if (values[valuesIndex] === computed) {
+ continue outer;
+ }
+ }
+ result.push(value);
+ }
+ else if (!includes(values, computed, comparator)) {
+ result.push(value);
+ }
+ }
+ return result;
+ }
+
+ /**
+ * The base implementation of `_.forEach` without support for iteratee shorthands.
+ *
+ * @private
+ * @param {Array|Object} collection The collection to iterate over.
+ * @param {Function} iteratee The function invoked per iteration.
+ * @returns {Array|Object} Returns `collection`.
+ */
+ var baseEach = createBaseEach(baseForOwn);
+
+ /**
+ * The base implementation of `_.forEachRight` without support for iteratee shorthands.
+ *
+ * @private
+ * @param {Array|Object} collection The collection to iterate over.
+ * @param {Function} iteratee The function invoked per iteration.
+ * @returns {Array|Object} Returns `collection`.
+ */
+ var baseEachRight = createBaseEach(baseForOwnRight, true);
+
+ /**
+ * The base implementation of `_.every` without support for iteratee shorthands.
+ *
+ * @private
+ * @param {Array|Object} collection The collection to iterate over.
+ * @param {Function} predicate The function invoked per iteration.
+ * @returns {boolean} Returns `true` if all elements pass the predicate check,
+ * else `false`
+ */
+ function baseEvery(collection, predicate) {
+ var result = true;
+ baseEach(collection, function(value, index, collection) {
+ result = !!predicate(value, index, collection);
+ return result;
+ });
+ return result;
+ }
+
+ /**
+ * The base implementation of methods like `_.max` and `_.min` which accepts a
+ * `comparator` to determine the extremum value.
+ *
+ * @private
+ * @param {Array} array The array to iterate over.
+ * @param {Function} iteratee The iteratee invoked per iteration.
+ * @param {Function} comparator The comparator used to compare values.
+ * @returns {*} Returns the extremum value.
+ */
+ function baseExtremum(array, iteratee, comparator) {
+ var index = -1,
+ length = array.length;
+
+ while (++index < length) {
+ var value = array[index],
+ current = iteratee(value);
+
+ if (current != null && (computed === undefined
+ ? (current === current && !isSymbol(current))
+ : comparator(current, computed)
+ )) {
+ var computed = current,
+ result = value;
+ }
+ }
+ return result;
+ }
+
+ /**
+ * The base implementation of `_.fill` without an iteratee call guard.
+ *
+ * @private
+ * @param {Array} array The array to fill.
+ * @param {*} value The value to fill `array` with.
+ * @param {number} [start=0] The start position.
+ * @param {number} [end=array.length] The end position.
+ * @returns {Array} Returns `array`.
+ */
+ function baseFill(array, value, start, end) {
+ var length = array.length;
+
+ start = toInteger(start);
+ if (start < 0) {
+ start = -start > length ? 0 : (length + start);
+ }
+ end = (end === undefined || end > length) ? length : toInteger(end);
+ if (end < 0) {
+ end += length;
+ }
+ end = start > end ? 0 : toLength(end);
+ while (start < end) {
+ array[start++] = value;
+ }
+ return array;
+ }
+
+ /**
+ * The base implementation of `_.filter` without support for iteratee shorthands.
+ *
+ * @private
+ * @param {Array|Object} collection The collection to iterate over.
+ * @param {Function} predicate The function invoked per iteration.
+ * @returns {Array} Returns the new filtered array.
+ */
+ function baseFilter(collection, predicate) {
+ var result = [];
+ baseEach(collection, function(value, index, collection) {
+ if (predicate(value, index, collection)) {
+ result.push(value);
+ }
+ });
+ return result;
+ }
+
+ /**
+ * The base implementation of `_.flatten` with support for restricting flattening.
+ *
+ * @private
+ * @param {Array} array The array to flatten.
+ * @param {number} depth The maximum recursion depth.
+ * @param {boolean} [predicate=isFlattenable] The function invoked per iteration.
+ * @param {boolean} [isStrict] Restrict to values that pass `predicate` checks.
+ * @param {Array} [result=[]] The initial result value.
+ * @returns {Array} Returns the new flattened array.
+ */
+ function baseFlatten(array, depth, predicate, isStrict, result) {
+ var index = -1,
+ length = array.length;
+
+ predicate || (predicate = isFlattenable);
+ result || (result = []);
+
+ while (++index < length) {
+ var value = array[index];
+ if (depth > 0 && predicate(value)) {
+ if (depth > 1) {
+ // Recursively flatten arrays (susceptible to call stack limits).
+ baseFlatten(value, depth - 1, predicate, isStrict, result);
+ } else {
+ arrayPush(result, value);
+ }
+ } else if (!isStrict) {
+ result[result.length] = value;
+ }
+ }
+ return result;
+ }
+
+ /**
+ * The base implementation of `baseForOwn` which iterates over `object`
+ * properties returned by `keysFunc` and invokes `iteratee` for each property.
+ * Iteratee functions may exit iteration early by explicitly returning `false`.
+ *
+ * @private
+ * @param {Object} object The object to iterate over.
+ * @param {Function} iteratee The function invoked per iteration.
+ * @param {Function} keysFunc The function to get the keys of `object`.
+ * @returns {Object} Returns `object`.
+ */
+ var baseFor = createBaseFor();
+
+ /**
+ * This function is like `baseFor` except that it iterates over properties
+ * in the opposite order.
+ *
+ * @private
+ * @param {Object} object The object to iterate over.
+ * @param {Function} iteratee The function invoked per iteration.
+ * @param {Function} keysFunc The function to get the keys of `object`.
+ * @returns {Object} Returns `object`.
+ */
+ var baseForRight = createBaseFor(true);
+
+ /**
+ * The base implementation of `_.forOwn` without support for iteratee shorthands.
+ *
+ * @private
+ * @param {Object} object The object to iterate over.
+ * @param {Function} iteratee The function invoked per iteration.
+ * @returns {Object} Returns `object`.
+ */
+ function baseForOwn(object, iteratee) {
+ return object && baseFor(object, iteratee, keys);
+ }
+
+ /**
+ * The base implementation of `_.forOwnRight` without support for iteratee shorthands.
+ *
+ * @private
+ * @param {Object} object The object to iterate over.
+ * @param {Function} iteratee The function invoked per iteration.
+ * @returns {Object} Returns `object`.
+ */
+ function baseForOwnRight(object, iteratee) {
+ return object && baseForRight(object, iteratee, keys);
+ }
+
+ /**
+ * The base implementation of `_.functions` which creates an array of
+ * `object` function property names filtered from `props`.
+ *
+ * @private
+ * @param {Object} object The object to inspect.
+ * @param {Array} props The property names to filter.
+ * @returns {Array} Returns the function names.
+ */
+ function baseFunctions(object, props) {
+ return arrayFilter(props, function(key) {
+ return isFunction(object[key]);
+ });
+ }
+
+ /**
+ * The base implementation of `_.get` without support for default values.
+ *
+ * @private
+ * @param {Object} object The object to query.
+ * @param {Array|string} path The path of the property to get.
+ * @returns {*} Returns the resolved value.
+ */
+ function baseGet(object, path) {
+ path = isKey(path, object) ? [path] : castPath(path);
+
+ var index = 0,
+ length = path.length;
+
+ while (object != null && index < length) {
+ object = object[toKey(path[index++])];
+ }
+ return (index && index == length) ? object : undefined;
+ }
+
+ /**
+ * The base implementation of `getAllKeys` and `getAllKeysIn` which uses
+ * `keysFunc` and `symbolsFunc` to get the enumerable property names and
+ * symbols of `object`.
+ *
+ * @private
+ * @param {Object} object The object to query.
+ * @param {Function} keysFunc The function to get the keys of `object`.
+ * @param {Function} symbolsFunc The function to get the symbols of `object`.
+ * @returns {Array} Returns the array of property names and symbols.
+ */
+ function baseGetAllKeys(object, keysFunc, symbolsFunc) {
+ var result = keysFunc(object);
+ return isArray(object) ? result : arrayPush(result, symbolsFunc(object));
+ }
+
+ /**
+ * The base implementation of `getTag`.
+ *
+ * @private
+ * @param {*} value The value to query.
+ * @returns {string} Returns the `toStringTag`.
+ */
+ function baseGetTag(value) {
+ return objectToString.call(value);
+ }
+
+ /**
+ * The base implementation of `_.gt` which doesn't coerce arguments.
+ *
+ * @private
+ * @param {*} value The value to compare.
+ * @param {*} other The other value to compare.
+ * @returns {boolean} Returns `true` if `value` is greater than `other`,
+ * else `false`.
+ */
+ function baseGt(value, other) {
+ return value > other;
+ }
+
+ /**
+ * The base implementation of `_.has` without support for deep paths.
+ *
+ * @private
+ * @param {Object} [object] The object to query.
+ * @param {Array|string} key The key to check.
+ * @returns {boolean} Returns `true` if `key` exists, else `false`.
+ */
+ function baseHas(object, key) {
+ return object != null && hasOwnProperty.call(object, key);
+ }
+
+ /**
+ * The base implementation of `_.hasIn` without support for deep paths.
+ *
+ * @private
+ * @param {Object} [object] The object to query.
+ * @param {Array|string} key The key to check.
+ * @returns {boolean} Returns `true` if `key` exists, else `false`.
+ */
+ function baseHasIn(object, key) {
+ return object != null && key in Object(object);
+ }
+
+ /**
+ * The base implementation of `_.inRange` which doesn't coerce arguments.
+ *
+ * @private
+ * @param {number} number The number to check.
+ * @param {number} start The start of the range.
+ * @param {number} end The end of the range.
+ * @returns {boolean} Returns `true` if `number` is in the range, else `false`.
+ */
+ function baseInRange(number, start, end) {
+ return number >= nativeMin(start, end) && number < nativeMax(start, end);
+ }
+
+ /**
+ * The base implementation of methods like `_.intersection`, without support
+ * for iteratee shorthands, that accepts an array of arrays to inspect.
+ *
+ * @private
+ * @param {Array} arrays The arrays to inspect.
+ * @param {Function} [iteratee] The iteratee invoked per element.
+ * @param {Function} [comparator] The comparator invoked per element.
+ * @returns {Array} Returns the new array of shared values.
+ */
+ function baseIntersection(arrays, iteratee, comparator) {
+ var includes = comparator ? arrayIncludesWith : arrayIncludes,
+ length = arrays[0].length,
+ othLength = arrays.length,
+ othIndex = othLength,
+ caches = Array(othLength),
+ maxLength = Infinity,
+ result = [];
+
+ while (othIndex--) {
+ var array = arrays[othIndex];
+ if (othIndex && iteratee) {
+ array = arrayMap(array, baseUnary(iteratee));
+ }
+ maxLength = nativeMin(array.length, maxLength);
+ caches[othIndex] = !comparator && (iteratee || (length >= 120 && array.length >= 120))
+ ? new SetCache(othIndex && array)
+ : undefined;
+ }
+ array = arrays[0];
+
+ var index = -1,
+ seen = caches[0];
+
+ outer:
+ while (++index < length && result.length < maxLength) {
+ var value = array[index],
+ computed = iteratee ? iteratee(value) : value;
+
+ value = (comparator || value !== 0) ? value : 0;
+ if (!(seen
+ ? cacheHas(seen, computed)
+ : includes(result, computed, comparator)
+ )) {
+ othIndex = othLength;
+ while (--othIndex) {
+ var cache = caches[othIndex];
+ if (!(cache
+ ? cacheHas(cache, computed)
+ : includes(arrays[othIndex], computed, comparator))
+ ) {
+ continue outer;
+ }
+ }
+ if (seen) {
+ seen.push(computed);
+ }
+ result.push(value);
+ }
+ }
+ return result;
+ }
+
+ /**
+ * The base implementation of `_.invert` and `_.invertBy` which inverts
+ * `object` with values transformed by `iteratee` and set by `setter`.
+ *
+ * @private
+ * @param {Object} object The object to iterate over.
+ * @param {Function} setter The function to set `accumulator` values.
+ * @param {Function} iteratee The iteratee to transform values.
+ * @param {Object} accumulator The initial inverted object.
+ * @returns {Function} Returns `accumulator`.
+ */
+ function baseInverter(object, setter, iteratee, accumulator) {
+ baseForOwn(object, function(value, key, object) {
+ setter(accumulator, iteratee(value), key, object);
+ });
+ return accumulator;
+ }
+
+ /**
+ * The base implementation of `_.invoke` without support for individual
+ * method arguments.
+ *
+ * @private
+ * @param {Object} object The object to query.
+ * @param {Array|string} path The path of the method to invoke.
+ * @param {Array} args The arguments to invoke the method with.
+ * @returns {*} Returns the result of the invoked method.
+ */
+ function baseInvoke(object, path, args) {
+ if (!isKey(path, object)) {
+ path = castPath(path);
+ object = parent(object, path);
+ path = last(path);
+ }
+ var func = object == null ? object : object[toKey(path)];
+ return func == null ? undefined : apply(func, object, args);
+ }
+
+ /**
+ * The base implementation of `_.isArguments`.
+ *
+ * @private
+ * @param {*} value The value to check.
+ * @returns {boolean} Returns `true` if `value` is an `arguments` object,
+ */
+ function baseIsArguments(value) {
+ return isObjectLike(value) && objectToString.call(value) == argsTag;
+ }
+
+ /**
+ * The base implementation of `_.isArrayBuffer` without Node.js optimizations.
+ *
+ * @private
+ * @param {*} value The value to check.
+ * @returns {boolean} Returns `true` if `value` is an array buffer, else `false`.
+ */
+ function baseIsArrayBuffer(value) {
+ return isObjectLike(value) && objectToString.call(value) == arrayBufferTag;
+ }
+
+ /**
+ * The base implementation of `_.isDate` without Node.js optimizations.
+ *
+ * @private
+ * @param {*} value The value to check.
+ * @returns {boolean} Returns `true` if `value` is a date object, else `false`.
+ */
+ function baseIsDate(value) {
+ return isObjectLike(value) && objectToString.call(value) == dateTag;
+ }
+
+ /**
+ * The base implementation of `_.isEqual` which supports partial comparisons
+ * and tracks traversed objects.
+ *
+ * @private
+ * @param {*} value The value to compare.
+ * @param {*} other The other value to compare.
+ * @param {Function} [customizer] The function to customize comparisons.
+ * @param {boolean} [bitmask] The bitmask of comparison flags.
+ * The bitmask may be composed of the following flags:
+ * 1 - Unordered comparison
+ * 2 - Partial comparison
+ * @param {Object} [stack] Tracks traversed `value` and `other` objects.
+ * @returns {boolean} Returns `true` if the values are equivalent, else `false`.
+ */
+ function baseIsEqual(value, other, customizer, bitmask, stack) {
+ if (value === other) {
+ return true;
+ }
+ if (value == null || other == null || (!isObject(value) && !isObjectLike(other))) {
+ return value !== value && other !== other;
+ }
+ return baseIsEqualDeep(value, other, baseIsEqual, customizer, bitmask, stack);
+ }
+
+ /**
+ * A specialized version of `baseIsEqual` for arrays and objects which performs
+ * deep comparisons and tracks traversed objects enabling objects with circular
+ * references to be compared.
+ *
+ * @private
+ * @param {Object} object The object to compare.
+ * @param {Object} other The other object to compare.
+ * @param {Function} equalFunc The function to determine equivalents of values.
+ * @param {Function} [customizer] The function to customize comparisons.
+ * @param {number} [bitmask] The bitmask of comparison flags. See `baseIsEqual`
+ * for more details.
+ * @param {Object} [stack] Tracks traversed `object` and `other` objects.
+ * @returns {boolean} Returns `true` if the objects are equivalent, else `false`.
+ */
+ function baseIsEqualDeep(object, other, equalFunc, customizer, bitmask, stack) {
+ var objIsArr = isArray(object),
+ othIsArr = isArray(other),
+ objTag = arrayTag,
+ othTag = arrayTag;
+
+ if (!objIsArr) {
+ objTag = getTag(object);
+ objTag = objTag == argsTag ? objectTag : objTag;
+ }
+ if (!othIsArr) {
+ othTag = getTag(other);
+ othTag = othTag == argsTag ? objectTag : othTag;
+ }
+ var objIsObj = objTag == objectTag,
+ othIsObj = othTag == objectTag,
+ isSameTag = objTag == othTag;
+
+ if (isSameTag && isBuffer(object)) {
+ if (!isBuffer(other)) {
+ return false;
+ }
+ objIsArr = true;
+ objIsObj = false;
+ }
+ if (isSameTag && !objIsObj) {
+ stack || (stack = new Stack);
+ return (objIsArr || isTypedArray(object))
+ ? equalArrays(object, other, equalFunc, customizer, bitmask, stack)
+ : equalByTag(object, other, objTag, equalFunc, customizer, bitmask, stack);
+ }
+ if (!(bitmask & PARTIAL_COMPARE_FLAG)) {
+ var objIsWrapped = objIsObj && hasOwnProperty.call(object, '__wrapped__'),
+ othIsWrapped = othIsObj && hasOwnProperty.call(other, '__wrapped__');
+
+ if (objIsWrapped || othIsWrapped) {
+ var objUnwrapped = objIsWrapped ? object.value() : object,
+ othUnwrapped = othIsWrapped ? other.value() : other;
+
+ stack || (stack = new Stack);
+ return equalFunc(objUnwrapped, othUnwrapped, customizer, bitmask, stack);
+ }
+ }
+ if (!isSameTag) {
+ return false;
+ }
+ stack || (stack = new Stack);
+ return equalObjects(object, other, equalFunc, customizer, bitmask, stack);
+ }
+
+ /**
+ * The base implementation of `_.isMap` without Node.js optimizations.
+ *
+ * @private
+ * @param {*} value The value to check.
+ * @returns {boolean} Returns `true` if `value` is a map, else `false`.
+ */
+ function baseIsMap(value) {
+ return isObjectLike(value) && getTag(value) == mapTag;
+ }
+
+ /**
+ * The base implementation of `_.isMatch` without support for iteratee shorthands.
+ *
+ * @private
+ * @param {Object} object The object to inspect.
+ * @param {Object} source The object of property values to match.
+ * @param {Array} matchData The property names, values, and compare flags to match.
+ * @param {Function} [customizer] The function to customize comparisons.
+ * @returns {boolean} Returns `true` if `object` is a match, else `false`.
+ */
+ function baseIsMatch(object, source, matchData, customizer) {
+ var index = matchData.length,
+ length = index,
+ noCustomizer = !customizer;
+
+ if (object == null) {
+ return !length;
+ }
+ object = Object(object);
+ while (index--) {
+ var data = matchData[index];
+ if ((noCustomizer && data[2])
+ ? data[1] !== object[data[0]]
+ : !(data[0] in object)
+ ) {
+ return false;
+ }
+ }
+ while (++index < length) {
+ data = matchData[index];
+ var key = data[0],
+ objValue = object[key],
+ srcValue = data[1];
+
+ if (noCustomizer && data[2]) {
+ if (objValue === undefined && !(key in object)) {
+ return false;
+ }
+ } else {
+ var stack = new Stack;
+ if (customizer) {
+ var result = customizer(objValue, srcValue, key, object, source, stack);
+ }
+ if (!(result === undefined
+ ? baseIsEqual(srcValue, objValue, customizer, UNORDERED_COMPARE_FLAG | PARTIAL_COMPARE_FLAG, stack)
+ : result
+ )) {
+ return false;
+ }
+ }
+ }
+ return true;
+ }
+
+ /**
+ * The base implementation of `_.isNative` without bad shim checks.
+ *
+ * @private
+ * @param {*} value The value to check.
+ * @returns {boolean} Returns `true` if `value` is a native function,
+ * else `false`.
+ */
+ function baseIsNative(value) {
+ if (!isObject(value) || isMasked(value)) {
+ return false;
+ }
+ var pattern = isFunction(value) ? reIsNative : reIsHostCtor;
+ return pattern.test(toSource(value));
+ }
+
+ /**
+ * The base implementation of `_.isRegExp` without Node.js optimizations.
+ *
+ * @private
+ * @param {*} value The value to check.
+ * @returns {boolean} Returns `true` if `value` is a regexp, else `false`.
+ */
+ function baseIsRegExp(value) {
+ return isObject(value) && objectToString.call(value) == regexpTag;
+ }
+
+ /**
+ * The base implementation of `_.isSet` without Node.js optimizations.
+ *
+ * @private
+ * @param {*} value The value to check.
+ * @returns {boolean} Returns `true` if `value` is a set, else `false`.
+ */
+ function baseIsSet(value) {
+ return isObjectLike(value) && getTag(value) == setTag;
+ }
+
+ /**
+ * The base implementation of `_.isTypedArray` without Node.js optimizations.
+ *
+ * @private
+ * @param {*} value The value to check.
+ * @returns {boolean} Returns `true` if `value` is a typed array, else `false`.
+ */
+ function baseIsTypedArray(value) {
+ return isObjectLike(value) &&
+ isLength(value.length) && !!typedArrayTags[objectToString.call(value)];
+ }
+
+ /**
+ * The base implementation of `_.iteratee`.
+ *
+ * @private
+ * @param {*} [value=_.identity] The value to convert to an iteratee.
+ * @returns {Function} Returns the iteratee.
+ */
+ function baseIteratee(value) {
+ // Don't store the `typeof` result in a variable to avoid a JIT bug in Safari 9.
+ // See https://bugs.webkit.org/show_bug.cgi?id=156034 for more details.
+ if (typeof value == 'function') {
+ return value;
+ }
+ if (value == null) {
+ return identity;
+ }
+ if (typeof value == 'object') {
+ return isArray(value)
+ ? baseMatchesProperty(value[0], value[1])
+ : baseMatches(value);
+ }
+ return property(value);
+ }
+
+ /**
+ * The base implementation of `_.keys` which doesn't treat sparse arrays as dense.
+ *
+ * @private
+ * @param {Object} object The object to query.
+ * @returns {Array} Returns the array of property names.
+ */
+ function baseKeys(object) {
+ if (!isPrototype(object)) {
+ return nativeKeys(object);
+ }
+ var result = [];
+ for (var key in Object(object)) {
+ if (hasOwnProperty.call(object, key) && key != 'constructor') {
+ result.push(key);
+ }
+ }
+ return result;
+ }
+
+ /**
+ * The base implementation of `_.keysIn` which doesn't treat sparse arrays as dense.
+ *
+ * @private
+ * @param {Object} object The object to query.
+ * @returns {Array} Returns the array of property names.
+ */
+ function baseKeysIn(object) {
+ if (!isObject(object)) {
+ return nativeKeysIn(object);
+ }
+ var isProto = isPrototype(object),
+ result = [];
+
+ for (var key in object) {
+ if (!(key == 'constructor' && (isProto || !hasOwnProperty.call(object, key)))) {
+ result.push(key);
+ }
+ }
+ return result;
+ }
+
+ /**
+ * The base implementation of `_.lt` which doesn't coerce arguments.
+ *
+ * @private
+ * @param {*} value The value to compare.
+ * @param {*} other The other value to compare.
+ * @returns {boolean} Returns `true` if `value` is less than `other`,
+ * else `false`.
+ */
+ function baseLt(value, other) {
+ return value < other;
+ }
+
+ /**
+ * The base implementation of `_.map` without support for iteratee shorthands.
+ *
+ * @private
+ * @param {Array|Object} collection The collection to iterate over.
+ * @param {Function} iteratee The function invoked per iteration.
+ * @returns {Array} Returns the new mapped array.
+ */
+ function baseMap(collection, iteratee) {
+ var index = -1,
+ result = isArrayLike(collection) ? Array(collection.length) : [];
+
+ baseEach(collection, function(value, key, collection) {
+ result[++index] = iteratee(value, key, collection);
+ });
+ return result;
+ }
+
+ /**
+ * The base implementation of `_.matches` which doesn't clone `source`.
+ *
+ * @private
+ * @param {Object} source The object of property values to match.
+ * @returns {Function} Returns the new spec function.
+ */
+ function baseMatches(source) {
+ var matchData = getMatchData(source);
+ if (matchData.length == 1 && matchData[0][2]) {
+ return matchesStrictComparable(matchData[0][0], matchData[0][1]);
+ }
+ return function(object) {
+ return object === source || baseIsMatch(object, source, matchData);
+ };
+ }
+
+ /**
+ * The base implementation of `_.matchesProperty` which doesn't clone `srcValue`.
+ *
+ * @private
+ * @param {string} path The path of the property to get.
+ * @param {*} srcValue The value to match.
+ * @returns {Function} Returns the new spec function.
+ */
+ function baseMatchesProperty(path, srcValue) {
+ if (isKey(path) && isStrictComparable(srcValue)) {
+ return matchesStrictComparable(toKey(path), srcValue);
+ }
+ return function(object) {
+ var objValue = get(object, path);
+ return (objValue === undefined && objValue === srcValue)
+ ? hasIn(object, path)
+ : baseIsEqual(srcValue, objValue, undefined, UNORDERED_COMPARE_FLAG | PARTIAL_COMPARE_FLAG);
+ };
+ }
+
+ /**
+ * The base implementation of `_.merge` without support for multiple sources.
+ *
+ * @private
+ * @param {Object} object The destination object.
+ * @param {Object} source The source object.
+ * @param {number} srcIndex The index of `source`.
+ * @param {Function} [customizer] The function to customize merged values.
+ * @param {Object} [stack] Tracks traversed source values and their merged
+ * counterparts.
+ */
+ function baseMerge(object, source, srcIndex, customizer, stack) {
+ if (object === source) {
+ return;
+ }
+ baseFor(source, function(srcValue, key) {
+ if (isObject(srcValue)) {
+ stack || (stack = new Stack);
+ baseMergeDeep(object, source, key, srcIndex, baseMerge, customizer, stack);
+ }
+ else {
+ var newValue = customizer
+ ? customizer(object[key], srcValue, (key + ''), object, source, stack)
+ : undefined;
+
+ if (newValue === undefined) {
+ newValue = srcValue;
+ }
+ assignMergeValue(object, key, newValue);
+ }
+ }, keysIn);
+ }
+
+ /**
+ * A specialized version of `baseMerge` for arrays and objects which performs
+ * deep merges and tracks traversed objects enabling objects with circular
+ * references to be merged.
+ *
+ * @private
+ * @param {Object} object The destination object.
+ * @param {Object} source The source object.
+ * @param {string} key The key of the value to merge.
+ * @param {number} srcIndex The index of `source`.
+ * @param {Function} mergeFunc The function to merge values.
+ * @param {Function} [customizer] The function to customize assigned values.
+ * @param {Object} [stack] Tracks traversed source values and their merged
+ * counterparts.
+ */
+ function baseMergeDeep(object, source, key, srcIndex, mergeFunc, customizer, stack) {
+ var objValue = object[key],
+ srcValue = source[key],
+ stacked = stack.get(srcValue);
+
+ if (stacked) {
+ assignMergeValue(object, key, stacked);
+ return;
+ }
+ var newValue = customizer
+ ? customizer(objValue, srcValue, (key + ''), object, source, stack)
+ : undefined;
+
+ var isCommon = newValue === undefined;
+
+ if (isCommon) {
+ var isArr = isArray(srcValue),
+ isBuff = !isArr && isBuffer(srcValue),
+ isTyped = !isArr && !isBuff && isTypedArray(srcValue);
+
+ newValue = srcValue;
+ if (isArr || isBuff || isTyped) {
+ if (isArray(objValue)) {
+ newValue = objValue;
+ }
+ else if (isArrayLikeObject(objValue)) {
+ newValue = copyArray(objValue);
+ }
+ else if (isBuff) {
+ isCommon = false;
+ newValue = cloneBuffer(srcValue, true);
+ }
+ else if (isTyped) {
+ isCommon = false;
+ newValue = cloneTypedArray(srcValue, true);
+ }
+ else {
+ newValue = [];
+ }
+ }
+ else if (isPlainObject(srcValue) || isArguments(srcValue)) {
+ newValue = objValue;
+ if (isArguments(objValue)) {
+ newValue = toPlainObject(objValue);
+ }
+ else if (!isObject(objValue) || (srcIndex && isFunction(objValue))) {
+ newValue = initCloneObject(srcValue);
+ }
+ }
+ else {
+ isCommon = false;
+ }
+ }
+ if (isCommon) {
+ // Recursively merge objects and arrays (susceptible to call stack limits).
+ stack.set(srcValue, newValue);
+ mergeFunc(newValue, srcValue, srcIndex, customizer, stack);
+ stack['delete'](srcValue);
+ }
+ assignMergeValue(object, key, newValue);
+ }
+
+ /**
+ * The base implementation of `_.nth` which doesn't coerce arguments.
+ *
+ * @private
+ * @param {Array} array The array to query.
+ * @param {number} n The index of the element to return.
+ * @returns {*} Returns the nth element of `array`.
+ */
+ function baseNth(array, n) {
+ var length = array.length;
+ if (!length) {
+ return;
+ }
+ n += n < 0 ? length : 0;
+ return isIndex(n, length) ? array[n] : undefined;
+ }
+
+ /**
+ * The base implementation of `_.orderBy` without param guards.
+ *
+ * @private
+ * @param {Array|Object} collection The collection to iterate over.
+ * @param {Function[]|Object[]|string[]} iteratees The iteratees to sort by.
+ * @param {string[]} orders The sort orders of `iteratees`.
+ * @returns {Array} Returns the new sorted array.
+ */
+ function baseOrderBy(collection, iteratees, orders) {
+ var index = -1;
+ iteratees = arrayMap(iteratees.length ? iteratees : [identity], baseUnary(getIteratee()));
+
+ var result = baseMap(collection, function(value, key, collection) {
+ var criteria = arrayMap(iteratees, function(iteratee) {
+ return iteratee(value);
+ });
+ return { 'criteria': criteria, 'index': ++index, 'value': value };
+ });
+
+ return baseSortBy(result, function(object, other) {
+ return compareMultiple(object, other, orders);
+ });
+ }
+
+ /**
+ * The base implementation of `_.pick` without support for individual
+ * property identifiers.
+ *
+ * @private
+ * @param {Object} object The source object.
+ * @param {string[]} props The property identifiers to pick.
+ * @returns {Object} Returns the new object.
+ */
+ function basePick(object, props) {
+ object = Object(object);
+ return basePickBy(object, props, function(value, key) {
+ return key in object;
+ });
+ }
+
+ /**
+ * The base implementation of `_.pickBy` without support for iteratee shorthands.
+ *
+ * @private
+ * @param {Object} object The source object.
+ * @param {string[]} props The property identifiers to pick from.
+ * @param {Function} predicate The function invoked per property.
+ * @returns {Object} Returns the new object.
+ */
+ function basePickBy(object, props, predicate) {
+ var index = -1,
+ length = props.length,
+ result = {};
+
+ while (++index < length) {
+ var key = props[index],
+ value = object[key];
+
+ if (predicate(value, key)) {
+ baseAssignValue(result, key, value);
+ }
+ }
+ return result;
+ }
+
+ /**
+ * A specialized version of `baseProperty` which supports deep paths.
+ *
+ * @private
+ * @param {Array|string} path The path of the property to get.
+ * @returns {Function} Returns the new accessor function.
+ */
+ function basePropertyDeep(path) {
+ return function(object) {
+ return baseGet(object, path);
+ };
+ }
+
+ /**
+ * The base implementation of `_.pullAllBy` without support for iteratee
+ * shorthands.
+ *
+ * @private
+ * @param {Array} array The array to modify.
+ * @param {Array} values The values to remove.
+ * @param {Function} [iteratee] The iteratee invoked per element.
+ * @param {Function} [comparator] The comparator invoked per element.
+ * @returns {Array} Returns `array`.
+ */
+ function basePullAll(array, values, iteratee, comparator) {
+ var indexOf = comparator ? baseIndexOfWith : baseIndexOf,
+ index = -1,
+ length = values.length,
+ seen = array;
+
+ if (array === values) {
+ values = copyArray(values);
+ }
+ if (iteratee) {
+ seen = arrayMap(array, baseUnary(iteratee));
+ }
+ while (++index < length) {
+ var fromIndex = 0,
+ value = values[index],
+ computed = iteratee ? iteratee(value) : value;
+
+ while ((fromIndex = indexOf(seen, computed, fromIndex, comparator)) > -1) {
+ if (seen !== array) {
+ splice.call(seen, fromIndex, 1);
+ }
+ splice.call(array, fromIndex, 1);
+ }
+ }
+ return array;
+ }
+
+ /**
+ * The base implementation of `_.pullAt` without support for individual
+ * indexes or capturing the removed elements.
+ *
+ * @private
+ * @param {Array} array The array to modify.
+ * @param {number[]} indexes The indexes of elements to remove.
+ * @returns {Array} Returns `array`.
+ */
+ function basePullAt(array, indexes) {
+ var length = array ? indexes.length : 0,
+ lastIndex = length - 1;
+
+ while (length--) {
+ var index = indexes[length];
+ if (length == lastIndex || index !== previous) {
+ var previous = index;
+ if (isIndex(index)) {
+ splice.call(array, index, 1);
+ }
+ else if (!isKey(index, array)) {
+ var path = castPath(index),
+ object = parent(array, path);
+
+ if (object != null) {
+ delete object[toKey(last(path))];
+ }
+ }
+ else {
+ delete array[toKey(index)];
+ }
+ }
+ }
+ return array;
+ }
+
+ /**
+ * The base implementation of `_.random` without support for returning
+ * floating-point numbers.
+ *
+ * @private
+ * @param {number} lower The lower bound.
+ * @param {number} upper The upper bound.
+ * @returns {number} Returns the random number.
+ */
+ function baseRandom(lower, upper) {
+ return lower + nativeFloor(nativeRandom() * (upper - lower + 1));
+ }
+
+ /**
+ * The base implementation of `_.range` and `_.rangeRight` which doesn't
+ * coerce arguments.
+ *
+ * @private
+ * @param {number} start The start of the range.
+ * @param {number} end The end of the range.
+ * @param {number} step The value to increment or decrement by.
+ * @param {boolean} [fromRight] Specify iterating from right to left.
+ * @returns {Array} Returns the range of numbers.
+ */
+ function baseRange(start, end, step, fromRight) {
+ var index = -1,
+ length = nativeMax(nativeCeil((end - start) / (step || 1)), 0),
+ result = Array(length);
+
+ while (length--) {
+ result[fromRight ? length : ++index] = start;
+ start += step;
+ }
+ return result;
+ }
+
+ /**
+ * The base implementation of `_.repeat` which doesn't coerce arguments.
+ *
+ * @private
+ * @param {string} string The string to repeat.
+ * @param {number} n The number of times to repeat the string.
+ * @returns {string} Returns the repeated string.
+ */
+ function baseRepeat(string, n) {
+ var result = '';
+ if (!string || n < 1 || n > MAX_SAFE_INTEGER) {
+ return result;
+ }
+ // Leverage the exponentiation by squaring algorithm for a faster repeat.
+ // See https://en.wikipedia.org/wiki/Exponentiation_by_squaring for more details.
+ do {
+ if (n % 2) {
+ result += string;
+ }
+ n = nativeFloor(n / 2);
+ if (n) {
+ string += string;
+ }
+ } while (n);
+
+ return result;
+ }
+
+ /**
+ * The base implementation of `_.rest` which doesn't validate or coerce arguments.
+ *
+ * @private
+ * @param {Function} func The function to apply a rest parameter to.
+ * @param {number} [start=func.length-1] The start position of the rest parameter.
+ * @returns {Function} Returns the new function.
+ */
+ function baseRest(func, start) {
+ return setToString(overRest(func, start, identity), func + '');
+ }
+
+ /**
+ * The base implementation of `_.sample`.
+ *
+ * @private
+ * @param {Array|Object} collection The collection to sample.
+ * @returns {*} Returns the random element.
+ */
+ function baseSample(collection) {
+ return arraySample(values(collection));
+ }
+
+ /**
+ * The base implementation of `_.sampleSize` without param guards.
+ *
+ * @private
+ * @param {Array|Object} collection The collection to sample.
+ * @param {number} n The number of elements to sample.
+ * @returns {Array} Returns the random elements.
+ */
+ function baseSampleSize(collection, n) {
+ var array = values(collection);
+ return shuffleSelf(array, baseClamp(n, 0, array.length));
+ }
+
+ /**
+ * The base implementation of `_.set`.
+ *
+ * @private
+ * @param {Object} object The object to modify.
+ * @param {Array|string} path The path of the property to set.
+ * @param {*} value The value to set.
+ * @param {Function} [customizer] The function to customize path creation.
+ * @returns {Object} Returns `object`.
+ */
+ function baseSet(object, path, value, customizer) {
+ if (!isObject(object)) {
+ return object;
+ }
+ path = isKey(path, object) ? [path] : castPath(path);
+
+ var index = -1,
+ length = path.length,
+ lastIndex = length - 1,
+ nested = object;
+
+ while (nested != null && ++index < length) {
+ var key = toKey(path[index]),
+ newValue = value;
+
+ if (index != lastIndex) {
+ var objValue = nested[key];
+ newValue = customizer ? customizer(objValue, key, nested) : undefined;
+ if (newValue === undefined) {
+ newValue = isObject(objValue)
+ ? objValue
+ : (isIndex(path[index + 1]) ? [] : {});
+ }
+ }
+ assignValue(nested, key, newValue);
+ nested = nested[key];
+ }
+ return object;
+ }
+
+ /**
+ * The base implementation of `setData` without support for hot loop shorting.
+ *
+ * @private
+ * @param {Function} func The function to associate metadata with.
+ * @param {*} data The metadata.
+ * @returns {Function} Returns `func`.
+ */
+ var baseSetData = !metaMap ? identity : function(func, data) {
+ metaMap.set(func, data);
+ return func;
+ };
+
+ /**
+ * The base implementation of `setToString` without support for hot loop shorting.
+ *
+ * @private
+ * @param {Function} func The function to modify.
+ * @param {Function} string The `toString` result.
+ * @returns {Function} Returns `func`.
+ */
+ var baseSetToString = !defineProperty ? identity : function(func, string) {
+ return defineProperty(func, 'toString', {
+ 'configurable': true,
+ 'enumerable': false,
+ 'value': constant(string),
+ 'writable': true
+ });
+ };
+
+ /**
+ * The base implementation of `_.shuffle`.
+ *
+ * @private
+ * @param {Array|Object} collection The collection to shuffle.
+ * @returns {Array} Returns the new shuffled array.
+ */
+ function baseShuffle(collection) {
+ return shuffleSelf(values(collection));
+ }
+
+ /**
+ * The base implementation of `_.slice` without an iteratee call guard.
+ *
+ * @private
+ * @param {Array} array The array to slice.
+ * @param {number} [start=0] The start position.
+ * @param {number} [end=array.length] The end position.
+ * @returns {Array} Returns the slice of `array`.
+ */
+ function baseSlice(array, start, end) {
+ var index = -1,
+ length = array.length;
+
+ if (start < 0) {
+ start = -start > length ? 0 : (length + start);
+ }
+ end = end > length ? length : end;
+ if (end < 0) {
+ end += length;
+ }
+ length = start > end ? 0 : ((end - start) >>> 0);
+ start >>>= 0;
+
+ var result = Array(length);
+ while (++index < length) {
+ result[index] = array[index + start];
+ }
+ return result;
+ }
+
+ /**
+ * The base implementation of `_.some` without support for iteratee shorthands.
+ *
+ * @private
+ * @param {Array|Object} collection The collection to iterate over.
+ * @param {Function} predicate The function invoked per iteration.
+ * @returns {boolean} Returns `true` if any element passes the predicate check,
+ * else `false`.
+ */
+ function baseSome(collection, predicate) {
+ var result;
+
+ baseEach(collection, function(value, index, collection) {
+ result = predicate(value, index, collection);
+ return !result;
+ });
+ return !!result;
+ }
+
+ /**
+ * The base implementation of `_.sortedIndex` and `_.sortedLastIndex` which
+ * performs a binary search of `array` to determine the index at which `value`
+ * should be inserted into `array` in order to maintain its sort order.
+ *
+ * @private
+ * @param {Array} array The sorted array to inspect.
+ * @param {*} value The value to evaluate.
+ * @param {boolean} [retHighest] Specify returning the highest qualified index.
+ * @returns {number} Returns the index at which `value` should be inserted
+ * into `array`.
+ */
+ function baseSortedIndex(array, value, retHighest) {
+ var low = 0,
+ high = array ? array.length : low;
+
+ if (typeof value == 'number' && value === value && high <= HALF_MAX_ARRAY_LENGTH) {
+ while (low < high) {
+ var mid = (low + high) >>> 1,
+ computed = array[mid];
+
+ if (computed !== null && !isSymbol(computed) &&
+ (retHighest ? (computed <= value) : (computed < value))) {
+ low = mid + 1;
+ } else {
+ high = mid;
+ }
+ }
+ return high;
+ }
+ return baseSortedIndexBy(array, value, identity, retHighest);
+ }
+
+ /**
+ * The base implementation of `_.sortedIndexBy` and `_.sortedLastIndexBy`
+ * which invokes `iteratee` for `value` and each element of `array` to compute
+ * their sort ranking. The iteratee is invoked with one argument; (value).
+ *
+ * @private
+ * @param {Array} array The sorted array to inspect.
+ * @param {*} value The value to evaluate.
+ * @param {Function} iteratee The iteratee invoked per element.
+ * @param {boolean} [retHighest] Specify returning the highest qualified index.
+ * @returns {number} Returns the index at which `value` should be inserted
+ * into `array`.
+ */
+ function baseSortedIndexBy(array, value, iteratee, retHighest) {
+ value = iteratee(value);
+
+ var low = 0,
+ high = array ? array.length : 0,
+ valIsNaN = value !== value,
+ valIsNull = value === null,
+ valIsSymbol = isSymbol(value),
+ valIsUndefined = value === undefined;
+
+ while (low < high) {
+ var mid = nativeFloor((low + high) / 2),
+ computed = iteratee(array[mid]),
+ othIsDefined = computed !== undefined,
+ othIsNull = computed === null,
+ othIsReflexive = computed === computed,
+ othIsSymbol = isSymbol(computed);
+
+ if (valIsNaN) {
+ var setLow = retHighest || othIsReflexive;
+ } else if (valIsUndefined) {
+ setLow = othIsReflexive && (retHighest || othIsDefined);
+ } else if (valIsNull) {
+ setLow = othIsReflexive && othIsDefined && (retHighest || !othIsNull);
+ } else if (valIsSymbol) {
+ setLow = othIsReflexive && othIsDefined && !othIsNull && (retHighest || !othIsSymbol);
+ } else if (othIsNull || othIsSymbol) {
+ setLow = false;
+ } else {
+ setLow = retHighest ? (computed <= value) : (computed < value);
+ }
+ if (setLow) {
+ low = mid + 1;
+ } else {
+ high = mid;
+ }
+ }
+ return nativeMin(high, MAX_ARRAY_INDEX);
+ }
+
+ /**
+ * The base implementation of `_.sortedUniq` and `_.sortedUniqBy` without
+ * support for iteratee shorthands.
+ *
+ * @private
+ * @param {Array} array The array to inspect.
+ * @param {Function} [iteratee] The iteratee invoked per element.
+ * @returns {Array} Returns the new duplicate free array.
+ */
+ function baseSortedUniq(array, iteratee) {
+ var index = -1,
+ length = array.length,
+ resIndex = 0,
+ result = [];
+
+ while (++index < length) {
+ var value = array[index],
+ computed = iteratee ? iteratee(value) : value;
+
+ if (!index || !eq(computed, seen)) {
+ var seen = computed;
+ result[resIndex++] = value === 0 ? 0 : value;
+ }
+ }
+ return result;
+ }
+
+ /**
+ * The base implementation of `_.toNumber` which doesn't ensure correct
+ * conversions of binary, hexadecimal, or octal string values.
+ *
+ * @private
+ * @param {*} value The value to process.
+ * @returns {number} Returns the number.
+ */
+ function baseToNumber(value) {
+ if (typeof value == 'number') {
+ return value;
+ }
+ if (isSymbol(value)) {
+ return NAN;
+ }
+ return +value;
+ }
+
+ /**
+ * The base implementation of `_.toString` which doesn't convert nullish
+ * values to empty strings.
+ *
+ * @private
+ * @param {*} value The value to process.
+ * @returns {string} Returns the string.
+ */
+ function baseToString(value) {
+ // Exit early for strings to avoid a performance hit in some environments.
+ if (typeof value == 'string') {
+ return value;
+ }
+ if (isArray(value)) {
+ // Recursively convert values (susceptible to call stack limits).
+ return arrayMap(value, baseToString) + '';
+ }
+ if (isSymbol(value)) {
+ return symbolToString ? symbolToString.call(value) : '';
+ }
+ var result = (value + '');
+ return (result == '0' && (1 / value) == -INFINITY) ? '-0' : result;
+ }
+
+ /**
+ * The base implementation of `_.uniqBy` without support for iteratee shorthands.
+ *
+ * @private
+ * @param {Array} array The array to inspect.
+ * @param {Function} [iteratee] The iteratee invoked per element.
+ * @param {Function} [comparator] The comparator invoked per element.
+ * @returns {Array} Returns the new duplicate free array.
+ */
+ function baseUniq(array, iteratee, comparator) {
+ var index = -1,
+ includes = arrayIncludes,
+ length = array.length,
+ isCommon = true,
+ result = [],
+ seen = result;
+
+ if (comparator) {
+ isCommon = false;
+ includes = arrayIncludesWith;
+ }
+ else if (length >= LARGE_ARRAY_SIZE) {
+ var set = iteratee ? null : createSet(array);
+ if (set) {
+ return setToArray(set);
+ }
+ isCommon = false;
+ includes = cacheHas;
+ seen = new SetCache;
+ }
+ else {
+ seen = iteratee ? [] : result;
+ }
+ outer:
+ while (++index < length) {
+ var value = array[index],
+ computed = iteratee ? iteratee(value) : value;
+
+ value = (comparator || value !== 0) ? value : 0;
+ if (isCommon && computed === computed) {
+ var seenIndex = seen.length;
+ while (seenIndex--) {
+ if (seen[seenIndex] === computed) {
+ continue outer;
+ }
+ }
+ if (iteratee) {
+ seen.push(computed);
+ }
+ result.push(value);
+ }
+ else if (!includes(seen, computed, comparator)) {
+ if (seen !== result) {
+ seen.push(computed);
+ }
+ result.push(value);
+ }
+ }
+ return result;
+ }
+
+ /**
+ * The base implementation of `_.unset`.
+ *
+ * @private
+ * @param {Object} object The object to modify.
+ * @param {Array|string} path The path of the property to unset.
+ * @returns {boolean} Returns `true` if the property is deleted, else `false`.
+ */
+ function baseUnset(object, path) {
+ path = isKey(path, object) ? [path] : castPath(path);
+ object = parent(object, path);
+
+ var key = toKey(last(path));
+ return !(object != null && hasOwnProperty.call(object, key)) || delete object[key];
+ }
+
+ /**
+ * The base implementation of `_.update`.
+ *
+ * @private
+ * @param {Object} object The object to modify.
+ * @param {Array|string} path The path of the property to update.
+ * @param {Function} updater The function to produce the updated value.
+ * @param {Function} [customizer] The function to customize path creation.
+ * @returns {Object} Returns `object`.
+ */
+ function baseUpdate(object, path, updater, customizer) {
+ return baseSet(object, path, updater(baseGet(object, path)), customizer);
+ }
+
+ /**
+ * The base implementation of methods like `_.dropWhile` and `_.takeWhile`
+ * without support for iteratee shorthands.
+ *
+ * @private
+ * @param {Array} array The array to query.
+ * @param {Function} predicate The function invoked per iteration.
+ * @param {boolean} [isDrop] Specify dropping elements instead of taking them.
+ * @param {boolean} [fromRight] Specify iterating from right to left.
+ * @returns {Array} Returns the slice of `array`.
+ */
+ function baseWhile(array, predicate, isDrop, fromRight) {
+ var length = array.length,
+ index = fromRight ? length : -1;
+
+ while ((fromRight ? index-- : ++index < length) &&
+ predicate(array[index], index, array)) {}
+
+ return isDrop
+ ? baseSlice(array, (fromRight ? 0 : index), (fromRight ? index + 1 : length))
+ : baseSlice(array, (fromRight ? index + 1 : 0), (fromRight ? length : index));
+ }
+
+ /**
+ * The base implementation of `wrapperValue` which returns the result of
+ * performing a sequence of actions on the unwrapped `value`, where each
+ * successive action is supplied the return value of the previous.
+ *
+ * @private
+ * @param {*} value The unwrapped value.
+ * @param {Array} actions Actions to perform to resolve the unwrapped value.
+ * @returns {*} Returns the resolved value.
+ */
+ function baseWrapperValue(value, actions) {
+ var result = value;
+ if (result instanceof LazyWrapper) {
+ result = result.value();
+ }
+ return arrayReduce(actions, function(result, action) {
+ return action.func.apply(action.thisArg, arrayPush([result], action.args));
+ }, result);
+ }
+
+ /**
+ * The base implementation of methods like `_.xor`, without support for
+ * iteratee shorthands, that accepts an array of arrays to inspect.
+ *
+ * @private
+ * @param {Array} arrays The arrays to inspect.
+ * @param {Function} [iteratee] The iteratee invoked per element.
+ * @param {Function} [comparator] The comparator invoked per element.
+ * @returns {Array} Returns the new array of values.
+ */
+ function baseXor(arrays, iteratee, comparator) {
+ var index = -1,
+ length = arrays.length;
+
+ while (++index < length) {
+ var result = result
+ ? arrayPush(
+ baseDifference(result, arrays[index], iteratee, comparator),
+ baseDifference(arrays[index], result, iteratee, comparator)
+ )
+ : arrays[index];
+ }
+ return (result && result.length) ? baseUniq(result, iteratee, comparator) : [];
+ }
+
+ /**
+ * This base implementation of `_.zipObject` which assigns values using `assignFunc`.
+ *
+ * @private
+ * @param {Array} props The property identifiers.
+ * @param {Array} values The property values.
+ * @param {Function} assignFunc The function to assign values.
+ * @returns {Object} Returns the new object.
+ */
+ function baseZipObject(props, values, assignFunc) {
+ var index = -1,
+ length = props.length,
+ valsLength = values.length,
+ result = {};
+
+ while (++index < length) {
+ var value = index < valsLength ? values[index] : undefined;
+ assignFunc(result, props[index], value);
+ }
+ return result;
+ }
+
+ /**
+ * Casts `value` to an empty array if it's not an array like object.
+ *
+ * @private
+ * @param {*} value The value to inspect.
+ * @returns {Array|Object} Returns the cast array-like object.
+ */
+ function castArrayLikeObject(value) {
+ return isArrayLikeObject(value) ? value : [];
+ }
+
+ /**
+ * Casts `value` to `identity` if it's not a function.
+ *
+ * @private
+ * @param {*} value The value to inspect.
+ * @returns {Function} Returns cast function.
+ */
+ function castFunction(value) {
+ return typeof value == 'function' ? value : identity;
+ }
+
+ /**
+ * Casts `value` to a path array if it's not one.
+ *
+ * @private
+ * @param {*} value The value to inspect.
+ * @returns {Array} Returns the cast property path array.
+ */
+ function castPath(value) {
+ return isArray(value) ? value : stringToPath(value);
+ }
+
+ /**
+ * A `baseRest` alias which can be replaced with `identity` by module
+ * replacement plugins.
+ *
+ * @private
+ * @type {Function}
+ * @param {Function} func The function to apply a rest parameter to.
+ * @returns {Function} Returns the new function.
+ */
+ var castRest = baseRest;
+
+ /**
+ * Casts `array` to a slice if it's needed.
+ *
+ * @private
+ * @param {Array} array The array to inspect.
+ * @param {number} start The start position.
+ * @param {number} [end=array.length] The end position.
+ * @returns {Array} Returns the cast slice.
+ */
+ function castSlice(array, start, end) {
+ var length = array.length;
+ end = end === undefined ? length : end;
+ return (!start && end >= length) ? array : baseSlice(array, start, end);
+ }
+
+ /**
+ * A simple wrapper around the global [`clearTimeout`](https://mdn.io/clearTimeout).
+ *
+ * @private
+ * @param {number|Object} id The timer id or timeout object of the timer to clear.
+ */
+ var clearTimeout = ctxClearTimeout || function(id) {
+ return root.clearTimeout(id);
+ };
+
+ /**
+ * Creates a clone of `buffer`.
+ *
+ * @private
+ * @param {Buffer} buffer The buffer to clone.
+ * @param {boolean} [isDeep] Specify a deep clone.
+ * @returns {Buffer} Returns the cloned buffer.
+ */
+ function cloneBuffer(buffer, isDeep) {
+ if (isDeep) {
+ return buffer.slice();
+ }
+ var length = buffer.length,
+ result = allocUnsafe ? allocUnsafe(length) : new buffer.constructor(length);
+
+ buffer.copy(result);
+ return result;
+ }
+
+ /**
+ * Creates a clone of `arrayBuffer`.
+ *
+ * @private
+ * @param {ArrayBuffer} arrayBuffer The array buffer to clone.
+ * @returns {ArrayBuffer} Returns the cloned array buffer.
+ */
+ function cloneArrayBuffer(arrayBuffer) {
+ var result = new arrayBuffer.constructor(arrayBuffer.byteLength);
+ new Uint8Array(result).set(new Uint8Array(arrayBuffer));
+ return result;
+ }
+
+ /**
+ * Creates a clone of `dataView`.
+ *
+ * @private
+ * @param {Object} dataView The data view to clone.
+ * @param {boolean} [isDeep] Specify a deep clone.
+ * @returns {Object} Returns the cloned data view.
+ */
+ function cloneDataView(dataView, isDeep) {
+ var buffer = isDeep ? cloneArrayBuffer(dataView.buffer) : dataView.buffer;
+ return new dataView.constructor(buffer, dataView.byteOffset, dataView.byteLength);
+ }
+
+ /**
+ * Creates a clone of `map`.
+ *
+ * @private
+ * @param {Object} map The map to clone.
+ * @param {Function} cloneFunc The function to clone values.
+ * @param {boolean} [isDeep] Specify a deep clone.
+ * @returns {Object} Returns the cloned map.
+ */
+ function cloneMap(map, isDeep, cloneFunc) {
+ var array = isDeep ? cloneFunc(mapToArray(map), true) : mapToArray(map);
+ return arrayReduce(array, addMapEntry, new map.constructor);
+ }
+
+ /**
+ * Creates a clone of `regexp`.
+ *
+ * @private
+ * @param {Object} regexp The regexp to clone.
+ * @returns {Object} Returns the cloned regexp.
+ */
+ function cloneRegExp(regexp) {
+ var result = new regexp.constructor(regexp.source, reFlags.exec(regexp));
+ result.lastIndex = regexp.lastIndex;
+ return result;
+ }
+
+ /**
+ * Creates a clone of `set`.
+ *
+ * @private
+ * @param {Object} set The set to clone.
+ * @param {Function} cloneFunc The function to clone values.
+ * @param {boolean} [isDeep] Specify a deep clone.
+ * @returns {Object} Returns the cloned set.
+ */
+ function cloneSet(set, isDeep, cloneFunc) {
+ var array = isDeep ? cloneFunc(setToArray(set), true) : setToArray(set);
+ return arrayReduce(array, addSetEntry, new set.constructor);
+ }
+
+ /**
+ * Creates a clone of the `symbol` object.
+ *
+ * @private
+ * @param {Object} symbol The symbol object to clone.
+ * @returns {Object} Returns the cloned symbol object.
+ */
+ function cloneSymbol(symbol) {
+ return symbolValueOf ? Object(symbolValueOf.call(symbol)) : {};
+ }
+
+ /**
+ * Creates a clone of `typedArray`.
+ *
+ * @private
+ * @param {Object} typedArray The typed array to clone.
+ * @param {boolean} [isDeep] Specify a deep clone.
+ * @returns {Object} Returns the cloned typed array.
+ */
+ function cloneTypedArray(typedArray, isDeep) {
+ var buffer = isDeep ? cloneArrayBuffer(typedArray.buffer) : typedArray.buffer;
+ return new typedArray.constructor(buffer, typedArray.byteOffset, typedArray.length);
+ }
+
+ /**
+ * Compares values to sort them in ascending order.
+ *
+ * @private
+ * @param {*} value The value to compare.
+ * @param {*} other The other value to compare.
+ * @returns {number} Returns the sort order indicator for `value`.
+ */
+ function compareAscending(value, other) {
+ if (value !== other) {
+ var valIsDefined = value !== undefined,
+ valIsNull = value === null,
+ valIsReflexive = value === value,
+ valIsSymbol = isSymbol(value);
+
+ var othIsDefined = other !== undefined,
+ othIsNull = other === null,
+ othIsReflexive = other === other,
+ othIsSymbol = isSymbol(other);
+
+ if ((!othIsNull && !othIsSymbol && !valIsSymbol && value > other) ||
+ (valIsSymbol && othIsDefined && othIsReflexive && !othIsNull && !othIsSymbol) ||
+ (valIsNull && othIsDefined && othIsReflexive) ||
+ (!valIsDefined && othIsReflexive) ||
+ !valIsReflexive) {
+ return 1;
+ }
+ if ((!valIsNull && !valIsSymbol && !othIsSymbol && value < other) ||
+ (othIsSymbol && valIsDefined && valIsReflexive && !valIsNull && !valIsSymbol) ||
+ (othIsNull && valIsDefined && valIsReflexive) ||
+ (!othIsDefined && valIsReflexive) ||
+ !othIsReflexive) {
+ return -1;
+ }
+ }
+ return 0;
+ }
+
+ /**
+ * Used by `_.orderBy` to compare multiple properties of a value to another
+ * and stable sort them.
+ *
+ * If `orders` is unspecified, all values are sorted in ascending order. Otherwise,
+ * specify an order of "desc" for descending or "asc" for ascending sort order
+ * of corresponding values.
+ *
+ * @private
+ * @param {Object} object The object to compare.
+ * @param {Object} other The other object to compare.
+ * @param {boolean[]|string[]} orders The order to sort by for each property.
+ * @returns {number} Returns the sort order indicator for `object`.
+ */
+ function compareMultiple(object, other, orders) {
+ var index = -1,
+ objCriteria = object.criteria,
+ othCriteria = other.criteria,
+ length = objCriteria.length,
+ ordersLength = orders.length;
+
+ while (++index < length) {
+ var result = compareAscending(objCriteria[index], othCriteria[index]);
+ if (result) {
+ if (index >= ordersLength) {
+ return result;
+ }
+ var order = orders[index];
+ return result * (order == 'desc' ? -1 : 1);
+ }
+ }
+ // Fixes an `Array#sort` bug in the JS engine embedded in Adobe applications
+ // that causes it, under certain circumstances, to provide the same value for
+ // `object` and `other`. See https://github.com/jashkenas/underscore/pull/1247
+ // for more details.
+ //
+ // This also ensures a stable sort in V8 and other engines.
+ // See https://bugs.chromium.org/p/v8/issues/detail?id=90 for more details.
+ return object.index - other.index;
+ }
+
+ /**
+ * Creates an array that is the composition of partially applied arguments,
+ * placeholders, and provided arguments into a single array of arguments.
+ *
+ * @private
+ * @param {Array} args The provided arguments.
+ * @param {Array} partials The arguments to prepend to those provided.
+ * @param {Array} holders The `partials` placeholder indexes.
+ * @params {boolean} [isCurried] Specify composing for a curried function.
+ * @returns {Array} Returns the new array of composed arguments.
+ */
+ function composeArgs(args, partials, holders, isCurried) {
+ var argsIndex = -1,
+ argsLength = args.length,
+ holdersLength = holders.length,
+ leftIndex = -1,
+ leftLength = partials.length,
+ rangeLength = nativeMax(argsLength - holdersLength, 0),
+ result = Array(leftLength + rangeLength),
+ isUncurried = !isCurried;
+
+ while (++leftIndex < leftLength) {
+ result[leftIndex] = partials[leftIndex];
+ }
+ while (++argsIndex < holdersLength) {
+ if (isUncurried || argsIndex < argsLength) {
+ result[holders[argsIndex]] = args[argsIndex];
+ }
+ }
+ while (rangeLength--) {
+ result[leftIndex++] = args[argsIndex++];
+ }
+ return result;
+ }
+
+ /**
+ * This function is like `composeArgs` except that the arguments composition
+ * is tailored for `_.partialRight`.
+ *
+ * @private
+ * @param {Array} args The provided arguments.
+ * @param {Array} partials The arguments to append to those provided.
+ * @param {Array} holders The `partials` placeholder indexes.
+ * @params {boolean} [isCurried] Specify composing for a curried function.
+ * @returns {Array} Returns the new array of composed arguments.
+ */
+ function composeArgsRight(args, partials, holders, isCurried) {
+ var argsIndex = -1,
+ argsLength = args.length,
+ holdersIndex = -1,
+ holdersLength = holders.length,
+ rightIndex = -1,
+ rightLength = partials.length,
+ rangeLength = nativeMax(argsLength - holdersLength, 0),
+ result = Array(rangeLength + rightLength),
+ isUncurried = !isCurried;
+
+ while (++argsIndex < rangeLength) {
+ result[argsIndex] = args[argsIndex];
+ }
+ var offset = argsIndex;
+ while (++rightIndex < rightLength) {
+ result[offset + rightIndex] = partials[rightIndex];
+ }
+ while (++holdersIndex < holdersLength) {
+ if (isUncurried || argsIndex < argsLength) {
+ result[offset + holders[holdersIndex]] = args[argsIndex++];
+ }
+ }
+ return result;
+ }
+
+ /**
+ * Copies the values of `source` to `array`.
+ *
+ * @private
+ * @param {Array} source The array to copy values from.
+ * @param {Array} [array=[]] The array to copy values to.
+ * @returns {Array} Returns `array`.
+ */
+ function copyArray(source, array) {
+ var index = -1,
+ length = source.length;
+
+ array || (array = Array(length));
+ while (++index < length) {
+ array[index] = source[index];
+ }
+ return array;
+ }
+
+ /**
+ * Copies properties of `source` to `object`.
+ *
+ * @private
+ * @param {Object} source The object to copy properties from.
+ * @param {Array} props The property identifiers to copy.
+ * @param {Object} [object={}] The object to copy properties to.
+ * @param {Function} [customizer] The function to customize copied values.
+ * @returns {Object} Returns `object`.
+ */
+ function copyObject(source, props, object, customizer) {
+ var isNew = !object;
+ object || (object = {});
+
+ var index = -1,
+ length = props.length;
+
+ while (++index < length) {
+ var key = props[index];
+
+ var newValue = customizer
+ ? customizer(object[key], source[key], key, object, source)
+ : undefined;
+
+ if (newValue === undefined) {
+ newValue = source[key];
+ }
+ if (isNew) {
+ baseAssignValue(object, key, newValue);
+ } else {
+ assignValue(object, key, newValue);
+ }
+ }
+ return object;
+ }
+
+ /**
+ * Copies own symbol properties of `source` to `object`.
+ *
+ * @private
+ * @param {Object} source The object to copy symbols from.
+ * @param {Object} [object={}] The object to copy symbols to.
+ * @returns {Object} Returns `object`.
+ */
+ function copySymbols(source, object) {
+ return copyObject(source, getSymbols(source), object);
+ }
+
+ /**
+ * Creates a function like `_.groupBy`.
+ *
+ * @private
+ * @param {Function} setter The function to set accumulator values.
+ * @param {Function} [initializer] The accumulator object initializer.
+ * @returns {Function} Returns the new aggregator function.
+ */
+ function createAggregator(setter, initializer) {
+ return function(collection, iteratee) {
+ var func = isArray(collection) ? arrayAggregator : baseAggregator,
+ accumulator = initializer ? initializer() : {};
+
+ return func(collection, setter, getIteratee(iteratee, 2), accumulator);
+ };
+ }
+
+ /**
+ * Creates a function like `_.assign`.
+ *
+ * @private
+ * @param {Function} assigner The function to assign values.
+ * @returns {Function} Returns the new assigner function.
+ */
+ function createAssigner(assigner) {
+ return baseRest(function(object, sources) {
+ var index = -1,
+ length = sources.length,
+ customizer = length > 1 ? sources[length - 1] : undefined,
+ guard = length > 2 ? sources[2] : undefined;
+
+ customizer = (assigner.length > 3 && typeof customizer == 'function')
+ ? (length--, customizer)
+ : undefined;
+
+ if (guard && isIterateeCall(sources[0], sources[1], guard)) {
+ customizer = length < 3 ? undefined : customizer;
+ length = 1;
+ }
+ object = Object(object);
+ while (++index < length) {
+ var source = sources[index];
+ if (source) {
+ assigner(object, source, index, customizer);
+ }
+ }
+ return object;
+ });
+ }
+
+ /**
+ * Creates a `baseEach` or `baseEachRight` function.
+ *
+ * @private
+ * @param {Function} eachFunc The function to iterate over a collection.
+ * @param {boolean} [fromRight] Specify iterating from right to left.
+ * @returns {Function} Returns the new base function.
+ */
+ function createBaseEach(eachFunc, fromRight) {
+ return function(collection, iteratee) {
+ if (collection == null) {
+ return collection;
+ }
+ if (!isArrayLike(collection)) {
+ return eachFunc(collection, iteratee);
+ }
+ var length = collection.length,
+ index = fromRight ? length : -1,
+ iterable = Object(collection);
+
+ while ((fromRight ? index-- : ++index < length)) {
+ if (iteratee(iterable[index], index, iterable) === false) {
+ break;
+ }
+ }
+ return collection;
+ };
+ }
+
+ /**
+ * Creates a base function for methods like `_.forIn` and `_.forOwn`.
+ *
+ * @private
+ * @param {boolean} [fromRight] Specify iterating from right to left.
+ * @returns {Function} Returns the new base function.
+ */
+ function createBaseFor(fromRight) {
+ return function(object, iteratee, keysFunc) {
+ var index = -1,
+ iterable = Object(object),
+ props = keysFunc(object),
+ length = props.length;
+
+ while (length--) {
+ var key = props[fromRight ? length : ++index];
+ if (iteratee(iterable[key], key, iterable) === false) {
+ break;
+ }
+ }
+ return object;
+ };
+ }
+
+ /**
+ * Creates a function that wraps `func` to invoke it with the optional `this`
+ * binding of `thisArg`.
+ *
+ * @private
+ * @param {Function} func The function to wrap.
+ * @param {number} bitmask The bitmask flags. See `createWrap` for more details.
+ * @param {*} [thisArg] The `this` binding of `func`.
+ * @returns {Function} Returns the new wrapped function.
+ */
+ function createBind(func, bitmask, thisArg) {
+ var isBind = bitmask & BIND_FLAG,
+ Ctor = createCtor(func);
+
+ function wrapper() {
+ var fn = (this && this !== root && this instanceof wrapper) ? Ctor : func;
+ return fn.apply(isBind ? thisArg : this, arguments);
+ }
+ return wrapper;
+ }
+
+ /**
+ * Creates a function like `_.lowerFirst`.
+ *
+ * @private
+ * @param {string} methodName The name of the `String` case method to use.
+ * @returns {Function} Returns the new case function.
+ */
+ function createCaseFirst(methodName) {
+ return function(string) {
+ string = toString(string);
+
+ var strSymbols = hasUnicode(string)
+ ? stringToArray(string)
+ : undefined;
+
+ var chr = strSymbols
+ ? strSymbols[0]
+ : string.charAt(0);
+
+ var trailing = strSymbols
+ ? castSlice(strSymbols, 1).join('')
+ : string.slice(1);
+
+ return chr[methodName]() + trailing;
+ };
+ }
+
+ /**
+ * Creates a function like `_.camelCase`.
+ *
+ * @private
+ * @param {Function} callback The function to combine each word.
+ * @returns {Function} Returns the new compounder function.
+ */
+ function createCompounder(callback) {
+ return function(string) {
+ return arrayReduce(words(deburr(string).replace(reApos, '')), callback, '');
+ };
+ }
+
+ /**
+ * Creates a function that produces an instance of `Ctor` regardless of
+ * whether it was invoked as part of a `new` expression or by `call` or `apply`.
+ *
+ * @private
+ * @param {Function} Ctor The constructor to wrap.
+ * @returns {Function} Returns the new wrapped function.
+ */
+ function createCtor(Ctor) {
+ return function() {
+ // Use a `switch` statement to work with class constructors. See
+ // http://ecma-international.org/ecma-262/7.0/#sec-ecmascript-function-objects-call-thisargument-argumentslist
+ // for more details.
+ var args = arguments;
+ switch (args.length) {
+ case 0: return new Ctor;
+ case 1: return new Ctor(args[0]);
+ case 2: return new Ctor(args[0], args[1]);
+ case 3: return new Ctor(args[0], args[1], args[2]);
+ case 4: return new Ctor(args[0], args[1], args[2], args[3]);
+ case 5: return new Ctor(args[0], args[1], args[2], args[3], args[4]);
+ case 6: return new Ctor(args[0], args[1], args[2], args[3], args[4], args[5]);
+ case 7: return new Ctor(args[0], args[1], args[2], args[3], args[4], args[5], args[6]);
+ }
+ var thisBinding = baseCreate(Ctor.prototype),
+ result = Ctor.apply(thisBinding, args);
+
+ // Mimic the constructor's `return` behavior.
+ // See https://es5.github.io/#x13.2.2 for more details.
+ return isObject(result) ? result : thisBinding;
+ };
+ }
+
+ /**
+ * Creates a function that wraps `func` to enable currying.
+ *
+ * @private
+ * @param {Function} func The function to wrap.
+ * @param {number} bitmask The bitmask flags. See `createWrap` for more details.
+ * @param {number} arity The arity of `func`.
+ * @returns {Function} Returns the new wrapped function.
+ */
+ function createCurry(func, bitmask, arity) {
+ var Ctor = createCtor(func);
+
+ function wrapper() {
+ var length = arguments.length,
+ args = Array(length),
+ index = length,
+ placeholder = getHolder(wrapper);
+
+ while (index--) {
+ args[index] = arguments[index];
+ }
+ var holders = (length < 3 && args[0] !== placeholder && args[length - 1] !== placeholder)
+ ? []
+ : replaceHolders(args, placeholder);
+
+ length -= holders.length;
+ if (length < arity) {
+ return createRecurry(
+ func, bitmask, createHybrid, wrapper.placeholder, undefined,
+ args, holders, undefined, undefined, arity - length);
+ }
+ var fn = (this && this !== root && this instanceof wrapper) ? Ctor : func;
+ return apply(fn, this, args);
+ }
+ return wrapper;
+ }
+
+ /**
+ * Creates a `_.find` or `_.findLast` function.
+ *
+ * @private
+ * @param {Function} findIndexFunc The function to find the collection index.
+ * @returns {Function} Returns the new find function.
+ */
+ function createFind(findIndexFunc) {
+ return function(collection, predicate, fromIndex) {
+ var iterable = Object(collection);
+ if (!isArrayLike(collection)) {
+ var iteratee = getIteratee(predicate, 3);
+ collection = keys(collection);
+ predicate = function(key) { return iteratee(iterable[key], key, iterable); };
+ }
+ var index = findIndexFunc(collection, predicate, fromIndex);
+ return index > -1 ? iterable[iteratee ? collection[index] : index] : undefined;
+ };
+ }
+
+ /**
+ * Creates a `_.flow` or `_.flowRight` function.
+ *
+ * @private
+ * @param {boolean} [fromRight] Specify iterating from right to left.
+ * @returns {Function} Returns the new flow function.
+ */
+ function createFlow(fromRight) {
+ return flatRest(function(funcs) {
+ var length = funcs.length,
+ index = length,
+ prereq = LodashWrapper.prototype.thru;
+
+ if (fromRight) {
+ funcs.reverse();
+ }
+ while (index--) {
+ var func = funcs[index];
+ if (typeof func != 'function') {
+ throw new TypeError(FUNC_ERROR_TEXT);
+ }
+ if (prereq && !wrapper && getFuncName(func) == 'wrapper') {
+ var wrapper = new LodashWrapper([], true);
+ }
+ }
+ index = wrapper ? index : length;
+ while (++index < length) {
+ func = funcs[index];
+
+ var funcName = getFuncName(func),
+ data = funcName == 'wrapper' ? getData(func) : undefined;
+
+ if (data && isLaziable(data[0]) &&
+ data[1] == (ARY_FLAG | CURRY_FLAG | PARTIAL_FLAG | REARG_FLAG) &&
+ !data[4].length && data[9] == 1
+ ) {
+ wrapper = wrapper[getFuncName(data[0])].apply(wrapper, data[3]);
+ } else {
+ wrapper = (func.length == 1 && isLaziable(func))
+ ? wrapper[funcName]()
+ : wrapper.thru(func);
+ }
+ }
+ return function() {
+ var args = arguments,
+ value = args[0];
+
+ if (wrapper && args.length == 1 &&
+ isArray(value) && value.length >= LARGE_ARRAY_SIZE) {
+ return wrapper.plant(value).value();
+ }
+ var index = 0,
+ result = length ? funcs[index].apply(this, args) : value;
+
+ while (++index < length) {
+ result = funcs[index].call(this, result);
+ }
+ return result;
+ };
+ });
+ }
+
+ /**
+ * Creates a function that wraps `func` to invoke it with optional `this`
+ * binding of `thisArg`, partial application, and currying.
+ *
+ * @private
+ * @param {Function|string} func The function or method name to wrap.
+ * @param {number} bitmask The bitmask flags. See `createWrap` for more details.
+ * @param {*} [thisArg] The `this` binding of `func`.
+ * @param {Array} [partials] The arguments to prepend to those provided to
+ * the new function.
+ * @param {Array} [holders] The `partials` placeholder indexes.
+ * @param {Array} [partialsRight] The arguments to append to those provided
+ * to the new function.
+ * @param {Array} [holdersRight] The `partialsRight` placeholder indexes.
+ * @param {Array} [argPos] The argument positions of the new function.
+ * @param {number} [ary] The arity cap of `func`.
+ * @param {number} [arity] The arity of `func`.
+ * @returns {Function} Returns the new wrapped function.
+ */
+ function createHybrid(func, bitmask, thisArg, partials, holders, partialsRight, holdersRight, argPos, ary, arity) {
+ var isAry = bitmask & ARY_FLAG,
+ isBind = bitmask & BIND_FLAG,
+ isBindKey = bitmask & BIND_KEY_FLAG,
+ isCurried = bitmask & (CURRY_FLAG | CURRY_RIGHT_FLAG),
+ isFlip = bitmask & FLIP_FLAG,
+ Ctor = isBindKey ? undefined : createCtor(func);
+
+ function wrapper() {
+ var length = arguments.length,
+ args = Array(length),
+ index = length;
+
+ while (index--) {
+ args[index] = arguments[index];
+ }
+ if (isCurried) {
+ var placeholder = getHolder(wrapper),
+ holdersCount = countHolders(args, placeholder);
+ }
+ if (partials) {
+ args = composeArgs(args, partials, holders, isCurried);
+ }
+ if (partialsRight) {
+ args = composeArgsRight(args, partialsRight, holdersRight, isCurried);
+ }
+ length -= holdersCount;
+ if (isCurried && length < arity) {
+ var newHolders = replaceHolders(args, placeholder);
+ return createRecurry(
+ func, bitmask, createHybrid, wrapper.placeholder, thisArg,
+ args, newHolders, argPos, ary, arity - length
+ );
+ }
+ var thisBinding = isBind ? thisArg : this,
+ fn = isBindKey ? thisBinding[func] : func;
+
+ length = args.length;
+ if (argPos) {
+ args = reorder(args, argPos);
+ } else if (isFlip && length > 1) {
+ args.reverse();
+ }
+ if (isAry && ary < length) {
+ args.length = ary;
+ }
+ if (this && this !== root && this instanceof wrapper) {
+ fn = Ctor || createCtor(fn);
+ }
+ return fn.apply(thisBinding, args);
+ }
+ return wrapper;
+ }
+
+ /**
+ * Creates a function like `_.invertBy`.
+ *
+ * @private
+ * @param {Function} setter The function to set accumulator values.
+ * @param {Function} toIteratee The function to resolve iteratees.
+ * @returns {Function} Returns the new inverter function.
+ */
+ function createInverter(setter, toIteratee) {
+ return function(object, iteratee) {
+ return baseInverter(object, setter, toIteratee(iteratee), {});
+ };
+ }
+
+ /**
+ * Creates a function that performs a mathematical operation on two values.
+ *
+ * @private
+ * @param {Function} operator The function to perform the operation.
+ * @param {number} [defaultValue] The value used for `undefined` arguments.
+ * @returns {Function} Returns the new mathematical operation function.
+ */
+ function createMathOperation(operator, defaultValue) {
+ return function(value, other) {
+ var result;
+ if (value === undefined && other === undefined) {
+ return defaultValue;
+ }
+ if (value !== undefined) {
+ result = value;
+ }
+ if (other !== undefined) {
+ if (result === undefined) {
+ return other;
+ }
+ if (typeof value == 'string' || typeof other == 'string') {
+ value = baseToString(value);
+ other = baseToString(other);
+ } else {
+ value = baseToNumber(value);
+ other = baseToNumber(other);
+ }
+ result = operator(value, other);
+ }
+ return result;
+ };
+ }
+
+ /**
+ * Creates a function like `_.over`.
+ *
+ * @private
+ * @param {Function} arrayFunc The function to iterate over iteratees.
+ * @returns {Function} Returns the new over function.
+ */
+ function createOver(arrayFunc) {
+ return flatRest(function(iteratees) {
+ iteratees = arrayMap(iteratees, baseUnary(getIteratee()));
+ return baseRest(function(args) {
+ var thisArg = this;
+ return arrayFunc(iteratees, function(iteratee) {
+ return apply(iteratee, thisArg, args);
+ });
+ });
+ });
+ }
+
+ /**
+ * Creates the padding for `string` based on `length`. The `chars` string
+ * is truncated if the number of characters exceeds `length`.
+ *
+ * @private
+ * @param {number} length The padding length.
+ * @param {string} [chars=' '] The string used as padding.
+ * @returns {string} Returns the padding for `string`.
+ */
+ function createPadding(length, chars) {
+ chars = chars === undefined ? ' ' : baseToString(chars);
+
+ var charsLength = chars.length;
+ if (charsLength < 2) {
+ return charsLength ? baseRepeat(chars, length) : chars;
+ }
+ var result = baseRepeat(chars, nativeCeil(length / stringSize(chars)));
+ return hasUnicode(chars)
+ ? castSlice(stringToArray(result), 0, length).join('')
+ : result.slice(0, length);
+ }
+
+ /**
+ * Creates a function that wraps `func` to invoke it with the `this` binding
+ * of `thisArg` and `partials` prepended to the arguments it receives.
+ *
+ * @private
+ * @param {Function} func The function to wrap.
+ * @param {number} bitmask The bitmask flags. See `createWrap` for more details.
+ * @param {*} thisArg The `this` binding of `func`.
+ * @param {Array} partials The arguments to prepend to those provided to
+ * the new function.
+ * @returns {Function} Returns the new wrapped function.
+ */
+ function createPartial(func, bitmask, thisArg, partials) {
+ var isBind = bitmask & BIND_FLAG,
+ Ctor = createCtor(func);
+
+ function wrapper() {
+ var argsIndex = -1,
+ argsLength = arguments.length,
+ leftIndex = -1,
+ leftLength = partials.length,
+ args = Array(leftLength + argsLength),
+ fn = (this && this !== root && this instanceof wrapper) ? Ctor : func;
+
+ while (++leftIndex < leftLength) {
+ args[leftIndex] = partials[leftIndex];
+ }
+ while (argsLength--) {
+ args[leftIndex++] = arguments[++argsIndex];
+ }
+ return apply(fn, isBind ? thisArg : this, args);
+ }
+ return wrapper;
+ }
+
+ /**
+ * Creates a `_.range` or `_.rangeRight` function.
+ *
+ * @private
+ * @param {boolean} [fromRight] Specify iterating from right to left.
+ * @returns {Function} Returns the new range function.
+ */
+ function createRange(fromRight) {
+ return function(start, end, step) {
+ if (step && typeof step != 'number' && isIterateeCall(start, end, step)) {
+ end = step = undefined;
+ }
+ // Ensure the sign of `-0` is preserved.
+ start = toFinite(start);
+ if (end === undefined) {
+ end = start;
+ start = 0;
+ } else {
+ end = toFinite(end);
+ }
+ step = step === undefined ? (start < end ? 1 : -1) : toFinite(step);
+ return baseRange(start, end, step, fromRight);
+ };
+ }
+
+ /**
+ * Creates a function that performs a relational operation on two values.
+ *
+ * @private
+ * @param {Function} operator The function to perform the operation.
+ * @returns {Function} Returns the new relational operation function.
+ */
+ function createRelationalOperation(operator) {
+ return function(value, other) {
+ if (!(typeof value == 'string' && typeof other == 'string')) {
+ value = toNumber(value);
+ other = toNumber(other);
+ }
+ return operator(value, other);
+ };
+ }
+
+ /**
+ * Creates a function that wraps `func` to continue currying.
+ *
+ * @private
+ * @param {Function} func The function to wrap.
+ * @param {number} bitmask The bitmask flags. See `createWrap` for more details.
+ * @param {Function} wrapFunc The function to create the `func` wrapper.
+ * @param {*} placeholder The placeholder value.
+ * @param {*} [thisArg] The `this` binding of `func`.
+ * @param {Array} [partials] The arguments to prepend to those provided to
+ * the new function.
+ * @param {Array} [holders] The `partials` placeholder indexes.
+ * @param {Array} [argPos] The argument positions of the new function.
+ * @param {number} [ary] The arity cap of `func`.
+ * @param {number} [arity] The arity of `func`.
+ * @returns {Function} Returns the new wrapped function.
+ */
+ function createRecurry(func, bitmask, wrapFunc, placeholder, thisArg, partials, holders, argPos, ary, arity) {
+ var isCurry = bitmask & CURRY_FLAG,
+ newHolders = isCurry ? holders : undefined,
+ newHoldersRight = isCurry ? undefined : holders,
+ newPartials = isCurry ? partials : undefined,
+ newPartialsRight = isCurry ? undefined : partials;
+
+ bitmask |= (isCurry ? PARTIAL_FLAG : PARTIAL_RIGHT_FLAG);
+ bitmask &= ~(isCurry ? PARTIAL_RIGHT_FLAG : PARTIAL_FLAG);
+
+ if (!(bitmask & CURRY_BOUND_FLAG)) {
+ bitmask &= ~(BIND_FLAG | BIND_KEY_FLAG);
+ }
+ var newData = [
+ func, bitmask, thisArg, newPartials, newHolders, newPartialsRight,
+ newHoldersRight, argPos, ary, arity
+ ];
+
+ var result = wrapFunc.apply(undefined, newData);
+ if (isLaziable(func)) {
+ setData(result, newData);
+ }
+ result.placeholder = placeholder;
+ return setWrapToString(result, func, bitmask);
+ }
+
+ /**
+ * Creates a function like `_.round`.
+ *
+ * @private
+ * @param {string} methodName The name of the `Math` method to use when rounding.
+ * @returns {Function} Returns the new round function.
+ */
+ function createRound(methodName) {
+ var func = Math[methodName];
+ return function(number, precision) {
+ number = toNumber(number);
+ precision = nativeMin(toInteger(precision), 292);
+ if (precision) {
+ // Shift with exponential notation to avoid floating-point issues.
+ // See [MDN](https://mdn.io/round#Examples) for more details.
+ var pair = (toString(number) + 'e').split('e'),
+ value = func(pair[0] + 'e' + (+pair[1] + precision));
+
+ pair = (toString(value) + 'e').split('e');
+ return +(pair[0] + 'e' + (+pair[1] - precision));
+ }
+ return func(number);
+ };
+ }
+
+ /**
+ * Creates a set object of `values`.
+ *
+ * @private
+ * @param {Array} values The values to add to the set.
+ * @returns {Object} Returns the new set.
+ */
+ var createSet = !(Set && (1 / setToArray(new Set([,-0]))[1]) == INFINITY) ? noop : function(values) {
+ return new Set(values);
+ };
+
+ /**
+ * Creates a `_.toPairs` or `_.toPairsIn` function.
+ *
+ * @private
+ * @param {Function} keysFunc The function to get the keys of a given object.
+ * @returns {Function} Returns the new pairs function.
+ */
+ function createToPairs(keysFunc) {
+ return function(object) {
+ var tag = getTag(object);
+ if (tag == mapTag) {
+ return mapToArray(object);
+ }
+ if (tag == setTag) {
+ return setToPairs(object);
+ }
+ return baseToPairs(object, keysFunc(object));
+ };
+ }
+
+ /**
+ * Creates a function that either curries or invokes `func` with optional
+ * `this` binding and partially applied arguments.
+ *
+ * @private
+ * @param {Function|string} func The function or method name to wrap.
+ * @param {number} bitmask The bitmask flags.
+ * The bitmask may be composed of the following flags:
+ * 1 - `_.bind`
+ * 2 - `_.bindKey`
+ * 4 - `_.curry` or `_.curryRight` of a bound function
+ * 8 - `_.curry`
+ * 16 - `_.curryRight`
+ * 32 - `_.partial`
+ * 64 - `_.partialRight`
+ * 128 - `_.rearg`
+ * 256 - `_.ary`
+ * 512 - `_.flip`
+ * @param {*} [thisArg] The `this` binding of `func`.
+ * @param {Array} [partials] The arguments to be partially applied.
+ * @param {Array} [holders] The `partials` placeholder indexes.
+ * @param {Array} [argPos] The argument positions of the new function.
+ * @param {number} [ary] The arity cap of `func`.
+ * @param {number} [arity] The arity of `func`.
+ * @returns {Function} Returns the new wrapped function.
+ */
+ function createWrap(func, bitmask, thisArg, partials, holders, argPos, ary, arity) {
+ var isBindKey = bitmask & BIND_KEY_FLAG;
+ if (!isBindKey && typeof func != 'function') {
+ throw new TypeError(FUNC_ERROR_TEXT);
+ }
+ var length = partials ? partials.length : 0;
+ if (!length) {
+ bitmask &= ~(PARTIAL_FLAG | PARTIAL_RIGHT_FLAG);
+ partials = holders = undefined;
+ }
+ ary = ary === undefined ? ary : nativeMax(toInteger(ary), 0);
+ arity = arity === undefined ? arity : toInteger(arity);
+ length -= holders ? holders.length : 0;
+
+ if (bitmask & PARTIAL_RIGHT_FLAG) {
+ var partialsRight = partials,
+ holdersRight = holders;
+
+ partials = holders = undefined;
+ }
+ var data = isBindKey ? undefined : getData(func);
+
+ var newData = [
+ func, bitmask, thisArg, partials, holders, partialsRight, holdersRight,
+ argPos, ary, arity
+ ];
+
+ if (data) {
+ mergeData(newData, data);
+ }
+ func = newData[0];
+ bitmask = newData[1];
+ thisArg = newData[2];
+ partials = newData[3];
+ holders = newData[4];
+ arity = newData[9] = newData[9] == null
+ ? (isBindKey ? 0 : func.length)
+ : nativeMax(newData[9] - length, 0);
+
+ if (!arity && bitmask & (CURRY_FLAG | CURRY_RIGHT_FLAG)) {
+ bitmask &= ~(CURRY_FLAG | CURRY_RIGHT_FLAG);
+ }
+ if (!bitmask || bitmask == BIND_FLAG) {
+ var result = createBind(func, bitmask, thisArg);
+ } else if (bitmask == CURRY_FLAG || bitmask == CURRY_RIGHT_FLAG) {
+ result = createCurry(func, bitmask, arity);
+ } else if ((bitmask == PARTIAL_FLAG || bitmask == (BIND_FLAG | PARTIAL_FLAG)) && !holders.length) {
+ result = createPartial(func, bitmask, thisArg, partials);
+ } else {
+ result = createHybrid.apply(undefined, newData);
+ }
+ var setter = data ? baseSetData : setData;
+ return setWrapToString(setter(result, newData), func, bitmask);
+ }
+
+ /**
+ * A specialized version of `baseIsEqualDeep` for arrays with support for
+ * partial deep comparisons.
+ *
+ * @private
+ * @param {Array} array The array to compare.
+ * @param {Array} other The other array to compare.
+ * @param {Function} equalFunc The function to determine equivalents of values.
+ * @param {Function} customizer The function to customize comparisons.
+ * @param {number} bitmask The bitmask of comparison flags. See `baseIsEqual`
+ * for more details.
+ * @param {Object} stack Tracks traversed `array` and `other` objects.
+ * @returns {boolean} Returns `true` if the arrays are equivalent, else `false`.
+ */
+ function equalArrays(array, other, equalFunc, customizer, bitmask, stack) {
+ var isPartial = bitmask & PARTIAL_COMPARE_FLAG,
+ arrLength = array.length,
+ othLength = other.length;
+
+ if (arrLength != othLength && !(isPartial && othLength > arrLength)) {
+ return false;
+ }
+ // Assume cyclic values are equal.
+ var stacked = stack.get(array);
+ if (stacked && stack.get(other)) {
+ return stacked == other;
+ }
+ var index = -1,
+ result = true,
+ seen = (bitmask & UNORDERED_COMPARE_FLAG) ? new SetCache : undefined;
+
+ stack.set(array, other);
+ stack.set(other, array);
+
+ // Ignore non-index properties.
+ while (++index < arrLength) {
+ var arrValue = array[index],
+ othValue = other[index];
+
+ if (customizer) {
+ var compared = isPartial
+ ? customizer(othValue, arrValue, index, other, array, stack)
+ : customizer(arrValue, othValue, index, array, other, stack);
+ }
+ if (compared !== undefined) {
+ if (compared) {
+ continue;
+ }
+ result = false;
+ break;
+ }
+ // Recursively compare arrays (susceptible to call stack limits).
+ if (seen) {
+ if (!arraySome(other, function(othValue, othIndex) {
+ if (!cacheHas(seen, othIndex) &&
+ (arrValue === othValue || equalFunc(arrValue, othValue, customizer, bitmask, stack))) {
+ return seen.push(othIndex);
+ }
+ })) {
+ result = false;
+ break;
+ }
+ } else if (!(
+ arrValue === othValue ||
+ equalFunc(arrValue, othValue, customizer, bitmask, stack)
+ )) {
+ result = false;
+ break;
+ }
+ }
+ stack['delete'](array);
+ stack['delete'](other);
+ return result;
+ }
+
+ /**
+ * A specialized version of `baseIsEqualDeep` for comparing objects of
+ * the same `toStringTag`.
+ *
+ * **Note:** This function only supports comparing values with tags of
+ * `Boolean`, `Date`, `Error`, `Number`, `RegExp`, or `String`.
+ *
+ * @private
+ * @param {Object} object The object to compare.
+ * @param {Object} other The other object to compare.
+ * @param {string} tag The `toStringTag` of the objects to compare.
+ * @param {Function} equalFunc The function to determine equivalents of values.
+ * @param {Function} customizer The function to customize comparisons.
+ * @param {number} bitmask The bitmask of comparison flags. See `baseIsEqual`
+ * for more details.
+ * @param {Object} stack Tracks traversed `object` and `other` objects.
+ * @returns {boolean} Returns `true` if the objects are equivalent, else `false`.
+ */
+ function equalByTag(object, other, tag, equalFunc, customizer, bitmask, stack) {
+ switch (tag) {
+ case dataViewTag:
+ if ((object.byteLength != other.byteLength) ||
+ (object.byteOffset != other.byteOffset)) {
+ return false;
+ }
+ object = object.buffer;
+ other = other.buffer;
+
+ case arrayBufferTag:
+ if ((object.byteLength != other.byteLength) ||
+ !equalFunc(new Uint8Array(object), new Uint8Array(other))) {
+ return false;
+ }
+ return true;
+
+ case boolTag:
+ case dateTag:
+ case numberTag:
+ // Coerce booleans to `1` or `0` and dates to milliseconds.
+ // Invalid dates are coerced to `NaN`.
+ return eq(+object, +other);
+
+ case errorTag:
+ return object.name == other.name && object.message == other.message;
+
+ case regexpTag:
+ case stringTag:
+ // Coerce regexes to strings and treat strings, primitives and objects,
+ // as equal. See http://www.ecma-international.org/ecma-262/7.0/#sec-regexp.prototype.tostring
+ // for more details.
+ return object == (other + '');
+
+ case mapTag:
+ var convert = mapToArray;
+
+ case setTag:
+ var isPartial = bitmask & PARTIAL_COMPARE_FLAG;
+ convert || (convert = setToArray);
+
+ if (object.size != other.size && !isPartial) {
+ return false;
+ }
+ // Assume cyclic values are equal.
+ var stacked = stack.get(object);
+ if (stacked) {
+ return stacked == other;
+ }
+ bitmask |= UNORDERED_COMPARE_FLAG;
+
+ // Recursively compare objects (susceptible to call stack limits).
+ stack.set(object, other);
+ var result = equalArrays(convert(object), convert(other), equalFunc, customizer, bitmask, stack);
+ stack['delete'](object);
+ return result;
+
+ case symbolTag:
+ if (symbolValueOf) {
+ return symbolValueOf.call(object) == symbolValueOf.call(other);
+ }
+ }
+ return false;
+ }
+
+ /**
+ * A specialized version of `baseIsEqualDeep` for objects with support for
+ * partial deep comparisons.
+ *
+ * @private
+ * @param {Object} object The object to compare.
+ * @param {Object} other The other object to compare.
+ * @param {Function} equalFunc The function to determine equivalents of values.
+ * @param {Function} customizer The function to customize comparisons.
+ * @param {number} bitmask The bitmask of comparison flags. See `baseIsEqual`
+ * for more details.
+ * @param {Object} stack Tracks traversed `object` and `other` objects.
+ * @returns {boolean} Returns `true` if the objects are equivalent, else `false`.
+ */
+ function equalObjects(object, other, equalFunc, customizer, bitmask, stack) {
+ var isPartial = bitmask & PARTIAL_COMPARE_FLAG,
+ objProps = keys(object),
+ objLength = objProps.length,
+ othProps = keys(other),
+ othLength = othProps.length;
+
+ if (objLength != othLength && !isPartial) {
+ return false;
+ }
+ var index = objLength;
+ while (index--) {
+ var key = objProps[index];
+ if (!(isPartial ? key in other : hasOwnProperty.call(other, key))) {
+ return false;
+ }
+ }
+ // Assume cyclic values are equal.
+ var stacked = stack.get(object);
+ if (stacked && stack.get(other)) {
+ return stacked == other;
+ }
+ var result = true;
+ stack.set(object, other);
+ stack.set(other, object);
+
+ var skipCtor = isPartial;
+ while (++index < objLength) {
+ key = objProps[index];
+ var objValue = object[key],
+ othValue = other[key];
+
+ if (customizer) {
+ var compared = isPartial
+ ? customizer(othValue, objValue, key, other, object, stack)
+ : customizer(objValue, othValue, key, object, other, stack);
+ }
+ // Recursively compare objects (susceptible to call stack limits).
+ if (!(compared === undefined
+ ? (objValue === othValue || equalFunc(objValue, othValue, customizer, bitmask, stack))
+ : compared
+ )) {
+ result = false;
+ break;
+ }
+ skipCtor || (skipCtor = key == 'constructor');
+ }
+ if (result && !skipCtor) {
+ var objCtor = object.constructor,
+ othCtor = other.constructor;
+
+ // Non `Object` object instances with different constructors are not equal.
+ if (objCtor != othCtor &&
+ ('constructor' in object && 'constructor' in other) &&
+ !(typeof objCtor == 'function' && objCtor instanceof objCtor &&
+ typeof othCtor == 'function' && othCtor instanceof othCtor)) {
+ result = false;
+ }
+ }
+ stack['delete'](object);
+ stack['delete'](other);
+ return result;
+ }
+
+ /**
+ * A specialized version of `baseRest` which flattens the rest array.
+ *
+ * @private
+ * @param {Function} func The function to apply a rest parameter to.
+ * @returns {Function} Returns the new function.
+ */
+ function flatRest(func) {
+ return setToString(overRest(func, undefined, flatten), func + '');
+ }
+
+ /**
+ * Creates an array of own enumerable property names and symbols of `object`.
+ *
+ * @private
+ * @param {Object} object The object to query.
+ * @returns {Array} Returns the array of property names and symbols.
+ */
+ function getAllKeys(object) {
+ return baseGetAllKeys(object, keys, getSymbols);
+ }
+
+ /**
+ * Creates an array of own and inherited enumerable property names and
+ * symbols of `object`.
+ *
+ * @private
+ * @param {Object} object The object to query.
+ * @returns {Array} Returns the array of property names and symbols.
+ */
+ function getAllKeysIn(object) {
+ return baseGetAllKeys(object, keysIn, getSymbolsIn);
+ }
+
+ /**
+ * Gets metadata for `func`.
+ *
+ * @private
+ * @param {Function} func The function to query.
+ * @returns {*} Returns the metadata for `func`.
+ */
+ var getData = !metaMap ? noop : function(func) {
+ return metaMap.get(func);
+ };
+
+ /**
+ * Gets the name of `func`.
+ *
+ * @private
+ * @param {Function} func The function to query.
+ * @returns {string} Returns the function name.
+ */
+ function getFuncName(func) {
+ var result = (func.name + ''),
+ array = realNames[result],
+ length = hasOwnProperty.call(realNames, result) ? array.length : 0;
+
+ while (length--) {
+ var data = array[length],
+ otherFunc = data.func;
+ if (otherFunc == null || otherFunc == func) {
+ return data.name;
+ }
+ }
+ return result;
+ }
+
+ /**
+ * Gets the argument placeholder value for `func`.
+ *
+ * @private
+ * @param {Function} func The function to inspect.
+ * @returns {*} Returns the placeholder value.
+ */
+ function getHolder(func) {
+ var object = hasOwnProperty.call(lodash, 'placeholder') ? lodash : func;
+ return object.placeholder;
+ }
+
+ /**
+ * Gets the appropriate "iteratee" function. If `_.iteratee` is customized,
+ * this function returns the custom method, otherwise it returns `baseIteratee`.
+ * If arguments are provided, the chosen function is invoked with them and
+ * its result is returned.
+ *
+ * @private
+ * @param {*} [value] The value to convert to an iteratee.
+ * @param {number} [arity] The arity of the created iteratee.
+ * @returns {Function} Returns the chosen function or its result.
+ */
+ function getIteratee() {
+ var result = lodash.iteratee || iteratee;
+ result = result === iteratee ? baseIteratee : result;
+ return arguments.length ? result(arguments[0], arguments[1]) : result;
+ }
+
+ /**
+ * Gets the data for `map`.
+ *
+ * @private
+ * @param {Object} map The map to query.
+ * @param {string} key The reference key.
+ * @returns {*} Returns the map data.
+ */
+ function getMapData(map, key) {
+ var data = map.__data__;
+ return isKeyable(key)
+ ? data[typeof key == 'string' ? 'string' : 'hash']
+ : data.map;
+ }
+
+ /**
+ * Gets the property names, values, and compare flags of `object`.
+ *
+ * @private
+ * @param {Object} object The object to query.
+ * @returns {Array} Returns the match data of `object`.
+ */
+ function getMatchData(object) {
+ var result = keys(object),
+ length = result.length;
+
+ while (length--) {
+ var key = result[length],
+ value = object[key];
+
+ result[length] = [key, value, isStrictComparable(value)];
+ }
+ return result;
+ }
+
+ /**
+ * Gets the native function at `key` of `object`.
+ *
+ * @private
+ * @param {Object} object The object to query.
+ * @param {string} key The key of the method to get.
+ * @returns {*} Returns the function if it's native, else `undefined`.
+ */
+ function getNative(object, key) {
+ var value = getValue(object, key);
+ return baseIsNative(value) ? value : undefined;
+ }
+
+ /**
+ * Creates an array of the own enumerable symbol properties of `object`.
+ *
+ * @private
+ * @param {Object} object The object to query.
+ * @returns {Array} Returns the array of symbols.
+ */
+ var getSymbols = nativeGetSymbols ? overArg(nativeGetSymbols, Object) : stubArray;
+
+ /**
+ * Creates an array of the own and inherited enumerable symbol properties
+ * of `object`.
+ *
+ * @private
+ * @param {Object} object The object to query.
+ * @returns {Array} Returns the array of symbols.
+ */
+ var getSymbolsIn = !nativeGetSymbols ? stubArray : function(object) {
+ var result = [];
+ while (object) {
+ arrayPush(result, getSymbols(object));
+ object = getPrototype(object);
+ }
+ return result;
+ };
+
+ /**
+ * Gets the `toStringTag` of `value`.
+ *
+ * @private
+ * @param {*} value The value to query.
+ * @returns {string} Returns the `toStringTag`.
+ */
+ var getTag = baseGetTag;
+
+ // Fallback for data views, maps, sets, and weak maps in IE 11 and promises in Node.js < 6.
+ if ((DataView && getTag(new DataView(new ArrayBuffer(1))) != dataViewTag) ||
+ (Map && getTag(new Map) != mapTag) ||
+ (Promise && getTag(Promise.resolve()) != promiseTag) ||
+ (Set && getTag(new Set) != setTag) ||
+ (WeakMap && getTag(new WeakMap) != weakMapTag)) {
+ getTag = function(value) {
+ var result = objectToString.call(value),
+ Ctor = result == objectTag ? value.constructor : undefined,
+ ctorString = Ctor ? toSource(Ctor) : undefined;
+
+ if (ctorString) {
+ switch (ctorString) {
+ case dataViewCtorString: return dataViewTag;
+ case mapCtorString: return mapTag;
+ case promiseCtorString: return promiseTag;
+ case setCtorString: return setTag;
+ case weakMapCtorString: return weakMapTag;
+ }
+ }
+ return result;
+ };
+ }
+
+ /**
+ * Gets the view, applying any `transforms` to the `start` and `end` positions.
+ *
+ * @private
+ * @param {number} start The start of the view.
+ * @param {number} end The end of the view.
+ * @param {Array} transforms The transformations to apply to the view.
+ * @returns {Object} Returns an object containing the `start` and `end`
+ * positions of the view.
+ */
+ function getView(start, end, transforms) {
+ var index = -1,
+ length = transforms.length;
+
+ while (++index < length) {
+ var data = transforms[index],
+ size = data.size;
+
+ switch (data.type) {
+ case 'drop': start += size; break;
+ case 'dropRight': end -= size; break;
+ case 'take': end = nativeMin(end, start + size); break;
+ case 'takeRight': start = nativeMax(start, end - size); break;
+ }
+ }
+ return { 'start': start, 'end': end };
+ }
+
+ /**
+ * Extracts wrapper details from the `source` body comment.
+ *
+ * @private
+ * @param {string} source The source to inspect.
+ * @returns {Array} Returns the wrapper details.
+ */
+ function getWrapDetails(source) {
+ var match = source.match(reWrapDetails);
+ return match ? match[1].split(reSplitDetails) : [];
+ }
+
+ /**
+ * Checks if `path` exists on `object`.
+ *
+ * @private
+ * @param {Object} object The object to query.
+ * @param {Array|string} path The path to check.
+ * @param {Function} hasFunc The function to check properties.
+ * @returns {boolean} Returns `true` if `path` exists, else `false`.
+ */
+ function hasPath(object, path, hasFunc) {
+ path = isKey(path, object) ? [path] : castPath(path);
+
+ var index = -1,
+ length = path.length,
+ result = false;
+
+ while (++index < length) {
+ var key = toKey(path[index]);
+ if (!(result = object != null && hasFunc(object, key))) {
+ break;
+ }
+ object = object[key];
+ }
+ if (result || ++index != length) {
+ return result;
+ }
+ length = object ? object.length : 0;
+ return !!length && isLength(length) && isIndex(key, length) &&
+ (isArray(object) || isArguments(object));
+ }
+
+ /**
+ * Initializes an array clone.
+ *
+ * @private
+ * @param {Array} array The array to clone.
+ * @returns {Array} Returns the initialized clone.
+ */
+ function initCloneArray(array) {
+ var length = array.length,
+ result = array.constructor(length);
+
+ // Add properties assigned by `RegExp#exec`.
+ if (length && typeof array[0] == 'string' && hasOwnProperty.call(array, 'index')) {
+ result.index = array.index;
+ result.input = array.input;
+ }
+ return result;
+ }
+
+ /**
+ * Initializes an object clone.
+ *
+ * @private
+ * @param {Object} object The object to clone.
+ * @returns {Object} Returns the initialized clone.
+ */
+ function initCloneObject(object) {
+ return (typeof object.constructor == 'function' && !isPrototype(object))
+ ? baseCreate(getPrototype(object))
+ : {};
+ }
+
+ /**
+ * Initializes an object clone based on its `toStringTag`.
+ *
+ * **Note:** This function only supports cloning values with tags of
+ * `Boolean`, `Date`, `Error`, `Number`, `RegExp`, or `String`.
+ *
+ * @private
+ * @param {Object} object The object to clone.
+ * @param {string} tag The `toStringTag` of the object to clone.
+ * @param {Function} cloneFunc The function to clone values.
+ * @param {boolean} [isDeep] Specify a deep clone.
+ * @returns {Object} Returns the initialized clone.
+ */
+ function initCloneByTag(object, tag, cloneFunc, isDeep) {
+ var Ctor = object.constructor;
+ switch (tag) {
+ case arrayBufferTag:
+ return cloneArrayBuffer(object);
+
+ case boolTag:
+ case dateTag:
+ return new Ctor(+object);
+
+ case dataViewTag:
+ return cloneDataView(object, isDeep);
+
+ case float32Tag: case float64Tag:
+ case int8Tag: case int16Tag: case int32Tag:
+ case uint8Tag: case uint8ClampedTag: case uint16Tag: case uint32Tag:
+ return cloneTypedArray(object, isDeep);
+
+ case mapTag:
+ return cloneMap(object, isDeep, cloneFunc);
+
+ case numberTag:
+ case stringTag:
+ return new Ctor(object);
+
+ case regexpTag:
+ return cloneRegExp(object);
+
+ case setTag:
+ return cloneSet(object, isDeep, cloneFunc);
+
+ case symbolTag:
+ return cloneSymbol(object);
+ }
+ }
+
+ /**
+ * Inserts wrapper `details` in a comment at the top of the `source` body.
+ *
+ * @private
+ * @param {string} source The source to modify.
+ * @returns {Array} details The details to insert.
+ * @returns {string} Returns the modified source.
+ */
+ function insertWrapDetails(source, details) {
+ var length = details.length;
+ if (!length) {
+ return source;
+ }
+ var lastIndex = length - 1;
+ details[lastIndex] = (length > 1 ? '& ' : '') + details[lastIndex];
+ details = details.join(length > 2 ? ', ' : ' ');
+ return source.replace(reWrapComment, '{\n/* [wrapped with ' + details + '] */\n');
+ }
+
+ /**
+ * Checks if `value` is a flattenable `arguments` object or array.
+ *
+ * @private
+ * @param {*} value The value to check.
+ * @returns {boolean} Returns `true` if `value` is flattenable, else `false`.
+ */
+ function isFlattenable(value) {
+ return isArray(value) || isArguments(value) ||
+ !!(spreadableSymbol && value && value[spreadableSymbol]);
+ }
+
+ /**
+ * Checks if `value` is a valid array-like index.
+ *
+ * @private
+ * @param {*} value The value to check.
+ * @param {number} [length=MAX_SAFE_INTEGER] The upper bounds of a valid index.
+ * @returns {boolean} Returns `true` if `value` is a valid index, else `false`.
+ */
+ function isIndex(value, length) {
+ length = length == null ? MAX_SAFE_INTEGER : length;
+ return !!length &&
+ (typeof value == 'number' || reIsUint.test(value)) &&
+ (value > -1 && value % 1 == 0 && value < length);
+ }
+
+ /**
+ * Checks if the given arguments are from an iteratee call.
+ *
+ * @private
+ * @param {*} value The potential iteratee value argument.
+ * @param {*} index The potential iteratee index or key argument.
+ * @param {*} object The potential iteratee object argument.
+ * @returns {boolean} Returns `true` if the arguments are from an iteratee call,
+ * else `false`.
+ */
+ function isIterateeCall(value, index, object) {
+ if (!isObject(object)) {
+ return false;
+ }
+ var type = typeof index;
+ if (type == 'number'
+ ? (isArrayLike(object) && isIndex(index, object.length))
+ : (type == 'string' && index in object)
+ ) {
+ return eq(object[index], value);
+ }
+ return false;
+ }
+
+ /**
+ * Checks if `value` is a property name and not a property path.
+ *
+ * @private
+ * @param {*} value The value to check.
+ * @param {Object} [object] The object to query keys on.
+ * @returns {boolean} Returns `true` if `value` is a property name, else `false`.
+ */
+ function isKey(value, object) {
+ if (isArray(value)) {
+ return false;
+ }
+ var type = typeof value;
+ if (type == 'number' || type == 'symbol' || type == 'boolean' ||
+ value == null || isSymbol(value)) {
+ return true;
+ }
+ return reIsPlainProp.test(value) || !reIsDeepProp.test(value) ||
+ (object != null && value in Object(object));
+ }
+
+ /**
+ * Checks if `value` is suitable for use as unique object key.
+ *
+ * @private
+ * @param {*} value The value to check.
+ * @returns {boolean} Returns `true` if `value` is suitable, else `false`.
+ */
+ function isKeyable(value) {
+ var type = typeof value;
+ return (type == 'string' || type == 'number' || type == 'symbol' || type == 'boolean')
+ ? (value !== '__proto__')
+ : (value === null);
+ }
+
+ /**
+ * Checks if `func` has a lazy counterpart.
+ *
+ * @private
+ * @param {Function} func The function to check.
+ * @returns {boolean} Returns `true` if `func` has a lazy counterpart,
+ * else `false`.
+ */
+ function isLaziable(func) {
+ var funcName = getFuncName(func),
+ other = lodash[funcName];
+
+ if (typeof other != 'function' || !(funcName in LazyWrapper.prototype)) {
+ return false;
+ }
+ if (func === other) {
+ return true;
+ }
+ var data = getData(other);
+ return !!data && func === data[0];
+ }
+
+ /**
+ * Checks if `func` has its source masked.
+ *
+ * @private
+ * @param {Function} func The function to check.
+ * @returns {boolean} Returns `true` if `func` is masked, else `false`.
+ */
+ function isMasked(func) {
+ return !!maskSrcKey && (maskSrcKey in func);
+ }
+
+ /**
+ * Checks if `func` is capable of being masked.
+ *
+ * @private
+ * @param {*} value The value to check.
+ * @returns {boolean} Returns `true` if `func` is maskable, else `false`.
+ */
+ var isMaskable = coreJsData ? isFunction : stubFalse;
+
+ /**
+ * Checks if `value` is likely a prototype object.
+ *
+ * @private
+ * @param {*} value The value to check.
+ * @returns {boolean} Returns `true` if `value` is a prototype, else `false`.
+ */
+ function isPrototype(value) {
+ var Ctor = value && value.constructor,
+ proto = (typeof Ctor == 'function' && Ctor.prototype) || objectProto;
+
+ return value === proto;
+ }
+
+ /**
+ * Checks if `value` is suitable for strict equality comparisons, i.e. `===`.
+ *
+ * @private
+ * @param {*} value The value to check.
+ * @returns {boolean} Returns `true` if `value` if suitable for strict
+ * equality comparisons, else `false`.
+ */
+ function isStrictComparable(value) {
+ return value === value && !isObject(value);
+ }
+
+ /**
+ * A specialized version of `matchesProperty` for source values suitable
+ * for strict equality comparisons, i.e. `===`.
+ *
+ * @private
+ * @param {string} key The key of the property to get.
+ * @param {*} srcValue The value to match.
+ * @returns {Function} Returns the new spec function.
+ */
+ function matchesStrictComparable(key, srcValue) {
+ return function(object) {
+ if (object == null) {
+ return false;
+ }
+ return object[key] === srcValue &&
+ (srcValue !== undefined || (key in Object(object)));
+ };
+ }
+
+ /**
+ * A specialized version of `_.memoize` which clears the memoized function's
+ * cache when it exceeds `MAX_MEMOIZE_SIZE`.
+ *
+ * @private
+ * @param {Function} func The function to have its output memoized.
+ * @returns {Function} Returns the new memoized function.
+ */
+ function memoizeCapped(func) {
+ var result = memoize(func, function(key) {
+ if (cache.size === MAX_MEMOIZE_SIZE) {
+ cache.clear();
+ }
+ return key;
+ });
+
+ var cache = result.cache;
+ return result;
+ }
+
+ /**
+ * Merges the function metadata of `source` into `data`.
+ *
+ * Merging metadata reduces the number of wrappers used to invoke a function.
+ * This is possible because methods like `_.bind`, `_.curry`, and `_.partial`
+ * may be applied regardless of execution order. Methods like `_.ary` and
+ * `_.rearg` modify function arguments, making the order in which they are
+ * executed important, preventing the merging of metadata. However, we make
+ * an exception for a safe combined case where curried functions have `_.ary`
+ * and or `_.rearg` applied.
+ *
+ * @private
+ * @param {Array} data The destination metadata.
+ * @param {Array} source The source metadata.
+ * @returns {Array} Returns `data`.
+ */
+ function mergeData(data, source) {
+ var bitmask = data[1],
+ srcBitmask = source[1],
+ newBitmask = bitmask | srcBitmask,
+ isCommon = newBitmask < (BIND_FLAG | BIND_KEY_FLAG | ARY_FLAG);
+
+ var isCombo =
+ ((srcBitmask == ARY_FLAG) && (bitmask == CURRY_FLAG)) ||
+ ((srcBitmask == ARY_FLAG) && (bitmask == REARG_FLAG) && (data[7].length <= source[8])) ||
+ ((srcBitmask == (ARY_FLAG | REARG_FLAG)) && (source[7].length <= source[8]) && (bitmask == CURRY_FLAG));
+
+ // Exit early if metadata can't be merged.
+ if (!(isCommon || isCombo)) {
+ return data;
+ }
+ // Use source `thisArg` if available.
+ if (srcBitmask & BIND_FLAG) {
+ data[2] = source[2];
+ // Set when currying a bound function.
+ newBitmask |= bitmask & BIND_FLAG ? 0 : CURRY_BOUND_FLAG;
+ }
+ // Compose partial arguments.
+ var value = source[3];
+ if (value) {
+ var partials = data[3];
+ data[3] = partials ? composeArgs(partials, value, source[4]) : value;
+ data[4] = partials ? replaceHolders(data[3], PLACEHOLDER) : source[4];
+ }
+ // Compose partial right arguments.
+ value = source[5];
+ if (value) {
+ partials = data[5];
+ data[5] = partials ? composeArgsRight(partials, value, source[6]) : value;
+ data[6] = partials ? replaceHolders(data[5], PLACEHOLDER) : source[6];
+ }
+ // Use source `argPos` if available.
+ value = source[7];
+ if (value) {
+ data[7] = value;
+ }
+ // Use source `ary` if it's smaller.
+ if (srcBitmask & ARY_FLAG) {
+ data[8] = data[8] == null ? source[8] : nativeMin(data[8], source[8]);
+ }
+ // Use source `arity` if one is not provided.
+ if (data[9] == null) {
+ data[9] = source[9];
+ }
+ // Use source `func` and merge bitmasks.
+ data[0] = source[0];
+ data[1] = newBitmask;
+
+ return data;
+ }
+
+ /**
+ * Used by `_.defaultsDeep` to customize its `_.merge` use.
+ *
+ * @private
+ * @param {*} objValue The destination value.
+ * @param {*} srcValue The source value.
+ * @param {string} key The key of the property to merge.
+ * @param {Object} object The parent object of `objValue`.
+ * @param {Object} source The parent object of `srcValue`.
+ * @param {Object} [stack] Tracks traversed source values and their merged
+ * counterparts.
+ * @returns {*} Returns the value to assign.
+ */
+ function mergeDefaults(objValue, srcValue, key, object, source, stack) {
+ if (isObject(objValue) && isObject(srcValue)) {
+ // Recursively merge objects and arrays (susceptible to call stack limits).
+ stack.set(srcValue, objValue);
+ baseMerge(objValue, srcValue, undefined, mergeDefaults, stack);
+ stack['delete'](srcValue);
+ }
+ return objValue;
+ }
+
+ /**
+ * This function is like
+ * [`Object.keys`](http://ecma-international.org/ecma-262/7.0/#sec-object.keys)
+ * except that it includes inherited enumerable properties.
+ *
+ * @private
+ * @param {Object} object The object to query.
+ * @returns {Array} Returns the array of property names.
+ */
+ function nativeKeysIn(object) {
+ var result = [];
+ if (object != null) {
+ for (var key in Object(object)) {
+ result.push(key);
+ }
+ }
+ return result;
+ }
+
+ /**
+ * A specialized version of `baseRest` which transforms the rest array.
+ *
+ * @private
+ * @param {Function} func The function to apply a rest parameter to.
+ * @param {number} [start=func.length-1] The start position of the rest parameter.
+ * @param {Function} transform The rest array transform.
+ * @returns {Function} Returns the new function.
+ */
+ function overRest(func, start, transform) {
+ start = nativeMax(start === undefined ? (func.length - 1) : start, 0);
+ return function() {
+ var args = arguments,
+ index = -1,
+ length = nativeMax(args.length - start, 0),
+ array = Array(length);
+
+ while (++index < length) {
+ array[index] = args[start + index];
+ }
+ index = -1;
+ var otherArgs = Array(start + 1);
+ while (++index < start) {
+ otherArgs[index] = args[index];
+ }
+ otherArgs[start] = transform(array);
+ return apply(func, this, otherArgs);
+ };
+ }
+
+ /**
+ * Gets the parent value at `path` of `object`.
+ *
+ * @private
+ * @param {Object} object The object to query.
+ * @param {Array} path The path to get the parent value of.
+ * @returns {*} Returns the parent value.
+ */
+ function parent(object, path) {
+ return path.length == 1 ? object : baseGet(object, baseSlice(path, 0, -1));
+ }
+
+ /**
+ * Reorder `array` according to the specified indexes where the element at
+ * the first index is assigned as the first element, the element at
+ * the second index is assigned as the second element, and so on.
+ *
+ * @private
+ * @param {Array} array The array to reorder.
+ * @param {Array} indexes The arranged array indexes.
+ * @returns {Array} Returns `array`.
+ */
+ function reorder(array, indexes) {
+ var arrLength = array.length,
+ length = nativeMin(indexes.length, arrLength),
+ oldArray = copyArray(array);
+
+ while (length--) {
+ var index = indexes[length];
+ array[length] = isIndex(index, arrLength) ? oldArray[index] : undefined;
+ }
+ return array;
+ }
+
+ /**
+ * Sets metadata for `func`.
+ *
+ * **Note:** If this function becomes hot, i.e. is invoked a lot in a short
+ * period of time, it will trip its breaker and transition to an identity
+ * function to avoid garbage collection pauses in V8. See
+ * [V8 issue 2070](https://bugs.chromium.org/p/v8/issues/detail?id=2070)
+ * for more details.
+ *
+ * @private
+ * @param {Function} func The function to associate metadata with.
+ * @param {*} data The metadata.
+ * @returns {Function} Returns `func`.
+ */
+ var setData = shortOut(baseSetData);
+
+ /**
+ * A simple wrapper around the global [`setTimeout`](https://mdn.io/setTimeout).
+ *
+ * @private
+ * @param {Function} func The function to delay.
+ * @param {number} wait The number of milliseconds to delay invocation.
+ * @returns {number|Object} Returns the timer id or timeout object.
+ */
+ var setTimeout = ctxSetTimeout || function(func, wait) {
+ return root.setTimeout(func, wait);
+ };
+
+ /**
+ * Sets the `toString` method of `func` to return `string`.
+ *
+ * @private
+ * @param {Function} func The function to modify.
+ * @param {Function} string The `toString` result.
+ * @returns {Function} Returns `func`.
+ */
+ var setToString = shortOut(baseSetToString);
+
+ /**
+ * Sets the `toString` method of `wrapper` to mimic the source of `reference`
+ * with wrapper details in a comment at the top of the source body.
+ *
+ * @private
+ * @param {Function} wrapper The function to modify.
+ * @param {Function} reference The reference function.
+ * @param {number} bitmask The bitmask flags. See `createWrap` for more details.
+ * @returns {Function} Returns `wrapper`.
+ */
+ function setWrapToString(wrapper, reference, bitmask) {
+ var source = (reference + '');
+ return setToString(wrapper, insertWrapDetails(source, updateWrapDetails(getWrapDetails(source), bitmask)));
+ }
+
+ /**
+ * Creates a function that'll short out and invoke `identity` instead
+ * of `func` when it's called `HOT_COUNT` or more times in `HOT_SPAN`
+ * milliseconds.
+ *
+ * @private
+ * @param {Function} func The function to restrict.
+ * @returns {Function} Returns the new shortable function.
+ */
+ function shortOut(func) {
+ var count = 0,
+ lastCalled = 0;
+
+ return function() {
+ var stamp = nativeNow(),
+ remaining = HOT_SPAN - (stamp - lastCalled);
+
+ lastCalled = stamp;
+ if (remaining > 0) {
+ if (++count >= HOT_COUNT) {
+ return arguments[0];
+ }
+ } else {
+ count = 0;
+ }
+ return func.apply(undefined, arguments);
+ };
+ }
+
+ /**
+ * A specialized version of `_.shuffle` which mutates and sets the size of `array`.
+ *
+ * @private
+ * @param {Array} array The array to shuffle.
+ * @param {number} [size=array.length] The size of `array`.
+ * @returns {Array} Returns `array`.
+ */
+ function shuffleSelf(array, size) {
+ var index = -1,
+ length = array.length,
+ lastIndex = length - 1;
+
+ size = size === undefined ? length : size;
+ while (++index < size) {
+ var rand = baseRandom(index, lastIndex),
+ value = array[rand];
+
+ array[rand] = array[index];
+ array[index] = value;
+ }
+ array.length = size;
+ return array;
+ }
+
+ /**
+ * Converts `string` to a property path array.
+ *
+ * @private
+ * @param {string} string The string to convert.
+ * @returns {Array} Returns the property path array.
+ */
+ var stringToPath = memoizeCapped(function(string) {
+ string = toString(string);
+
+ var result = [];
+ if (reLeadingDot.test(string)) {
+ result.push('');
+ }
+ string.replace(rePropName, function(match, number, quote, string) {
+ result.push(quote ? string.replace(reEscapeChar, '$1') : (number || match));
+ });
+ return result;
+ });
+
+ /**
+ * Converts `value` to a string key if it's not a string or symbol.
+ *
+ * @private
+ * @param {*} value The value to inspect.
+ * @returns {string|symbol} Returns the key.
+ */
+ function toKey(value) {
+ if (typeof value == 'string' || isSymbol(value)) {
+ return value;
+ }
+ var result = (value + '');
+ return (result == '0' && (1 / value) == -INFINITY) ? '-0' : result;
+ }
+
+ /**
+ * Converts `func` to its source code.
+ *
+ * @private
+ * @param {Function} func The function to process.
+ * @returns {string} Returns the source code.
+ */
+ function toSource(func) {
+ if (func != null) {
+ try {
+ return funcToString.call(func);
+ } catch (e) {}
+ try {
+ return (func + '');
+ } catch (e) {}
+ }
+ return '';
+ }
+
+ /**
+ * Updates wrapper `details` based on `bitmask` flags.
+ *
+ * @private
+ * @returns {Array} details The details to modify.
+ * @param {number} bitmask The bitmask flags. See `createWrap` for more details.
+ * @returns {Array} Returns `details`.
+ */
+ function updateWrapDetails(details, bitmask) {
+ arrayEach(wrapFlags, function(pair) {
+ var value = '_.' + pair[0];
+ if ((bitmask & pair[1]) && !arrayIncludes(details, value)) {
+ details.push(value);
+ }
+ });
+ return details.sort();
+ }
+
+ /**
+ * Creates a clone of `wrapper`.
+ *
+ * @private
+ * @param {Object} wrapper The wrapper to clone.
+ * @returns {Object} Returns the cloned wrapper.
+ */
+ function wrapperClone(wrapper) {
+ if (wrapper instanceof LazyWrapper) {
+ return wrapper.clone();
+ }
+ var result = new LodashWrapper(wrapper.__wrapped__, wrapper.__chain__);
+ result.__actions__ = copyArray(wrapper.__actions__);
+ result.__index__ = wrapper.__index__;
+ result.__values__ = wrapper.__values__;
+ return result;
+ }
+
+ /*------------------------------------------------------------------------*/
+
+ /**
+ * Creates an array of elements split into groups the length of `size`.
+ * If `array` can't be split evenly, the final chunk will be the remaining
+ * elements.
+ *
+ * @static
+ * @memberOf _
+ * @since 3.0.0
+ * @category Array
+ * @param {Array} array The array to process.
+ * @param {number} [size=1] The length of each chunk
+ * @param- {Object} [guard] Enables use as an iteratee for methods like `_.map`.
+ * @returns {Array} Returns the new array of chunks.
+ * @example
+ *
+ * _.chunk(['a', 'b', 'c', 'd'], 2);
+ * // => [['a', 'b'], ['c', 'd']]
+ *
+ * _.chunk(['a', 'b', 'c', 'd'], 3);
+ * // => [['a', 'b', 'c'], ['d']]
+ */
+ function chunk(array, size, guard) {
+ if ((guard ? isIterateeCall(array, size, guard) : size === undefined)) {
+ size = 1;
+ } else {
+ size = nativeMax(toInteger(size), 0);
+ }
+ var length = array ? array.length : 0;
+ if (!length || size < 1) {
+ return [];
+ }
+ var index = 0,
+ resIndex = 0,
+ result = Array(nativeCeil(length / size));
+
+ while (index < length) {
+ result[resIndex++] = baseSlice(array, index, (index += size));
+ }
+ return result;
+ }
+
+ /**
+ * Creates an array with all falsey values removed. The values `false`, `null`,
+ * `0`, `""`, `undefined`, and `NaN` are falsey.
+ *
+ * @static
+ * @memberOf _
+ * @since 0.1.0
+ * @category Array
+ * @param {Array} array The array to compact.
+ * @returns {Array} Returns the new array of filtered values.
+ * @example
+ *
+ * _.compact([0, 1, false, 2, '', 3]);
+ * // => [1, 2, 3]
+ */
+ function compact(array) {
+ var index = -1,
+ length = array ? array.length : 0,
+ resIndex = 0,
+ result = [];
+
+ while (++index < length) {
+ var value = array[index];
+ if (value) {
+ result[resIndex++] = value;
+ }
+ }
+ return result;
+ }
+
+ /**
+ * Creates a new array concatenating `array` with any additional arrays
+ * and/or values.
+ *
+ * @static
+ * @memberOf _
+ * @since 4.0.0
+ * @category Array
+ * @param {Array} array The array to concatenate.
+ * @param {...*} [values] The values to concatenate.
+ * @returns {Array} Returns the new concatenated array.
+ * @example
+ *
+ * var array = [1];
+ * var other = _.concat(array, 2, [3], [[4]]);
+ *
+ * console.log(other);
+ * // => [1, 2, 3, [4]]
+ *
+ * console.log(array);
+ * // => [1]
+ */
+ function concat() {
+ var length = arguments.length;
+ if (!length) {
+ return [];
+ }
+ var args = Array(length - 1),
+ array = arguments[0],
+ index = length;
+
+ while (index--) {
+ args[index - 1] = arguments[index];
+ }
+ return arrayPush(isArray(array) ? copyArray(array) : [array], baseFlatten(args, 1));
+ }
+
+ /**
+ * Creates an array of `array` values not included in the other given arrays
+ * using [`SameValueZero`](http://ecma-international.org/ecma-262/7.0/#sec-samevaluezero)
+ * for equality comparisons. The order and references of result values are
+ * determined by the first array.
+ *
+ * **Note:** Unlike `_.pullAll`, this method returns a new array.
+ *
+ * @static
+ * @memberOf _
+ * @since 0.1.0
+ * @category Array
+ * @param {Array} array The array to inspect.
+ * @param {...Array} [values] The values to exclude.
+ * @returns {Array} Returns the new array of filtered values.
+ * @see _.without, _.xor
+ * @example
+ *
+ * _.difference([2, 1], [2, 3]);
+ * // => [1]
+ */
+ var difference = baseRest(function(array, values) {
+ return isArrayLikeObject(array)
+ ? baseDifference(array, baseFlatten(values, 1, isArrayLikeObject, true))
+ : [];
+ });
+
+ /**
+ * This method is like `_.difference` except that it accepts `iteratee` which
+ * is invoked for each element of `array` and `values` to generate the criterion
+ * by which they're compared. The order and references of result values are
+ * determined by the first array. The iteratee is invoked with one argument:
+ * (value).
+ *
+ * **Note:** Unlike `_.pullAllBy`, this method returns a new array.
+ *
+ * @static
+ * @memberOf _
+ * @since 4.0.0
+ * @category Array
+ * @param {Array} array The array to inspect.
+ * @param {...Array} [values] The values to exclude.
+ * @param {Function} [iteratee=_.identity] The iteratee invoked per element.
+ * @returns {Array} Returns the new array of filtered values.
+ * @example
+ *
+ * _.differenceBy([2.1, 1.2], [2.3, 3.4], Math.floor);
+ * // => [1.2]
+ *
+ * // The `_.property` iteratee shorthand.
+ * _.differenceBy([{ 'x': 2 }, { 'x': 1 }], [{ 'x': 1 }], 'x');
+ * // => [{ 'x': 2 }]
+ */
+ var differenceBy = baseRest(function(array, values) {
+ var iteratee = last(values);
+ if (isArrayLikeObject(iteratee)) {
+ iteratee = undefined;
+ }
+ return isArrayLikeObject(array)
+ ? baseDifference(array, baseFlatten(values, 1, isArrayLikeObject, true), getIteratee(iteratee, 2))
+ : [];
+ });
+
+ /**
+ * This method is like `_.difference` except that it accepts `comparator`
+ * which is invoked to compare elements of `array` to `values`. The order and
+ * references of result values are determined by the first array. The comparator
+ * is invoked with two arguments: (arrVal, othVal).
+ *
+ * **Note:** Unlike `_.pullAllWith`, this method returns a new array.
+ *
+ * @static
+ * @memberOf _
+ * @since 4.0.0
+ * @category Array
+ * @param {Array} array The array to inspect.
+ * @param {...Array} [values] The values to exclude.
+ * @param {Function} [comparator] The comparator invoked per element.
+ * @returns {Array} Returns the new array of filtered values.
+ * @example
+ *
+ * var objects = [{ 'x': 1, 'y': 2 }, { 'x': 2, 'y': 1 }];
+ *
+ * _.differenceWith(objects, [{ 'x': 1, 'y': 2 }], _.isEqual);
+ * // => [{ 'x': 2, 'y': 1 }]
+ */
+ var differenceWith = baseRest(function(array, values) {
+ var comparator = last(values);
+ if (isArrayLikeObject(comparator)) {
+ comparator = undefined;
+ }
+ return isArrayLikeObject(array)
+ ? baseDifference(array, baseFlatten(values, 1, isArrayLikeObject, true), undefined, comparator)
+ : [];
+ });
+
+ /**
+ * Creates a slice of `array` with `n` elements dropped from the beginning.
+ *
+ * @static
+ * @memberOf _
+ * @since 0.5.0
+ * @category Array
+ * @param {Array} array The array to query.
+ * @param {number} [n=1] The number of elements to drop.
+ * @param- {Object} [guard] Enables use as an iteratee for methods like `_.map`.
+ * @returns {Array} Returns the slice of `array`.
+ * @example
+ *
+ * _.drop([1, 2, 3]);
+ * // => [2, 3]
+ *
+ * _.drop([1, 2, 3], 2);
+ * // => [3]
+ *
+ * _.drop([1, 2, 3], 5);
+ * // => []
+ *
+ * _.drop([1, 2, 3], 0);
+ * // => [1, 2, 3]
+ */
+ function drop(array, n, guard) {
+ var length = array ? array.length : 0;
+ if (!length) {
+ return [];
+ }
+ n = (guard || n === undefined) ? 1 : toInteger(n);
+ return baseSlice(array, n < 0 ? 0 : n, length);
+ }
+
+ /**
+ * Creates a slice of `array` with `n` elements dropped from the end.
+ *
+ * @static
+ * @memberOf _
+ * @since 3.0.0
+ * @category Array
+ * @param {Array} array The array to query.
+ * @param {number} [n=1] The number of elements to drop.
+ * @param- {Object} [guard] Enables use as an iteratee for methods like `_.map`.
+ * @returns {Array} Returns the slice of `array`.
+ * @example
+ *
+ * _.dropRight([1, 2, 3]);
+ * // => [1, 2]
+ *
+ * _.dropRight([1, 2, 3], 2);
+ * // => [1]
+ *
+ * _.dropRight([1, 2, 3], 5);
+ * // => []
+ *
+ * _.dropRight([1, 2, 3], 0);
+ * // => [1, 2, 3]
+ */
+ function dropRight(array, n, guard) {
+ var length = array ? array.length : 0;
+ if (!length) {
+ return [];
+ }
+ n = (guard || n === undefined) ? 1 : toInteger(n);
+ n = length - n;
+ return baseSlice(array, 0, n < 0 ? 0 : n);
+ }
+
+ /**
+ * Creates a slice of `array` excluding elements dropped from the end.
+ * Elements are dropped until `predicate` returns falsey. The predicate is
+ * invoked with three arguments: (value, index, array).
+ *
+ * @static
+ * @memberOf _
+ * @since 3.0.0
+ * @category Array
+ * @param {Array} array The array to query.
+ * @param {Function} [predicate=_.identity] The function invoked per iteration.
+ * @returns {Array} Returns the slice of `array`.
+ * @example
+ *
+ * var users = [
+ * { 'user': 'barney', 'active': true },
+ * { 'user': 'fred', 'active': false },
+ * { 'user': 'pebbles', 'active': false }
+ * ];
+ *
+ * _.dropRightWhile(users, function(o) { return !o.active; });
+ * // => objects for ['barney']
+ *
+ * // The `_.matches` iteratee shorthand.
+ * _.dropRightWhile(users, { 'user': 'pebbles', 'active': false });
+ * // => objects for ['barney', 'fred']
+ *
+ * // The `_.matchesProperty` iteratee shorthand.
+ * _.dropRightWhile(users, ['active', false]);
+ * // => objects for ['barney']
+ *
+ * // The `_.property` iteratee shorthand.
+ * _.dropRightWhile(users, 'active');
+ * // => objects for ['barney', 'fred', 'pebbles']
+ */
+ function dropRightWhile(array, predicate) {
+ return (array && array.length)
+ ? baseWhile(array, getIteratee(predicate, 3), true, true)
+ : [];
+ }
+
+ /**
+ * Creates a slice of `array` excluding elements dropped from the beginning.
+ * Elements are dropped until `predicate` returns falsey. The predicate is
+ * invoked with three arguments: (value, index, array).
+ *
+ * @static
+ * @memberOf _
+ * @since 3.0.0
+ * @category Array
+ * @param {Array} array The array to query.
+ * @param {Function} [predicate=_.identity]
+ * The function invoked per iteration.
+ * @returns {Array} Returns the slice of `array`.
+ * @example
+ *
+ * var users = [
+ * { 'user': 'barney', 'active': false },
+ * { 'user': 'fred', 'active': false },
+ * { 'user': 'pebbles', 'active': true }
+ * ];
+ *
+ * _.dropWhile(users, function(o) { return !o.active; });
+ * // => objects for ['pebbles']
+ *
+ * // The `_.matches` iteratee shorthand.
+ * _.dropWhile(users, { 'user': 'barney', 'active': false });
+ * // => objects for ['fred', 'pebbles']
+ *
+ * // The `_.matchesProperty` iteratee shorthand.
+ * _.dropWhile(users, ['active', false]);
+ * // => objects for ['pebbles']
+ *
+ * // The `_.property` iteratee shorthand.
+ * _.dropWhile(users, 'active');
+ * // => objects for ['barney', 'fred', 'pebbles']
+ */
+ function dropWhile(array, predicate) {
+ return (array && array.length)
+ ? baseWhile(array, getIteratee(predicate, 3), true)
+ : [];
+ }
+
+ /**
+ * Fills elements of `array` with `value` from `start` up to, but not
+ * including, `end`.
+ *
+ * **Note:** This method mutates `array`.
+ *
+ * @static
+ * @memberOf _
+ * @since 3.2.0
+ * @category Array
+ * @param {Array} array The array to fill.
+ * @param {*} value The value to fill `array` with.
+ * @param {number} [start=0] The start position.
+ * @param {number} [end=array.length] The end position.
+ * @returns {Array} Returns `array`.
+ * @example
+ *
+ * var array = [1, 2, 3];
+ *
+ * _.fill(array, 'a');
+ * console.log(array);
+ * // => ['a', 'a', 'a']
+ *
+ * _.fill(Array(3), 2);
+ * // => [2, 2, 2]
+ *
+ * _.fill([4, 6, 8, 10], '*', 1, 3);
+ * // => [4, '*', '*', 10]
+ */
+ function fill(array, value, start, end) {
+ var length = array ? array.length : 0;
+ if (!length) {
+ return [];
+ }
+ if (start && typeof start != 'number' && isIterateeCall(array, value, start)) {
+ start = 0;
+ end = length;
+ }
+ return baseFill(array, value, start, end);
+ }
+
+ /**
+ * This method is like `_.find` except that it returns the index of the first
+ * element `predicate` returns truthy for instead of the element itself.
+ *
+ * @static
+ * @memberOf _
+ * @since 1.1.0
+ * @category Array
+ * @param {Array} array The array to inspect.
+ * @param {Function} [predicate=_.identity]
+ * The function invoked per iteration.
+ * @param {number} [fromIndex=0] The index to search from.
+ * @returns {number} Returns the index of the found element, else `-1`.
+ * @example
+ *
+ * var users = [
+ * { 'user': 'barney', 'active': false },
+ * { 'user': 'fred', 'active': false },
+ * { 'user': 'pebbles', 'active': true }
+ * ];
+ *
+ * _.findIndex(users, function(o) { return o.user == 'barney'; });
+ * // => 0
+ *
+ * // The `_.matches` iteratee shorthand.
+ * _.findIndex(users, { 'user': 'fred', 'active': false });
+ * // => 1
+ *
+ * // The `_.matchesProperty` iteratee shorthand.
+ * _.findIndex(users, ['active', false]);
+ * // => 0
+ *
+ * // The `_.property` iteratee shorthand.
+ * _.findIndex(users, 'active');
+ * // => 2
+ */
+ function findIndex(array, predicate, fromIndex) {
+ var length = array ? array.length : 0;
+ if (!length) {
+ return -1;
+ }
+ var index = fromIndex == null ? 0 : toInteger(fromIndex);
+ if (index < 0) {
+ index = nativeMax(length + index, 0);
+ }
+ return baseFindIndex(array, getIteratee(predicate, 3), index);
+ }
+
+ /**
+ * This method is like `_.findIndex` except that it iterates over elements
+ * of `collection` from right to left.
+ *
+ * @static
+ * @memberOf _
+ * @since 2.0.0
+ * @category Array
+ * @param {Array} array The array to inspect.
+ * @param {Function} [predicate=_.identity]
+ * The function invoked per iteration.
+ * @param {number} [fromIndex=array.length-1] The index to search from.
+ * @returns {number} Returns the index of the found element, else `-1`.
+ * @example
+ *
+ * var users = [
+ * { 'user': 'barney', 'active': true },
+ * { 'user': 'fred', 'active': false },
+ * { 'user': 'pebbles', 'active': false }
+ * ];
+ *
+ * _.findLastIndex(users, function(o) { return o.user == 'pebbles'; });
+ * // => 2
+ *
+ * // The `_.matches` iteratee shorthand.
+ * _.findLastIndex(users, { 'user': 'barney', 'active': true });
+ * // => 0
+ *
+ * // The `_.matchesProperty` iteratee shorthand.
+ * _.findLastIndex(users, ['active', false]);
+ * // => 2
+ *
+ * // The `_.property` iteratee shorthand.
+ * _.findLastIndex(users, 'active');
+ * // => 0
+ */
+ function findLastIndex(array, predicate, fromIndex) {
+ var length = array ? array.length : 0;
+ if (!length) {
+ return -1;
+ }
+ var index = length - 1;
+ if (fromIndex !== undefined) {
+ index = toInteger(fromIndex);
+ index = fromIndex < 0
+ ? nativeMax(length + index, 0)
+ : nativeMin(index, length - 1);
+ }
+ return baseFindIndex(array, getIteratee(predicate, 3), index, true);
+ }
+
+ /**
+ * Flattens `array` a single level deep.
+ *
+ * @static
+ * @memberOf _
+ * @since 0.1.0
+ * @category Array
+ * @param {Array} array The array to flatten.
+ * @returns {Array} Returns the new flattened array.
+ * @example
+ *
+ * _.flatten([1, [2, [3, [4]], 5]]);
+ * // => [1, 2, [3, [4]], 5]
+ */
+ function flatten(array) {
+ var length = array ? array.length : 0;
+ return length ? baseFlatten(array, 1) : [];
+ }
+
+ /**
+ * Recursively flattens `array`.
+ *
+ * @static
+ * @memberOf _
+ * @since 3.0.0
+ * @category Array
+ * @param {Array} array The array to flatten.
+ * @returns {Array} Returns the new flattened array.
+ * @example
+ *
+ * _.flattenDeep([1, [2, [3, [4]], 5]]);
+ * // => [1, 2, 3, 4, 5]
+ */
+ function flattenDeep(array) {
+ var length = array ? array.length : 0;
+ return length ? baseFlatten(array, INFINITY) : [];
+ }
+
+ /**
+ * Recursively flatten `array` up to `depth` times.
+ *
+ * @static
+ * @memberOf _
+ * @since 4.4.0
+ * @category Array
+ * @param {Array} array The array to flatten.
+ * @param {number} [depth=1] The maximum recursion depth.
+ * @returns {Array} Returns the new flattened array.
+ * @example
+ *
+ * var array = [1, [2, [3, [4]], 5]];
+ *
+ * _.flattenDepth(array, 1);
+ * // => [1, 2, [3, [4]], 5]
+ *
+ * _.flattenDepth(array, 2);
+ * // => [1, 2, 3, [4], 5]
+ */
+ function flattenDepth(array, depth) {
+ var length = array ? array.length : 0;
+ if (!length) {
+ return [];
+ }
+ depth = depth === undefined ? 1 : toInteger(depth);
+ return baseFlatten(array, depth);
+ }
+
+ /**
+ * The inverse of `_.toPairs`; this method returns an object composed
+ * from key-value `pairs`.
+ *
+ * @static
+ * @memberOf _
+ * @since 4.0.0
+ * @category Array
+ * @param {Array} pairs The key-value pairs.
+ * @returns {Object} Returns the new object.
+ * @example
+ *
+ * _.fromPairs([['a', 1], ['b', 2]]);
+ * // => { 'a': 1, 'b': 2 }
+ */
+ function fromPairs(pairs) {
+ var index = -1,
+ length = pairs ? pairs.length : 0,
+ result = {};
+
+ while (++index < length) {
+ var pair = pairs[index];
+ result[pair[0]] = pair[1];
+ }
+ return result;
+ }
+
+ /**
+ * Gets the first element of `array`.
+ *
+ * @static
+ * @memberOf _
+ * @since 0.1.0
+ * @alias first
+ * @category Array
+ * @param {Array} array The array to query.
+ * @returns {*} Returns the first element of `array`.
+ * @example
+ *
+ * _.head([1, 2, 3]);
+ * // => 1
+ *
+ * _.head([]);
+ * // => undefined
+ */
+ function head(array) {
+ return (array && array.length) ? array[0] : undefined;
+ }
+
+ /**
+ * Gets the index at which the first occurrence of `value` is found in `array`
+ * using [`SameValueZero`](http://ecma-international.org/ecma-262/7.0/#sec-samevaluezero)
+ * for equality comparisons. If `fromIndex` is negative, it's used as the
+ * offset from the end of `array`.
+ *
+ * @static
+ * @memberOf _
+ * @since 0.1.0
+ * @category Array
+ * @param {Array} array The array to inspect.
+ * @param {*} value The value to search for.
+ * @param {number} [fromIndex=0] The index to search from.
+ * @returns {number} Returns the index of the matched value, else `-1`.
+ * @example
+ *
+ * _.indexOf([1, 2, 1, 2], 2);
+ * // => 1
+ *
+ * // Search from the `fromIndex`.
+ * _.indexOf([1, 2, 1, 2], 2, 2);
+ * // => 3
+ */
+ function indexOf(array, value, fromIndex) {
+ var length = array ? array.length : 0;
+ if (!length) {
+ return -1;
+ }
+ var index = fromIndex == null ? 0 : toInteger(fromIndex);
+ if (index < 0) {
+ index = nativeMax(length + index, 0);
+ }
+ return baseIndexOf(array, value, index);
+ }
+
+ /**
+ * Gets all but the last element of `array`.
+ *
+ * @static
+ * @memberOf _
+ * @since 0.1.0
+ * @category Array
+ * @param {Array} array The array to query.
+ * @returns {Array} Returns the slice of `array`.
+ * @example
+ *
+ * _.initial([1, 2, 3]);
+ * // => [1, 2]
+ */
+ function initial(array) {
+ var length = array ? array.length : 0;
+ return length ? baseSlice(array, 0, -1) : [];
+ }
+
+ /**
+ * Creates an array of unique values that are included in all given arrays
+ * using [`SameValueZero`](http://ecma-international.org/ecma-262/7.0/#sec-samevaluezero)
+ * for equality comparisons. The order and references of result values are
+ * determined by the first array.
+ *
+ * @static
+ * @memberOf _
+ * @since 0.1.0
+ * @category Array
+ * @param {...Array} [arrays] The arrays to inspect.
+ * @returns {Array} Returns the new array of intersecting values.
+ * @example
+ *
+ * _.intersection([2, 1], [2, 3]);
+ * // => [2]
+ */
+ var intersection = baseRest(function(arrays) {
+ var mapped = arrayMap(arrays, castArrayLikeObject);
+ return (mapped.length && mapped[0] === arrays[0])
+ ? baseIntersection(mapped)
+ : [];
+ });
+
+ /**
+ * This method is like `_.intersection` except that it accepts `iteratee`
+ * which is invoked for each element of each `arrays` to generate the criterion
+ * by which they're compared. The order and references of result values are
+ * determined by the first array. The iteratee is invoked with one argument:
+ * (value).
+ *
+ * @static
+ * @memberOf _
+ * @since 4.0.0
+ * @category Array
+ * @param {...Array} [arrays] The arrays to inspect.
+ * @param {Function} [iteratee=_.identity] The iteratee invoked per element.
+ * @returns {Array} Returns the new array of intersecting values.
+ * @example
+ *
+ * _.intersectionBy([2.1, 1.2], [2.3, 3.4], Math.floor);
+ * // => [2.1]
+ *
+ * // The `_.property` iteratee shorthand.
+ * _.intersectionBy([{ 'x': 1 }], [{ 'x': 2 }, { 'x': 1 }], 'x');
+ * // => [{ 'x': 1 }]
+ */
+ var intersectionBy = baseRest(function(arrays) {
+ var iteratee = last(arrays),
+ mapped = arrayMap(arrays, castArrayLikeObject);
+
+ if (iteratee === last(mapped)) {
+ iteratee = undefined;
+ } else {
+ mapped.pop();
+ }
+ return (mapped.length && mapped[0] === arrays[0])
+ ? baseIntersection(mapped, getIteratee(iteratee, 2))
+ : [];
+ });
+
+ /**
+ * This method is like `_.intersection` except that it accepts `comparator`
+ * which is invoked to compare elements of `arrays`. The order and references
+ * of result values are determined by the first array. The comparator is
+ * invoked with two arguments: (arrVal, othVal).
+ *
+ * @static
+ * @memberOf _
+ * @since 4.0.0
+ * @category Array
+ * @param {...Array} [arrays] The arrays to inspect.
+ * @param {Function} [comparator] The comparator invoked per element.
+ * @returns {Array} Returns the new array of intersecting values.
+ * @example
+ *
+ * var objects = [{ 'x': 1, 'y': 2 }, { 'x': 2, 'y': 1 }];
+ * var others = [{ 'x': 1, 'y': 1 }, { 'x': 1, 'y': 2 }];
+ *
+ * _.intersectionWith(objects, others, _.isEqual);
+ * // => [{ 'x': 1, 'y': 2 }]
+ */
+ var intersectionWith = baseRest(function(arrays) {
+ var comparator = last(arrays),
+ mapped = arrayMap(arrays, castArrayLikeObject);
+
+ if (comparator === last(mapped)) {
+ comparator = undefined;
+ } else {
+ mapped.pop();
+ }
+ return (mapped.length && mapped[0] === arrays[0])
+ ? baseIntersection(mapped, undefined, comparator)
+ : [];
+ });
+
+ /**
+ * Converts all elements in `array` into a string separated by `separator`.
+ *
+ * @static
+ * @memberOf _
+ * @since 4.0.0
+ * @category Array
+ * @param {Array} array The array to convert.
+ * @param {string} [separator=','] The element separator.
+ * @returns {string} Returns the joined string.
+ * @example
+ *
+ * _.join(['a', 'b', 'c'], '~');
+ * // => 'a~b~c'
+ */
+ function join(array, separator) {
+ return array ? nativeJoin.call(array, separator) : '';
+ }
+
+ /**
+ * Gets the last element of `array`.
+ *
+ * @static
+ * @memberOf _
+ * @since 0.1.0
+ * @category Array
+ * @param {Array} array The array to query.
+ * @returns {*} Returns the last element of `array`.
+ * @example
+ *
+ * _.last([1, 2, 3]);
+ * // => 3
+ */
+ function last(array) {
+ var length = array ? array.length : 0;
+ return length ? array[length - 1] : undefined;
+ }
+
+ /**
+ * This method is like `_.indexOf` except that it iterates over elements of
+ * `array` from right to left.
+ *
+ * @static
+ * @memberOf _
+ * @since 0.1.0
+ * @category Array
+ * @param {Array} array The array to inspect.
+ * @param {*} value The value to search for.
+ * @param {number} [fromIndex=array.length-1] The index to search from.
+ * @returns {number} Returns the index of the matched value, else `-1`.
+ * @example
+ *
+ * _.lastIndexOf([1, 2, 1, 2], 2);
+ * // => 3
+ *
+ * // Search from the `fromIndex`.
+ * _.lastIndexOf([1, 2, 1, 2], 2, 2);
+ * // => 1
+ */
+ function lastIndexOf(array, value, fromIndex) {
+ var length = array ? array.length : 0;
+ if (!length) {
+ return -1;
+ }
+ var index = length;
+ if (fromIndex !== undefined) {
+ index = toInteger(fromIndex);
+ index = index < 0 ? nativeMax(length + index, 0) : nativeMin(index, length - 1);
+ }
+ return value === value
+ ? strictLastIndexOf(array, value, index)
+ : baseFindIndex(array, baseIsNaN, index, true);
+ }
+
+ /**
+ * Gets the element at index `n` of `array`. If `n` is negative, the nth
+ * element from the end is returned.
+ *
+ * @static
+ * @memberOf _
+ * @since 4.11.0
+ * @category Array
+ * @param {Array} array The array to query.
+ * @param {number} [n=0] The index of the element to return.
+ * @returns {*} Returns the nth element of `array`.
+ * @example
+ *
+ * var array = ['a', 'b', 'c', 'd'];
+ *
+ * _.nth(array, 1);
+ * // => 'b'
+ *
+ * _.nth(array, -2);
+ * // => 'c';
+ */
+ function nth(array, n) {
+ return (array && array.length) ? baseNth(array, toInteger(n)) : undefined;
+ }
+
+ /**
+ * Removes all given values from `array` using
+ * [`SameValueZero`](http://ecma-international.org/ecma-262/7.0/#sec-samevaluezero)
+ * for equality comparisons.
+ *
+ * **Note:** Unlike `_.without`, this method mutates `array`. Use `_.remove`
+ * to remove elements from an array by predicate.
+ *
+ * @static
+ * @memberOf _
+ * @since 2.0.0
+ * @category Array
+ * @param {Array} array The array to modify.
+ * @param {...*} [values] The values to remove.
+ * @returns {Array} Returns `array`.
+ * @example
+ *
+ * var array = ['a', 'b', 'c', 'a', 'b', 'c'];
+ *
+ * _.pull(array, 'a', 'c');
+ * console.log(array);
+ * // => ['b', 'b']
+ */
+ var pull = baseRest(pullAll);
+
+ /**
+ * This method is like `_.pull` except that it accepts an array of values to remove.
+ *
+ * **Note:** Unlike `_.difference`, this method mutates `array`.
+ *
+ * @static
+ * @memberOf _
+ * @since 4.0.0
+ * @category Array
+ * @param {Array} array The array to modify.
+ * @param {Array} values The values to remove.
+ * @returns {Array} Returns `array`.
+ * @example
+ *
+ * var array = ['a', 'b', 'c', 'a', 'b', 'c'];
+ *
+ * _.pullAll(array, ['a', 'c']);
+ * console.log(array);
+ * // => ['b', 'b']
+ */
+ function pullAll(array, values) {
+ return (array && array.length && values && values.length)
+ ? basePullAll(array, values)
+ : array;
+ }
+
+ /**
+ * This method is like `_.pullAll` except that it accepts `iteratee` which is
+ * invoked for each element of `array` and `values` to generate the criterion
+ * by which they're compared. The iteratee is invoked with one argument: (value).
+ *
+ * **Note:** Unlike `_.differenceBy`, this method mutates `array`.
+ *
+ * @static
+ * @memberOf _
+ * @since 4.0.0
+ * @category Array
+ * @param {Array} array The array to modify.
+ * @param {Array} values The values to remove.
+ * @param {Function} [iteratee=_.identity]
+ * The iteratee invoked per element.
+ * @returns {Array} Returns `array`.
+ * @example
+ *
+ * var array = [{ 'x': 1 }, { 'x': 2 }, { 'x': 3 }, { 'x': 1 }];
+ *
+ * _.pullAllBy(array, [{ 'x': 1 }, { 'x': 3 }], 'x');
+ * console.log(array);
+ * // => [{ 'x': 2 }]
+ */
+ function pullAllBy(array, values, iteratee) {
+ return (array && array.length && values && values.length)
+ ? basePullAll(array, values, getIteratee(iteratee, 2))
+ : array;
+ }
+
+ /**
+ * This method is like `_.pullAll` except that it accepts `comparator` which
+ * is invoked to compare elements of `array` to `values`. The comparator is
+ * invoked with two arguments: (arrVal, othVal).
+ *
+ * **Note:** Unlike `_.differenceWith`, this method mutates `array`.
+ *
+ * @static
+ * @memberOf _
+ * @since 4.6.0
+ * @category Array
+ * @param {Array} array The array to modify.
+ * @param {Array} values The values to remove.
+ * @param {Function} [comparator] The comparator invoked per element.
+ * @returns {Array} Returns `array`.
+ * @example
+ *
+ * var array = [{ 'x': 1, 'y': 2 }, { 'x': 3, 'y': 4 }, { 'x': 5, 'y': 6 }];
+ *
+ * _.pullAllWith(array, [{ 'x': 3, 'y': 4 }], _.isEqual);
+ * console.log(array);
+ * // => [{ 'x': 1, 'y': 2 }, { 'x': 5, 'y': 6 }]
+ */
+ function pullAllWith(array, values, comparator) {
+ return (array && array.length && values && values.length)
+ ? basePullAll(array, values, undefined, comparator)
+ : array;
+ }
+
+ /**
+ * Removes elements from `array` corresponding to `indexes` and returns an
+ * array of removed elements.
+ *
+ * **Note:** Unlike `_.at`, this method mutates `array`.
+ *
+ * @static
+ * @memberOf _
+ * @since 3.0.0
+ * @category Array
+ * @param {Array} array The array to modify.
+ * @param {...(number|number[])} [indexes] The indexes of elements to remove.
+ * @returns {Array} Returns the new array of removed elements.
+ * @example
+ *
+ * var array = ['a', 'b', 'c', 'd'];
+ * var pulled = _.pullAt(array, [1, 3]);
+ *
+ * console.log(array);
+ * // => ['a', 'c']
+ *
+ * console.log(pulled);
+ * // => ['b', 'd']
+ */
+ var pullAt = flatRest(function(array, indexes) {
+ var length = array ? array.length : 0,
+ result = baseAt(array, indexes);
+
+ basePullAt(array, arrayMap(indexes, function(index) {
+ return isIndex(index, length) ? +index : index;
+ }).sort(compareAscending));
+
+ return result;
+ });
+
+ /**
+ * Removes all elements from `array` that `predicate` returns truthy for
+ * and returns an array of the removed elements. The predicate is invoked
+ * with three arguments: (value, index, array).
+ *
+ * **Note:** Unlike `_.filter`, this method mutates `array`. Use `_.pull`
+ * to pull elements from an array by value.
+ *
+ * @static
+ * @memberOf _
+ * @since 2.0.0
+ * @category Array
+ * @param {Array} array The array to modify.
+ * @param {Function} [predicate=_.identity]
+ * The function invoked per iteration.
+ * @returns {Array} Returns the new array of removed elements.
+ * @example
+ *
+ * var array = [1, 2, 3, 4];
+ * var evens = _.remove(array, function(n) {
+ * return n % 2 == 0;
+ * });
+ *
+ * console.log(array);
+ * // => [1, 3]
+ *
+ * console.log(evens);
+ * // => [2, 4]
+ */
+ function remove(array, predicate) {
+ var result = [];
+ if (!(array && array.length)) {
+ return result;
+ }
+ var index = -1,
+ indexes = [],
+ length = array.length;
+
+ predicate = getIteratee(predicate, 3);
+ while (++index < length) {
+ var value = array[index];
+ if (predicate(value, index, array)) {
+ result.push(value);
+ indexes.push(index);
+ }
+ }
+ basePullAt(array, indexes);
+ return result;
+ }
+
+ /**
+ * Reverses `array` so that the first element becomes the last, the second
+ * element becomes the second to last, and so on.
+ *
+ * **Note:** This method mutates `array` and is based on
+ * [`Array#reverse`](https://mdn.io/Array/reverse).
+ *
+ * @static
+ * @memberOf _
+ * @since 4.0.0
+ * @category Array
+ * @param {Array} array The array to modify.
+ * @returns {Array} Returns `array`.
+ * @example
+ *
+ * var array = [1, 2, 3];
+ *
+ * _.reverse(array);
+ * // => [3, 2, 1]
+ *
+ * console.log(array);
+ * // => [3, 2, 1]
+ */
+ function reverse(array) {
+ return array ? nativeReverse.call(array) : array;
+ }
+
+ /**
+ * Creates a slice of `array` from `start` up to, but not including, `end`.
+ *
+ * **Note:** This method is used instead of
+ * [`Array#slice`](https://mdn.io/Array/slice) to ensure dense arrays are
+ * returned.
+ *
+ * @static
+ * @memberOf _
+ * @since 3.0.0
+ * @category Array
+ * @param {Array} array The array to slice.
+ * @param {number} [start=0] The start position.
+ * @param {number} [end=array.length] The end position.
+ * @returns {Array} Returns the slice of `array`.
+ */
+ function slice(array, start, end) {
+ var length = array ? array.length : 0;
+ if (!length) {
+ return [];
+ }
+ if (end && typeof end != 'number' && isIterateeCall(array, start, end)) {
+ start = 0;
+ end = length;
+ }
+ else {
+ start = start == null ? 0 : toInteger(start);
+ end = end === undefined ? length : toInteger(end);
+ }
+ return baseSlice(array, start, end);
+ }
+
+ /**
+ * Uses a binary search to determine the lowest index at which `value`
+ * should be inserted into `array` in order to maintain its sort order.
+ *
+ * @static
+ * @memberOf _
+ * @since 0.1.0
+ * @category Array
+ * @param {Array} array The sorted array to inspect.
+ * @param {*} value The value to evaluate.
+ * @returns {number} Returns the index at which `value` should be inserted
+ * into `array`.
+ * @example
+ *
+ * _.sortedIndex([30, 50], 40);
+ * // => 1
+ */
+ function sortedIndex(array, value) {
+ return baseSortedIndex(array, value);
+ }
+
+ /**
+ * This method is like `_.sortedIndex` except that it accepts `iteratee`
+ * which is invoked for `value` and each element of `array` to compute their
+ * sort ranking. The iteratee is invoked with one argument: (value).
+ *
+ * @static
+ * @memberOf _
+ * @since 4.0.0
+ * @category Array
+ * @param {Array} array The sorted array to inspect.
+ * @param {*} value The value to evaluate.
+ * @param {Function} [iteratee=_.identity]
+ * The iteratee invoked per element.
+ * @returns {number} Returns the index at which `value` should be inserted
+ * into `array`.
+ * @example
+ *
+ * var objects = [{ 'x': 4 }, { 'x': 5 }];
+ *
+ * _.sortedIndexBy(objects, { 'x': 4 }, function(o) { return o.x; });
+ * // => 0
+ *
+ * // The `_.property` iteratee shorthand.
+ * _.sortedIndexBy(objects, { 'x': 4 }, 'x');
+ * // => 0
+ */
+ function sortedIndexBy(array, value, iteratee) {
+ return baseSortedIndexBy(array, value, getIteratee(iteratee, 2));
+ }
+
+ /**
+ * This method is like `_.indexOf` except that it performs a binary
+ * search on a sorted `array`.
+ *
+ * @static
+ * @memberOf _
+ * @since 4.0.0
+ * @category Array
+ * @param {Array} array The array to inspect.
+ * @param {*} value The value to search for.
+ * @returns {number} Returns the index of the matched value, else `-1`.
+ * @example
+ *
+ * _.sortedIndexOf([4, 5, 5, 5, 6], 5);
+ * // => 1
+ */
+ function sortedIndexOf(array, value) {
+ var length = array ? array.length : 0;
+ if (length) {
+ var index = baseSortedIndex(array, value);
+ if (index < length && eq(array[index], value)) {
+ return index;
+ }
+ }
+ return -1;
+ }
+
+ /**
+ * This method is like `_.sortedIndex` except that it returns the highest
+ * index at which `value` should be inserted into `array` in order to
+ * maintain its sort order.
+ *
+ * @static
+ * @memberOf _
+ * @since 3.0.0
+ * @category Array
+ * @param {Array} array The sorted array to inspect.
+ * @param {*} value The value to evaluate.
+ * @returns {number} Returns the index at which `value` should be inserted
+ * into `array`.
+ * @example
+ *
+ * _.sortedLastIndex([4, 5, 5, 5, 6], 5);
+ * // => 4
+ */
+ function sortedLastIndex(array, value) {
+ return baseSortedIndex(array, value, true);
+ }
+
+ /**
+ * This method is like `_.sortedLastIndex` except that it accepts `iteratee`
+ * which is invoked for `value` and each element of `array` to compute their
+ * sort ranking. The iteratee is invoked with one argument: (value).
+ *
+ * @static
+ * @memberOf _
+ * @since 4.0.0
+ * @category Array
+ * @param {Array} array The sorted array to inspect.
+ * @param {*} value The value to evaluate.
+ * @param {Function} [iteratee=_.identity]
+ * The iteratee invoked per element.
+ * @returns {number} Returns the index at which `value` should be inserted
+ * into `array`.
+ * @example
+ *
+ * var objects = [{ 'x': 4 }, { 'x': 5 }];
+ *
+ * _.sortedLastIndexBy(objects, { 'x': 4 }, function(o) { return o.x; });
+ * // => 1
+ *
+ * // The `_.property` iteratee shorthand.
+ * _.sortedLastIndexBy(objects, { 'x': 4 }, 'x');
+ * // => 1
+ */
+ function sortedLastIndexBy(array, value, iteratee) {
+ return baseSortedIndexBy(array, value, getIteratee(iteratee, 2), true);
+ }
+
+ /**
+ * This method is like `_.lastIndexOf` except that it performs a binary
+ * search on a sorted `array`.
+ *
+ * @static
+ * @memberOf _
+ * @since 4.0.0
+ * @category Array
+ * @param {Array} array The array to inspect.
+ * @param {*} value The value to search for.
+ * @returns {number} Returns the index of the matched value, else `-1`.
+ * @example
+ *
+ * _.sortedLastIndexOf([4, 5, 5, 5, 6], 5);
+ * // => 3
+ */
+ function sortedLastIndexOf(array, value) {
+ var length = array ? array.length : 0;
+ if (length) {
+ var index = baseSortedIndex(array, value, true) - 1;
+ if (eq(array[index], value)) {
+ return index;
+ }
+ }
+ return -1;
+ }
+
+ /**
+ * This method is like `_.uniq` except that it's designed and optimized
+ * for sorted arrays.
+ *
+ * @static
+ * @memberOf _
+ * @since 4.0.0
+ * @category Array
+ * @param {Array} array The array to inspect.
+ * @returns {Array} Returns the new duplicate free array.
+ * @example
+ *
+ * _.sortedUniq([1, 1, 2]);
+ * // => [1, 2]
+ */
+ function sortedUniq(array) {
+ return (array && array.length)
+ ? baseSortedUniq(array)
+ : [];
+ }
+
+ /**
+ * This method is like `_.uniqBy` except that it's designed and optimized
+ * for sorted arrays.
+ *
+ * @static
+ * @memberOf _
+ * @since 4.0.0
+ * @category Array
+ * @param {Array} array The array to inspect.
+ * @param {Function} [iteratee] The iteratee invoked per element.
+ * @returns {Array} Returns the new duplicate free array.
+ * @example
+ *
+ * _.sortedUniqBy([1.1, 1.2, 2.3, 2.4], Math.floor);
+ * // => [1.1, 2.3]
+ */
+ function sortedUniqBy(array, iteratee) {
+ return (array && array.length)
+ ? baseSortedUniq(array, getIteratee(iteratee, 2))
+ : [];
+ }
+
+ /**
+ * Gets all but the first element of `array`.
+ *
+ * @static
+ * @memberOf _
+ * @since 4.0.0
+ * @category Array
+ * @param {Array} array The array to query.
+ * @returns {Array} Returns the slice of `array`.
+ * @example
+ *
+ * _.tail([1, 2, 3]);
+ * // => [2, 3]
+ */
+ function tail(array) {
+ var length = array ? array.length : 0;
+ return length ? baseSlice(array, 1, length) : [];
+ }
+
+ /**
+ * Creates a slice of `array` with `n` elements taken from the beginning.
+ *
+ * @static
+ * @memberOf _
+ * @since 0.1.0
+ * @category Array
+ * @param {Array} array The array to query.
+ * @param {number} [n=1] The number of elements to take.
+ * @param- {Object} [guard] Enables use as an iteratee for methods like `_.map`.
+ * @returns {Array} Returns the slice of `array`.
+ * @example
+ *
+ * _.take([1, 2, 3]);
+ * // => [1]
+ *
+ * _.take([1, 2, 3], 2);
+ * // => [1, 2]
+ *
+ * _.take([1, 2, 3], 5);
+ * // => [1, 2, 3]
+ *
+ * _.take([1, 2, 3], 0);
+ * // => []
+ */
+ function take(array, n, guard) {
+ if (!(array && array.length)) {
+ return [];
+ }
+ n = (guard || n === undefined) ? 1 : toInteger(n);
+ return baseSlice(array, 0, n < 0 ? 0 : n);
+ }
+
+ /**
+ * Creates a slice of `array` with `n` elements taken from the end.
+ *
+ * @static
+ * @memberOf _
+ * @since 3.0.0
+ * @category Array
+ * @param {Array} array The array to query.
+ * @param {number} [n=1] The number of elements to take.
+ * @param- {Object} [guard] Enables use as an iteratee for methods like `_.map`.
+ * @returns {Array} Returns the slice of `array`.
+ * @example
+ *
+ * _.takeRight([1, 2, 3]);
+ * // => [3]
+ *
+ * _.takeRight([1, 2, 3], 2);
+ * // => [2, 3]
+ *
+ * _.takeRight([1, 2, 3], 5);
+ * // => [1, 2, 3]
+ *
+ * _.takeRight([1, 2, 3], 0);
+ * // => []
+ */
+ function takeRight(array, n, guard) {
+ var length = array ? array.length : 0;
+ if (!length) {
+ return [];
+ }
+ n = (guard || n === undefined) ? 1 : toInteger(n);
+ n = length - n;
+ return baseSlice(array, n < 0 ? 0 : n, length);
+ }
+
+ /**
+ * Creates a slice of `array` with elements taken from the end. Elements are
+ * taken until `predicate` returns falsey. The predicate is invoked with
+ * three arguments: (value, index, array).
+ *
+ * @static
+ * @memberOf _
+ * @since 3.0.0
+ * @category Array
+ * @param {Array} array The array to query.
+ * @param {Function} [predicate=_.identity]
+ * The function invoked per iteration.
+ * @returns {Array} Returns the slice of `array`.
+ * @example
+ *
+ * var users = [
+ * { 'user': 'barney', 'active': true },
+ * { 'user': 'fred', 'active': false },
+ * { 'user': 'pebbles', 'active': false }
+ * ];
+ *
+ * _.takeRightWhile(users, function(o) { return !o.active; });
+ * // => objects for ['fred', 'pebbles']
+ *
+ * // The `_.matches` iteratee shorthand.
+ * _.takeRightWhile(users, { 'user': 'pebbles', 'active': false });
+ * // => objects for ['pebbles']
+ *
+ * // The `_.matchesProperty` iteratee shorthand.
+ * _.takeRightWhile(users, ['active', false]);
+ * // => objects for ['fred', 'pebbles']
+ *
+ * // The `_.property` iteratee shorthand.
+ * _.takeRightWhile(users, 'active');
+ * // => []
+ */
+ function takeRightWhile(array, predicate) {
+ return (array && array.length)
+ ? baseWhile(array, getIteratee(predicate, 3), false, true)
+ : [];
+ }
+
+ /**
+ * Creates a slice of `array` with elements taken from the beginning. Elements
+ * are taken until `predicate` returns falsey. The predicate is invoked with
+ * three arguments: (value, index, array).
+ *
+ * @static
+ * @memberOf _
+ * @since 3.0.0
+ * @category Array
+ * @param {Array} array The array to query.
+ * @param {Function} [predicate=_.identity]
+ * The function invoked per iteration.
+ * @returns {Array} Returns the slice of `array`.
+ * @example
+ *
+ * var users = [
+ * { 'user': 'barney', 'active': false },
+ * { 'user': 'fred', 'active': false},
+ * { 'user': 'pebbles', 'active': true }
+ * ];
+ *
+ * _.takeWhile(users, function(o) { return !o.active; });
+ * // => objects for ['barney', 'fred']
+ *
+ * // The `_.matches` iteratee shorthand.
+ * _.takeWhile(users, { 'user': 'barney', 'active': false });
+ * // => objects for ['barney']
+ *
+ * // The `_.matchesProperty` iteratee shorthand.
+ * _.takeWhile(users, ['active', false]);
+ * // => objects for ['barney', 'fred']
+ *
+ * // The `_.property` iteratee shorthand.
+ * _.takeWhile(users, 'active');
+ * // => []
+ */
+ function takeWhile(array, predicate) {
+ return (array && array.length)
+ ? baseWhile(array, getIteratee(predicate, 3))
+ : [];
+ }
+
+ /**
+ * Creates an array of unique values, in order, from all given arrays using
+ * [`SameValueZero`](http://ecma-international.org/ecma-262/7.0/#sec-samevaluezero)
+ * for equality comparisons.
+ *
+ * @static
+ * @memberOf _
+ * @since 0.1.0
+ * @category Array
+ * @param {...Array} [arrays] The arrays to inspect.
+ * @returns {Array} Returns the new array of combined values.
+ * @example
+ *
+ * _.union([2], [1, 2]);
+ * // => [2, 1]
+ */
+ var union = baseRest(function(arrays) {
+ return baseUniq(baseFlatten(arrays, 1, isArrayLikeObject, true));
+ });
+
+ /**
+ * This method is like `_.union` except that it accepts `iteratee` which is
+ * invoked for each element of each `arrays` to generate the criterion by
+ * which uniqueness is computed. Result values are chosen from the first
+ * array in which the value occurs. The iteratee is invoked with one argument:
+ * (value).
+ *
+ * @static
+ * @memberOf _
+ * @since 4.0.0
+ * @category Array
+ * @param {...Array} [arrays] The arrays to inspect.
+ * @param {Function} [iteratee=_.identity]
+ * The iteratee invoked per element.
+ * @returns {Array} Returns the new array of combined values.
+ * @example
+ *
+ * _.unionBy([2.1], [1.2, 2.3], Math.floor);
+ * // => [2.1, 1.2]
+ *
+ * // The `_.property` iteratee shorthand.
+ * _.unionBy([{ 'x': 1 }], [{ 'x': 2 }, { 'x': 1 }], 'x');
+ * // => [{ 'x': 1 }, { 'x': 2 }]
+ */
+ var unionBy = baseRest(function(arrays) {
+ var iteratee = last(arrays);
+ if (isArrayLikeObject(iteratee)) {
+ iteratee = undefined;
+ }
+ return baseUniq(baseFlatten(arrays, 1, isArrayLikeObject, true), getIteratee(iteratee, 2));
+ });
+
+ /**
+ * This method is like `_.union` except that it accepts `comparator` which
+ * is invoked to compare elements of `arrays`. Result values are chosen from
+ * the first array in which the value occurs. The comparator is invoked
+ * with two arguments: (arrVal, othVal).
+ *
+ * @static
+ * @memberOf _
+ * @since 4.0.0
+ * @category Array
+ * @param {...Array} [arrays] The arrays to inspect.
+ * @param {Function} [comparator] The comparator invoked per element.
+ * @returns {Array} Returns the new array of combined values.
+ * @example
+ *
+ * var objects = [{ 'x': 1, 'y': 2 }, { 'x': 2, 'y': 1 }];
+ * var others = [{ 'x': 1, 'y': 1 }, { 'x': 1, 'y': 2 }];
+ *
+ * _.unionWith(objects, others, _.isEqual);
+ * // => [{ 'x': 1, 'y': 2 }, { 'x': 2, 'y': 1 }, { 'x': 1, 'y': 1 }]
+ */
+ var unionWith = baseRest(function(arrays) {
+ var comparator = last(arrays);
+ if (isArrayLikeObject(comparator)) {
+ comparator = undefined;
+ }
+ return baseUniq(baseFlatten(arrays, 1, isArrayLikeObject, true), undefined, comparator);
+ });
+
+ /**
+ * Creates a duplicate-free version of an array, using
+ * [`SameValueZero`](http://ecma-international.org/ecma-262/7.0/#sec-samevaluezero)
+ * for equality comparisons, in which only the first occurrence of each element
+ * is kept. The order of result values is determined by the order they occur
+ * in the array.
+ *
+ * @static
+ * @memberOf _
+ * @since 0.1.0
+ * @category Array
+ * @param {Array} array The array to inspect.
+ * @returns {Array} Returns the new duplicate free array.
+ * @example
+ *
+ * _.uniq([2, 1, 2]);
+ * // => [2, 1]
+ */
+ function uniq(array) {
+ return (array && array.length)
+ ? baseUniq(array)
+ : [];
+ }
+
+ /**
+ * This method is like `_.uniq` except that it accepts `iteratee` which is
+ * invoked for each element in `array` to generate the criterion by which
+ * uniqueness is computed. The order of result values is determined by the
+ * order they occur in the array. The iteratee is invoked with one argument:
+ * (value).
+ *
+ * @static
+ * @memberOf _
+ * @since 4.0.0
+ * @category Array
+ * @param {Array} array The array to inspect.
+ * @param {Function} [iteratee=_.identity]
+ * The iteratee invoked per element.
+ * @returns {Array} Returns the new duplicate free array.
+ * @example
+ *
+ * _.uniqBy([2.1, 1.2, 2.3], Math.floor);
+ * // => [2.1, 1.2]
+ *
+ * // The `_.property` iteratee shorthand.
+ * _.uniqBy([{ 'x': 1 }, { 'x': 2 }, { 'x': 1 }], 'x');
+ * // => [{ 'x': 1 }, { 'x': 2 }]
+ */
+ function uniqBy(array, iteratee) {
+ return (array && array.length)
+ ? baseUniq(array, getIteratee(iteratee, 2))
+ : [];
+ }
+
+ /**
+ * This method is like `_.uniq` except that it accepts `comparator` which
+ * is invoked to compare elements of `array`. The order of result values is
+ * determined by the order they occur in the array.The comparator is invoked
+ * with two arguments: (arrVal, othVal).
+ *
+ * @static
+ * @memberOf _
+ * @since 4.0.0
+ * @category Array
+ * @param {Array} array The array to inspect.
+ * @param {Function} [comparator] The comparator invoked per element.
+ * @returns {Array} Returns the new duplicate free array.
+ * @example
+ *
+ * var objects = [{ 'x': 1, 'y': 2 }, { 'x': 2, 'y': 1 }, { 'x': 1, 'y': 2 }];
+ *
+ * _.uniqWith(objects, _.isEqual);
+ * // => [{ 'x': 1, 'y': 2 }, { 'x': 2, 'y': 1 }]
+ */
+ function uniqWith(array, comparator) {
+ return (array && array.length)
+ ? baseUniq(array, undefined, comparator)
+ : [];
+ }
+
+ /**
+ * This method is like `_.zip` except that it accepts an array of grouped
+ * elements and creates an array regrouping the elements to their pre-zip
+ * configuration.
+ *
+ * @static
+ * @memberOf _
+ * @since 1.2.0
+ * @category Array
+ * @param {Array} array The array of grouped elements to process.
+ * @returns {Array} Returns the new array of regrouped elements.
+ * @example
+ *
+ * var zipped = _.zip(['a', 'b'], [1, 2], [true, false]);
+ * // => [['a', 1, true], ['b', 2, false]]
+ *
+ * _.unzip(zipped);
+ * // => [['a', 'b'], [1, 2], [true, false]]
+ */
+ function unzip(array) {
+ if (!(array && array.length)) {
+ return [];
+ }
+ var length = 0;
+ array = arrayFilter(array, function(group) {
+ if (isArrayLikeObject(group)) {
+ length = nativeMax(group.length, length);
+ return true;
+ }
+ });
+ return baseTimes(length, function(index) {
+ return arrayMap(array, baseProperty(index));
+ });
+ }
+
+ /**
+ * This method is like `_.unzip` except that it accepts `iteratee` to specify
+ * how regrouped values should be combined. The iteratee is invoked with the
+ * elements of each group: (...group).
+ *
+ * @static
+ * @memberOf _
+ * @since 3.8.0
+ * @category Array
+ * @param {Array} array The array of grouped elements to process.
+ * @param {Function} [iteratee=_.identity] The function to combine
+ * regrouped values.
+ * @returns {Array} Returns the new array of regrouped elements.
+ * @example
+ *
+ * var zipped = _.zip([1, 2], [10, 20], [100, 200]);
+ * // => [[1, 10, 100], [2, 20, 200]]
+ *
+ * _.unzipWith(zipped, _.add);
+ * // => [3, 30, 300]
+ */
+ function unzipWith(array, iteratee) {
+ if (!(array && array.length)) {
+ return [];
+ }
+ var result = unzip(array);
+ if (iteratee == null) {
+ return result;
+ }
+ return arrayMap(result, function(group) {
+ return apply(iteratee, undefined, group);
+ });
+ }
+
+ /**
+ * Creates an array excluding all given values using
+ * [`SameValueZero`](http://ecma-international.org/ecma-262/7.0/#sec-samevaluezero)
+ * for equality comparisons.
+ *
+ * **Note:** Unlike `_.pull`, this method returns a new array.
+ *
+ * @static
+ * @memberOf _
+ * @since 0.1.0
+ * @category Array
+ * @param {Array} array The array to inspect.
+ * @param {...*} [values] The values to exclude.
+ * @returns {Array} Returns the new array of filtered values.
+ * @see _.difference, _.xor
+ * @example
+ *
+ * _.without([2, 1, 2, 3], 1, 2);
+ * // => [3]
+ */
+ var without = baseRest(function(array, values) {
+ return isArrayLikeObject(array)
+ ? baseDifference(array, values)
+ : [];
+ });
+
+ /**
+ * Creates an array of unique values that is the
+ * [symmetric difference](https://en.wikipedia.org/wiki/Symmetric_difference)
+ * of the given arrays. The order of result values is determined by the order
+ * they occur in the arrays.
+ *
+ * @static
+ * @memberOf _
+ * @since 2.4.0
+ * @category Array
+ * @param {...Array} [arrays] The arrays to inspect.
+ * @returns {Array} Returns the new array of filtered values.
+ * @see _.difference, _.without
+ * @example
+ *
+ * _.xor([2, 1], [2, 3]);
+ * // => [1, 3]
+ */
+ var xor = baseRest(function(arrays) {
+ return baseXor(arrayFilter(arrays, isArrayLikeObject));
+ });
+
+ /**
+ * This method is like `_.xor` except that it accepts `iteratee` which is
+ * invoked for each element of each `arrays` to generate the criterion by
+ * which by which they're compared. The order of result values is determined
+ * by the order they occur in the arrays. The iteratee is invoked with one
+ * argument: (value).
+ *
+ * @static
+ * @memberOf _
+ * @since 4.0.0
+ * @category Array
+ * @param {...Array} [arrays] The arrays to inspect.
+ * @param {Function} [iteratee=_.identity]
+ * The iteratee invoked per element.
+ * @returns {Array} Returns the new array of filtered values.
+ * @example
+ *
+ * _.xorBy([2.1, 1.2], [2.3, 3.4], Math.floor);
+ * // => [1.2, 3.4]
+ *
+ * // The `_.property` iteratee shorthand.
+ * _.xorBy([{ 'x': 1 }], [{ 'x': 2 }, { 'x': 1 }], 'x');
+ * // => [{ 'x': 2 }]
+ */
+ var xorBy = baseRest(function(arrays) {
+ var iteratee = last(arrays);
+ if (isArrayLikeObject(iteratee)) {
+ iteratee = undefined;
+ }
+ return baseXor(arrayFilter(arrays, isArrayLikeObject), getIteratee(iteratee, 2));
+ });
+
+ /**
+ * This method is like `_.xor` except that it accepts `comparator` which is
+ * invoked to compare elements of `arrays`. The order of result values is
+ * determined by the order they occur in the arrays. The comparator is invoked
+ * with two arguments: (arrVal, othVal).
+ *
+ * @static
+ * @memberOf _
+ * @since 4.0.0
+ * @category Array
+ * @param {...Array} [arrays] The arrays to inspect.
+ * @param {Function} [comparator] The comparator invoked per element.
+ * @returns {Array} Returns the new array of filtered values.
+ * @example
+ *
+ * var objects = [{ 'x': 1, 'y': 2 }, { 'x': 2, 'y': 1 }];
+ * var others = [{ 'x': 1, 'y': 1 }, { 'x': 1, 'y': 2 }];
+ *
+ * _.xorWith(objects, others, _.isEqual);
+ * // => [{ 'x': 2, 'y': 1 }, { 'x': 1, 'y': 1 }]
+ */
+ var xorWith = baseRest(function(arrays) {
+ var comparator = last(arrays);
+ if (isArrayLikeObject(comparator)) {
+ comparator = undefined;
+ }
+ return baseXor(arrayFilter(arrays, isArrayLikeObject), undefined, comparator);
+ });
+
+ /**
+ * Creates an array of grouped elements, the first of which contains the
+ * first elements of the given arrays, the second of which contains the
+ * second elements of the given arrays, and so on.
+ *
+ * @static
+ * @memberOf _
+ * @since 0.1.0
+ * @category Array
+ * @param {...Array} [arrays] The arrays to process.
+ * @returns {Array} Returns the new array of grouped elements.
+ * @example
+ *
+ * _.zip(['a', 'b'], [1, 2], [true, false]);
+ * // => [['a', 1, true], ['b', 2, false]]
+ */
+ var zip = baseRest(unzip);
+
+ /**
+ * This method is like `_.fromPairs` except that it accepts two arrays,
+ * one of property identifiers and one of corresponding values.
+ *
+ * @static
+ * @memberOf _
+ * @since 0.4.0
+ * @category Array
+ * @param {Array} [props=[]] The property identifiers.
+ * @param {Array} [values=[]] The property values.
+ * @returns {Object} Returns the new object.
+ * @example
+ *
+ * _.zipObject(['a', 'b'], [1, 2]);
+ * // => { 'a': 1, 'b': 2 }
+ */
+ function zipObject(props, values) {
+ return baseZipObject(props || [], values || [], assignValue);
+ }
+
+ /**
+ * This method is like `_.zipObject` except that it supports property paths.
+ *
+ * @static
+ * @memberOf _
+ * @since 4.1.0
+ * @category Array
+ * @param {Array} [props=[]] The property identifiers.
+ * @param {Array} [values=[]] The property values.
+ * @returns {Object} Returns the new object.
+ * @example
+ *
+ * _.zipObjectDeep(['a.b[0].c', 'a.b[1].d'], [1, 2]);
+ * // => { 'a': { 'b': [{ 'c': 1 }, { 'd': 2 }] } }
+ */
+ function zipObjectDeep(props, values) {
+ return baseZipObject(props || [], values || [], baseSet);
+ }
+
+ /**
+ * This method is like `_.zip` except that it accepts `iteratee` to specify
+ * how grouped values should be combined. The iteratee is invoked with the
+ * elements of each group: (...group).
+ *
+ * @static
+ * @memberOf _
+ * @since 3.8.0
+ * @category Array
+ * @param {...Array} [arrays] The arrays to process.
+ * @param {Function} [iteratee=_.identity] The function to combine grouped values.
+ * @returns {Array} Returns the new array of grouped elements.
+ * @example
+ *
+ * _.zipWith([1, 2], [10, 20], [100, 200], function(a, b, c) {
+ * return a + b + c;
+ * });
+ * // => [111, 222]
+ */
+ var zipWith = baseRest(function(arrays) {
+ var length = arrays.length,
+ iteratee = length > 1 ? arrays[length - 1] : undefined;
+
+ iteratee = typeof iteratee == 'function' ? (arrays.pop(), iteratee) : undefined;
+ return unzipWith(arrays, iteratee);
+ });
+
+ /*------------------------------------------------------------------------*/
+
+ /**
+ * Creates a `lodash` wrapper instance that wraps `value` with explicit method
+ * chain sequences enabled. The result of such sequences must be unwrapped
+ * with `_#value`.
+ *
+ * @static
+ * @memberOf _
+ * @since 1.3.0
+ * @category Seq
+ * @param {*} value The value to wrap.
+ * @returns {Object} Returns the new `lodash` wrapper instance.
+ * @example
+ *
+ * var users = [
+ * { 'user': 'barney', 'age': 36 },
+ * { 'user': 'fred', 'age': 40 },
+ * { 'user': 'pebbles', 'age': 1 }
+ * ];
+ *
+ * var youngest = _
+ * .chain(users)
+ * .sortBy('age')
+ * .map(function(o) {
+ * return o.user + ' is ' + o.age;
+ * })
+ * .head()
+ * .value();
+ * // => 'pebbles is 1'
+ */
+ function chain(value) {
+ var result = lodash(value);
+ result.__chain__ = true;
+ return result;
+ }
+
+ /**
+ * This method invokes `interceptor` and returns `value`. The interceptor
+ * is invoked with one argument; (value). The purpose of this method is to
+ * "tap into" a method chain sequence in order to modify intermediate results.
+ *
+ * @static
+ * @memberOf _
+ * @since 0.1.0
+ * @category Seq
+ * @param {*} value The value to provide to `interceptor`.
+ * @param {Function} interceptor The function to invoke.
+ * @returns {*} Returns `value`.
+ * @example
+ *
+ * _([1, 2, 3])
+ * .tap(function(array) {
+ * // Mutate input array.
+ * array.pop();
+ * })
+ * .reverse()
+ * .value();
+ * // => [2, 1]
+ */
+ function tap(value, interceptor) {
+ interceptor(value);
+ return value;
+ }
+
+ /**
+ * This method is like `_.tap` except that it returns the result of `interceptor`.
+ * The purpose of this method is to "pass thru" values replacing intermediate
+ * results in a method chain sequence.
+ *
+ * @static
+ * @memberOf _
+ * @since 3.0.0
+ * @category Seq
+ * @param {*} value The value to provide to `interceptor`.
+ * @param {Function} interceptor The function to invoke.
+ * @returns {*} Returns the result of `interceptor`.
+ * @example
+ *
+ * _(' abc ')
+ * .chain()
+ * .trim()
+ * .thru(function(value) {
+ * return [value];
+ * })
+ * .value();
+ * // => ['abc']
+ */
+ function thru(value, interceptor) {
+ return interceptor(value);
+ }
+
+ /**
+ * This method is the wrapper version of `_.at`.
+ *
+ * @name at
+ * @memberOf _
+ * @since 1.0.0
+ * @category Seq
+ * @param {...(string|string[])} [paths] The property paths of elements to pick.
+ * @returns {Object} Returns the new `lodash` wrapper instance.
+ * @example
+ *
+ * var object = { 'a': [{ 'b': { 'c': 3 } }, 4] };
+ *
+ * _(object).at(['a[0].b.c', 'a[1]']).value();
+ * // => [3, 4]
+ */
+ var wrapperAt = flatRest(function(paths) {
+ var length = paths.length,
+ start = length ? paths[0] : 0,
+ value = this.__wrapped__,
+ interceptor = function(object) { return baseAt(object, paths); };
+
+ if (length > 1 || this.__actions__.length ||
+ !(value instanceof LazyWrapper) || !isIndex(start)) {
+ return this.thru(interceptor);
+ }
+ value = value.slice(start, +start + (length ? 1 : 0));
+ value.__actions__.push({
+ 'func': thru,
+ 'args': [interceptor],
+ 'thisArg': undefined
+ });
+ return new LodashWrapper(value, this.__chain__).thru(function(array) {
+ if (length && !array.length) {
+ array.push(undefined);
+ }
+ return array;
+ });
+ });
+
+ /**
+ * Creates a `lodash` wrapper instance with explicit method chain sequences enabled.
+ *
+ * @name chain
+ * @memberOf _
+ * @since 0.1.0
+ * @category Seq
+ * @returns {Object} Returns the new `lodash` wrapper instance.
+ * @example
+ *
+ * var users = [
+ * { 'user': 'barney', 'age': 36 },
+ * { 'user': 'fred', 'age': 40 }
+ * ];
+ *
+ * // A sequence without explicit chaining.
+ * _(users).head();
+ * // => { 'user': 'barney', 'age': 36 }
+ *
+ * // A sequence with explicit chaining.
+ * _(users)
+ * .chain()
+ * .head()
+ * .pick('user')
+ * .value();
+ * // => { 'user': 'barney' }
+ */
+ function wrapperChain() {
+ return chain(this);
+ }
+
+ /**
+ * Executes the chain sequence and returns the wrapped result.
+ *
+ * @name commit
+ * @memberOf _
+ * @since 3.2.0
+ * @category Seq
+ * @returns {Object} Returns the new `lodash` wrapper instance.
+ * @example
+ *
+ * var array = [1, 2];
+ * var wrapped = _(array).push(3);
+ *
+ * console.log(array);
+ * // => [1, 2]
+ *
+ * wrapped = wrapped.commit();
+ * console.log(array);
+ * // => [1, 2, 3]
+ *
+ * wrapped.last();
+ * // => 3
+ *
+ * console.log(array);
+ * // => [1, 2, 3]
+ */
+ function wrapperCommit() {
+ return new LodashWrapper(this.value(), this.__chain__);
+ }
+
+ /**
+ * Gets the next value on a wrapped object following the
+ * [iterator protocol](https://mdn.io/iteration_protocols#iterator).
+ *
+ * @name next
+ * @memberOf _
+ * @since 4.0.0
+ * @category Seq
+ * @returns {Object} Returns the next iterator value.
+ * @example
+ *
+ * var wrapped = _([1, 2]);
+ *
+ * wrapped.next();
+ * // => { 'done': false, 'value': 1 }
+ *
+ * wrapped.next();
+ * // => { 'done': false, 'value': 2 }
+ *
+ * wrapped.next();
+ * // => { 'done': true, 'value': undefined }
+ */
+ function wrapperNext() {
+ if (this.__values__ === undefined) {
+ this.__values__ = toArray(this.value());
+ }
+ var done = this.__index__ >= this.__values__.length,
+ value = done ? undefined : this.__values__[this.__index__++];
+
+ return { 'done': done, 'value': value };
+ }
+
+ /**
+ * Enables the wrapper to be iterable.
+ *
+ * @name Symbol.iterator
+ * @memberOf _
+ * @since 4.0.0
+ * @category Seq
+ * @returns {Object} Returns the wrapper object.
+ * @example
+ *
+ * var wrapped = _([1, 2]);
+ *
+ * wrapped[Symbol.iterator]() === wrapped;
+ * // => true
+ *
+ * Array.from(wrapped);
+ * // => [1, 2]
+ */
+ function wrapperToIterator() {
+ return this;
+ }
+
+ /**
+ * Creates a clone of the chain sequence planting `value` as the wrapped value.
+ *
+ * @name plant
+ * @memberOf _
+ * @since 3.2.0
+ * @category Seq
+ * @param {*} value The value to plant.
+ * @returns {Object} Returns the new `lodash` wrapper instance.
+ * @example
+ *
+ * function square(n) {
+ * return n * n;
+ * }
+ *
+ * var wrapped = _([1, 2]).map(square);
+ * var other = wrapped.plant([3, 4]);
+ *
+ * other.value();
+ * // => [9, 16]
+ *
+ * wrapped.value();
+ * // => [1, 4]
+ */
+ function wrapperPlant(value) {
+ var result,
+ parent = this;
+
+ while (parent instanceof baseLodash) {
+ var clone = wrapperClone(parent);
+ clone.__index__ = 0;
+ clone.__values__ = undefined;
+ if (result) {
+ previous.__wrapped__ = clone;
+ } else {
+ result = clone;
+ }
+ var previous = clone;
+ parent = parent.__wrapped__;
+ }
+ previous.__wrapped__ = value;
+ return result;
+ }
+
+ /**
+ * This method is the wrapper version of `_.reverse`.
+ *
+ * **Note:** This method mutates the wrapped array.
+ *
+ * @name reverse
+ * @memberOf _
+ * @since 0.1.0
+ * @category Seq
+ * @returns {Object} Returns the new `lodash` wrapper instance.
+ * @example
+ *
+ * var array = [1, 2, 3];
+ *
+ * _(array).reverse().value()
+ * // => [3, 2, 1]
+ *
+ * console.log(array);
+ * // => [3, 2, 1]
+ */
+ function wrapperReverse() {
+ var value = this.__wrapped__;
+ if (value instanceof LazyWrapper) {
+ var wrapped = value;
+ if (this.__actions__.length) {
+ wrapped = new LazyWrapper(this);
+ }
+ wrapped = wrapped.reverse();
+ wrapped.__actions__.push({
+ 'func': thru,
+ 'args': [reverse],
+ 'thisArg': undefined
+ });
+ return new LodashWrapper(wrapped, this.__chain__);
+ }
+ return this.thru(reverse);
+ }
+
+ /**
+ * Executes the chain sequence to resolve the unwrapped value.
+ *
+ * @name value
+ * @memberOf _
+ * @since 0.1.0
+ * @alias toJSON, valueOf
+ * @category Seq
+ * @returns {*} Returns the resolved unwrapped value.
+ * @example
+ *
+ * _([1, 2, 3]).value();
+ * // => [1, 2, 3]
+ */
+ function wrapperValue() {
+ return baseWrapperValue(this.__wrapped__, this.__actions__);
+ }
+
+ /*------------------------------------------------------------------------*/
+
+ /**
+ * Creates an object composed of keys generated from the results of running
+ * each element of `collection` thru `iteratee`. The corresponding value of
+ * each key is the number of times the key was returned by `iteratee`. The
+ * iteratee is invoked with one argument: (value).
+ *
+ * @static
+ * @memberOf _
+ * @since 0.5.0
+ * @category Collection
+ * @param {Array|Object} collection The collection to iterate over.
+ * @param {Function} [iteratee=_.identity]
+ * The iteratee to transform keys.
+ * @returns {Object} Returns the composed aggregate object.
+ * @example
+ *
+ * _.countBy([6.1, 4.2, 6.3], Math.floor);
+ * // => { '4': 1, '6': 2 }
+ *
+ * // The `_.property` iteratee shorthand.
+ * _.countBy(['one', 'two', 'three'], 'length');
+ * // => { '3': 2, '5': 1 }
+ */
+ var countBy = createAggregator(function(result, value, key) {
+ if (hasOwnProperty.call(result, key)) {
+ ++result[key];
+ } else {
+ baseAssignValue(result, key, 1);
+ }
+ });
+
+ /**
+ * Checks if `predicate` returns truthy for **all** elements of `collection`.
+ * Iteration is stopped once `predicate` returns falsey. The predicate is
+ * invoked with three arguments: (value, index|key, collection).
+ *
+ * **Note:** This method returns `true` for
+ * [empty collections](https://en.wikipedia.org/wiki/Empty_set) because
+ * [everything is true](https://en.wikipedia.org/wiki/Vacuous_truth) of
+ * elements of empty collections.
+ *
+ * @static
+ * @memberOf _
+ * @since 0.1.0
+ * @category Collection
+ * @param {Array|Object} collection The collection to iterate over.
+ * @param {Function} [predicate=_.identity]
+ * The function invoked per iteration.
+ * @param- {Object} [guard] Enables use as an iteratee for methods like `_.map`.
+ * @returns {boolean} Returns `true` if all elements pass the predicate check,
+ * else `false`.
+ * @example
+ *
+ * _.every([true, 1, null, 'yes'], Boolean);
+ * // => false
+ *
+ * var users = [
+ * { 'user': 'barney', 'age': 36, 'active': false },
+ * { 'user': 'fred', 'age': 40, 'active': false }
+ * ];
+ *
+ * // The `_.matches` iteratee shorthand.
+ * _.every(users, { 'user': 'barney', 'active': false });
+ * // => false
+ *
+ * // The `_.matchesProperty` iteratee shorthand.
+ * _.every(users, ['active', false]);
+ * // => true
+ *
+ * // The `_.property` iteratee shorthand.
+ * _.every(users, 'active');
+ * // => false
+ */
+ function every(collection, predicate, guard) {
+ var func = isArray(collection) ? arrayEvery : baseEvery;
+ if (guard && isIterateeCall(collection, predicate, guard)) {
+ predicate = undefined;
+ }
+ return func(collection, getIteratee(predicate, 3));
+ }
+
+ /**
+ * Iterates over elements of `collection`, returning an array of all elements
+ * `predicate` returns truthy for. The predicate is invoked with three
+ * arguments: (value, index|key, collection).
+ *
+ * **Note:** Unlike `_.remove`, this method returns a new array.
+ *
+ * @static
+ * @memberOf _
+ * @since 0.1.0
+ * @category Collection
+ * @param {Array|Object} collection The collection to iterate over.
+ * @param {Function} [predicate=_.identity]
+ * The function invoked per iteration.
+ * @returns {Array} Returns the new filtered array.
+ * @see _.reject
+ * @example
+ *
+ * var users = [
+ * { 'user': 'barney', 'age': 36, 'active': true },
+ * { 'user': 'fred', 'age': 40, 'active': false }
+ * ];
+ *
+ * _.filter(users, function(o) { return !o.active; });
+ * // => objects for ['fred']
+ *
+ * // The `_.matches` iteratee shorthand.
+ * _.filter(users, { 'age': 36, 'active': true });
+ * // => objects for ['barney']
+ *
+ * // The `_.matchesProperty` iteratee shorthand.
+ * _.filter(users, ['active', false]);
+ * // => objects for ['fred']
+ *
+ * // The `_.property` iteratee shorthand.
+ * _.filter(users, 'active');
+ * // => objects for ['barney']
+ */
+ function filter(collection, predicate) {
+ var func = isArray(collection) ? arrayFilter : baseFilter;
+ return func(collection, getIteratee(predicate, 3));
+ }
+
+ /**
+ * Iterates over elements of `collection`, returning the first element
+ * `predicate` returns truthy for. The predicate is invoked with three
+ * arguments: (value, index|key, collection).
+ *
+ * @static
+ * @memberOf _
+ * @since 0.1.0
+ * @category Collection
+ * @param {Array|Object} collection The collection to inspect.
+ * @param {Function} [predicate=_.identity]
+ * The function invoked per iteration.
+ * @param {number} [fromIndex=0] The index to search from.
+ * @returns {*} Returns the matched element, else `undefined`.
+ * @example
+ *
+ * var users = [
+ * { 'user': 'barney', 'age': 36, 'active': true },
+ * { 'user': 'fred', 'age': 40, 'active': false },
+ * { 'user': 'pebbles', 'age': 1, 'active': true }
+ * ];
+ *
+ * _.find(users, function(o) { return o.age < 40; });
+ * // => object for 'barney'
+ *
+ * // The `_.matches` iteratee shorthand.
+ * _.find(users, { 'age': 1, 'active': true });
+ * // => object for 'pebbles'
+ *
+ * // The `_.matchesProperty` iteratee shorthand.
+ * _.find(users, ['active', false]);
+ * // => object for 'fred'
+ *
+ * // The `_.property` iteratee shorthand.
+ * _.find(users, 'active');
+ * // => object for 'barney'
+ */
+ var find = createFind(findIndex);
+
+ /**
+ * This method is like `_.find` except that it iterates over elements of
+ * `collection` from right to left.
+ *
+ * @static
+ * @memberOf _
+ * @since 2.0.0
+ * @category Collection
+ * @param {Array|Object} collection The collection to inspect.
+ * @param {Function} [predicate=_.identity]
+ * The function invoked per iteration.
+ * @param {number} [fromIndex=collection.length-1] The index to search from.
+ * @returns {*} Returns the matched element, else `undefined`.
+ * @example
+ *
+ * _.findLast([1, 2, 3, 4], function(n) {
+ * return n % 2 == 1;
+ * });
+ * // => 3
+ */
+ var findLast = createFind(findLastIndex);
+
+ /**
+ * Creates a flattened array of values by running each element in `collection`
+ * thru `iteratee` and flattening the mapped results. The iteratee is invoked
+ * with three arguments: (value, index|key, collection).
+ *
+ * @static
+ * @memberOf _
+ * @since 4.0.0
+ * @category Collection
+ * @param {Array|Object} collection The collection to iterate over.
+ * @param {Function} [iteratee=_.identity]
+ * The function invoked per iteration.
+ * @returns {Array} Returns the new flattened array.
+ * @example
+ *
+ * function duplicate(n) {
+ * return [n, n];
+ * }
+ *
+ * _.flatMap([1, 2], duplicate);
+ * // => [1, 1, 2, 2]
+ */
+ function flatMap(collection, iteratee) {
+ return baseFlatten(map(collection, iteratee), 1);
+ }
+
+ /**
+ * This method is like `_.flatMap` except that it recursively flattens the
+ * mapped results.
+ *
+ * @static
+ * @memberOf _
+ * @since 4.7.0
+ * @category Collection
+ * @param {Array|Object} collection The collection to iterate over.
+ * @param {Function} [iteratee=_.identity]
+ * The function invoked per iteration.
+ * @returns {Array} Returns the new flattened array.
+ * @example
+ *
+ * function duplicate(n) {
+ * return [[[n, n]]];
+ * }
+ *
+ * _.flatMapDeep([1, 2], duplicate);
+ * // => [1, 1, 2, 2]
+ */
+ function flatMapDeep(collection, iteratee) {
+ return baseFlatten(map(collection, iteratee), INFINITY);
+ }
+
+ /**
+ * This method is like `_.flatMap` except that it recursively flattens the
+ * mapped results up to `depth` times.
+ *
+ * @static
+ * @memberOf _
+ * @since 4.7.0
+ * @category Collection
+ * @param {Array|Object} collection The collection to iterate over.
+ * @param {Function} [iteratee=_.identity]
+ * The function invoked per iteration.
+ * @param {number} [depth=1] The maximum recursion depth.
+ * @returns {Array} Returns the new flattened array.
+ * @example
+ *
+ * function duplicate(n) {
+ * return [[[n, n]]];
+ * }
+ *
+ * _.flatMapDepth([1, 2], duplicate, 2);
+ * // => [[1, 1], [2, 2]]
+ */
+ function flatMapDepth(collection, iteratee, depth) {
+ depth = depth === undefined ? 1 : toInteger(depth);
+ return baseFlatten(map(collection, iteratee), depth);
+ }
+
+ /**
+ * Iterates over elements of `collection` and invokes `iteratee` for each element.
+ * The iteratee is invoked with three arguments: (value, index|key, collection).
+ * Iteratee functions may exit iteration early by explicitly returning `false`.
+ *
+ * **Note:** As with other "Collections" methods, objects with a "length"
+ * property are iterated like arrays. To avoid this behavior use `_.forIn`
+ * or `_.forOwn` for object iteration.
+ *
+ * @static
+ * @memberOf _
+ * @since 0.1.0
+ * @alias each
+ * @category Collection
+ * @param {Array|Object} collection The collection to iterate over.
+ * @param {Function} [iteratee=_.identity] The function invoked per iteration.
+ * @returns {Array|Object} Returns `collection`.
+ * @see _.forEachRight
+ * @example
+ *
+ * _.forEach([1, 2], function(value) {
+ * console.log(value);
+ * });
+ * // => Logs `1` then `2`.
+ *
+ * _.forEach({ 'a': 1, 'b': 2 }, function(value, key) {
+ * console.log(key);
+ * });
+ * // => Logs 'a' then 'b' (iteration order is not guaranteed).
+ */
+ function forEach(collection, iteratee) {
+ var func = isArray(collection) ? arrayEach : baseEach;
+ return func(collection, getIteratee(iteratee, 3));
+ }
+
+ /**
+ * This method is like `_.forEach` except that it iterates over elements of
+ * `collection` from right to left.
+ *
+ * @static
+ * @memberOf _
+ * @since 2.0.0
+ * @alias eachRight
+ * @category Collection
+ * @param {Array|Object} collection The collection to iterate over.
+ * @param {Function} [iteratee=_.identity] The function invoked per iteration.
+ * @returns {Array|Object} Returns `collection`.
+ * @see _.forEach
+ * @example
+ *
+ * _.forEachRight([1, 2], function(value) {
+ * console.log(value);
+ * });
+ * // => Logs `2` then `1`.
+ */
+ function forEachRight(collection, iteratee) {
+ var func = isArray(collection) ? arrayEachRight : baseEachRight;
+ return func(collection, getIteratee(iteratee, 3));
+ }
+
+ /**
+ * Creates an object composed of keys generated from the results of running
+ * each element of `collection` thru `iteratee`. The order of grouped values
+ * is determined by the order they occur in `collection`. The corresponding
+ * value of each key is an array of elements responsible for generating the
+ * key. The iteratee is invoked with one argument: (value).
+ *
+ * @static
+ * @memberOf _
+ * @since 0.1.0
+ * @category Collection
+ * @param {Array|Object} collection The collection to iterate over.
+ * @param {Function} [iteratee=_.identity]
+ * The iteratee to transform keys.
+ * @returns {Object} Returns the composed aggregate object.
+ * @example
+ *
+ * _.groupBy([6.1, 4.2, 6.3], Math.floor);
+ * // => { '4': [4.2], '6': [6.1, 6.3] }
+ *
+ * // The `_.property` iteratee shorthand.
+ * _.groupBy(['one', 'two', 'three'], 'length');
+ * // => { '3': ['one', 'two'], '5': ['three'] }
+ */
+ var groupBy = createAggregator(function(result, value, key) {
+ if (hasOwnProperty.call(result, key)) {
+ result[key].push(value);
+ } else {
+ baseAssignValue(result, key, [value]);
+ }
+ });
+
+ /**
+ * Checks if `value` is in `collection`. If `collection` is a string, it's
+ * checked for a substring of `value`, otherwise
+ * [`SameValueZero`](http://ecma-international.org/ecma-262/7.0/#sec-samevaluezero)
+ * is used for equality comparisons. If `fromIndex` is negative, it's used as
+ * the offset from the end of `collection`.
+ *
+ * @static
+ * @memberOf _
+ * @since 0.1.0
+ * @category Collection
+ * @param {Array|Object|string} collection The collection to inspect.
+ * @param {*} value The value to search for.
+ * @param {number} [fromIndex=0] The index to search from.
+ * @param- {Object} [guard] Enables use as an iteratee for methods like `_.reduce`.
+ * @returns {boolean} Returns `true` if `value` is found, else `false`.
+ * @example
+ *
+ * _.includes([1, 2, 3], 1);
+ * // => true
+ *
+ * _.includes([1, 2, 3], 1, 2);
+ * // => false
+ *
+ * _.includes({ 'a': 1, 'b': 2 }, 1);
+ * // => true
+ *
+ * _.includes('abcd', 'bc');
+ * // => true
+ */
+ function includes(collection, value, fromIndex, guard) {
+ collection = isArrayLike(collection) ? collection : values(collection);
+ fromIndex = (fromIndex && !guard) ? toInteger(fromIndex) : 0;
+
+ var length = collection.length;
+ if (fromIndex < 0) {
+ fromIndex = nativeMax(length + fromIndex, 0);
+ }
+ return isString(collection)
+ ? (fromIndex <= length && collection.indexOf(value, fromIndex) > -1)
+ : (!!length && baseIndexOf(collection, value, fromIndex) > -1);
+ }
+
+ /**
+ * Invokes the method at `path` of each element in `collection`, returning
+ * an array of the results of each invoked method. Any additional arguments
+ * are provided to each invoked method. If `path` is a function, it's invoked
+ * for, and `this` bound to, each element in `collection`.
+ *
+ * @static
+ * @memberOf _
+ * @since 4.0.0
+ * @category Collection
+ * @param {Array|Object} collection The collection to iterate over.
+ * @param {Array|Function|string} path The path of the method to invoke or
+ * the function invoked per iteration.
+ * @param {...*} [args] The arguments to invoke each method with.
+ * @returns {Array} Returns the array of results.
+ * @example
+ *
+ * _.invokeMap([[5, 1, 7], [3, 2, 1]], 'sort');
+ * // => [[1, 5, 7], [1, 2, 3]]
+ *
+ * _.invokeMap([123, 456], String.prototype.split, '');
+ * // => [['1', '2', '3'], ['4', '5', '6']]
+ */
+ var invokeMap = baseRest(function(collection, path, args) {
+ var index = -1,
+ isFunc = typeof path == 'function',
+ isProp = isKey(path),
+ result = isArrayLike(collection) ? Array(collection.length) : [];
+
+ baseEach(collection, function(value) {
+ var func = isFunc ? path : ((isProp && value != null) ? value[path] : undefined);
+ result[++index] = func ? apply(func, value, args) : baseInvoke(value, path, args);
+ });
+ return result;
+ });
+
+ /**
+ * Creates an object composed of keys generated from the results of running
+ * each element of `collection` thru `iteratee`. The corresponding value of
+ * each key is the last element responsible for generating the key. The
+ * iteratee is invoked with one argument: (value).
+ *
+ * @static
+ * @memberOf _
+ * @since 4.0.0
+ * @category Collection
+ * @param {Array|Object} collection The collection to iterate over.
+ * @param {Function} [iteratee=_.identity]
+ * The iteratee to transform keys.
+ * @returns {Object} Returns the composed aggregate object.
+ * @example
+ *
+ * var array = [
+ * { 'dir': 'left', 'code': 97 },
+ * { 'dir': 'right', 'code': 100 }
+ * ];
+ *
+ * _.keyBy(array, function(o) {
+ * return String.fromCharCode(o.code);
+ * });
+ * // => { 'a': { 'dir': 'left', 'code': 97 }, 'd': { 'dir': 'right', 'code': 100 } }
+ *
+ * _.keyBy(array, 'dir');
+ * // => { 'left': { 'dir': 'left', 'code': 97 }, 'right': { 'dir': 'right', 'code': 100 } }
+ */
+ var keyBy = createAggregator(function(result, value, key) {
+ baseAssignValue(result, key, value);
+ });
+
+ /**
+ * Creates an array of values by running each element in `collection` thru
+ * `iteratee`. The iteratee is invoked with three arguments:
+ * (value, index|key, collection).
+ *
+ * Many lodash methods are guarded to work as iteratees for methods like
+ * `_.every`, `_.filter`, `_.map`, `_.mapValues`, `_.reject`, and `_.some`.
+ *
+ * The guarded methods are:
+ * `ary`, `chunk`, `curry`, `curryRight`, `drop`, `dropRight`, `every`,
+ * `fill`, `invert`, `parseInt`, `random`, `range`, `rangeRight`, `repeat`,
+ * `sampleSize`, `slice`, `some`, `sortBy`, `split`, `take`, `takeRight`,
+ * `template`, `trim`, `trimEnd`, `trimStart`, and `words`
+ *
+ * @static
+ * @memberOf _
+ * @since 0.1.0
+ * @category Collection
+ * @param {Array|Object} collection The collection to iterate over.
+ * @param {Function} [iteratee=_.identity] The function invoked per iteration.
+ * @returns {Array} Returns the new mapped array.
+ * @example
+ *
+ * function square(n) {
+ * return n * n;
+ * }
+ *
+ * _.map([4, 8], square);
+ * // => [16, 64]
+ *
+ * _.map({ 'a': 4, 'b': 8 }, square);
+ * // => [16, 64] (iteration order is not guaranteed)
+ *
+ * var users = [
+ * { 'user': 'barney' },
+ * { 'user': 'fred' }
+ * ];
+ *
+ * // The `_.property` iteratee shorthand.
+ * _.map(users, 'user');
+ * // => ['barney', 'fred']
+ */
+ function map(collection, iteratee) {
+ var func = isArray(collection) ? arrayMap : baseMap;
+ return func(collection, getIteratee(iteratee, 3));
+ }
+
+ /**
+ * This method is like `_.sortBy` except that it allows specifying the sort
+ * orders of the iteratees to sort by. If `orders` is unspecified, all values
+ * are sorted in ascending order. Otherwise, specify an order of "desc" for
+ * descending or "asc" for ascending sort order of corresponding values.
+ *
+ * @static
+ * @memberOf _
+ * @since 4.0.0
+ * @category Collection
+ * @param {Array|Object} collection The collection to iterate over.
+ * @param {Array[]|Function[]|Object[]|string[]} [iteratees=[_.identity]]
+ * The iteratees to sort by.
+ * @param {string[]} [orders] The sort orders of `iteratees`.
+ * @param- {Object} [guard] Enables use as an iteratee for methods like `_.reduce`.
+ * @returns {Array} Returns the new sorted array.
+ * @example
+ *
+ * var users = [
+ * { 'user': 'fred', 'age': 48 },
+ * { 'user': 'barney', 'age': 34 },
+ * { 'user': 'fred', 'age': 40 },
+ * { 'user': 'barney', 'age': 36 }
+ * ];
+ *
+ * // Sort by `user` in ascending order and by `age` in descending order.
+ * _.orderBy(users, ['user', 'age'], ['asc', 'desc']);
+ * // => objects for [['barney', 36], ['barney', 34], ['fred', 48], ['fred', 40]]
+ */
+ function orderBy(collection, iteratees, orders, guard) {
+ if (collection == null) {
+ return [];
+ }
+ if (!isArray(iteratees)) {
+ iteratees = iteratees == null ? [] : [iteratees];
+ }
+ orders = guard ? undefined : orders;
+ if (!isArray(orders)) {
+ orders = orders == null ? [] : [orders];
+ }
+ return baseOrderBy(collection, iteratees, orders);
+ }
+
+ /**
+ * Creates an array of elements split into two groups, the first of which
+ * contains elements `predicate` returns truthy for, the second of which
+ * contains elements `predicate` returns falsey for. The predicate is
+ * invoked with one argument: (value).
+ *
+ * @static
+ * @memberOf _
+ * @since 3.0.0
+ * @category Collection
+ * @param {Array|Object} collection The collection to iterate over.
+ * @param {Function} [predicate=_.identity] The function invoked per iteration.
+ * @returns {Array} Returns the array of grouped elements.
+ * @example
+ *
+ * var users = [
+ * { 'user': 'barney', 'age': 36, 'active': false },
+ * { 'user': 'fred', 'age': 40, 'active': true },
+ * { 'user': 'pebbles', 'age': 1, 'active': false }
+ * ];
+ *
+ * _.partition(users, function(o) { return o.active; });
+ * // => objects for [['fred'], ['barney', 'pebbles']]
+ *
+ * // The `_.matches` iteratee shorthand.
+ * _.partition(users, { 'age': 1, 'active': false });
+ * // => objects for [['pebbles'], ['barney', 'fred']]
+ *
+ * // The `_.matchesProperty` iteratee shorthand.
+ * _.partition(users, ['active', false]);
+ * // => objects for [['barney', 'pebbles'], ['fred']]
+ *
+ * // The `_.property` iteratee shorthand.
+ * _.partition(users, 'active');
+ * // => objects for [['fred'], ['barney', 'pebbles']]
+ */
+ var partition = createAggregator(function(result, value, key) {
+ result[key ? 0 : 1].push(value);
+ }, function() { return [[], []]; });
+
+ /**
+ * Reduces `collection` to a value which is the accumulated result of running
+ * each element in `collection` thru `iteratee`, where each successive
+ * invocation is supplied the return value of the previous. If `accumulator`
+ * is not given, the first element of `collection` is used as the initial
+ * value. The iteratee is invoked with four arguments:
+ * (accumulator, value, index|key, collection).
+ *
+ * Many lodash methods are guarded to work as iteratees for methods like
+ * `_.reduce`, `_.reduceRight`, and `_.transform`.
+ *
+ * The guarded methods are:
+ * `assign`, `defaults`, `defaultsDeep`, `includes`, `merge`, `orderBy`,
+ * and `sortBy`
+ *
+ * @static
+ * @memberOf _
+ * @since 0.1.0
+ * @category Collection
+ * @param {Array|Object} collection The collection to iterate over.
+ * @param {Function} [iteratee=_.identity] The function invoked per iteration.
+ * @param {*} [accumulator] The initial value.
+ * @returns {*} Returns the accumulated value.
+ * @see _.reduceRight
+ * @example
+ *
+ * _.reduce([1, 2], function(sum, n) {
+ * return sum + n;
+ * }, 0);
+ * // => 3
+ *
+ * _.reduce({ 'a': 1, 'b': 2, 'c': 1 }, function(result, value, key) {
+ * (result[value] || (result[value] = [])).push(key);
+ * return result;
+ * }, {});
+ * // => { '1': ['a', 'c'], '2': ['b'] } (iteration order is not guaranteed)
+ */
+ function reduce(collection, iteratee, accumulator) {
+ var func = isArray(collection) ? arrayReduce : baseReduce,
+ initAccum = arguments.length < 3;
+
+ return func(collection, getIteratee(iteratee, 4), accumulator, initAccum, baseEach);
+ }
+
+ /**
+ * This method is like `_.reduce` except that it iterates over elements of
+ * `collection` from right to left.
+ *
+ * @static
+ * @memberOf _
+ * @since 0.1.0
+ * @category Collection
+ * @param {Array|Object} collection The collection to iterate over.
+ * @param {Function} [iteratee=_.identity] The function invoked per iteration.
+ * @param {*} [accumulator] The initial value.
+ * @returns {*} Returns the accumulated value.
+ * @see _.reduce
+ * @example
+ *
+ * var array = [[0, 1], [2, 3], [4, 5]];
+ *
+ * _.reduceRight(array, function(flattened, other) {
+ * return flattened.concat(other);
+ * }, []);
+ * // => [4, 5, 2, 3, 0, 1]
+ */
+ function reduceRight(collection, iteratee, accumulator) {
+ var func = isArray(collection) ? arrayReduceRight : baseReduce,
+ initAccum = arguments.length < 3;
+
+ return func(collection, getIteratee(iteratee, 4), accumulator, initAccum, baseEachRight);
+ }
+
+ /**
+ * The opposite of `_.filter`; this method returns the elements of `collection`
+ * that `predicate` does **not** return truthy for.
+ *
+ * @static
+ * @memberOf _
+ * @since 0.1.0
+ * @category Collection
+ * @param {Array|Object} collection The collection to iterate over.
+ * @param {Function} [predicate=_.identity] The function invoked per iteration.
+ * @returns {Array} Returns the new filtered array.
+ * @see _.filter
+ * @example
+ *
+ * var users = [
+ * { 'user': 'barney', 'age': 36, 'active': false },
+ * { 'user': 'fred', 'age': 40, 'active': true }
+ * ];
+ *
+ * _.reject(users, function(o) { return !o.active; });
+ * // => objects for ['fred']
+ *
+ * // The `_.matches` iteratee shorthand.
+ * _.reject(users, { 'age': 40, 'active': true });
+ * // => objects for ['barney']
+ *
+ * // The `_.matchesProperty` iteratee shorthand.
+ * _.reject(users, ['active', false]);
+ * // => objects for ['fred']
+ *
+ * // The `_.property` iteratee shorthand.
+ * _.reject(users, 'active');
+ * // => objects for ['barney']
+ */
+ function reject(collection, predicate) {
+ var func = isArray(collection) ? arrayFilter : baseFilter;
+ return func(collection, negate(getIteratee(predicate, 3)));
+ }
+
+ /**
+ * Gets a random element from `collection`.
+ *
+ * @static
+ * @memberOf _
+ * @since 2.0.0
+ * @category Collection
+ * @param {Array|Object} collection The collection to sample.
+ * @returns {*} Returns the random element.
+ * @example
+ *
+ * _.sample([1, 2, 3, 4]);
+ * // => 2
+ */
+ function sample(collection) {
+ var func = isArray(collection) ? arraySample : baseSample;
+ return func(collection);
+ }
+
+ /**
+ * Gets `n` random elements at unique keys from `collection` up to the
+ * size of `collection`.
+ *
+ * @static
+ * @memberOf _
+ * @since 4.0.0
+ * @category Collection
+ * @param {Array|Object} collection The collection to sample.
+ * @param {number} [n=1] The number of elements to sample.
+ * @param- {Object} [guard] Enables use as an iteratee for methods like `_.map`.
+ * @returns {Array} Returns the random elements.
+ * @example
+ *
+ * _.sampleSize([1, 2, 3], 2);
+ * // => [3, 1]
+ *
+ * _.sampleSize([1, 2, 3], 4);
+ * // => [2, 3, 1]
+ */
+ function sampleSize(collection, n, guard) {
+ if ((guard ? isIterateeCall(collection, n, guard) : n === undefined)) {
+ n = 1;
+ } else {
+ n = toInteger(n);
+ }
+ var func = isArray(collection) ? arraySampleSize : baseSampleSize;
+ return func(collection, n);
+ }
+
+ /**
+ * Creates an array of shuffled values, using a version of the
+ * [Fisher-Yates shuffle](https://en.wikipedia.org/wiki/Fisher-Yates_shuffle).
+ *
+ * @static
+ * @memberOf _
+ * @since 0.1.0
+ * @category Collection
+ * @param {Array|Object} collection The collection to shuffle.
+ * @returns {Array} Returns the new shuffled array.
+ * @example
+ *
+ * _.shuffle([1, 2, 3, 4]);
+ * // => [4, 1, 3, 2]
+ */
+ function shuffle(collection) {
+ var func = isArray(collection) ? arrayShuffle : baseShuffle;
+ return func(collection);
+ }
+
+ /**
+ * Gets the size of `collection` by returning its length for array-like
+ * values or the number of own enumerable string keyed properties for objects.
+ *
+ * @static
+ * @memberOf _
+ * @since 0.1.0
+ * @category Collection
+ * @param {Array|Object|string} collection The collection to inspect.
+ * @returns {number} Returns the collection size.
+ * @example
+ *
+ * _.size([1, 2, 3]);
+ * // => 3
+ *
+ * _.size({ 'a': 1, 'b': 2 });
+ * // => 2
+ *
+ * _.size('pebbles');
+ * // => 7
+ */
+ function size(collection) {
+ if (collection == null) {
+ return 0;
+ }
+ if (isArrayLike(collection)) {
+ return isString(collection) ? stringSize(collection) : collection.length;
+ }
+ var tag = getTag(collection);
+ if (tag == mapTag || tag == setTag) {
+ return collection.size;
+ }
+ return baseKeys(collection).length;
+ }
+
+ /**
+ * Checks if `predicate` returns truthy for **any** element of `collection`.
+ * Iteration is stopped once `predicate` returns truthy. The predicate is
+ * invoked with three arguments: (value, index|key, collection).
+ *
+ * @static
+ * @memberOf _
+ * @since 0.1.0
+ * @category Collection
+ * @param {Array|Object} collection The collection to iterate over.
+ * @param {Function} [predicate=_.identity] The function invoked per iteration.
+ * @param- {Object} [guard] Enables use as an iteratee for methods like `_.map`.
+ * @returns {boolean} Returns `true` if any element passes the predicate check,
+ * else `false`.
+ * @example
+ *
+ * _.some([null, 0, 'yes', false], Boolean);
+ * // => true
+ *
+ * var users = [
+ * { 'user': 'barney', 'active': true },
+ * { 'user': 'fred', 'active': false }
+ * ];
+ *
+ * // The `_.matches` iteratee shorthand.
+ * _.some(users, { 'user': 'barney', 'active': false });
+ * // => false
+ *
+ * // The `_.matchesProperty` iteratee shorthand.
+ * _.some(users, ['active', false]);
+ * // => true
+ *
+ * // The `_.property` iteratee shorthand.
+ * _.some(users, 'active');
+ * // => true
+ */
+ function some(collection, predicate, guard) {
+ var func = isArray(collection) ? arraySome : baseSome;
+ if (guard && isIterateeCall(collection, predicate, guard)) {
+ predicate = undefined;
+ }
+ return func(collection, getIteratee(predicate, 3));
+ }
+
+ /**
+ * Creates an array of elements, sorted in ascending order by the results of
+ * running each element in a collection thru each iteratee. This method
+ * performs a stable sort, that is, it preserves the original sort order of
+ * equal elements. The iteratees are invoked with one argument: (value).
+ *
+ * @static
+ * @memberOf _
+ * @since 0.1.0
+ * @category Collection
+ * @param {Array|Object} collection The collection to iterate over.
+ * @param {...(Function|Function[])} [iteratees=[_.identity]]
+ * The iteratees to sort by.
+ * @returns {Array} Returns the new sorted array.
+ * @example
+ *
+ * var users = [
+ * { 'user': 'fred', 'age': 48 },
+ * { 'user': 'barney', 'age': 36 },
+ * { 'user': 'fred', 'age': 40 },
+ * { 'user': 'barney', 'age': 34 }
+ * ];
+ *
+ * _.sortBy(users, [function(o) { return o.user; }]);
+ * // => objects for [['barney', 36], ['barney', 34], ['fred', 48], ['fred', 40]]
+ *
+ * _.sortBy(users, ['user', 'age']);
+ * // => objects for [['barney', 34], ['barney', 36], ['fred', 40], ['fred', 48]]
+ */
+ var sortBy = baseRest(function(collection, iteratees) {
+ if (collection == null) {
+ return [];
+ }
+ var length = iteratees.length;
+ if (length > 1 && isIterateeCall(collection, iteratees[0], iteratees[1])) {
+ iteratees = [];
+ } else if (length > 2 && isIterateeCall(iteratees[0], iteratees[1], iteratees[2])) {
+ iteratees = [iteratees[0]];
+ }
+ return baseOrderBy(collection, baseFlatten(iteratees, 1), []);
+ });
+
+ /*------------------------------------------------------------------------*/
+
+ /**
+ * Gets the timestamp of the number of milliseconds that have elapsed since
+ * the Unix epoch (1 January 1970 00:00:00 UTC).
+ *
+ * @static
+ * @memberOf _
+ * @since 2.4.0
+ * @category Date
+ * @returns {number} Returns the timestamp.
+ * @example
+ *
+ * _.defer(function(stamp) {
+ * console.log(_.now() - stamp);
+ * }, _.now());
+ * // => Logs the number of milliseconds it took for the deferred invocation.
+ */
+ var now = ctxNow || function() {
+ return root.Date.now();
+ };
+
+ /*------------------------------------------------------------------------*/
+
+ /**
+ * The opposite of `_.before`; this method creates a function that invokes
+ * `func` once it's called `n` or more times.
+ *
+ * @static
+ * @memberOf _
+ * @since 0.1.0
+ * @category Function
+ * @param {number} n The number of calls before `func` is invoked.
+ * @param {Function} func The function to restrict.
+ * @returns {Function} Returns the new restricted function.
+ * @example
+ *
+ * var saves = ['profile', 'settings'];
+ *
+ * var done = _.after(saves.length, function() {
+ * console.log('done saving!');
+ * });
+ *
+ * _.forEach(saves, function(type) {
+ * asyncSave({ 'type': type, 'complete': done });
+ * });
+ * // => Logs 'done saving!' after the two async saves have completed.
+ */
+ function after(n, func) {
+ if (typeof func != 'function') {
+ throw new TypeError(FUNC_ERROR_TEXT);
+ }
+ n = toInteger(n);
+ return function() {
+ if (--n < 1) {
+ return func.apply(this, arguments);
+ }
+ };
+ }
+
+ /**
+ * Creates a function that invokes `func`, with up to `n` arguments,
+ * ignoring any additional arguments.
+ *
+ * @static
+ * @memberOf _
+ * @since 3.0.0
+ * @category Function
+ * @param {Function} func The function to cap arguments for.
+ * @param {number} [n=func.length] The arity cap.
+ * @param- {Object} [guard] Enables use as an iteratee for methods like `_.map`.
+ * @returns {Function} Returns the new capped function.
+ * @example
+ *
+ * _.map(['6', '8', '10'], _.ary(parseInt, 1));
+ * // => [6, 8, 10]
+ */
+ function ary(func, n, guard) {
+ n = guard ? undefined : n;
+ n = (func && n == null) ? func.length : n;
+ return createWrap(func, ARY_FLAG, undefined, undefined, undefined, undefined, n);
+ }
+
+ /**
+ * Creates a function that invokes `func`, with the `this` binding and arguments
+ * of the created function, while it's called less than `n` times. Subsequent
+ * calls to the created function return the result of the last `func` invocation.
+ *
+ * @static
+ * @memberOf _
+ * @since 3.0.0
+ * @category Function
+ * @param {number} n The number of calls at which `func` is no longer invoked.
+ * @param {Function} func The function to restrict.
+ * @returns {Function} Returns the new restricted function.
+ * @example
+ *
+ * jQuery(element).on('click', _.before(5, addContactToList));
+ * // => Allows adding up to 4 contacts to the list.
+ */
+ function before(n, func) {
+ var result;
+ if (typeof func != 'function') {
+ throw new TypeError(FUNC_ERROR_TEXT);
+ }
+ n = toInteger(n);
+ return function() {
+ if (--n > 0) {
+ result = func.apply(this, arguments);
+ }
+ if (n <= 1) {
+ func = undefined;
+ }
+ return result;
+ };
+ }
+
+ /**
+ * Creates a function that invokes `func` with the `this` binding of `thisArg`
+ * and `partials` prepended to the arguments it receives.
+ *
+ * The `_.bind.placeholder` value, which defaults to `_` in monolithic builds,
+ * may be used as a placeholder for partially applied arguments.
+ *
+ * **Note:** Unlike native `Function#bind`, this method doesn't set the "length"
+ * property of bound functions.
+ *
+ * @static
+ * @memberOf _
+ * @since 0.1.0
+ * @category Function
+ * @param {Function} func The function to bind.
+ * @param {*} thisArg The `this` binding of `func`.
+ * @param {...*} [partials] The arguments to be partially applied.
+ * @returns {Function} Returns the new bound function.
+ * @example
+ *
+ * function greet(greeting, punctuation) {
+ * return greeting + ' ' + this.user + punctuation;
+ * }
+ *
+ * var object = { 'user': 'fred' };
+ *
+ * var bound = _.bind(greet, object, 'hi');
+ * bound('!');
+ * // => 'hi fred!'
+ *
+ * // Bound with placeholders.
+ * var bound = _.bind(greet, object, _, '!');
+ * bound('hi');
+ * // => 'hi fred!'
+ */
+ var bind = baseRest(function(func, thisArg, partials) {
+ var bitmask = BIND_FLAG;
+ if (partials.length) {
+ var holders = replaceHolders(partials, getHolder(bind));
+ bitmask |= PARTIAL_FLAG;
+ }
+ return createWrap(func, bitmask, thisArg, partials, holders);
+ });
+
+ /**
+ * Creates a function that invokes the method at `object[key]` with `partials`
+ * prepended to the arguments it receives.
+ *
+ * This method differs from `_.bind` by allowing bound functions to reference
+ * methods that may be redefined or don't yet exist. See
+ * [Peter Michaux's article](http://peter.michaux.ca/articles/lazy-function-definition-pattern)
+ * for more details.
+ *
+ * The `_.bindKey.placeholder` value, which defaults to `_` in monolithic
+ * builds, may be used as a placeholder for partially applied arguments.
+ *
+ * @static
+ * @memberOf _
+ * @since 0.10.0
+ * @category Function
+ * @param {Object} object The object to invoke the method on.
+ * @param {string} key The key of the method.
+ * @param {...*} [partials] The arguments to be partially applied.
+ * @returns {Function} Returns the new bound function.
+ * @example
+ *
+ * var object = {
+ * 'user': 'fred',
+ * 'greet': function(greeting, punctuation) {
+ * return greeting + ' ' + this.user + punctuation;
+ * }
+ * };
+ *
+ * var bound = _.bindKey(object, 'greet', 'hi');
+ * bound('!');
+ * // => 'hi fred!'
+ *
+ * object.greet = function(greeting, punctuation) {
+ * return greeting + 'ya ' + this.user + punctuation;
+ * };
+ *
+ * bound('!');
+ * // => 'hiya fred!'
+ *
+ * // Bound with placeholders.
+ * var bound = _.bindKey(object, 'greet', _, '!');
+ * bound('hi');
+ * // => 'hiya fred!'
+ */
+ var bindKey = baseRest(function(object, key, partials) {
+ var bitmask = BIND_FLAG | BIND_KEY_FLAG;
+ if (partials.length) {
+ var holders = replaceHolders(partials, getHolder(bindKey));
+ bitmask |= PARTIAL_FLAG;
+ }
+ return createWrap(key, bitmask, object, partials, holders);
+ });
+
+ /**
+ * Creates a function that accepts arguments of `func` and either invokes
+ * `func` returning its result, if at least `arity` number of arguments have
+ * been provided, or returns a function that accepts the remaining `func`
+ * arguments, and so on. The arity of `func` may be specified if `func.length`
+ * is not sufficient.
+ *
+ * The `_.curry.placeholder` value, which defaults to `_` in monolithic builds,
+ * may be used as a placeholder for provided arguments.
+ *
+ * **Note:** This method doesn't set the "length" property of curried functions.
+ *
+ * @static
+ * @memberOf _
+ * @since 2.0.0
+ * @category Function
+ * @param {Function} func The function to curry.
+ * @param {number} [arity=func.length] The arity of `func`.
+ * @param- {Object} [guard] Enables use as an iteratee for methods like `_.map`.
+ * @returns {Function} Returns the new curried function.
+ * @example
+ *
+ * var abc = function(a, b, c) {
+ * return [a, b, c];
+ * };
+ *
+ * var curried = _.curry(abc);
+ *
+ * curried(1)(2)(3);
+ * // => [1, 2, 3]
+ *
+ * curried(1, 2)(3);
+ * // => [1, 2, 3]
+ *
+ * curried(1, 2, 3);
+ * // => [1, 2, 3]
+ *
+ * // Curried with placeholders.
+ * curried(1)(_, 3)(2);
+ * // => [1, 2, 3]
+ */
+ function curry(func, arity, guard) {
+ arity = guard ? undefined : arity;
+ var result = createWrap(func, CURRY_FLAG, undefined, undefined, undefined, undefined, undefined, arity);
+ result.placeholder = curry.placeholder;
+ return result;
+ }
+
+ /**
+ * This method is like `_.curry` except that arguments are applied to `func`
+ * in the manner of `_.partialRight` instead of `_.partial`.
+ *
+ * The `_.curryRight.placeholder` value, which defaults to `_` in monolithic
+ * builds, may be used as a placeholder for provided arguments.
+ *
+ * **Note:** This method doesn't set the "length" property of curried functions.
+ *
+ * @static
+ * @memberOf _
+ * @since 3.0.0
+ * @category Function
+ * @param {Function} func The function to curry.
+ * @param {number} [arity=func.length] The arity of `func`.
+ * @param- {Object} [guard] Enables use as an iteratee for methods like `_.map`.
+ * @returns {Function} Returns the new curried function.
+ * @example
+ *
+ * var abc = function(a, b, c) {
+ * return [a, b, c];
+ * };
+ *
+ * var curried = _.curryRight(abc);
+ *
+ * curried(3)(2)(1);
+ * // => [1, 2, 3]
+ *
+ * curried(2, 3)(1);
+ * // => [1, 2, 3]
+ *
+ * curried(1, 2, 3);
+ * // => [1, 2, 3]
+ *
+ * // Curried with placeholders.
+ * curried(3)(1, _)(2);
+ * // => [1, 2, 3]
+ */
+ function curryRight(func, arity, guard) {
+ arity = guard ? undefined : arity;
+ var result = createWrap(func, CURRY_RIGHT_FLAG, undefined, undefined, undefined, undefined, undefined, arity);
+ result.placeholder = curryRight.placeholder;
+ return result;
+ }
+
+ /**
+ * Creates a debounced function that delays invoking `func` until after `wait`
+ * milliseconds have elapsed since the last time the debounced function was
+ * invoked. The debounced function comes with a `cancel` method to cancel
+ * delayed `func` invocations and a `flush` method to immediately invoke them.
+ * Provide `options` to indicate whether `func` should be invoked on the
+ * leading and/or trailing edge of the `wait` timeout. The `func` is invoked
+ * with the last arguments provided to the debounced function. Subsequent
+ * calls to the debounced function return the result of the last `func`
+ * invocation.
+ *
+ * **Note:** If `leading` and `trailing` options are `true`, `func` is
+ * invoked on the trailing edge of the timeout only if the debounced function
+ * is invoked more than once during the `wait` timeout.
+ *
+ * If `wait` is `0` and `leading` is `false`, `func` invocation is deferred
+ * until to the next tick, similar to `setTimeout` with a timeout of `0`.
+ *
+ * See [David Corbacho's article](https://css-tricks.com/debouncing-throttling-explained-examples/)
+ * for details over the differences between `_.debounce` and `_.throttle`.
+ *
+ * @static
+ * @memberOf _
+ * @since 0.1.0
+ * @category Function
+ * @param {Function} func The function to debounce.
+ * @param {number} [wait=0] The number of milliseconds to delay.
+ * @param {Object} [options={}] The options object.
+ * @param {boolean} [options.leading=false]
+ * Specify invoking on the leading edge of the timeout.
+ * @param {number} [options.maxWait]
+ * The maximum time `func` is allowed to be delayed before it's invoked.
+ * @param {boolean} [options.trailing=true]
+ * Specify invoking on the trailing edge of the timeout.
+ * @returns {Function} Returns the new debounced function.
+ * @example
+ *
+ * // Avoid costly calculations while the window size is in flux.
+ * jQuery(window).on('resize', _.debounce(calculateLayout, 150));
+ *
+ * // Invoke `sendMail` when clicked, debouncing subsequent calls.
+ * jQuery(element).on('click', _.debounce(sendMail, 300, {
+ * 'leading': true,
+ * 'trailing': false
+ * }));
+ *
+ * // Ensure `batchLog` is invoked once after 1 second of debounced calls.
+ * var debounced = _.debounce(batchLog, 250, { 'maxWait': 1000 });
+ * var source = new EventSource('/stream');
+ * jQuery(source).on('message', debounced);
+ *
+ * // Cancel the trailing debounced invocation.
+ * jQuery(window).on('popstate', debounced.cancel);
+ */
+ function debounce(func, wait, options) {
+ var lastArgs,
+ lastThis,
+ maxWait,
+ result,
+ timerId,
+ lastCallTime,
+ lastInvokeTime = 0,
+ leading = false,
+ maxing = false,
+ trailing = true;
+
+ if (typeof func != 'function') {
+ throw new TypeError(FUNC_ERROR_TEXT);
+ }
+ wait = toNumber(wait) || 0;
+ if (isObject(options)) {
+ leading = !!options.leading;
+ maxing = 'maxWait' in options;
+ maxWait = maxing ? nativeMax(toNumber(options.maxWait) || 0, wait) : maxWait;
+ trailing = 'trailing' in options ? !!options.trailing : trailing;
+ }
+
+ function invokeFunc(time) {
+ var args = lastArgs,
+ thisArg = lastThis;
+
+ lastArgs = lastThis = undefined;
+ lastInvokeTime = time;
+ result = func.apply(thisArg, args);
+ return result;
+ }
+
+ function leadingEdge(time) {
+ // Reset any `maxWait` timer.
+ lastInvokeTime = time;
+ // Start the timer for the trailing edge.
+ timerId = setTimeout(timerExpired, wait);
+ // Invoke the leading edge.
+ return leading ? invokeFunc(time) : result;
+ }
+
+ function remainingWait(time) {
+ var timeSinceLastCall = time - lastCallTime,
+ timeSinceLastInvoke = time - lastInvokeTime,
+ result = wait - timeSinceLastCall;
+
+ return maxing ? nativeMin(result, maxWait - timeSinceLastInvoke) : result;
+ }
+
+ function shouldInvoke(time) {
+ var timeSinceLastCall = time - lastCallTime,
+ timeSinceLastInvoke = time - lastInvokeTime;
+
+ // Either this is the first call, activity has stopped and we're at the
+ // trailing edge, the system time has gone backwards and we're treating
+ // it as the trailing edge, or we've hit the `maxWait` limit.
+ return (lastCallTime === undefined || (timeSinceLastCall >= wait) ||
+ (timeSinceLastCall < 0) || (maxing && timeSinceLastInvoke >= maxWait));
+ }
+
+ function timerExpired() {
+ var time = now();
+ if (shouldInvoke(time)) {
+ return trailingEdge(time);
+ }
+ // Restart the timer.
+ timerId = setTimeout(timerExpired, remainingWait(time));
+ }
+
+ function trailingEdge(time) {
+ timerId = undefined;
+
+ // Only invoke if we have `lastArgs` which means `func` has been
+ // debounced at least once.
+ if (trailing && lastArgs) {
+ return invokeFunc(time);
+ }
+ lastArgs = lastThis = undefined;
+ return result;
+ }
+
+ function cancel() {
+ if (timerId !== undefined) {
+ clearTimeout(timerId);
+ }
+ lastInvokeTime = 0;
+ lastArgs = lastCallTime = lastThis = timerId = undefined;
+ }
+
+ function flush() {
+ return timerId === undefined ? result : trailingEdge(now());
+ }
+
+ function debounced() {
+ var time = now(),
+ isInvoking = shouldInvoke(time);
+
+ lastArgs = arguments;
+ lastThis = this;
+ lastCallTime = time;
+
+ if (isInvoking) {
+ if (timerId === undefined) {
+ return leadingEdge(lastCallTime);
+ }
+ if (maxing) {
+ // Handle invocations in a tight loop.
+ timerId = setTimeout(timerExpired, wait);
+ return invokeFunc(lastCallTime);
+ }
+ }
+ if (timerId === undefined) {
+ timerId = setTimeout(timerExpired, wait);
+ }
+ return result;
+ }
+ debounced.cancel = cancel;
+ debounced.flush = flush;
+ return debounced;
+ }
+
+ /**
+ * Defers invoking the `func` until the current call stack has cleared. Any
+ * additional arguments are provided to `func` when it's invoked.
+ *
+ * @static
+ * @memberOf _
+ * @since 0.1.0
+ * @category Function
+ * @param {Function} func The function to defer.
+ * @param {...*} [args] The arguments to invoke `func` with.
+ * @returns {number} Returns the timer id.
+ * @example
+ *
+ * _.defer(function(text) {
+ * console.log(text);
+ * }, 'deferred');
+ * // => Logs 'deferred' after one millisecond.
+ */
+ var defer = baseRest(function(func, args) {
+ return baseDelay(func, 1, args);
+ });
+
+ /**
+ * Invokes `func` after `wait` milliseconds. Any additional arguments are
+ * provided to `func` when it's invoked.
+ *
+ * @static
+ * @memberOf _
+ * @since 0.1.0
+ * @category Function
+ * @param {Function} func The function to delay.
+ * @param {number} wait The number of milliseconds to delay invocation.
+ * @param {...*} [args] The arguments to invoke `func` with.
+ * @returns {number} Returns the timer id.
+ * @example
+ *
+ * _.delay(function(text) {
+ * console.log(text);
+ * }, 1000, 'later');
+ * // => Logs 'later' after one second.
+ */
+ var delay = baseRest(function(func, wait, args) {
+ return baseDelay(func, toNumber(wait) || 0, args);
+ });
+
+ /**
+ * Creates a function that invokes `func` with arguments reversed.
+ *
+ * @static
+ * @memberOf _
+ * @since 4.0.0
+ * @category Function
+ * @param {Function} func The function to flip arguments for.
+ * @returns {Function} Returns the new flipped function.
+ * @example
+ *
+ * var flipped = _.flip(function() {
+ * return _.toArray(arguments);
+ * });
+ *
+ * flipped('a', 'b', 'c', 'd');
+ * // => ['d', 'c', 'b', 'a']
+ */
+ function flip(func) {
+ return createWrap(func, FLIP_FLAG);
+ }
+
+ /**
+ * Creates a function that memoizes the result of `func`. If `resolver` is
+ * provided, it determines the cache key for storing the result based on the
+ * arguments provided to the memoized function. By default, the first argument
+ * provided to the memoized function is used as the map cache key. The `func`
+ * is invoked with the `this` binding of the memoized function.
+ *
+ * **Note:** The cache is exposed as the `cache` property on the memoized
+ * function. Its creation may be customized by replacing the `_.memoize.Cache`
+ * constructor with one whose instances implement the
+ * [`Map`](http://ecma-international.org/ecma-262/7.0/#sec-properties-of-the-map-prototype-object)
+ * method interface of `delete`, `get`, `has`, and `set`.
+ *
+ * @static
+ * @memberOf _
+ * @since 0.1.0
+ * @category Function
+ * @param {Function} func The function to have its output memoized.
+ * @param {Function} [resolver] The function to resolve the cache key.
+ * @returns {Function} Returns the new memoized function.
+ * @example
+ *
+ * var object = { 'a': 1, 'b': 2 };
+ * var other = { 'c': 3, 'd': 4 };
+ *
+ * var values = _.memoize(_.values);
+ * values(object);
+ * // => [1, 2]
+ *
+ * values(other);
+ * // => [3, 4]
+ *
+ * object.a = 2;
+ * values(object);
+ * // => [1, 2]
+ *
+ * // Modify the result cache.
+ * values.cache.set(object, ['a', 'b']);
+ * values(object);
+ * // => ['a', 'b']
+ *
+ * // Replace `_.memoize.Cache`.
+ * _.memoize.Cache = WeakMap;
+ */
+ function memoize(func, resolver) {
+ if (typeof func != 'function' || (resolver && typeof resolver != 'function')) {
+ throw new TypeError(FUNC_ERROR_TEXT);
+ }
+ var memoized = function() {
+ var args = arguments,
+ key = resolver ? resolver.apply(this, args) : args[0],
+ cache = memoized.cache;
+
+ if (cache.has(key)) {
+ return cache.get(key);
+ }
+ var result = func.apply(this, args);
+ memoized.cache = cache.set(key, result) || cache;
+ return result;
+ };
+ memoized.cache = new (memoize.Cache || MapCache);
+ return memoized;
+ }
+
+ // Expose `MapCache`.
+ memoize.Cache = MapCache;
+
+ /**
+ * Creates a function that negates the result of the predicate `func`. The
+ * `func` predicate is invoked with the `this` binding and arguments of the
+ * created function.
+ *
+ * @static
+ * @memberOf _
+ * @since 3.0.0
+ * @category Function
+ * @param {Function} predicate The predicate to negate.
+ * @returns {Function} Returns the new negated function.
+ * @example
+ *
+ * function isEven(n) {
+ * return n % 2 == 0;
+ * }
+ *
+ * _.filter([1, 2, 3, 4, 5, 6], _.negate(isEven));
+ * // => [1, 3, 5]
+ */
+ function negate(predicate) {
+ if (typeof predicate != 'function') {
+ throw new TypeError(FUNC_ERROR_TEXT);
+ }
+ return function() {
+ var args = arguments;
+ switch (args.length) {
+ case 0: return !predicate.call(this);
+ case 1: return !predicate.call(this, args[0]);
+ case 2: return !predicate.call(this, args[0], args[1]);
+ case 3: return !predicate.call(this, args[0], args[1], args[2]);
+ }
+ return !predicate.apply(this, args);
+ };
+ }
+
+ /**
+ * Creates a function that is restricted to invoking `func` once. Repeat calls
+ * to the function return the value of the first invocation. The `func` is
+ * invoked with the `this` binding and arguments of the created function.
+ *
+ * @static
+ * @memberOf _
+ * @since 0.1.0
+ * @category Function
+ * @param {Function} func The function to restrict.
+ * @returns {Function} Returns the new restricted function.
+ * @example
+ *
+ * var initialize = _.once(createApplication);
+ * initialize();
+ * initialize();
+ * // => `createApplication` is invoked once
+ */
+ function once(func) {
+ return before(2, func);
+ }
+
+ /**
+ * Creates a function that invokes `func` with its arguments transformed.
+ *
+ * @static
+ * @since 4.0.0
+ * @memberOf _
+ * @category Function
+ * @param {Function} func The function to wrap.
+ * @param {...(Function|Function[])} [transforms=[_.identity]]
+ * The argument transforms.
+ * @returns {Function} Returns the new function.
+ * @example
+ *
+ * function doubled(n) {
+ * return n * 2;
+ * }
+ *
+ * function square(n) {
+ * return n * n;
+ * }
+ *
+ * var func = _.overArgs(function(x, y) {
+ * return [x, y];
+ * }, [square, doubled]);
+ *
+ * func(9, 3);
+ * // => [81, 6]
+ *
+ * func(10, 5);
+ * // => [100, 10]
+ */
+ var overArgs = castRest(function(func, transforms) {
+ transforms = (transforms.length == 1 && isArray(transforms[0]))
+ ? arrayMap(transforms[0], baseUnary(getIteratee()))
+ : arrayMap(baseFlatten(transforms, 1), baseUnary(getIteratee()));
+
+ var funcsLength = transforms.length;
+ return baseRest(function(args) {
+ var index = -1,
+ length = nativeMin(args.length, funcsLength);
+
+ while (++index < length) {
+ args[index] = transforms[index].call(this, args[index]);
+ }
+ return apply(func, this, args);
+ });
+ });
+
+ /**
+ * Creates a function that invokes `func` with `partials` prepended to the
+ * arguments it receives. This method is like `_.bind` except it does **not**
+ * alter the `this` binding.
+ *
+ * The `_.partial.placeholder` value, which defaults to `_` in monolithic
+ * builds, may be used as a placeholder for partially applied arguments.
+ *
+ * **Note:** This method doesn't set the "length" property of partially
+ * applied functions.
+ *
+ * @static
+ * @memberOf _
+ * @since 0.2.0
+ * @category Function
+ * @param {Function} func The function to partially apply arguments to.
+ * @param {...*} [partials] The arguments to be partially applied.
+ * @returns {Function} Returns the new partially applied function.
+ * @example
+ *
+ * function greet(greeting, name) {
+ * return greeting + ' ' + name;
+ * }
+ *
+ * var sayHelloTo = _.partial(greet, 'hello');
+ * sayHelloTo('fred');
+ * // => 'hello fred'
+ *
+ * // Partially applied with placeholders.
+ * var greetFred = _.partial(greet, _, 'fred');
+ * greetFred('hi');
+ * // => 'hi fred'
+ */
+ var partial = baseRest(function(func, partials) {
+ var holders = replaceHolders(partials, getHolder(partial));
+ return createWrap(func, PARTIAL_FLAG, undefined, partials, holders);
+ });
+
+ /**
+ * This method is like `_.partial` except that partially applied arguments
+ * are appended to the arguments it receives.
+ *
+ * The `_.partialRight.placeholder` value, which defaults to `_` in monolithic
+ * builds, may be used as a placeholder for partially applied arguments.
+ *
+ * **Note:** This method doesn't set the "length" property of partially
+ * applied functions.
+ *
+ * @static
+ * @memberOf _
+ * @since 1.0.0
+ * @category Function
+ * @param {Function} func The function to partially apply arguments to.
+ * @param {...*} [partials] The arguments to be partially applied.
+ * @returns {Function} Returns the new partially applied function.
+ * @example
+ *
+ * function greet(greeting, name) {
+ * return greeting + ' ' + name;
+ * }
+ *
+ * var greetFred = _.partialRight(greet, 'fred');
+ * greetFred('hi');
+ * // => 'hi fred'
+ *
+ * // Partially applied with placeholders.
+ * var sayHelloTo = _.partialRight(greet, 'hello', _);
+ * sayHelloTo('fred');
+ * // => 'hello fred'
+ */
+ var partialRight = baseRest(function(func, partials) {
+ var holders = replaceHolders(partials, getHolder(partialRight));
+ return createWrap(func, PARTIAL_RIGHT_FLAG, undefined, partials, holders);
+ });
+
+ /**
+ * Creates a function that invokes `func` with arguments arranged according
+ * to the specified `indexes` where the argument value at the first index is
+ * provided as the first argument, the argument value at the second index is
+ * provided as the second argument, and so on.
+ *
+ * @static
+ * @memberOf _
+ * @since 3.0.0
+ * @category Function
+ * @param {Function} func The function to rearrange arguments for.
+ * @param {...(number|number[])} indexes The arranged argument indexes.
+ * @returns {Function} Returns the new function.
+ * @example
+ *
+ * var rearged = _.rearg(function(a, b, c) {
+ * return [a, b, c];
+ * }, [2, 0, 1]);
+ *
+ * rearged('b', 'c', 'a')
+ * // => ['a', 'b', 'c']
+ */
+ var rearg = flatRest(function(func, indexes) {
+ return createWrap(func, REARG_FLAG, undefined, undefined, undefined, indexes);
+ });
+
+ /**
+ * Creates a function that invokes `func` with the `this` binding of the
+ * created function and arguments from `start` and beyond provided as
+ * an array.
+ *
+ * **Note:** This method is based on the
+ * [rest parameter](https://mdn.io/rest_parameters).
+ *
+ * @static
+ * @memberOf _
+ * @since 4.0.0
+ * @category Function
+ * @param {Function} func The function to apply a rest parameter to.
+ * @param {number} [start=func.length-1] The start position of the rest parameter.
+ * @returns {Function} Returns the new function.
+ * @example
+ *
+ * var say = _.rest(function(what, names) {
+ * return what + ' ' + _.initial(names).join(', ') +
+ * (_.size(names) > 1 ? ', & ' : '') + _.last(names);
+ * });
+ *
+ * say('hello', 'fred', 'barney', 'pebbles');
+ * // => 'hello fred, barney, & pebbles'
+ */
+ function rest(func, start) {
+ if (typeof func != 'function') {
+ throw new TypeError(FUNC_ERROR_TEXT);
+ }
+ start = start === undefined ? start : toInteger(start);
+ return baseRest(func, start);
+ }
+
+ /**
+ * Creates a function that invokes `func` with the `this` binding of the
+ * create function and an array of arguments much like
+ * [`Function#apply`](http://www.ecma-international.org/ecma-262/7.0/#sec-function.prototype.apply).
+ *
+ * **Note:** This method is based on the
+ * [spread operator](https://mdn.io/spread_operator).
+ *
+ * @static
+ * @memberOf _
+ * @since 3.2.0
+ * @category Function
+ * @param {Function} func The function to spread arguments over.
+ * @param {number} [start=0] The start position of the spread.
+ * @returns {Function} Returns the new function.
+ * @example
+ *
+ * var say = _.spread(function(who, what) {
+ * return who + ' says ' + what;
+ * });
+ *
+ * say(['fred', 'hello']);
+ * // => 'fred says hello'
+ *
+ * var numbers = Promise.all([
+ * Promise.resolve(40),
+ * Promise.resolve(36)
+ * ]);
+ *
+ * numbers.then(_.spread(function(x, y) {
+ * return x + y;
+ * }));
+ * // => a Promise of 76
+ */
+ function spread(func, start) {
+ if (typeof func != 'function') {
+ throw new TypeError(FUNC_ERROR_TEXT);
+ }
+ start = start === undefined ? 0 : nativeMax(toInteger(start), 0);
+ return baseRest(function(args) {
+ var array = args[start],
+ otherArgs = castSlice(args, 0, start);
+
+ if (array) {
+ arrayPush(otherArgs, array);
+ }
+ return apply(func, this, otherArgs);
+ });
+ }
+
+ /**
+ * Creates a throttled function that only invokes `func` at most once per
+ * every `wait` milliseconds. The throttled function comes with a `cancel`
+ * method to cancel delayed `func` invocations and a `flush` method to
+ * immediately invoke them. Provide `options` to indicate whether `func`
+ * should be invoked on the leading and/or trailing edge of the `wait`
+ * timeout. The `func` is invoked with the last arguments provided to the
+ * throttled function. Subsequent calls to the throttled function return the
+ * result of the last `func` invocation.
+ *
+ * **Note:** If `leading` and `trailing` options are `true`, `func` is
+ * invoked on the trailing edge of the timeout only if the throttled function
+ * is invoked more than once during the `wait` timeout.
+ *
+ * If `wait` is `0` and `leading` is `false`, `func` invocation is deferred
+ * until to the next tick, similar to `setTimeout` with a timeout of `0`.
+ *
+ * See [David Corbacho's article](https://css-tricks.com/debouncing-throttling-explained-examples/)
+ * for details over the differences between `_.throttle` and `_.debounce`.
+ *
+ * @static
+ * @memberOf _
+ * @since 0.1.0
+ * @category Function
+ * @param {Function} func The function to throttle.
+ * @param {number} [wait=0] The number of milliseconds to throttle invocations to.
+ * @param {Object} [options={}] The options object.
+ * @param {boolean} [options.leading=true]
+ * Specify invoking on the leading edge of the timeout.
+ * @param {boolean} [options.trailing=true]
+ * Specify invoking on the trailing edge of the timeout.
+ * @returns {Function} Returns the new throttled function.
+ * @example
+ *
+ * // Avoid excessively updating the position while scrolling.
+ * jQuery(window).on('scroll', _.throttle(updatePosition, 100));
+ *
+ * // Invoke `renewToken` when the click event is fired, but not more than once every 5 minutes.
+ * var throttled = _.throttle(renewToken, 300000, { 'trailing': false });
+ * jQuery(element).on('click', throttled);
+ *
+ * // Cancel the trailing throttled invocation.
+ * jQuery(window).on('popstate', throttled.cancel);
+ */
+ function throttle(func, wait, options) {
+ var leading = true,
+ trailing = true;
+
+ if (typeof func != 'function') {
+ throw new TypeError(FUNC_ERROR_TEXT);
+ }
+ if (isObject(options)) {
+ leading = 'leading' in options ? !!options.leading : leading;
+ trailing = 'trailing' in options ? !!options.trailing : trailing;
+ }
+ return debounce(func, wait, {
+ 'leading': leading,
+ 'maxWait': wait,
+ 'trailing': trailing
+ });
+ }
+
+ /**
+ * Creates a function that accepts up to one argument, ignoring any
+ * additional arguments.
+ *
+ * @static
+ * @memberOf _
+ * @since 4.0.0
+ * @category Function
+ * @param {Function} func The function to cap arguments for.
+ * @returns {Function} Returns the new capped function.
+ * @example
+ *
+ * _.map(['6', '8', '10'], _.unary(parseInt));
+ * // => [6, 8, 10]
+ */
+ function unary(func) {
+ return ary(func, 1);
+ }
+
+ /**
+ * Creates a function that provides `value` to `wrapper` as its first
+ * argument. Any additional arguments provided to the function are appended
+ * to those provided to the `wrapper`. The wrapper is invoked with the `this`
+ * binding of the created function.
+ *
+ * @static
+ * @memberOf _
+ * @since 0.1.0
+ * @category Function
+ * @param {*} value The value to wrap.
+ * @param {Function} [wrapper=identity] The wrapper function.
+ * @returns {Function} Returns the new function.
+ * @example
+ *
+ * var p = _.wrap(_.escape, function(func, text) {
+ * return '<p>' + func(text) + '</p>';
+ * });
+ *
+ * p('fred, barney, & pebbles');
+ * // => '<p>fred, barney, &amp; pebbles</p>'
+ */
+ function wrap(value, wrapper) {
+ wrapper = wrapper == null ? identity : wrapper;
+ return partial(wrapper, value);
+ }
+
+ /*------------------------------------------------------------------------*/
+
+ /**
+ * Casts `value` as an array if it's not one.
+ *
+ * @static
+ * @memberOf _
+ * @since 4.4.0
+ * @category Lang
+ * @param {*} value The value to inspect.
+ * @returns {Array} Returns the cast array.
+ * @example
+ *
+ * _.castArray(1);
+ * // => [1]
+ *
+ * _.castArray({ 'a': 1 });
+ * // => [{ 'a': 1 }]
+ *
+ * _.castArray('abc');
+ * // => ['abc']
+ *
+ * _.castArray(null);
+ * // => [null]
+ *
+ * _.castArray(undefined);
+ * // => [undefined]
+ *
+ * _.castArray();
+ * // => []
+ *
+ * var array = [1, 2, 3];
+ * console.log(_.castArray(array) === array);
+ * // => true
+ */
+ function castArray() {
+ if (!arguments.length) {
+ return [];
+ }
+ var value = arguments[0];
+ return isArray(value) ? value : [value];
+ }
+
+ /**
+ * Creates a shallow clone of `value`.
+ *
+ * **Note:** This method is loosely based on the
+ * [structured clone algorithm](https://mdn.io/Structured_clone_algorithm)
+ * and supports cloning arrays, array buffers, booleans, date objects, maps,
+ * numbers, `Object` objects, regexes, sets, strings, symbols, and typed
+ * arrays. The own enumerable properties of `arguments` objects are cloned
+ * as plain objects. An empty object is returned for uncloneable values such
+ * as error objects, functions, DOM nodes, and WeakMaps.
+ *
+ * @static
+ * @memberOf _
+ * @since 0.1.0
+ * @category Lang
+ * @param {*} value The value to clone.
+ * @returns {*} Returns the cloned value.
+ * @see _.cloneDeep
+ * @example
+ *
+ * var objects = [{ 'a': 1 }, { 'b': 2 }];
+ *
+ * var shallow = _.clone(objects);
+ * console.log(shallow[0] === objects[0]);
+ * // => true
+ */
+ function clone(value) {
+ return baseClone(value, false, true);
+ }
+
+ /**
+ * This method is like `_.clone` except that it accepts `customizer` which
+ * is invoked to produce the cloned value. If `customizer` returns `undefined`,
+ * cloning is handled by the method instead. The `customizer` is invoked with
+ * up to four arguments; (value [, index|key, object, stack]).
+ *
+ * @static
+ * @memberOf _
+ * @since 4.0.0
+ * @category Lang
+ * @param {*} value The value to clone.
+ * @param {Function} [customizer] The function to customize cloning.
+ * @returns {*} Returns the cloned value.
+ * @see _.cloneDeepWith
+ * @example
+ *
+ * function customizer(value) {
+ * if (_.isElement(value)) {
+ * return value.cloneNode(false);
+ * }
+ * }
+ *
+ * var el = _.cloneWith(document.body, customizer);
+ *
+ * console.log(el === document.body);
+ * // => false
+ * console.log(el.nodeName);
+ * // => 'BODY'
+ * console.log(el.childNodes.length);
+ * // => 0
+ */
+ function cloneWith(value, customizer) {
+ return baseClone(value, false, true, customizer);
+ }
+
+ /**
+ * This method is like `_.clone` except that it recursively clones `value`.
+ *
+ * @static
+ * @memberOf _
+ * @since 1.0.0
+ * @category Lang
+ * @param {*} value The value to recursively clone.
+ * @returns {*} Returns the deep cloned value.
+ * @see _.clone
+ * @example
+ *
+ * var objects = [{ 'a': 1 }, { 'b': 2 }];
+ *
+ * var deep = _.cloneDeep(objects);
+ * console.log(deep[0] === objects[0]);
+ * // => false
+ */
+ function cloneDeep(value) {
+ return baseClone(value, true, true);
+ }
+
+ /**
+ * This method is like `_.cloneWith` except that it recursively clones `value`.
+ *
+ * @static
+ * @memberOf _
+ * @since 4.0.0
+ * @category Lang
+ * @param {*} value The value to recursively clone.
+ * @param {Function} [customizer] The function to customize cloning.
+ * @returns {*} Returns the deep cloned value.
+ * @see _.cloneWith
+ * @example
+ *
+ * function customizer(value) {
+ * if (_.isElement(value)) {
+ * return value.cloneNode(true);
+ * }
+ * }
+ *
+ * var el = _.cloneDeepWith(document.body, customizer);
+ *
+ * console.log(el === document.body);
+ * // => false
+ * console.log(el.nodeName);
+ * // => 'BODY'
+ * console.log(el.childNodes.length);
+ * // => 20
+ */
+ function cloneDeepWith(value, customizer) {
+ return baseClone(value, true, true, customizer);
+ }
+
+ /**
+ * Checks if `object` conforms to `source` by invoking the predicate
+ * properties of `source` with the corresponding property values of `object`.
+ *
+ * **Note:** This method is equivalent to `_.conforms` when `source` is
+ * partially applied.
+ *
+ * @static
+ * @memberOf _
+ * @since 4.14.0
+ * @category Lang
+ * @param {Object} object The object to inspect.
+ * @param {Object} source The object of property predicates to conform to.
+ * @returns {boolean} Returns `true` if `object` conforms, else `false`.
+ * @example
+ *
+ * var object = { 'a': 1, 'b': 2 };
+ *
+ * _.conformsTo(object, { 'b': function(n) { return n > 1; } });
+ * // => true
+ *
+ * _.conformsTo(object, { 'b': function(n) { return n > 2; } });
+ * // => false
+ */
+ function conformsTo(object, source) {
+ return source == null || baseConformsTo(object, source, keys(source));
+ }
+
+ /**
+ * Performs a
+ * [`SameValueZero`](http://ecma-international.org/ecma-262/7.0/#sec-samevaluezero)
+ * comparison between two values to determine if they are equivalent.
+ *
+ * @static
+ * @memberOf _
+ * @since 4.0.0
+ * @category Lang
+ * @param {*} value The value to compare.
+ * @param {*} other The other value to compare.
+ * @returns {boolean} Returns `true` if the values are equivalent, else `false`.
+ * @example
+ *
+ * var object = { 'a': 1 };
+ * var other = { 'a': 1 };
+ *
+ * _.eq(object, object);
+ * // => true
+ *
+ * _.eq(object, other);
+ * // => false
+ *
+ * _.eq('a', 'a');
+ * // => true
+ *
+ * _.eq('a', Object('a'));
+ * // => false
+ *
+ * _.eq(NaN, NaN);
+ * // => true
+ */
+ function eq(value, other) {
+ return value === other || (value !== value && other !== other);
+ }
+
+ /**
+ * Checks if `value` is greater than `other`.
+ *
+ * @static
+ * @memberOf _
+ * @since 3.9.0
+ * @category Lang
+ * @param {*} value The value to compare.
+ * @param {*} other The other value to compare.
+ * @returns {boolean} Returns `true` if `value` is greater than `other`,
+ * else `false`.
+ * @see _.lt
+ * @example
+ *
+ * _.gt(3, 1);
+ * // => true
+ *
+ * _.gt(3, 3);
+ * // => false
+ *
+ * _.gt(1, 3);
+ * // => false
+ */
+ var gt = createRelationalOperation(baseGt);
+
+ /**
+ * Checks if `value` is greater than or equal to `other`.
+ *
+ * @static
+ * @memberOf _
+ * @since 3.9.0
+ * @category Lang
+ * @param {*} value The value to compare.
+ * @param {*} other The other value to compare.
+ * @returns {boolean} Returns `true` if `value` is greater than or equal to
+ * `other`, else `false`.
+ * @see _.lte
+ * @example
+ *
+ * _.gte(3, 1);
+ * // => true
+ *
+ * _.gte(3, 3);
+ * // => true
+ *
+ * _.gte(1, 3);
+ * // => false
+ */
+ var gte = createRelationalOperation(function(value, other) {
+ return value >= other;
+ });
+
+ /**
+ * Checks if `value` is likely an `arguments` object.
+ *
+ * @static
+ * @memberOf _
+ * @since 0.1.0
+ * @category Lang
+ * @param {*} value The value to check.
+ * @returns {boolean} Returns `true` if `value` is an `arguments` object,
+ * else `false`.
+ * @example
+ *
+ * _.isArguments(function() { return arguments; }());
+ * // => true
+ *
+ * _.isArguments([1, 2, 3]);
+ * // => false
+ */
+ var isArguments = baseIsArguments(function() { return arguments; }()) ? baseIsArguments : function(value) {
+ return isObjectLike(value) && hasOwnProperty.call(value, 'callee') &&
+ !propertyIsEnumerable.call(value, 'callee');
+ };
+
+ /**
+ * Checks if `value` is classified as an `Array` object.
+ *
+ * @static
+ * @memberOf _
+ * @since 0.1.0
+ * @category Lang
+ * @param {*} value The value to check.
+ * @returns {boolean} Returns `true` if `value` is an array, else `false`.
+ * @example
+ *
+ * _.isArray([1, 2, 3]);
+ * // => true
+ *
+ * _.isArray(document.body.children);
+ * // => false
+ *
+ * _.isArray('abc');
+ * // => false
+ *
+ * _.isArray(_.noop);
+ * // => false
+ */
+ var isArray = Array.isArray;
+
+ /**
+ * Checks if `value` is classified as an `ArrayBuffer` object.
+ *
+ * @static
+ * @memberOf _
+ * @since 4.3.0
+ * @category Lang
+ * @param {*} value The value to check.
+ * @returns {boolean} Returns `true` if `value` is an array buffer, else `false`.
+ * @example
+ *
+ * _.isArrayBuffer(new ArrayBuffer(2));
+ * // => true
+ *
+ * _.isArrayBuffer(new Array(2));
+ * // => false
+ */
+ var isArrayBuffer = nodeIsArrayBuffer ? baseUnary(nodeIsArrayBuffer) : baseIsArrayBuffer;
+
+ /**
+ * Checks if `value` is array-like. A value is considered array-like if it's
+ * not a function and has a `value.length` that's an integer greater than or
+ * equal to `0` and less than or equal to `Number.MAX_SAFE_INTEGER`.
+ *
+ * @static
+ * @memberOf _
+ * @since 4.0.0
+ * @category Lang
+ * @param {*} value The value to check.
+ * @returns {boolean} Returns `true` if `value` is array-like, else `false`.
+ * @example
+ *
+ * _.isArrayLike([1, 2, 3]);
+ * // => true
+ *
+ * _.isArrayLike(document.body.children);
+ * // => true
+ *
+ * _.isArrayLike('abc');
+ * // => true
+ *
+ * _.isArrayLike(_.noop);
+ * // => false
+ */
+ function isArrayLike(value) {
+ return value != null && isLength(value.length) && !isFunction(value);
+ }
+
+ /**
+ * This method is like `_.isArrayLike` except that it also checks if `value`
+ * is an object.
+ *
+ * @static
+ * @memberOf _
+ * @since 4.0.0
+ * @category Lang
+ * @param {*} value The value to check.
+ * @returns {boolean} Returns `true` if `value` is an array-like object,
+ * else `false`.
+ * @example
+ *
+ * _.isArrayLikeObject([1, 2, 3]);
+ * // => true
+ *
+ * _.isArrayLikeObject(document.body.children);
+ * // => true
+ *
+ * _.isArrayLikeObject('abc');
+ * // => false
+ *
+ * _.isArrayLikeObject(_.noop);
+ * // => false
+ */
+ function isArrayLikeObject(value) {
+ return isObjectLike(value) && isArrayLike(value);
+ }
+
+ /**
+ * Checks if `value` is classified as a boolean primitive or object.
+ *
+ * @static
+ * @memberOf _
+ * @since 0.1.0
+ * @category Lang
+ * @param {*} value The value to check.
+ * @returns {boolean} Returns `true` if `value` is a boolean, else `false`.
+ * @example
+ *
+ * _.isBoolean(false);
+ * // => true
+ *
+ * _.isBoolean(null);
+ * // => false
+ */
+ function isBoolean(value) {
+ return value === true || value === false ||
+ (isObjectLike(value) && objectToString.call(value) == boolTag);
+ }
+
+ /**
+ * Checks if `value` is a buffer.
+ *
+ * @static
+ * @memberOf _
+ * @since 4.3.0
+ * @category Lang
+ * @param {*} value The value to check.
+ * @returns {boolean} Returns `true` if `value` is a buffer, else `false`.
+ * @example
+ *
+ * _.isBuffer(new Buffer(2));
+ * // => true
+ *
+ * _.isBuffer(new Uint8Array(2));
+ * // => false
+ */
+ var isBuffer = nativeIsBuffer || stubFalse;
+
+ /**
+ * Checks if `value` is classified as a `Date` object.
+ *
+ * @static
+ * @memberOf _
+ * @since 0.1.0
+ * @category Lang
+ * @param {*} value The value to check.
+ * @returns {boolean} Returns `true` if `value` is a date object, else `false`.
+ * @example
+ *
+ * _.isDate(new Date);
+ * // => true
+ *
+ * _.isDate('Mon April 23 2012');
+ * // => false
+ */
+ var isDate = nodeIsDate ? baseUnary(nodeIsDate) : baseIsDate;
+
+ /**
+ * Checks if `value` is likely a DOM element.
+ *
+ * @static
+ * @memberOf _
+ * @since 0.1.0
+ * @category Lang
+ * @param {*} value The value to check.
+ * @returns {boolean} Returns `true` if `value` is a DOM element, else `false`.
+ * @example
+ *
+ * _.isElement(document.body);
+ * // => true
+ *
+ * _.isElement('<body>');
+ * // => false
+ */
+ function isElement(value) {
+ return value != null && value.nodeType === 1 && isObjectLike(value) && !isPlainObject(value);
+ }
+
+ /**
+ * Checks if `value` is an empty object, collection, map, or set.
+ *
+ * Objects are considered empty if they have no own enumerable string keyed
+ * properties.
+ *
+ * Array-like values such as `arguments` objects, arrays, buffers, strings, or
+ * jQuery-like collections are considered empty if they have a `length` of `0`.
+ * Similarly, maps and sets are considered empty if they have a `size` of `0`.
+ *
+ * @static
+ * @memberOf _
+ * @since 0.1.0
+ * @category Lang
+ * @param {*} value The value to check.
+ * @returns {boolean} Returns `true` if `value` is empty, else `false`.
+ * @example
+ *
+ * _.isEmpty(null);
+ * // => true
+ *
+ * _.isEmpty(true);
+ * // => true
+ *
+ * _.isEmpty(1);
+ * // => true
+ *
+ * _.isEmpty([1, 2, 3]);
+ * // => false
+ *
+ * _.isEmpty({ 'a': 1 });
+ * // => false
+ */
+ function isEmpty(value) {
+ if (isArrayLike(value) &&
+ (isArray(value) || typeof value == 'string' || typeof value.splice == 'function' ||
+ isBuffer(value) || isTypedArray(value) || isArguments(value))) {
+ return !value.length;
+ }
+ var tag = getTag(value);
+ if (tag == mapTag || tag == setTag) {
+ return !value.size;
+ }
+ if (isPrototype(value)) {
+ return !baseKeys(value).length;
+ }
+ for (var key in value) {
+ if (hasOwnProperty.call(value, key)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Performs a deep comparison between two values to determine if they are
+ * equivalent.
+ *
+ * **Note:** This method supports comparing arrays, array buffers, booleans,
+ * date objects, error objects, maps, numbers, `Object` objects, regexes,
+ * sets, strings, symbols, and typed arrays. `Object` objects are compared
+ * by their own, not inherited, enumerable properties. Functions and DOM
+ * nodes are **not** supported.
+ *
+ * @static
+ * @memberOf _
+ * @since 0.1.0
+ * @category Lang
+ * @param {*} value The value to compare.
+ * @param {*} other The other value to compare.
+ * @returns {boolean} Returns `true` if the values are equivalent, else `false`.
+ * @example
+ *
+ * var object = { 'a': 1 };
+ * var other = { 'a': 1 };
+ *
+ * _.isEqual(object, other);
+ * // => true
+ *
+ * object === other;
+ * // => false
+ */
+ function isEqual(value, other) {
+ return baseIsEqual(value, other);
+ }
+
+ /**
+ * This method is like `_.isEqual` except that it accepts `customizer` which
+ * is invoked to compare values. If `customizer` returns `undefined`, comparisons
+ * are handled by the method instead. The `customizer` is invoked with up to
+ * six arguments: (objValue, othValue [, index|key, object, other, stack]).
+ *
+ * @static
+ * @memberOf _
+ * @since 4.0.0
+ * @category Lang
+ * @param {*} value The value to compare.
+ * @param {*} other The other value to compare.
+ * @param {Function} [customizer] The function to customize comparisons.
+ * @returns {boolean} Returns `true` if the values are equivalent, else `false`.
+ * @example
+ *
+ * function isGreeting(value) {
+ * return /^h(?:i|ello)$/.test(value);
+ * }
+ *
+ * function customizer(objValue, othValue) {
+ * if (isGreeting(objValue) && isGreeting(othValue)) {
+ * return true;
+ * }
+ * }
+ *
+ * var array = ['hello', 'goodbye'];
+ * var other = ['hi', 'goodbye'];
+ *
+ * _.isEqualWith(array, other, customizer);
+ * // => true
+ */
+ function isEqualWith(value, other, customizer) {
+ customizer = typeof customizer == 'function' ? customizer : undefined;
+ var result = customizer ? customizer(value, other) : undefined;
+ return result === undefined ? baseIsEqual(value, other, customizer) : !!result;
+ }
+
+ /**
+ * Checks if `value` is an `Error`, `EvalError`, `RangeError`, `ReferenceError`,
+ * `SyntaxError`, `TypeError`, or `URIError` object.
+ *
+ * @static
+ * @memberOf _
+ * @since 3.0.0
+ * @category Lang
+ * @param {*} value The value to check.
+ * @returns {boolean} Returns `true` if `value` is an error object, else `false`.
+ * @example
+ *
+ * _.isError(new Error);
+ * // => true
+ *
+ * _.isError(Error);
+ * // => false
+ */
+ function isError(value) {
+ if (!isObjectLike(value)) {
+ return false;
+ }
+ return (objectToString.call(value) == errorTag) ||
+ (typeof value.message == 'string' && typeof value.name == 'string');
+ }
+
+ /**
+ * Checks if `value` is a finite primitive number.
+ *
+ * **Note:** This method is based on
+ * [`Number.isFinite`](https://mdn.io/Number/isFinite).
+ *
+ * @static
+ * @memberOf _
+ * @since 0.1.0
+ * @category Lang
+ * @param {*} value The value to check.
+ * @returns {boolean} Returns `true` if `value` is a finite number, else `false`.
+ * @example
+ *
+ * _.isFinite(3);
+ * // => true
+ *
+ * _.isFinite(Number.MIN_VALUE);
+ * // => true
+ *
+ * _.isFinite(Infinity);
+ * // => false
+ *
+ * _.isFinite('3');
+ * // => false
+ */
+ function isFinite(value) {
+ return typeof value == 'number' && nativeIsFinite(value);
+ }
+
+ /**
+ * Checks if `value` is classified as a `Function` object.
+ *
+ * @static
+ * @memberOf _
+ * @since 0.1.0
+ * @category Lang
+ * @param {*} value The value to check.
+ * @returns {boolean} Returns `true` if `value` is a function, else `false`.
+ * @example
+ *
+ * _.isFunction(_);
+ * // => true
+ *
+ * _.isFunction(/abc/);
+ * // => false
+ */
+ function isFunction(value) {
+ // The use of `Object#toString` avoids issues with the `typeof` operator
+ // in Safari 9 which returns 'object' for typed array and other constructors.
+ var tag = isObject(value) ? objectToString.call(value) : '';
+ return tag == funcTag || tag == genTag || tag == proxyTag;
+ }
+
+ /**
+ * Checks if `value` is an integer.
+ *
+ * **Note:** This method is based on
+ * [`Number.isInteger`](https://mdn.io/Number/isInteger).
+ *
+ * @static
+ * @memberOf _
+ * @since 4.0.0
+ * @category Lang
+ * @param {*} value The value to check.
+ * @returns {boolean} Returns `true` if `value` is an integer, else `false`.
+ * @example
+ *
+ * _.isInteger(3);
+ * // => true
+ *
+ * _.isInteger(Number.MIN_VALUE);
+ * // => false
+ *
+ * _.isInteger(Infinity);
+ * // => false
+ *
+ * _.isInteger('3');
+ * // => false
+ */
+ function isInteger(value) {
+ return typeof value == 'number' && value == toInteger(value);
+ }
+
+ /**
+ * Checks if `value` is a valid array-like length.
+ *
+ * **Note:** This method is loosely based on
+ * [`ToLength`](http://ecma-international.org/ecma-262/7.0/#sec-tolength).
+ *
+ * @static
+ * @memberOf _
+ * @since 4.0.0
+ * @category Lang
+ * @param {*} value The value to check.
+ * @returns {boolean} Returns `true` if `value` is a valid length, else `false`.
+ * @example
+ *
+ * _.isLength(3);
+ * // => true
+ *
+ * _.isLength(Number.MIN_VALUE);
+ * // => false
+ *
+ * _.isLength(Infinity);
+ * // => false
+ *
+ * _.isLength('3');
+ * // => false
+ */
+ function isLength(value) {
+ return typeof value == 'number' &&
+ value > -1 && value % 1 == 0 && value <= MAX_SAFE_INTEGER;
+ }
+
+ /**
+ * Checks if `value` is the
+ * [language type](http://www.ecma-international.org/ecma-262/7.0/#sec-ecmascript-language-types)
+ * of `Object`. (e.g. arrays, functions, objects, regexes, `new Number(0)`, and `new String('')`)
+ *
+ * @static
+ * @memberOf _
+ * @since 0.1.0
+ * @category Lang
+ * @param {*} value The value to check.
+ * @returns {boolean} Returns `true` if `value` is an object, else `false`.
+ * @example
+ *
+ * _.isObject({});
+ * // => true
+ *
+ * _.isObject([1, 2, 3]);
+ * // => true
+ *
+ * _.isObject(_.noop);
+ * // => true
+ *
+ * _.isObject(null);
+ * // => false
+ */
+ function isObject(value) {
+ var type = typeof value;
+ return value != null && (type == 'object' || type == 'function');
+ }
+
+ /**
+ * Checks if `value` is object-like. A value is object-like if it's not `null`
+ * and has a `typeof` result of "object".
+ *
+ * @static
+ * @memberOf _
+ * @since 4.0.0
+ * @category Lang
+ * @param {*} value The value to check.
+ * @returns {boolean} Returns `true` if `value` is object-like, else `false`.
+ * @example
+ *
+ * _.isObjectLike({});
+ * // => true
+ *
+ * _.isObjectLike([1, 2, 3]);
+ * // => true
+ *
+ * _.isObjectLike(_.noop);
+ * // => false
+ *
+ * _.isObjectLike(null);
+ * // => false
+ */
+ function isObjectLike(value) {
+ return value != null && typeof value == 'object';
+ }
+
+ /**
+ * Checks if `value` is classified as a `Map` object.
+ *
+ * @static
+ * @memberOf _
+ * @since 4.3.0
+ * @category Lang
+ * @param {*} value The value to check.
+ * @returns {boolean} Returns `true` if `value` is a map, else `false`.
+ * @example
+ *
+ * _.isMap(new Map);
+ * // => true
+ *
+ * _.isMap(new WeakMap);
+ * // => false
+ */
+ var isMap = nodeIsMap ? baseUnary(nodeIsMap) : baseIsMap;
+
+ /**
+ * Performs a partial deep comparison between `object` and `source` to
+ * determine if `object` contains equivalent property values.
+ *
+ * **Note:** This method is equivalent to `_.matches` when `source` is
+ * partially applied.
+ *
+ * Partial comparisons will match empty array and empty object `source`
+ * values against any array or object value, respectively. See `_.isEqual`
+ * for a list of supported value comparisons.
+ *
+ * @static
+ * @memberOf _
+ * @since 3.0.0
+ * @category Lang
+ * @param {Object} object The object to inspect.
+ * @param {Object} source The object of property values to match.
+ * @returns {boolean} Returns `true` if `object` is a match, else `false`.
+ * @example
+ *
+ * var object = { 'a': 1, 'b': 2 };
+ *
+ * _.isMatch(object, { 'b': 2 });
+ * // => true
+ *
+ * _.isMatch(object, { 'b': 1 });
+ * // => false
+ */
+ function isMatch(object, source) {
+ return object === source || baseIsMatch(object, source, getMatchData(source));
+ }
+
+ /**
+ * This method is like `_.isMatch` except that it accepts `customizer` which
+ * is invoked to compare values. If `customizer` returns `undefined`, comparisons
+ * are handled by the method instead. The `customizer` is invoked with five
+ * arguments: (objValue, srcValue, index|key, object, source).
+ *
+ * @static
+ * @memberOf _
+ * @since 4.0.0
+ * @category Lang
+ * @param {Object} object The object to inspect.
+ * @param {Object} source The object of property values to match.
+ * @param {Function} [customizer] The function to customize comparisons.
+ * @returns {boolean} Returns `true` if `object` is a match, else `false`.
+ * @example
+ *
+ * function isGreeting(value) {
+ * return /^h(?:i|ello)$/.test(value);
+ * }
+ *
+ * function customizer(objValue, srcValue) {
+ * if (isGreeting(objValue) && isGreeting(srcValue)) {
+ * return true;
+ * }
+ * }
+ *
+ * var object = { 'greeting': 'hello' };
+ * var source = { 'greeting': 'hi' };
+ *
+ * _.isMatchWith(object, source, customizer);
+ * // => true
+ */
+ function isMatchWith(object, source, customizer) {
+ customizer = typeof customizer == 'function' ? customizer : undefined;
+ return baseIsMatch(object, source, getMatchData(source), customizer);
+ }
+
+ /**
+ * Checks if `value` is `NaN`.
+ *
+ * **Note:** This method is based on
+ * [`Number.isNaN`](https://mdn.io/Number/isNaN) and is not the same as
+ * global [`isNaN`](https://mdn.io/isNaN) which returns `true` for
+ * `undefined` and other non-number values.
+ *
+ * @static
+ * @memberOf _
+ * @since 0.1.0
+ * @category Lang
+ * @param {*} value The value to check.
+ * @returns {boolean} Returns `true` if `value` is `NaN`, else `false`.
+ * @example
+ *
+ * _.isNaN(NaN);
+ * // => true
+ *
+ * _.isNaN(new Number(NaN));
+ * // => true
+ *
+ * isNaN(undefined);
+ * // => true
+ *
+ * _.isNaN(undefined);
+ * // => false
+ */
+ function isNaN(value) {
+ // An `NaN` primitive is the only value that is not equal to itself.
+ // Perform the `toStringTag` check first to avoid errors with some
+ // ActiveX objects in IE.
+ return isNumber(value) && value != +value;
+ }
+
+ /**
+ * Checks if `value` is a pristine native function.
+ *
+ * **Note:** This method can't reliably detect native functions in the presence
+ * of the core-js package because core-js circumvents this kind of detection.
+ * Despite multiple requests, the core-js maintainer has made it clear: any
+ * attempt to fix the detection will be obstructed. As a result, we're left
+ * with little choice but to throw an error. Unfortunately, this also affects
+ * packages, like [babel-polyfill](https://www.npmjs.com/package/babel-polyfill),
+ * which rely on core-js.
+ *
+ * @static
+ * @memberOf _
+ * @since 3.0.0
+ * @category Lang
+ * @param {*} value The value to check.
+ * @returns {boolean} Returns `true` if `value` is a native function,
+ * else `false`.
+ * @example
+ *
+ * _.isNative(Array.prototype.push);
+ * // => true
+ *
+ * _.isNative(_);
+ * // => false
+ */
+ function isNative(value) {
+ if (isMaskable(value)) {
+ throw new Error(CORE_ERROR_TEXT);
+ }
+ return baseIsNative(value);
+ }
+
+ /**
+ * Checks if `value` is `null`.
+ *
+ * @static
+ * @memberOf _
+ * @since 0.1.0
+ * @category Lang
+ * @param {*} value The value to check.
+ * @returns {boolean} Returns `true` if `value` is `null`, else `false`.
+ * @example
+ *
+ * _.isNull(null);
+ * // => true
+ *
+ * _.isNull(void 0);
+ * // => false
+ */
+ function isNull(value) {
+ return value === null;
+ }
+
+ /**
+ * Checks if `value` is `null` or `undefined`.
+ *
+ * @static
+ * @memberOf _
+ * @since 4.0.0
+ * @category Lang
+ * @param {*} value The value to check.
+ * @returns {boolean} Returns `true` if `value` is nullish, else `false`.
+ * @example
+ *
+ * _.isNil(null);
+ * // => true
+ *
+ * _.isNil(void 0);
+ * // => true
+ *
+ * _.isNil(NaN);
+ * // => false
+ */
+ function isNil(value) {
+ return value == null;
+ }
+
+ /**
+ * Checks if `value` is classified as a `Number` primitive or object.
+ *
+ * **Note:** To exclude `Infinity`, `-Infinity`, and `NaN`, which are
+ * classified as numbers, use the `_.isFinite` method.
+ *
+ * @static
+ * @memberOf _
+ * @since 0.1.0
+ * @category Lang
+ * @param {*} value The value to check.
+ * @returns {boolean} Returns `true` if `value` is a number, else `false`.
+ * @example
+ *
+ * _.isNumber(3);
+ * // => true
+ *
+ * _.isNumber(Number.MIN_VALUE);
+ * // => true
+ *
+ * _.isNumber(Infinity);
+ * // => true
+ *
+ * _.isNumber('3');
+ * // => false
+ */
+ function isNumber(value) {
+ return typeof value == 'number' ||
+ (isObjectLike(value) && objectToString.call(value) == numberTag);
+ }
+
+ /**
+ * Checks if `value` is a plain object, that is, an object created by the
+ * `Object` constructor or one with a `[[Prototype]]` of `null`.
+ *
+ * @static
+ * @memberOf _
+ * @since 0.8.0
+ * @category Lang
+ * @param {*} value The value to check.
+ * @returns {boolean} Returns `true` if `value` is a plain object, else `false`.
+ * @example
+ *
+ * function Foo() {
+ * this.a = 1;
+ * }
+ *
+ * _.isPlainObject(new Foo);
+ * // => false
+ *
+ * _.isPlainObject([1, 2, 3]);
+ * // => false
+ *
+ * _.isPlainObject({ 'x': 0, 'y': 0 });
+ * // => true
+ *
+ * _.isPlainObject(Object.create(null));
+ * // => true
+ */
+ function isPlainObject(value) {
+ if (!isObjectLike(value) || objectToString.call(value) != objectTag) {
+ return false;
+ }
+ var proto = getPrototype(value);
+ if (proto === null) {
+ return true;
+ }
+ var Ctor = hasOwnProperty.call(proto, 'constructor') && proto.constructor;
+ return (typeof Ctor == 'function' &&
+ Ctor instanceof Ctor && funcToString.call(Ctor) == objectCtorString);
+ }
+
+ /**
+ * Checks if `value` is classified as a `RegExp` object.
+ *
+ * @static
+ * @memberOf _
+ * @since 0.1.0
+ * @category Lang
+ * @param {*} value The value to check.
+ * @returns {boolean} Returns `true` if `value` is a regexp, else `false`.
+ * @example
+ *
+ * _.isRegExp(/abc/);
+ * // => true
+ *
+ * _.isRegExp('/abc/');
+ * // => false
+ */
+ var isRegExp = nodeIsRegExp ? baseUnary(nodeIsRegExp) : baseIsRegExp;
+
+ /**
+ * Checks if `value` is a safe integer. An integer is safe if it's an IEEE-754
+ * double precision number which isn't the result of a rounded unsafe integer.
+ *
+ * **Note:** This method is based on
+ * [`Number.isSafeInteger`](https://mdn.io/Number/isSafeInteger).
+ *
+ * @static
+ * @memberOf _
+ * @since 4.0.0
+ * @category Lang
+ * @param {*} value The value to check.
+ * @returns {boolean} Returns `true` if `value` is a safe integer, else `false`.
+ * @example
+ *
+ * _.isSafeInteger(3);
+ * // => true
+ *
+ * _.isSafeInteger(Number.MIN_VALUE);
+ * // => false
+ *
+ * _.isSafeInteger(Infinity);
+ * // => false
+ *
+ * _.isSafeInteger('3');
+ * // => false
+ */
+ function isSafeInteger(value) {
+ return isInteger(value) && value >= -MAX_SAFE_INTEGER && value <= MAX_SAFE_INTEGER;
+ }
+
+ /**
+ * Checks if `value` is classified as a `Set` object.
+ *
+ * @static
+ * @memberOf _
+ * @since 4.3.0
+ * @category Lang
+ * @param {*} value The value to check.
+ * @returns {boolean} Returns `true` if `value` is a set, else `false`.
+ * @example
+ *
+ * _.isSet(new Set);
+ * // => true
+ *
+ * _.isSet(new WeakSet);
+ * // => false
+ */
+ var isSet = nodeIsSet ? baseUnary(nodeIsSet) : baseIsSet;
+
+ /**
+ * Checks if `value` is classified as a `String` primitive or object.
+ *
+ * @static
+ * @since 0.1.0
+ * @memberOf _
+ * @category Lang
+ * @param {*} value The value to check.
+ * @returns {boolean} Returns `true` if `value` is a string, else `false`.
+ * @example
+ *
+ * _.isString('abc');
+ * // => true
+ *
+ * _.isString(1);
+ * // => false
+ */
+ function isString(value) {
+ return typeof value == 'string' ||
+ (!isArray(value) && isObjectLike(value) && objectToString.call(value) == stringTag);
+ }
+
+ /**
+ * Checks if `value` is classified as a `Symbol` primitive or object.
+ *
+ * @static
+ * @memberOf _
+ * @since 4.0.0
+ * @category Lang
+ * @param {*} value The value to check.
+ * @returns {boolean} Returns `true` if `value` is a symbol, else `false`.
+ * @example
+ *
+ * _.isSymbol(Symbol.iterator);
+ * // => true
+ *
+ * _.isSymbol('abc');
+ * // => false
+ */
+ function isSymbol(value) {
+ return typeof value == 'symbol' ||
+ (isObjectLike(value) && objectToString.call(value) == symbolTag);
+ }
+
+ /**
+ * Checks if `value` is classified as a typed array.
+ *
+ * @static
+ * @memberOf _
+ * @since 3.0.0
+ * @category Lang
+ * @param {*} value The value to check.
+ * @returns {boolean} Returns `true` if `value` is a typed array, else `false`.
+ * @example
+ *
+ * _.isTypedArray(new Uint8Array);
+ * // => true
+ *
+ * _.isTypedArray([]);
+ * // => false
+ */
+ var isTypedArray = nodeIsTypedArray ? baseUnary(nodeIsTypedArray) : baseIsTypedArray;
+
+ /**
+ * Checks if `value` is `undefined`.
+ *
+ * @static
+ * @since 0.1.0
+ * @memberOf _
+ * @category Lang
+ * @param {*} value The value to check.
+ * @returns {boolean} Returns `true` if `value` is `undefined`, else `false`.
+ * @example
+ *
+ * _.isUndefined(void 0);
+ * // => true
+ *
+ * _.isUndefined(null);
+ * // => false
+ */
+ function isUndefined(value) {
+ return value === undefined;
+ }
+
+ /**
+ * Checks if `value` is classified as a `WeakMap` object.
+ *
+ * @static
+ * @memberOf _
+ * @since 4.3.0
+ * @category Lang
+ * @param {*} value The value to check.
+ * @returns {boolean} Returns `true` if `value` is a weak map, else `false`.
+ * @example
+ *
+ * _.isWeakMap(new WeakMap);
+ * // => true
+ *
+ * _.isWeakMap(new Map);
+ * // => false
+ */
+ function isWeakMap(value) {
+ return isObjectLike(value) && getTag(value) == weakMapTag;
+ }
+
+ /**
+ * Checks if `value` is classified as a `WeakSet` object.
+ *
+ * @static
+ * @memberOf _
+ * @since 4.3.0
+ * @category Lang
+ * @param {*} value The value to check.
+ * @returns {boolean} Returns `true` if `value` is a weak set, else `false`.
+ * @example
+ *
+ * _.isWeakSet(new WeakSet);
+ * // => true
+ *
+ * _.isWeakSet(new Set);
+ * // => false
+ */
+ function isWeakSet(value) {
+ return isObjectLike(value) && objectToString.call(value) == weakSetTag;
+ }
+
+ /**
+ * Checks if `value` is less than `other`.
+ *
+ * @static
+ * @memberOf _
+ * @since 3.9.0
+ * @category Lang
+ * @param {*} value The value to compare.
+ * @param {*} other The other value to compare.
+ * @returns {boolean} Returns `true` if `value` is less than `other`,
+ * else `false`.
+ * @see _.gt
+ * @example
+ *
+ * _.lt(1, 3);
+ * // => true
+ *
+ * _.lt(3, 3);
+ * // => false
+ *
+ * _.lt(3, 1);
+ * // => false
+ */
+ var lt = createRelationalOperation(baseLt);
+
+ /**
+ * Checks if `value` is less than or equal to `other`.
+ *
+ * @static
+ * @memberOf _
+ * @since 3.9.0
+ * @category Lang
+ * @param {*} value The value to compare.
+ * @param {*} other The other value to compare.
+ * @returns {boolean} Returns `true` if `value` is less than or equal to
+ * `other`, else `false`.
+ * @see _.gte
+ * @example
+ *
+ * _.lte(1, 3);
+ * // => true
+ *
+ * _.lte(3, 3);
+ * // => true
+ *
+ * _.lte(3, 1);
+ * // => false
+ */
+ var lte = createRelationalOperation(function(value, other) {
+ return value <= other;
+ });
+
+ /**
+ * Converts `value` to an array.
+ *
+ * @static
+ * @since 0.1.0
+ * @memberOf _
+ * @category Lang
+ * @param {*} value The value to convert.
+ * @returns {Array} Returns the converted array.
+ * @example
+ *
+ * _.toArray({ 'a': 1, 'b': 2 });
+ * // => [1, 2]
+ *
+ * _.toArray('abc');
+ * // => ['a', 'b', 'c']
+ *
+ * _.toArray(1);
+ * // => []
+ *
+ * _.toArray(null);
+ * // => []
+ */
+ function toArray(value) {
+ if (!value) {
+ return [];
+ }
+ if (isArrayLike(value)) {
+ return isString(value) ? stringToArray(value) : copyArray(value);
+ }
+ if (iteratorSymbol && value[iteratorSymbol]) {
+ return iteratorToArray(value[iteratorSymbol]());
+ }
+ var tag = getTag(value),
+ func = tag == mapTag ? mapToArray : (tag == setTag ? setToArray : values);
+
+ return func(value);
+ }
+
+ /**
+ * Converts `value` to a finite number.
+ *
+ * @static
+ * @memberOf _
+ * @since 4.12.0
+ * @category Lang
+ * @param {*} value The value to convert.
+ * @returns {number} Returns the converted number.
+ * @example
+ *
+ * _.toFinite(3.2);
+ * // => 3.2
+ *
+ * _.toFinite(Number.MIN_VALUE);
+ * // => 5e-324
+ *
+ * _.toFinite(Infinity);
+ * // => 1.7976931348623157e+308
+ *
+ * _.toFinite('3.2');
+ * // => 3.2
+ */
+ function toFinite(value) {
+ if (!value) {
+ return value === 0 ? value : 0;
+ }
+ value = toNumber(value);
+ if (value === INFINITY || value === -INFINITY) {
+ var sign = (value < 0 ? -1 : 1);
+ return sign * MAX_INTEGER;
+ }
+ return value === value ? value : 0;
+ }
+
+ /**
+ * Converts `value` to an integer.
+ *
+ * **Note:** This method is loosely based on
+ * [`ToInteger`](http://www.ecma-international.org/ecma-262/7.0/#sec-tointeger).
+ *
+ * @static
+ * @memberOf _
+ * @since 4.0.0
+ * @category Lang
+ * @param {*} value The value to convert.
+ * @returns {number} Returns the converted integer.
+ * @example
+ *
+ * _.toInteger(3.2);
+ * // => 3
+ *
+ * _.toInteger(Number.MIN_VALUE);
+ * // => 0
+ *
+ * _.toInteger(Infinity);
+ * // => 1.7976931348623157e+308
+ *
+ * _.toInteger('3.2');
+ * // => 3
+ */
+ function toInteger(value) {
+ var result = toFinite(value),
+ remainder = result % 1;
+
+ return result === result ? (remainder ? result - remainder : result) : 0;
+ }
+
+ /**
+ * Converts `value` to an integer suitable for use as the length of an
+ * array-like object.
+ *
+ * **Note:** This method is based on
+ * [`ToLength`](http://ecma-international.org/ecma-262/7.0/#sec-tolength).
+ *
+ * @static
+ * @memberOf _
+ * @since 4.0.0
+ * @category Lang
+ * @param {*} value The value to convert.
+ * @returns {number} Returns the converted integer.
+ * @example
+ *
+ * _.toLength(3.2);
+ * // => 3
+ *
+ * _.toLength(Number.MIN_VALUE);
+ * // => 0
+ *
+ * _.toLength(Infinity);
+ * // => 4294967295
+ *
+ * _.toLength('3.2');
+ * // => 3
+ */
+ function toLength(value) {
+ return value ? baseClamp(toInteger(value), 0, MAX_ARRAY_LENGTH) : 0;
+ }
+
+ /**
+ * Converts `value` to a number.
+ *
+ * @static
+ * @memberOf _
+ * @since 4.0.0
+ * @category Lang
+ * @param {*} value The value to process.
+ * @returns {number} Returns the number.
+ * @example
+ *
+ * _.toNumber(3.2);
+ * // => 3.2
+ *
+ * _.toNumber(Number.MIN_VALUE);
+ * // => 5e-324
+ *
+ * _.toNumber(Infinity);
+ * // => Infinity
+ *
+ * _.toNumber('3.2');
+ * // => 3.2
+ */
+ function toNumber(value) {
+ if (typeof value == 'number') {
+ return value;
+ }
+ if (isSymbol(value)) {
+ return NAN;
+ }
+ if (isObject(value)) {
+ var other = typeof value.valueOf == 'function' ? value.valueOf() : value;
+ value = isObject(other) ? (other + '') : other;
+ }
+ if (typeof value != 'string') {
+ return value === 0 ? value : +value;
+ }
+ value = value.replace(reTrim, '');
+ var isBinary = reIsBinary.test(value);
+ return (isBinary || reIsOctal.test(value))
+ ? freeParseInt(value.slice(2), isBinary ? 2 : 8)
+ : (reIsBadHex.test(value) ? NAN : +value);
+ }
+
+ /**
+ * Converts `value` to a plain object flattening inherited enumerable string
+ * keyed properties of `value` to own properties of the plain object.
+ *
+ * @static
+ * @memberOf _
+ * @since 3.0.0
+ * @category Lang
+ * @param {*} value The value to convert.
+ * @returns {Object} Returns the converted plain object.
+ * @example
+ *
+ * function Foo() {
+ * this.b = 2;
+ * }
+ *
+ * Foo.prototype.c = 3;
+ *
+ * _.assign({ 'a': 1 }, new Foo);
+ * // => { 'a': 1, 'b': 2 }
+ *
+ * _.assign({ 'a': 1 }, _.toPlainObject(new Foo));
+ * // => { 'a': 1, 'b': 2, 'c': 3 }
+ */
+ function toPlainObject(value) {
+ return copyObject(value, keysIn(value));
+ }
+
+ /**
+ * Converts `value` to a safe integer. A safe integer can be compared and
+ * represented correctly.
+ *
+ * @static
+ * @memberOf _
+ * @since 4.0.0
+ * @category Lang
+ * @param {*} value The value to convert.
+ * @returns {number} Returns the converted integer.
+ * @example
+ *
+ * _.toSafeInteger(3.2);
+ * // => 3
+ *
+ * _.toSafeInteger(Number.MIN_VALUE);
+ * // => 0
+ *
+ * _.toSafeInteger(Infinity);
+ * // => 9007199254740991
+ *
+ * _.toSafeInteger('3.2');
+ * // => 3
+ */
+ function toSafeInteger(value) {
+ return baseClamp(toInteger(value), -MAX_SAFE_INTEGER, MAX_SAFE_INTEGER);
+ }
+
+ /**
+ * Converts `value` to a string. An empty string is returned for `null`
+ * and `undefined` values. The sign of `-0` is preserved.
+ *
+ * @static
+ * @memberOf _
+ * @since 4.0.0
+ * @category Lang
+ * @param {*} value The value to convert.
+ * @returns {string} Returns the converted string.
+ * @example
+ *
+ * _.toString(null);
+ * // => ''
+ *
+ * _.toString(-0);
+ * // => '-0'
+ *
+ * _.toString([1, 2, 3]);
+ * // => '1,2,3'
+ */
+ function toString(value) {
+ return value == null ? '' : baseToString(value);
+ }
+
+ /*------------------------------------------------------------------------*/
+
+ /**
+ * Assigns own enumerable string keyed properties of source objects to the
+ * destination object. Source objects are applied from left to right.
+ * Subsequent sources overwrite property assignments of previous sources.
+ *
+ * **Note:** This method mutates `object` and is loosely based on
+ * [`Object.assign`](https://mdn.io/Object/assign).
+ *
+ * @static
+ * @memberOf _
+ * @since 0.10.0
+ * @category Object
+ * @param {Object} object The destination object.
+ * @param {...Object} [sources] The source objects.
+ * @returns {Object} Returns `object`.
+ * @see _.assignIn
+ * @example
+ *
+ * function Foo() {
+ * this.a = 1;
+ * }
+ *
+ * function Bar() {
+ * this.c = 3;
+ * }
+ *
+ * Foo.prototype.b = 2;
+ * Bar.prototype.d = 4;
+ *
+ * _.assign({ 'a': 0 }, new Foo, new Bar);
+ * // => { 'a': 1, 'c': 3 }
+ */
+ var assign = createAssigner(function(object, source) {
+ if (isPrototype(source) || isArrayLike(source)) {
+ copyObject(source, keys(source), object);
+ return;
+ }
+ for (var key in source) {
+ if (hasOwnProperty.call(source, key)) {
+ assignValue(object, key, source[key]);
+ }
+ }
+ });
+
+ /**
+ * This method is like `_.assign` except that it iterates over own and
+ * inherited source properties.
+ *
+ * **Note:** This method mutates `object`.
+ *
+ * @static
+ * @memberOf _
+ * @since 4.0.0
+ * @alias extend
+ * @category Object
+ * @param {Object} object The destination object.
+ * @param {...Object} [sources] The source objects.
+ * @returns {Object} Returns `object`.
+ * @see _.assign
+ * @example
+ *
+ * function Foo() {
+ * this.a = 1;
+ * }
+ *
+ * function Bar() {
+ * this.c = 3;
+ * }
+ *
+ * Foo.prototype.b = 2;
+ * Bar.prototype.d = 4;
+ *
+ * _.assignIn({ 'a': 0 }, new Foo, new Bar);
+ * // => { 'a': 1, 'b': 2, 'c': 3, 'd': 4 }
+ */
+ var assignIn = createAssigner(function(object, source) {
+ copyObject(source, keysIn(source), object);
+ });
+
+ /**
+ * This method is like `_.assignIn` except that it accepts `customizer`
+ * which is invoked to produce the assigned values. If `customizer` returns
+ * `undefined`, assignment is handled by the method instead. The `customizer`
+ * is invoked with five arguments: (objValue, srcValue, key, object, source).
+ *
+ * **Note:** This method mutates `object`.
+ *
+ * @static
+ * @memberOf _
+ * @since 4.0.0
+ * @alias extendWith
+ * @category Object
+ * @param {Object} object The destination object.
+ * @param {...Object} sources The source objects.
+ * @param {Function} [customizer] The function to customize assigned values.
+ * @returns {Object} Returns `object`.
+ * @see _.assignWith
+ * @example
+ *
+ * function customizer(objValue, srcValue) {
+ * return _.isUndefined(objValue) ? srcValue : objValue;
+ * }
+ *
+ * var defaults = _.partialRight(_.assignInWith, customizer);
+ *
+ * defaults({ 'a': 1 }, { 'b': 2 }, { 'a': 3 });
+ * // => { 'a': 1, 'b': 2 }
+ */
+ var assignInWith = createAssigner(function(object, source, srcIndex, customizer) {
+ copyObject(source, keysIn(source), object, customizer);
+ });
+
+ /**
+ * This method is like `_.assign` except that it accepts `customizer`
+ * which is invoked to produce the assigned values. If `customizer` returns
+ * `undefined`, assignment is handled by the method instead. The `customizer`
+ * is invoked with five arguments: (objValue, srcValue, key, object, source).
+ *
+ * **Note:** This method mutates `object`.
+ *
+ * @static
+ * @memberOf _
+ * @since 4.0.0
+ * @category Object
+ * @param {Object} object The destination object.
+ * @param {...Object} sources The source objects.
+ * @param {Function} [customizer] The function to customize assigned values.
+ * @returns {Object} Returns `object`.
+ * @see _.assignInWith
+ * @example
+ *
+ * function customizer(objValue, srcValue) {
+ * return _.isUndefined(objValue) ? srcValue : objValue;
+ * }
+ *
+ * var defaults = _.partialRight(_.assignWith, customizer);
+ *
+ * defaults({ 'a': 1 }, { 'b': 2 }, { 'a': 3 });
+ * // => { 'a': 1, 'b': 2 }
+ */
+ var assignWith = createAssigner(function(object, source, srcIndex, customizer) {
+ copyObject(source, keys(source), object, customizer);
+ });
+
+ /**
+ * Creates an array of values corresponding to `paths` of `object`.
+ *
+ * @static
+ * @memberOf _
+ * @since 1.0.0
+ * @category Object
+ * @param {Object} object The object to iterate over.
+ * @param {...(string|string[])} [paths] The property paths of elements to pick.
+ * @returns {Array} Returns the picked values.
+ * @example
+ *
+ * var object = { 'a': [{ 'b': { 'c': 3 } }, 4] };
+ *
+ * _.at(object, ['a[0].b.c', 'a[1]']);
+ * // => [3, 4]
+ */
+ var at = flatRest(baseAt);
+
+ /**
+ * Creates an object that inherits from the `prototype` object. If a
+ * `properties` object is given, its own enumerable string keyed properties
+ * are assigned to the created object.
+ *
+ * @static
+ * @memberOf _
+ * @since 2.3.0
+ * @category Object
+ * @param {Object} prototype The object to inherit from.
+ * @param {Object} [properties] The properties to assign to the object.
+ * @returns {Object} Returns the new object.
+ * @example
+ *
+ * function Shape() {
+ * this.x = 0;
+ * this.y = 0;
+ * }
+ *
+ * function Circle() {
+ * Shape.call(this);
+ * }
+ *
+ * Circle.prototype = _.create(Shape.prototype, {
+ * 'constructor': Circle
+ * });
+ *
+ * var circle = new Circle;
+ * circle instanceof Circle;
+ * // => true
+ *
+ * circle instanceof Shape;
+ * // => true
+ */
+ function create(prototype, properties) {
+ var result = baseCreate(prototype);
+ return properties ? baseAssign(result, properties) : result;
+ }
+
+ /**
+ * Assigns own and inherited enumerable string keyed properties of source
+ * objects to the destination object for all destination properties that
+ * resolve to `undefined`. Source objects are applied from left to right.
+ * Once a property is set, additional values of the same property are ignored.
+ *
+ * **Note:** This method mutates `object`.
+ *
+ * @static
+ * @since 0.1.0
+ * @memberOf _
+ * @category Object
+ * @param {Object} object The destination object.
+ * @param {...Object} [sources] The source objects.
+ * @returns {Object} Returns `object`.
+ * @see _.defaultsDeep
+ * @example
+ *
+ * _.defaults({ 'a': 1 }, { 'b': 2 }, { 'a': 3 });
+ * // => { 'a': 1, 'b': 2 }
+ */
+ var defaults = baseRest(function(args) {
+ args.push(undefined, assignInDefaults);
+ return apply(assignInWith, undefined, args);
+ });
+
+ /**
+ * This method is like `_.defaults` except that it recursively assigns
+ * default properties.
+ *
+ * **Note:** This method mutates `object`.
+ *
+ * @static
+ * @memberOf _
+ * @since 3.10.0
+ * @category Object
+ * @param {Object} object The destination object.
+ * @param {...Object} [sources] The source objects.
+ * @returns {Object} Returns `object`.
+ * @see _.defaults
+ * @example
+ *
+ * _.defaultsDeep({ 'a': { 'b': 2 } }, { 'a': { 'b': 1, 'c': 3 } });
+ * // => { 'a': { 'b': 2, 'c': 3 } }
+ */
+ var defaultsDeep = baseRest(function(args) {
+ args.push(undefined, mergeDefaults);
+ return apply(mergeWith, undefined, args);
+ });
+
+ /**
+ * This method is like `_.find` except that it returns the key of the first
+ * element `predicate` returns truthy for instead of the element itself.
+ *
+ * @static
+ * @memberOf _
+ * @since 1.1.0
+ * @category Object
+ * @param {Object} object The object to inspect.
+ * @param {Function} [predicate=_.identity] The function invoked per iteration.
+ * @returns {string|undefined} Returns the key of the matched element,
+ * else `undefined`.
+ * @example
+ *
+ * var users = {
+ * 'barney': { 'age': 36, 'active': true },
+ * 'fred': { 'age': 40, 'active': false },
+ * 'pebbles': { 'age': 1, 'active': true }
+ * };
+ *
+ * _.findKey(users, function(o) { return o.age < 40; });
+ * // => 'barney' (iteration order is not guaranteed)
+ *
+ * // The `_.matches` iteratee shorthand.
+ * _.findKey(users, { 'age': 1, 'active': true });
+ * // => 'pebbles'
+ *
+ * // The `_.matchesProperty` iteratee shorthand.
+ * _.findKey(users, ['active', false]);
+ * // => 'fred'
+ *
+ * // The `_.property` iteratee shorthand.
+ * _.findKey(users, 'active');
+ * // => 'barney'
+ */
+ function findKey(object, predicate) {
+ return baseFindKey(object, getIteratee(predicate, 3), baseForOwn);
+ }
+
+ /**
+ * This method is like `_.findKey` except that it iterates over elements of
+ * a collection in the opposite order.
+ *
+ * @static
+ * @memberOf _
+ * @since 2.0.0
+ * @category Object
+ * @param {Object} object The object to inspect.
+ * @param {Function} [predicate=_.identity] The function invoked per iteration.
+ * @returns {string|undefined} Returns the key of the matched element,
+ * else `undefined`.
+ * @example
+ *
+ * var users = {
+ * 'barney': { 'age': 36, 'active': true },
+ * 'fred': { 'age': 40, 'active': false },
+ * 'pebbles': { 'age': 1, 'active': true }
+ * };
+ *
+ * _.findLastKey(users, function(o) { return o.age < 40; });
+ * // => returns 'pebbles' assuming `_.findKey` returns 'barney'
+ *
+ * // The `_.matches` iteratee shorthand.
+ * _.findLastKey(users, { 'age': 36, 'active': true });
+ * // => 'barney'
+ *
+ * // The `_.matchesProperty` iteratee shorthand.
+ * _.findLastKey(users, ['active', false]);
+ * // => 'fred'
+ *
+ * // The `_.property` iteratee shorthand.
+ * _.findLastKey(users, 'active');
+ * // => 'pebbles'
+ */
+ function findLastKey(object, predicate) {
+ return baseFindKey(object, getIteratee(predicate, 3), baseForOwnRight);
+ }
+
+ /**
+ * Iterates over own and inherited enumerable string keyed properties of an
+ * object and invokes `iteratee` for each property. The iteratee is invoked
+ * with three arguments: (value, key, object). Iteratee functions may exit
+ * iteration early by explicitly returning `false`.
+ *
+ * @static
+ * @memberOf _
+ * @since 0.3.0
+ * @category Object
+ * @param {Object} object The object to iterate over.
+ * @param {Function} [iteratee=_.identity] The function invoked per iteration.
+ * @returns {Object} Returns `object`.
+ * @see _.forInRight
+ * @example
+ *
+ * function Foo() {
+ * this.a = 1;
+ * this.b = 2;
+ * }
+ *
+ * Foo.prototype.c = 3;
+ *
+ * _.forIn(new Foo, function(value, key) {
+ * console.log(key);
+ * });
+ * // => Logs 'a', 'b', then 'c' (iteration order is not guaranteed).
+ */
+ function forIn(object, iteratee) {
+ return object == null
+ ? object
+ : baseFor(object, getIteratee(iteratee, 3), keysIn);
+ }
+
+ /**
+ * This method is like `_.forIn` except that it iterates over properties of
+ * `object` in the opposite order.
+ *
+ * @static
+ * @memberOf _
+ * @since 2.0.0
+ * @category Object
+ * @param {Object} object The object to iterate over.
+ * @param {Function} [iteratee=_.identity] The function invoked per iteration.
+ * @returns {Object} Returns `object`.
+ * @see _.forIn
+ * @example
+ *
+ * function Foo() {
+ * this.a = 1;
+ * this.b = 2;
+ * }
+ *
+ * Foo.prototype.c = 3;
+ *
+ * _.forInRight(new Foo, function(value, key) {
+ * console.log(key);
+ * });
+ * // => Logs 'c', 'b', then 'a' assuming `_.forIn` logs 'a', 'b', then 'c'.
+ */
+ function forInRight(object, iteratee) {
+ return object == null
+ ? object
+ : baseForRight(object, getIteratee(iteratee, 3), keysIn);
+ }
+
+ /**
+ * Iterates over own enumerable string keyed properties of an object and
+ * invokes `iteratee` for each property. The iteratee is invoked with three
+ * arguments: (value, key, object). Iteratee functions may exit iteration
+ * early by explicitly returning `false`.
+ *
+ * @static
+ * @memberOf _
+ * @since 0.3.0
+ * @category Object
+ * @param {Object} object The object to iterate over.
+ * @param {Function} [iteratee=_.identity] The function invoked per iteration.
+ * @returns {Object} Returns `object`.
+ * @see _.forOwnRight
+ * @example
+ *
+ * function Foo() {
+ * this.a = 1;
+ * this.b = 2;
+ * }
+ *
+ * Foo.prototype.c = 3;
+ *
+ * _.forOwn(new Foo, function(value, key) {
+ * console.log(key);
+ * });
+ * // => Logs 'a' then 'b' (iteration order is not guaranteed).
+ */
+ function forOwn(object, iteratee) {
+ return object && baseForOwn(object, getIteratee(iteratee, 3));
+ }
+
+ /**
+ * This method is like `_.forOwn` except that it iterates over properties of
+ * `object` in the opposite order.
+ *
+ * @static
+ * @memberOf _
+ * @since 2.0.0
+ * @category Object
+ * @param {Object} object The object to iterate over.
+ * @param {Function} [iteratee=_.identity] The function invoked per iteration.
+ * @returns {Object} Returns `object`.
+ * @see _.forOwn
+ * @example
+ *
+ * function Foo() {
+ * this.a = 1;
+ * this.b = 2;
+ * }
+ *
+ * Foo.prototype.c = 3;
+ *
+ * _.forOwnRight(new Foo, function(value, key) {
+ * console.log(key);
+ * });
+ * // => Logs 'b' then 'a' assuming `_.forOwn` logs 'a' then 'b'.
+ */
+ function forOwnRight(object, iteratee) {
+ return object && baseForOwnRight(object, getIteratee(iteratee, 3));
+ }
+
+ /**
+ * Creates an array of function property names from own enumerable properties
+ * of `object`.
+ *
+ * @static
+ * @since 0.1.0
+ * @memberOf _
+ * @category Object
+ * @param {Object} object The object to inspect.
+ * @returns {Array} Returns the function names.
+ * @see _.functionsIn
+ * @example
+ *
+ * function Foo() {
+ * this.a = _.constant('a');
+ * this.b = _.constant('b');
+ * }
+ *
+ * Foo.prototype.c = _.constant('c');
+ *
+ * _.functions(new Foo);
+ * // => ['a', 'b']
+ */
+ function functions(object) {
+ return object == null ? [] : baseFunctions(object, keys(object));
+ }
+
+ /**
+ * Creates an array of function property names from own and inherited
+ * enumerable properties of `object`.
+ *
+ * @static
+ * @memberOf _
+ * @since 4.0.0
+ * @category Object
+ * @param {Object} object The object to inspect.
+ * @returns {Array} Returns the function names.
+ * @see _.functions
+ * @example
+ *
+ * function Foo() {
+ * this.a = _.constant('a');
+ * this.b = _.constant('b');
+ * }
+ *
+ * Foo.prototype.c = _.constant('c');
+ *
+ * _.functionsIn(new Foo);
+ * // => ['a', 'b', 'c']
+ */
+ function functionsIn(object) {
+ return object == null ? [] : baseFunctions(object, keysIn(object));
+ }
+
+ /**
+ * Gets the value at `path` of `object`. If the resolved value is
+ * `undefined`, the `defaultValue` is returned in its place.
+ *
+ * @static
+ * @memberOf _
+ * @since 3.7.0
+ * @category Object
+ * @param {Object} object The object to query.
+ * @param {Array|string} path The path of the property to get.
+ * @param {*} [defaultValue] The value returned for `undefined` resolved values.
+ * @returns {*} Returns the resolved value.
+ * @example
+ *
+ * var object = { 'a': [{ 'b': { 'c': 3 } }] };
+ *
+ * _.get(object, 'a[0].b.c');
+ * // => 3
+ *
+ * _.get(object, ['a', '0', 'b', 'c']);
+ * // => 3
+ *
+ * _.get(object, 'a.b.c', 'default');
+ * // => 'default'
+ */
+ function get(object, path, defaultValue) {
+ var result = object == null ? undefined : baseGet(object, path);
+ return result === undefined ? defaultValue : result;
+ }
+
+ /**
+ * Checks if `path` is a direct property of `object`.
+ *
+ * @static
+ * @since 0.1.0
+ * @memberOf _
+ * @category Object
+ * @param {Object} object The object to query.
+ * @param {Array|string} path The path to check.
+ * @returns {boolean} Returns `true` if `path` exists, else `false`.
+ * @example
+ *
+ * var object = { 'a': { 'b': 2 } };
+ * var other = _.create({ 'a': _.create({ 'b': 2 }) });
+ *
+ * _.has(object, 'a');
+ * // => true
+ *
+ * _.has(object, 'a.b');
+ * // => true
+ *
+ * _.has(object, ['a', 'b']);
+ * // => true
+ *
+ * _.has(other, 'a');
+ * // => false
+ */
+ function has(object, path) {
+ return object != null && hasPath(object, path, baseHas);
+ }
+
+ /**
+ * Checks if `path` is a direct or inherited property of `object`.
+ *
+ * @static
+ * @memberOf _
+ * @since 4.0.0
+ * @category Object
+ * @param {Object} object The object to query.
+ * @param {Array|string} path The path to check.
+ * @returns {boolean} Returns `true` if `path` exists, else `false`.
+ * @example
+ *
+ * var object = _.create({ 'a': _.create({ 'b': 2 }) });
+ *
+ * _.hasIn(object, 'a');
+ * // => true
+ *
+ * _.hasIn(object, 'a.b');
+ * // => true
+ *
+ * _.hasIn(object, ['a', 'b']);
+ * // => true
+ *
+ * _.hasIn(object, 'b');
+ * // => false
+ */
+ function hasIn(object, path) {
+ return object != null && hasPath(object, path, baseHasIn);
+ }
+
+ /**
+ * Creates an object composed of the inverted keys and values of `object`.
+ * If `object` contains duplicate values, subsequent values overwrite
+ * property assignments of previous values.
+ *
+ * @static
+ * @memberOf _
+ * @since 0.7.0
+ * @category Object
+ * @param {Object} object The object to invert.
+ * @returns {Object} Returns the new inverted object.
+ * @example
+ *
+ * var object = { 'a': 1, 'b': 2, 'c': 1 };
+ *
+ * _.invert(object);
+ * // => { '1': 'c', '2': 'b' }
+ */
+ var invert = createInverter(function(result, value, key) {
+ result[value] = key;
+ }, constant(identity));
+
+ /**
+ * This method is like `_.invert` except that the inverted object is generated
+ * from the results of running each element of `object` thru `iteratee`. The
+ * corresponding inverted value of each inverted key is an array of keys
+ * responsible for generating the inverted value. The iteratee is invoked
+ * with one argument: (value).
+ *
+ * @static
+ * @memberOf _
+ * @since 4.1.0
+ * @category Object
+ * @param {Object} object The object to invert.
+ * @param {Function} [iteratee=_.identity] The iteratee invoked per element.
+ * @returns {Object} Returns the new inverted object.
+ * @example
+ *
+ * var object = { 'a': 1, 'b': 2, 'c': 1 };
+ *
+ * _.invertBy(object);
+ * // => { '1': ['a', 'c'], '2': ['b'] }
+ *
+ * _.invertBy(object, function(value) {
+ * return 'group' + value;
+ * });
+ * // => { 'group1': ['a', 'c'], 'group2': ['b'] }
+ */
+ var invertBy = createInverter(function(result, value, key) {
+ if (hasOwnProperty.call(result, value)) {
+ result[value].push(key);
+ } else {
+ result[value] = [key];
+ }
+ }, getIteratee);
+
+ /**
+ * Invokes the method at `path` of `object`.
+ *
+ * @static
+ * @memberOf _
+ * @since 4.0.0
+ * @category Object
+ * @param {Object} object The object to query.
+ * @param {Array|string} path The path of the method to invoke.
+ * @param {...*} [args] The arguments to invoke the method with.
+ * @returns {*} Returns the result of the invoked method.
+ * @example
+ *
+ * var object = { 'a': [{ 'b': { 'c': [1, 2, 3, 4] } }] };
+ *
+ * _.invoke(object, 'a[0].b.c.slice', 1, 3);
+ * // => [2, 3]
+ */
+ var invoke = baseRest(baseInvoke);
+
+ /**
+ * Creates an array of the own enumerable property names of `object`.
+ *
+ * **Note:** Non-object values are coerced to objects. See the
+ * [ES spec](http://ecma-international.org/ecma-262/7.0/#sec-object.keys)
+ * for more details.
+ *
+ * @static
+ * @since 0.1.0
+ * @memberOf _
+ * @category Object
+ * @param {Object} object The object to query.
+ * @returns {Array} Returns the array of property names.
+ * @example
+ *
+ * function Foo() {
+ * this.a = 1;
+ * this.b = 2;
+ * }
+ *
+ * Foo.prototype.c = 3;
+ *
+ * _.keys(new Foo);
+ * // => ['a', 'b'] (iteration order is not guaranteed)
+ *
+ * _.keys('hi');
+ * // => ['0', '1']
+ */
+ function keys(object) {
+ return isArrayLike(object) ? arrayLikeKeys(object) : baseKeys(object);
+ }
+
+ /**
+ * Creates an array of the own and inherited enumerable property names of `object`.
+ *
+ * **Note:** Non-object values are coerced to objects.
+ *
+ * @static
+ * @memberOf _
+ * @since 3.0.0
+ * @category Object
+ * @param {Object} object The object to query.
+ * @returns {Array} Returns the array of property names.
+ * @example
+ *
+ * function Foo() {
+ * this.a = 1;
+ * this.b = 2;
+ * }
+ *
+ * Foo.prototype.c = 3;
+ *
+ * _.keysIn(new Foo);
+ * // => ['a', 'b', 'c'] (iteration order is not guaranteed)
+ */
+ function keysIn(object) {
+ return isArrayLike(object) ? arrayLikeKeys(object, true) : baseKeysIn(object);
+ }
+
+ /**
+ * The opposite of `_.mapValues`; this method creates an object with the
+ * same values as `object` and keys generated by running each own enumerable
+ * string keyed property of `object` thru `iteratee`. The iteratee is invoked
+ * with three arguments: (value, key, object).
+ *
+ * @static
+ * @memberOf _
+ * @since 3.8.0
+ * @category Object
+ * @param {Object} object The object to iterate over.
+ * @param {Function} [iteratee=_.identity] The function invoked per iteration.
+ * @returns {Object} Returns the new mapped object.
+ * @see _.mapValues
+ * @example
+ *
+ * _.mapKeys({ 'a': 1, 'b': 2 }, function(value, key) {
+ * return key + value;
+ * });
+ * // => { 'a1': 1, 'b2': 2 }
+ */
+ function mapKeys(object, iteratee) {
+ var result = {};
+ iteratee = getIteratee(iteratee, 3);
+
+ baseForOwn(object, function(value, key, object) {
+ baseAssignValue(result, iteratee(value, key, object), value);
+ });
+ return result;
+ }
+
+ /**
+ * Creates an object with the same keys as `object` and values generated
+ * by running each own enumerable string keyed property of `object` thru
+ * `iteratee`. The iteratee is invoked with three arguments:
+ * (value, key, object).
+ *
+ * @static
+ * @memberOf _
+ * @since 2.4.0
+ * @category Object
+ * @param {Object} object The object to iterate over.
+ * @param {Function} [iteratee=_.identity] The function invoked per iteration.
+ * @returns {Object} Returns the new mapped object.
+ * @see _.mapKeys
+ * @example
+ *
+ * var users = {
+ * 'fred': { 'user': 'fred', 'age': 40 },
+ * 'pebbles': { 'user': 'pebbles', 'age': 1 }
+ * };
+ *
+ * _.mapValues(users, function(o) { return o.age; });
+ * // => { 'fred': 40, 'pebbles': 1 } (iteration order is not guaranteed)
+ *
+ * // The `_.property` iteratee shorthand.
+ * _.mapValues(users, 'age');
+ * // => { 'fred': 40, 'pebbles': 1 } (iteration order is not guaranteed)
+ */
+ function mapValues(object, iteratee) {
+ var result = {};
+ iteratee = getIteratee(iteratee, 3);
+
+ baseForOwn(object, function(value, key, object) {
+ baseAssignValue(result, key, iteratee(value, key, object));
+ });
+ return result;
+ }
+
+ /**
+ * This method is like `_.assign` except that it recursively merges own and
+ * inherited enumerable string keyed properties of source objects into the
+ * destination object. Source properties that resolve to `undefined` are
+ * skipped if a destination value exists. Array and plain object properties
+ * are merged recursively. Other objects and value types are overridden by
+ * assignment. Source objects are applied from left to right. Subsequent
+ * sources overwrite property assignments of previous sources.
+ *
+ * **Note:** This method mutates `object`.
+ *
+ * @static
+ * @memberOf _
+ * @since 0.5.0
+ * @category Object
+ * @param {Object} object The destination object.
+ * @param {...Object} [sources] The source objects.
+ * @returns {Object} Returns `object`.
+ * @example
+ *
+ * var object = {
+ * 'a': [{ 'b': 2 }, { 'd': 4 }]
+ * };
+ *
+ * var other = {
+ * 'a': [{ 'c': 3 }, { 'e': 5 }]
+ * };
+ *
+ * _.merge(object, other);
+ * // => { 'a': [{ 'b': 2, 'c': 3 }, { 'd': 4, 'e': 5 }] }
+ */
+ var merge = createAssigner(function(object, source, srcIndex) {
+ baseMerge(object, source, srcIndex);
+ });
+
+ /**
+ * This method is like `_.merge` except that it accepts `customizer` which
+ * is invoked to produce the merged values of the destination and source
+ * properties. If `customizer` returns `undefined`, merging is handled by the
+ * method instead. The `customizer` is invoked with six arguments:
+ * (objValue, srcValue, key, object, source, stack).
+ *
+ * **Note:** This method mutates `object`.
+ *
+ * @static
+ * @memberOf _
+ * @since 4.0.0
+ * @category Object
+ * @param {Object} object The destination object.
+ * @param {...Object} sources The source objects.
+ * @param {Function} customizer The function to customize assigned values.
+ * @returns {Object} Returns `object`.
+ * @example
+ *
+ * function customizer(objValue, srcValue) {
+ * if (_.isArray(objValue)) {
+ * return objValue.concat(srcValue);
+ * }
+ * }
+ *
+ * var object = { 'a': [1], 'b': [2] };
+ * var other = { 'a': [3], 'b': [4] };
+ *
+ * _.mergeWith(object, other, customizer);
+ * // => { 'a': [1, 3], 'b': [2, 4] }
+ */
+ var mergeWith = createAssigner(function(object, source, srcIndex, customizer) {
+ baseMerge(object, source, srcIndex, customizer);
+ });
+
+ /**
+ * The opposite of `_.pick`; this method creates an object composed of the
+ * own and inherited enumerable string keyed properties of `object` that are
+ * not omitted.
+ *
+ * @static
+ * @since 0.1.0
+ * @memberOf _
+ * @category Object
+ * @param {Object} object The source object.
+ * @param {...(string|string[])} [props] The property identifiers to omit.
+ * @returns {Object} Returns the new object.
+ * @example
+ *
+ * var object = { 'a': 1, 'b': '2', 'c': 3 };
+ *
+ * _.omit(object, ['a', 'c']);
+ * // => { 'b': '2' }
+ */
+ var omit = flatRest(function(object, props) {
+ if (object == null) {
+ return {};
+ }
+ props = arrayMap(props, toKey);
+ return basePick(object, baseDifference(getAllKeysIn(object), props));
+ });
+
+ /**
+ * The opposite of `_.pickBy`; this method creates an object composed of
+ * the own and inherited enumerable string keyed properties of `object` that
+ * `predicate` doesn't return truthy for. The predicate is invoked with two
+ * arguments: (value, key).
+ *
+ * @static
+ * @memberOf _
+ * @since 4.0.0
+ * @category Object
+ * @param {Object} object The source object.
+ * @param {Function} [predicate=_.identity] The function invoked per property.
+ * @returns {Object} Returns the new object.
+ * @example
+ *
+ * var object = { 'a': 1, 'b': '2', 'c': 3 };
+ *
+ * _.omitBy(object, _.isNumber);
+ * // => { 'b': '2' }
+ */
+ function omitBy(object, predicate) {
+ return pickBy(object, negate(getIteratee(predicate)));
+ }
+
+ /**
+ * Creates an object composed of the picked `object` properties.
+ *
+ * @static
+ * @since 0.1.0
+ * @memberOf _
+ * @category Object
+ * @param {Object} object The source object.
+ * @param {...(string|string[])} [props] The property identifiers to pick.
+ * @returns {Object} Returns the new object.
+ * @example
+ *
+ * var object = { 'a': 1, 'b': '2', 'c': 3 };
+ *
+ * _.pick(object, ['a', 'c']);
+ * // => { 'a': 1, 'c': 3 }
+ */
+ var pick = flatRest(function(object, props) {
+ return object == null ? {} : basePick(object, arrayMap(props, toKey));
+ });
+
+ /**
+ * Creates an object composed of the `object` properties `predicate` returns
+ * truthy for. The predicate is invoked with two arguments: (value, key).
+ *
+ * @static
+ * @memberOf _
+ * @since 4.0.0
+ * @category Object
+ * @param {Object} object The source object.
+ * @param {Function} [predicate=_.identity] The function invoked per property.
+ * @returns {Object} Returns the new object.
+ * @example
+ *
+ * var object = { 'a': 1, 'b': '2', 'c': 3 };
+ *
+ * _.pickBy(object, _.isNumber);
+ * // => { 'a': 1, 'c': 3 }
+ */
+ function pickBy(object, predicate) {
+ return object == null ? {} : basePickBy(object, getAllKeysIn(object), getIteratee(predicate));
+ }
+
+ /**
+ * This method is like `_.get` except that if the resolved value is a
+ * function it's invoked with the `this` binding of its parent object and
+ * its result is returned.
+ *
+ * @static
+ * @since 0.1.0
+ * @memberOf _
+ * @category Object
+ * @param {Object} object The object to query.
+ * @param {Array|string} path The path of the property to resolve.
+ * @param {*} [defaultValue] The value returned for `undefined` resolved values.
+ * @returns {*} Returns the resolved value.
+ * @example
+ *
+ * var object = { 'a': [{ 'b': { 'c1': 3, 'c2': _.constant(4) } }] };
+ *
+ * _.result(object, 'a[0].b.c1');
+ * // => 3
+ *
+ * _.result(object, 'a[0].b.c2');
+ * // => 4
+ *
+ * _.result(object, 'a[0].b.c3', 'default');
+ * // => 'default'
+ *
+ * _.result(object, 'a[0].b.c3', _.constant('default'));
+ * // => 'default'
+ */
+ function result(object, path, defaultValue) {
+ path = isKey(path, object) ? [path] : castPath(path);
+
+ var index = -1,
+ length = path.length;
+
+ // Ensure the loop is entered when path is empty.
+ if (!length) {
+ object = undefined;
+ length = 1;
+ }
+ while (++index < length) {
+ var value = object == null ? undefined : object[toKey(path[index])];
+ if (value === undefined) {
+ index = length;
+ value = defaultValue;
+ }
+ object = isFunction(value) ? value.call(object) : value;
+ }
+ return object;
+ }
+
+ /**
+ * Sets the value at `path` of `object`. If a portion of `path` doesn't exist,
+ * it's created. Arrays are created for missing index properties while objects
+ * are created for all other missing properties. Use `_.setWith` to customize
+ * `path` creation.
+ *
+ * **Note:** This method mutates `object`.
+ *
+ * @static
+ * @memberOf _
+ * @since 3.7.0
+ * @category Object
+ * @param {Object} object The object to modify.
+ * @param {Array|string} path The path of the property to set.
+ * @param {*} value The value to set.
+ * @returns {Object} Returns `object`.
+ * @example
+ *
+ * var object = { 'a': [{ 'b': { 'c': 3 } }] };
+ *
+ * _.set(object, 'a[0].b.c', 4);
+ * console.log(object.a[0].b.c);
+ * // => 4
+ *
+ * _.set(object, ['x', '0', 'y', 'z'], 5);
+ * console.log(object.x[0].y.z);
+ * // => 5
+ */
+ function set(object, path, value) {
+ return object == null ? object : baseSet(object, path, value);
+ }
+
+ /**
+ * This method is like `_.set` except that it accepts `customizer` which is
+ * invoked to produce the objects of `path`. If `customizer` returns `undefined`
+ * path creation is handled by the method instead. The `customizer` is invoked
+ * with three arguments: (nsValue, key, nsObject).
+ *
+ * **Note:** This method mutates `object`.
+ *
+ * @static
+ * @memberOf _
+ * @since 4.0.0
+ * @category Object
+ * @param {Object} object The object to modify.
+ * @param {Array|string} path The path of the property to set.
+ * @param {*} value The value to set.
+ * @param {Function} [customizer] The function to customize assigned values.
+ * @returns {Object} Returns `object`.
+ * @example
+ *
+ * var object = {};
+ *
+ * _.setWith(object, '[0][1]', 'a', Object);
+ * // => { '0': { '1': 'a' } }
+ */
+ function setWith(object, path, value, customizer) {
+ customizer = typeof customizer == 'function' ? customizer : undefined;
+ return object == null ? object : baseSet(object, path, value, customizer);
+ }
+
+ /**
+ * Creates an array of own enumerable string keyed-value pairs for `object`
+ * which can be consumed by `_.fromPairs`. If `object` is a map or set, its
+ * entries are returned.
+ *
+ * @static
+ * @memberOf _
+ * @since 4.0.0
+ * @alias entries
+ * @category Object
+ * @param {Object} object The object to query.
+ * @returns {Array} Returns the key-value pairs.
+ * @example
+ *
+ * function Foo() {
+ * this.a = 1;
+ * this.b = 2;
+ * }
+ *
+ * Foo.prototype.c = 3;
+ *
+ * _.toPairs(new Foo);
+ * // => [['a', 1], ['b', 2]] (iteration order is not guaranteed)
+ */
+ var toPairs = createToPairs(keys);
+
+ /**
+ * Creates an array of own and inherited enumerable string keyed-value pairs
+ * for `object` which can be consumed by `_.fromPairs`. If `object` is a map
+ * or set, its entries are returned.
+ *
+ * @static
+ * @memberOf _
+ * @since 4.0.0
+ * @alias entriesIn
+ * @category Object
+ * @param {Object} object The object to query.
+ * @returns {Array} Returns the key-value pairs.
+ * @example
+ *
+ * function Foo() {
+ * this.a = 1;
+ * this.b = 2;
+ * }
+ *
+ * Foo.prototype.c = 3;
+ *
+ * _.toPairsIn(new Foo);
+ * // => [['a', 1], ['b', 2], ['c', 3]] (iteration order is not guaranteed)
+ */
+ var toPairsIn = createToPairs(keysIn);
+
+ /**
+ * An alternative to `_.reduce`; this method transforms `object` to a new
+ * `accumulator` object which is the result of running each of its own
+ * enumerable string keyed properties thru `iteratee`, with each invocation
+ * potentially mutating the `accumulator` object. If `accumulator` is not
+ * provided, a new object with the same `[[Prototype]]` will be used. The
+ * iteratee is invoked with four arguments: (accumulator, value, key, object).
+ * Iteratee functions may exit iteration early by explicitly returning `false`.
+ *
+ * @static
+ * @memberOf _
+ * @since 1.3.0
+ * @category Object
+ * @param {Object} object The object to iterate over.
+ * @param {Function} [iteratee=_.identity] The function invoked per iteration.
+ * @param {*} [accumulator] The custom accumulator value.
+ * @returns {*} Returns the accumulated value.
+ * @example
+ *
+ * _.transform([2, 3, 4], function(result, n) {
+ * result.push(n *= n);
+ * return n % 2 == 0;
+ * }, []);
+ * // => [4, 9]
+ *
+ * _.transform({ 'a': 1, 'b': 2, 'c': 1 }, function(result, value, key) {
+ * (result[value] || (result[value] = [])).push(key);
+ * }, {});
+ * // => { '1': ['a', 'c'], '2': ['b'] }
+ */
+ function transform(object, iteratee, accumulator) {
+ var isArr = isArray(object),
+ isArrLike = isArr || isBuffer(object) || isTypedArray(object);
+
+ iteratee = getIteratee(iteratee, 4);
+ if (accumulator == null) {
+ var Ctor = object && object.constructor;
+ if (isArrLike) {
+ accumulator = isArr ? new Ctor : [];
+ }
+ else if (isObject(object)) {
+ accumulator = isFunction(Ctor) ? baseCreate(getPrototype(object)) : {};
+ }
+ else {
+ accumulator = {};
+ }
+ }
+ (isArrLike ? arrayEach : baseForOwn)(object, function(value, index, object) {
+ return iteratee(accumulator, value, index, object);
+ });
+ return accumulator;
+ }
+
+ /**
+ * Removes the property at `path` of `object`.
+ *
+ * **Note:** This method mutates `object`.
+ *
+ * @static
+ * @memberOf _
+ * @since 4.0.0
+ * @category Object
+ * @param {Object} object The object to modify.
+ * @param {Array|string} path The path of the property to unset.
+ * @returns {boolean} Returns `true` if the property is deleted, else `false`.
+ * @example
+ *
+ * var object = { 'a': [{ 'b': { 'c': 7 } }] };
+ * _.unset(object, 'a[0].b.c');
+ * // => true
+ *
+ * console.log(object);
+ * // => { 'a': [{ 'b': {} }] };
+ *
+ * _.unset(object, ['a', '0', 'b', 'c']);
+ * // => true
+ *
+ * console.log(object);
+ * // => { 'a': [{ 'b': {} }] };
+ */
+ function unset(object, path) {
+ return object == null ? true : baseUnset(object, path);
+ }
+
+ /**
+ * This method is like `_.set` except that accepts `updater` to produce the
+ * value to set. Use `_.updateWith` to customize `path` creation. The `updater`
+ * is invoked with one argument: (value).
+ *
+ * **Note:** This method mutates `object`.
+ *
+ * @static
+ * @memberOf _
+ * @since 4.6.0
+ * @category Object
+ * @param {Object} object The object to modify.
+ * @param {Array|string} path The path of the property to set.
+ * @param {Function} updater The function to produce the updated value.
+ * @returns {Object} Returns `object`.
+ * @example
+ *
+ * var object = { 'a': [{ 'b': { 'c': 3 } }] };
+ *
+ * _.update(object, 'a[0].b.c', function(n) { return n * n; });
+ * console.log(object.a[0].b.c);
+ * // => 9
+ *
+ * _.update(object, 'x[0].y.z', function(n) { return n ? n + 1 : 0; });
+ * console.log(object.x[0].y.z);
+ * // => 0
+ */
+ function update(object, path, updater) {
+ return object == null ? object : baseUpdate(object, path, castFunction(updater));
+ }
+
+ /**
+ * This method is like `_.update` except that it accepts `customizer` which is
+ * invoked to produce the objects of `path`. If `customizer` returns `undefined`
+ * path creation is handled by the method instead. The `customizer` is invoked
+ * with three arguments: (nsValue, key, nsObject).
+ *
+ * **Note:** This method mutates `object`.
+ *
+ * @static
+ * @memberOf _
+ * @since 4.6.0
+ * @category Object
+ * @param {Object} object The object to modify.
+ * @param {Array|string} path The path of the property to set.
+ * @param {Function} updater The function to produce the updated value.
+ * @param {Function} [customizer] The function to customize assigned values.
+ * @returns {Object} Returns `object`.
+ * @example
+ *
+ * var object = {};
+ *
+ * _.updateWith(object, '[0][1]', _.constant('a'), Object);
+ * // => { '0': { '1': 'a' } }
+ */
+ function updateWith(object, path, updater, customizer) {
+ customizer = typeof customizer == 'function' ? customizer : undefined;
+ return object == null ? object : baseUpdate(object, path, castFunction(updater), customizer);
+ }
+
+ /**
+ * Creates an array of the own enumerable string keyed property values of `object`.
+ *
+ * **Note:** Non-object values are coerced to objects.
+ *
+ * @static
+ * @since 0.1.0
+ * @memberOf _
+ * @category Object
+ * @param {Object} object The object to query.
+ * @returns {Array} Returns the array of property values.
+ * @example
+ *
+ * function Foo() {
+ * this.a = 1;
+ * this.b = 2;
+ * }
+ *
+ * Foo.prototype.c = 3;
+ *
+ * _.values(new Foo);
+ * // => [1, 2] (iteration order is not guaranteed)
+ *
+ * _.values('hi');
+ * // => ['h', 'i']
+ */
+ function values(object) {
+ return object ? baseValues(object, keys(object)) : [];
+ }
+
+ /**
+ * Creates an array of the own and inherited enumerable string keyed property
+ * values of `object`.
+ *
+ * **Note:** Non-object values are coerced to objects.
+ *
+ * @static
+ * @memberOf _
+ * @since 3.0.0
+ * @category Object
+ * @param {Object} object The object to query.
+ * @returns {Array} Returns the array of property values.
+ * @example
+ *
+ * function Foo() {
+ * this.a = 1;
+ * this.b = 2;
+ * }
+ *
+ * Foo.prototype.c = 3;
+ *
+ * _.valuesIn(new Foo);
+ * // => [1, 2, 3] (iteration order is not guaranteed)
+ */
+ function valuesIn(object) {
+ return object == null ? [] : baseValues(object, keysIn(object));
+ }
+
+ /*------------------------------------------------------------------------*/
+
+ /**
+ * Clamps `number` within the inclusive `lower` and `upper` bounds.
+ *
+ * @static
+ * @memberOf _
+ * @since 4.0.0
+ * @category Number
+ * @param {number} number The number to clamp.
+ * @param {number} [lower] The lower bound.
+ * @param {number} upper The upper bound.
+ * @returns {number} Returns the clamped number.
+ * @example
+ *
+ * _.clamp(-10, -5, 5);
+ * // => -5
+ *
+ * _.clamp(10, -5, 5);
+ * // => 5
+ */
+ function clamp(number, lower, upper) {
+ if (upper === undefined) {
+ upper = lower;
+ lower = undefined;
+ }
+ if (upper !== undefined) {
+ upper = toNumber(upper);
+ upper = upper === upper ? upper : 0;
+ }
+ if (lower !== undefined) {
+ lower = toNumber(lower);
+ lower = lower === lower ? lower : 0;
+ }
+ return baseClamp(toNumber(number), lower, upper);
+ }
+
+ /**
+ * Checks if `n` is between `start` and up to, but not including, `end`. If
+ * `end` is not specified, it's set to `start` with `start` then set to `0`.
+ * If `start` is greater than `end` the params are swapped to support
+ * negative ranges.
+ *
+ * @static
+ * @memberOf _
+ * @since 3.3.0
+ * @category Number
+ * @param {number} number The number to check.
+ * @param {number} [start=0] The start of the range.
+ * @param {number} end The end of the range.
+ * @returns {boolean} Returns `true` if `number` is in the range, else `false`.
+ * @see _.range, _.rangeRight
+ * @example
+ *
+ * _.inRange(3, 2, 4);
+ * // => true
+ *
+ * _.inRange(4, 8);
+ * // => true
+ *
+ * _.inRange(4, 2);
+ * // => false
+ *
+ * _.inRange(2, 2);
+ * // => false
+ *
+ * _.inRange(1.2, 2);
+ * // => true
+ *
+ * _.inRange(5.2, 4);
+ * // => false
+ *
+ * _.inRange(-3, -2, -6);
+ * // => true
+ */
+ function inRange(number, start, end) {
+ start = toFinite(start);
+ if (end === undefined) {
+ end = start;
+ start = 0;
+ } else {
+ end = toFinite(end);
+ }
+ number = toNumber(number);
+ return baseInRange(number, start, end);
+ }
+
+ /**
+ * Produces a random number between the inclusive `lower` and `upper` bounds.
+ * If only one argument is provided a number between `0` and the given number
+ * is returned. If `floating` is `true`, or either `lower` or `upper` are
+ * floats, a floating-point number is returned instead of an integer.
+ *
+ * **Note:** JavaScript follows the IEEE-754 standard for resolving
+ * floating-point values which can produce unexpected results.
+ *
+ * @static
+ * @memberOf _
+ * @since 0.7.0
+ * @category Number
+ * @param {number} [lower=0] The lower bound.
+ * @param {number} [upper=1] The upper bound.
+ * @param {boolean} [floating] Specify returning a floating-point number.
+ * @returns {number} Returns the random number.
+ * @example
+ *
+ * _.random(0, 5);
+ * // => an integer between 0 and 5
+ *
+ * _.random(5);
+ * // => also an integer between 0 and 5
+ *
+ * _.random(5, true);
+ * // => a floating-point number between 0 and 5
+ *
+ * _.random(1.2, 5.2);
+ * // => a floating-point number between 1.2 and 5.2
+ */
+ function random(lower, upper, floating) {
+ if (floating && typeof floating != 'boolean' && isIterateeCall(lower, upper, floating)) {
+ upper = floating = undefined;
+ }
+ if (floating === undefined) {
+ if (typeof upper == 'boolean') {
+ floating = upper;
+ upper = undefined;
+ }
+ else if (typeof lower == 'boolean') {
+ floating = lower;
+ lower = undefined;
+ }
+ }
+ if (lower === undefined && upper === undefined) {
+ lower = 0;
+ upper = 1;
+ }
+ else {
+ lower = toFinite(lower);
+ if (upper === undefined) {
+ upper = lower;
+ lower = 0;
+ } else {
+ upper = toFinite(upper);
+ }
+ }
+ if (lower > upper) {
+ var temp = lower;
+ lower = upper;
+ upper = temp;
+ }
+ if (floating || lower % 1 || upper % 1) {
+ var rand = nativeRandom();
+ return nativeMin(lower + (rand * (upper - lower + freeParseFloat('1e-' + ((rand + '').length - 1)))), upper);
+ }
+ return baseRandom(lower, upper);
+ }
+
+ /*------------------------------------------------------------------------*/
+
+ /**
+ * Converts `string` to [camel case](https://en.wikipedia.org/wiki/CamelCase).
+ *
+ * @static
+ * @memberOf _
+ * @since 3.0.0
+ * @category String
+ * @param {string} [string=''] The string to convert.
+ * @returns {string} Returns the camel cased string.
+ * @example
+ *
+ * _.camelCase('Foo Bar');
+ * // => 'fooBar'
+ *
+ * _.camelCase('--foo-bar--');
+ * // => 'fooBar'
+ *
+ * _.camelCase('__FOO_BAR__');
+ * // => 'fooBar'
+ */
+ var camelCase = createCompounder(function(result, word, index) {
+ word = word.toLowerCase();
+ return result + (index ? capitalize(word) : word);
+ });
+
+ /**
+ * Converts the first character of `string` to upper case and the remaining
+ * to lower case.
+ *
+ * @static
+ * @memberOf _
+ * @since 3.0.0
+ * @category String
+ * @param {string} [string=''] The string to capitalize.
+ * @returns {string} Returns the capitalized string.
+ * @example
+ *
+ * _.capitalize('FRED');
+ * // => 'Fred'
+ */
+ function capitalize(string) {
+ return upperFirst(toString(string).toLowerCase());
+ }
+
+ /**
+ * Deburrs `string` by converting
+ * [Latin-1 Supplement](https://en.wikipedia.org/wiki/Latin-1_Supplement_(Unicode_block)#Character_table)
+ * and [Latin Extended-A](https://en.wikipedia.org/wiki/Latin_Extended-A)
+ * letters to basic Latin letters and removing
+ * [combining diacritical marks](https://en.wikipedia.org/wiki/Combining_Diacritical_Marks).
+ *
+ * @static
+ * @memberOf _
+ * @since 3.0.0
+ * @category String
+ * @param {string} [string=''] The string to deburr.
+ * @returns {string} Returns the deburred string.
+ * @example
+ *
+ * _.deburr('déjà vu');
+ * // => 'deja vu'
+ */
+ function deburr(string) {
+ string = toString(string);
+ return string && string.replace(reLatin, deburrLetter).replace(reComboMark, '');
+ }
+
+ /**
+ * Checks if `string` ends with the given target string.
+ *
+ * @static
+ * @memberOf _
+ * @since 3.0.0
+ * @category String
+ * @param {string} [string=''] The string to inspect.
+ * @param {string} [target] The string to search for.
+ * @param {number} [position=string.length] The position to search up to.
+ * @returns {boolean} Returns `true` if `string` ends with `target`,
+ * else `false`.
+ * @example
+ *
+ * _.endsWith('abc', 'c');
+ * // => true
+ *
+ * _.endsWith('abc', 'b');
+ * // => false
+ *
+ * _.endsWith('abc', 'b', 2);
+ * // => true
+ */
+ function endsWith(string, target, position) {
+ string = toString(string);
+ target = baseToString(target);
+
+ var length = string.length;
+ position = position === undefined
+ ? length
+ : baseClamp(toInteger(position), 0, length);
+
+ var end = position;
+ position -= target.length;
+ return position >= 0 && string.slice(position, end) == target;
+ }
+
+ /**
+ * Converts the characters "&", "<", ">", '"', and "'" in `string` to their
+ * corresponding HTML entities.
+ *
+ * **Note:** No other characters are escaped. To escape additional
+ * characters use a third-party library like [_he_](https://mths.be/he).
+ *
+ * Though the ">" character is escaped for symmetry, characters like
+ * ">" and "/" don't need escaping in HTML and have no special meaning
+ * unless they're part of a tag or unquoted attribute value. See
+ * [Mathias Bynens's article](https://mathiasbynens.be/notes/ambiguous-ampersands)
+ * (under "semi-related fun fact") for more details.
+ *
+ * When working with HTML you should always
+ * [quote attribute values](http://wonko.com/post/html-escaping) to reduce
+ * XSS vectors.
+ *
+ * @static
+ * @since 0.1.0
+ * @memberOf _
+ * @category String
+ * @param {string} [string=''] The string to escape.
+ * @returns {string} Returns the escaped string.
+ * @example
+ *
+ * _.escape('fred, barney, & pebbles');
+ * // => 'fred, barney, &amp; pebbles'
+ */
+ function escape(string) {
+ string = toString(string);
+ return (string && reHasUnescapedHtml.test(string))
+ ? string.replace(reUnescapedHtml, escapeHtmlChar)
+ : string;
+ }
+
+ /**
+ * Escapes the `RegExp` special characters "^", "$", "\", ".", "*", "+",
+ * "?", "(", ")", "[", "]", "{", "}", and "|" in `string`.
+ *
+ * @static
+ * @memberOf _
+ * @since 3.0.0
+ * @category String
+ * @param {string} [string=''] The string to escape.
+ * @returns {string} Returns the escaped string.
+ * @example
+ *
+ * _.escapeRegExp('[lodash](https://lodash.com/)');
+ * // => '\[lodash\]\(https://lodash\.com/\)'
+ */
+ function escapeRegExp(string) {
+ string = toString(string);
+ return (string && reHasRegExpChar.test(string))
+ ? string.replace(reRegExpChar, '\\$&')
+ : string;
+ }
+
+ /**
+ * Converts `string` to
+ * [kebab case](https://en.wikipedia.org/wiki/Letter_case#Special_case_styles).
+ *
+ * @static
+ * @memberOf _
+ * @since 3.0.0
+ * @category String
+ * @param {string} [string=''] The string to convert.
+ * @returns {string} Returns the kebab cased string.
+ * @example
+ *
+ * _.kebabCase('Foo Bar');
+ * // => 'foo-bar'
+ *
+ * _.kebabCase('fooBar');
+ * // => 'foo-bar'
+ *
+ * _.kebabCase('__FOO_BAR__');
+ * // => 'foo-bar'
+ */
+ var kebabCase = createCompounder(function(result, word, index) {
+ return result + (index ? '-' : '') + word.toLowerCase();
+ });
+
+ /**
+ * Converts `string`, as space separated words, to lower case.
+ *
+ * @static
+ * @memberOf _
+ * @since 4.0.0
+ * @category String
+ * @param {string} [string=''] The string to convert.
+ * @returns {string} Returns the lower cased string.
+ * @example
+ *
+ * _.lowerCase('--Foo-Bar--');
+ * // => 'foo bar'
+ *
+ * _.lowerCase('fooBar');
+ * // => 'foo bar'
+ *
+ * _.lowerCase('__FOO_BAR__');
+ * // => 'foo bar'
+ */
+ var lowerCase = createCompounder(function(result, word, index) {
+ return result + (index ? ' ' : '') + word.toLowerCase();
+ });
+
+ /**
+ * Converts the first character of `string` to lower case.
+ *
+ * @static
+ * @memberOf _
+ * @since 4.0.0
+ * @category String
+ * @param {string} [string=''] The string to convert.
+ * @returns {string} Returns the converted string.
+ * @example
+ *
+ * _.lowerFirst('Fred');
+ * // => 'fred'
+ *
+ * _.lowerFirst('FRED');
+ * // => 'fRED'
+ */
+ var lowerFirst = createCaseFirst('toLowerCase');
+
+ /**
+ * Pads `string` on the left and right sides if it's shorter than `length`.
+ * Padding characters are truncated if they can't be evenly divided by `length`.
+ *
+ * @static
+ * @memberOf _
+ * @since 3.0.0
+ * @category String
+ * @param {string} [string=''] The string to pad.
+ * @param {number} [length=0] The padding length.
+ * @param {string} [chars=' '] The string used as padding.
+ * @returns {string} Returns the padded string.
+ * @example
+ *
+ * _.pad('abc', 8);
+ * // => ' abc '
+ *
+ * _.pad('abc', 8, '_-');
+ * // => '_-abc_-_'
+ *
+ * _.pad('abc', 3);
+ * // => 'abc'
+ */
+ function pad(string, length, chars) {
+ string = toString(string);
+ length = toInteger(length);
+
+ var strLength = length ? stringSize(string) : 0;
+ if (!length || strLength >= length) {
+ return string;
+ }
+ var mid = (length - strLength) / 2;
+ return (
+ createPadding(nativeFloor(mid), chars) +
+ string +
+ createPadding(nativeCeil(mid), chars)
+ );
+ }
+
+ /**
+ * Pads `string` on the right side if it's shorter than `length`. Padding
+ * characters are truncated if they exceed `length`.
+ *
+ * @static
+ * @memberOf _
+ * @since 4.0.0
+ * @category String
+ * @param {string} [string=''] The string to pad.
+ * @param {number} [length=0] The padding length.
+ * @param {string} [chars=' '] The string used as padding.
+ * @returns {string} Returns the padded string.
+ * @example
+ *
+ * _.padEnd('abc', 6);
+ * // => 'abc '
+ *
+ * _.padEnd('abc', 6, '_-');
+ * // => 'abc_-_'
+ *
+ * _.padEnd('abc', 3);
+ * // => 'abc'
+ */
+ function padEnd(string, length, chars) {
+ string = toString(string);
+ length = toInteger(length);
+
+ var strLength = length ? stringSize(string) : 0;
+ return (length && strLength < length)
+ ? (string + createPadding(length - strLength, chars))
+ : string;
+ }
+
+ /**
+ * Pads `string` on the left side if it's shorter than `length`. Padding
+ * characters are truncated if they exceed `length`.
+ *
+ * @static
+ * @memberOf _
+ * @since 4.0.0
+ * @category String
+ * @param {string} [string=''] The string to pad.
+ * @param {number} [length=0] The padding length.
+ * @param {string} [chars=' '] The string used as padding.
+ * @returns {string} Returns the padded string.
+ * @example
+ *
+ * _.padStart('abc', 6);
+ * // => ' abc'
+ *
+ * _.padStart('abc', 6, '_-');
+ * // => '_-_abc'
+ *
+ * _.padStart('abc', 3);
+ * // => 'abc'
+ */
+ function padStart(string, length, chars) {
+ string = toString(string);
+ length = toInteger(length);
+
+ var strLength = length ? stringSize(string) : 0;
+ return (length && strLength < length)
+ ? (createPadding(length - strLength, chars) + string)
+ : string;
+ }
+
+ /**
+ * Converts `string` to an integer of the specified radix. If `radix` is
+ * `undefined` or `0`, a `radix` of `10` is used unless `value` is a
+ * hexadecimal, in which case a `radix` of `16` is used.
+ *
+ * **Note:** This method aligns with the
+ * [ES5 implementation](https://es5.github.io/#x15.1.2.2) of `parseInt`.
+ *
+ * @static
+ * @memberOf _
+ * @since 1.1.0
+ * @category String
+ * @param {string} string The string to convert.
+ * @param {number} [radix=10] The radix to interpret `value` by.
+ * @param- {Object} [guard] Enables use as an iteratee for methods like `_.map`.
+ * @returns {number} Returns the converted integer.
+ * @example
+ *
+ * _.parseInt('08');
+ * // => 8
+ *
+ * _.map(['6', '08', '10'], _.parseInt);
+ * // => [6, 8, 10]
+ */
+ function parseInt(string, radix, guard) {
+ if (guard || radix == null) {
+ radix = 0;
+ } else if (radix) {
+ radix = +radix;
+ }
+ return nativeParseInt(toString(string).replace(reTrimStart, ''), radix || 0);
+ }
+
+ /**
+ * Repeats the given string `n` times.
+ *
+ * @static
+ * @memberOf _
+ * @since 3.0.0
+ * @category String
+ * @param {string} [string=''] The string to repeat.
+ * @param {number} [n=1] The number of times to repeat the string.
+ * @param- {Object} [guard] Enables use as an iteratee for methods like `_.map`.
+ * @returns {string} Returns the repeated string.
+ * @example
+ *
+ * _.repeat('*', 3);
+ * // => '***'
+ *
+ * _.repeat('abc', 2);
+ * // => 'abcabc'
+ *
+ * _.repeat('abc', 0);
+ * // => ''
+ */
+ function repeat(string, n, guard) {
+ if ((guard ? isIterateeCall(string, n, guard) : n === undefined)) {
+ n = 1;
+ } else {
+ n = toInteger(n);
+ }
+ return baseRepeat(toString(string), n);
+ }
+
+ /**
+ * Replaces matches for `pattern` in `string` with `replacement`.
+ *
+ * **Note:** This method is based on
+ * [`String#replace`](https://mdn.io/String/replace).
+ *
+ * @static
+ * @memberOf _
+ * @since 4.0.0
+ * @category String
+ * @param {string} [string=''] The string to modify.
+ * @param {RegExp|string} pattern The pattern to replace.
+ * @param {Function|string} replacement The match replacement.
+ * @returns {string} Returns the modified string.
+ * @example
+ *
+ * _.replace('Hi Fred', 'Fred', 'Barney');
+ * // => 'Hi Barney'
+ */
+ function replace() {
+ var args = arguments,
+ string = toString(args[0]);
+
+ return args.length < 3 ? string : string.replace(args[1], args[2]);
+ }
+
+ /**
+ * Converts `string` to
+ * [snake case](https://en.wikipedia.org/wiki/Snake_case).
+ *
+ * @static
+ * @memberOf _
+ * @since 3.0.0
+ * @category String
+ * @param {string} [string=''] The string to convert.
+ * @returns {string} Returns the snake cased string.
+ * @example
+ *
+ * _.snakeCase('Foo Bar');
+ * // => 'foo_bar'
+ *
+ * _.snakeCase('fooBar');
+ * // => 'foo_bar'
+ *
+ * _.snakeCase('--FOO-BAR--');
+ * // => 'foo_bar'
+ */
+ var snakeCase = createCompounder(function(result, word, index) {
+ return result + (index ? '_' : '') + word.toLowerCase();
+ });
+
+ /**
+ * Splits `string` by `separator`.
+ *
+ * **Note:** This method is based on
+ * [`String#split`](https://mdn.io/String/split).
+ *
+ * @static
+ * @memberOf _
+ * @since 4.0.0
+ * @category String
+ * @param {string} [string=''] The string to split.
+ * @param {RegExp|string} separator The separator pattern to split by.
+ * @param {number} [limit] The length to truncate results to.
+ * @returns {Array} Returns the string segments.
+ * @example
+ *
+ * _.split('a-b-c', '-', 2);
+ * // => ['a', 'b']
+ */
+ function split(string, separator, limit) {
+ if (limit && typeof limit != 'number' && isIterateeCall(string, separator, limit)) {
+ separator = limit = undefined;
+ }
+ limit = limit === undefined ? MAX_ARRAY_LENGTH : limit >>> 0;
+ if (!limit) {
+ return [];
+ }
+ string = toString(string);
+ if (string && (
+ typeof separator == 'string' ||
+ (separator != null && !isRegExp(separator))
+ )) {
+ separator = baseToString(separator);
+ if (!separator && hasUnicode(string)) {
+ return castSlice(stringToArray(string), 0, limit);
+ }
+ }
+ return string.split(separator, limit);
+ }
+
+ /**
+ * Converts `string` to
+ * [start case](https://en.wikipedia.org/wiki/Letter_case#Stylistic_or_specialised_usage).
+ *
+ * @static
+ * @memberOf _
+ * @since 3.1.0
+ * @category String
+ * @param {string} [string=''] The string to convert.
+ * @returns {string} Returns the start cased string.
+ * @example
+ *
+ * _.startCase('--foo-bar--');
+ * // => 'Foo Bar'
+ *
+ * _.startCase('fooBar');
+ * // => 'Foo Bar'
+ *
+ * _.startCase('__FOO_BAR__');
+ * // => 'FOO BAR'
+ */
+ var startCase = createCompounder(function(result, word, index) {
+ return result + (index ? ' ' : '') + upperFirst(word);
+ });
+
+ /**
+ * Checks if `string` starts with the given target string.
+ *
+ * @static
+ * @memberOf _
+ * @since 3.0.0
+ * @category String
+ * @param {string} [string=''] The string to inspect.
+ * @param {string} [target] The string to search for.
+ * @param {number} [position=0] The position to search from.
+ * @returns {boolean} Returns `true` if `string` starts with `target`,
+ * else `false`.
+ * @example
+ *
+ * _.startsWith('abc', 'a');
+ * // => true
+ *
+ * _.startsWith('abc', 'b');
+ * // => false
+ *
+ * _.startsWith('abc', 'b', 1);
+ * // => true
+ */
+ function startsWith(string, target, position) {
+ string = toString(string);
+ position = baseClamp(toInteger(position), 0, string.length);
+ target = baseToString(target);
+ return string.slice(position, position + target.length) == target;
+ }
+
+ /**
+ * Creates a compiled template function that can interpolate data properties
+ * in "interpolate" delimiters, HTML-escape interpolated data properties in
+ * "escape" delimiters, and execute JavaScript in "evaluate" delimiters. Data
+ * properties may be accessed as free variables in the template. If a setting
+ * object is given, it takes precedence over `_.templateSettings` values.
+ *
+ * **Note:** In the development build `_.template` utilizes
+ * [sourceURLs](http://www.html5rocks.com/en/tutorials/developertools/sourcemaps/#toc-sourceurl)
+ * for easier debugging.
+ *
+ * For more information on precompiling templates see
+ * [lodash's custom builds documentation](https://lodash.com/custom-builds).
+ *
+ * For more information on Chrome extension sandboxes see
+ * [Chrome's extensions documentation](https://developer.chrome.com/extensions/sandboxingEval).
+ *
+ * @static
+ * @since 0.1.0
+ * @memberOf _
+ * @category String
+ * @param {string} [string=''] The template string.
+ * @param {Object} [options={}] The options object.
+ * @param {RegExp} [options.escape=_.templateSettings.escape]
+ * The HTML "escape" delimiter.
+ * @param {RegExp} [options.evaluate=_.templateSettings.evaluate]
+ * The "evaluate" delimiter.
+ * @param {Object} [options.imports=_.templateSettings.imports]
+ * An object to import into the template as free variables.
+ * @param {RegExp} [options.interpolate=_.templateSettings.interpolate]
+ * The "interpolate" delimiter.
+ * @param {string} [options.sourceURL='lodash.templateSources[n]']
+ * The sourceURL of the compiled template.
+ * @param {string} [options.variable='obj']
+ * The data object variable name.
+ * @param- {Object} [guard] Enables use as an iteratee for methods like `_.map`.
+ * @returns {Function} Returns the compiled template function.
+ * @example
+ *
+ * // Use the "interpolate" delimiter to create a compiled template.
+ * var compiled = _.template('hello <%= user %>!');
+ * compiled({ 'user': 'fred' });
+ * // => 'hello fred!'
+ *
+ * // Use the HTML "escape" delimiter to escape data property values.
+ * var compiled = _.template('<b><%- value %></b>');
+ * compiled({ 'value': '<script>' });
+ * // => '<b>&lt;script&gt;</b>'
+ *
+ * // Use the "evaluate" delimiter to execute JavaScript and generate HTML.
+ * var compiled = _.template('<% _.forEach(users, function(user) { %><li><%- user %></li><% }); %>');
+ * compiled({ 'users': ['fred', 'barney'] });
+ * // => '<li>fred</li><li>barney</li>'
+ *
+ * // Use the internal `print` function in "evaluate" delimiters.
+ * var compiled = _.template('<% print("hello " + user); %>!');
+ * compiled({ 'user': 'barney' });
+ * // => 'hello barney!'
+ *
+ * // Use the ES template literal delimiter as an "interpolate" delimiter.
+ * // Disable support by replacing the "interpolate" delimiter.
+ * var compiled = _.template('hello ${ user }!');
+ * compiled({ 'user': 'pebbles' });
+ * // => 'hello pebbles!'
+ *
+ * // Use backslashes to treat delimiters as plain text.
+ * var compiled = _.template('<%= "\\<%- value %\\>" %>');
+ * compiled({ 'value': 'ignored' });
+ * // => '<%- value %>'
+ *
+ * // Use the `imports` option to import `jQuery` as `jq`.
+ * var text = '<% jq.each(users, function(user) { %><li><%- user %></li><% }); %>';
+ * var compiled = _.template(text, { 'imports': { 'jq': jQuery } });
+ * compiled({ 'users': ['fred', 'barney'] });
+ * // => '<li>fred</li><li>barney</li>'
+ *
+ * // Use the `sourceURL` option to specify a custom sourceURL for the template.
+ * var compiled = _.template('hello <%= user %>!', { 'sourceURL': '/basic/greeting.jst' });
+ * compiled(data);
+ * // => Find the source of "greeting.jst" under the Sources tab or Resources panel of the web inspector.
+ *
+ * // Use the `variable` option to ensure a with-statement isn't used in the compiled template.
+ * var compiled = _.template('hi <%= data.user %>!', { 'variable': 'data' });
+ * compiled.source;
+ * // => function(data) {
+ * // var __t, __p = '';
+ * // __p += 'hi ' + ((__t = ( data.user )) == null ? '' : __t) + '!';
+ * // return __p;
+ * // }
+ *
+ * // Use custom template delimiters.
+ * _.templateSettings.interpolate = /{{([\s\S]+?)}}/g;
+ * var compiled = _.template('hello {{ user }}!');
+ * compiled({ 'user': 'mustache' });
+ * // => 'hello mustache!'
+ *
+ * // Use the `source` property to inline compiled templates for meaningful
+ * // line numbers in error messages and stack traces.
+ * fs.writeFileSync(path.join(process.cwd(), 'jst.js'), '\
+ * var JST = {\
+ * "main": ' + _.template(mainText).source + '\
+ * };\
+ * ');
+ */
+ function template(string, options, guard) {
+ // Based on John Resig's `tmpl` implementation
+ // (http://ejohn.org/blog/javascript-micro-templating/)
+ // and Laura Doktorova's doT.js (https://github.com/olado/doT).
+ var settings = lodash.templateSettings;
+
+ if (guard && isIterateeCall(string, options, guard)) {
+ options = undefined;
+ }
+ string = toString(string);
+ options = assignInWith({}, options, settings, assignInDefaults);
+
+ var imports = assignInWith({}, options.imports, settings.imports, assignInDefaults),
+ importsKeys = keys(imports),
+ importsValues = baseValues(imports, importsKeys);
+
+ var isEscaping,
+ isEvaluating,
+ index = 0,
+ interpolate = options.interpolate || reNoMatch,
+ source = "__p += '";
+
+ // Compile the regexp to match each delimiter.
+ var reDelimiters = RegExp(
+ (options.escape || reNoMatch).source + '|' +
+ interpolate.source + '|' +
+ (interpolate === reInterpolate ? reEsTemplate : reNoMatch).source + '|' +
+ (options.evaluate || reNoMatch).source + '|$'
+ , 'g');
+
+ // Use a sourceURL for easier debugging.
+ var sourceURL = '//# sourceURL=' +
+ ('sourceURL' in options
+ ? options.sourceURL
+ : ('lodash.templateSources[' + (++templateCounter) + ']')
+ ) + '\n';
+
+ string.replace(reDelimiters, function(match, escapeValue, interpolateValue, esTemplateValue, evaluateValue, offset) {
+ interpolateValue || (interpolateValue = esTemplateValue);
+
+ // Escape characters that can't be included in string literals.
+ source += string.slice(index, offset).replace(reUnescapedString, escapeStringChar);
+
+ // Replace delimiters with snippets.
+ if (escapeValue) {
+ isEscaping = true;
+ source += "' +\n__e(" + escapeValue + ") +\n'";
+ }
+ if (evaluateValue) {
+ isEvaluating = true;
+ source += "';\n" + evaluateValue + ";\n__p += '";
+ }
+ if (interpolateValue) {
+ source += "' +\n((__t = (" + interpolateValue + ")) == null ? '' : __t) +\n'";
+ }
+ index = offset + match.length;
+
+ // The JS engine embedded in Adobe products needs `match` returned in
+ // order to produce the correct `offset` value.
+ return match;
+ });
+
+ source += "';\n";
+
+ // If `variable` is not specified wrap a with-statement around the generated
+ // code to add the data object to the top of the scope chain.
+ var variable = options.variable;
+ if (!variable) {
+ source = 'with (obj) {\n' + source + '\n}\n';
+ }
+ // Cleanup code by stripping empty strings.
+ source = (isEvaluating ? source.replace(reEmptyStringLeading, '') : source)
+ .replace(reEmptyStringMiddle, '$1')
+ .replace(reEmptyStringTrailing, '$1;');
+
+ // Frame code as the function body.
+ source = 'function(' + (variable || 'obj') + ') {\n' +
+ (variable
+ ? ''
+ : 'obj || (obj = {});\n'
+ ) +
+ "var __t, __p = ''" +
+ (isEscaping
+ ? ', __e = _.escape'
+ : ''
+ ) +
+ (isEvaluating
+ ? ', __j = Array.prototype.join;\n' +
+ "function print() { __p += __j.call(arguments, '') }\n"
+ : ';\n'
+ ) +
+ source +
+ 'return __p\n}';
+
+ var result = attempt(function() {
+ return Function(importsKeys, sourceURL + 'return ' + source)
+ .apply(undefined, importsValues);
+ });
+
+ // Provide the compiled function's source by its `toString` method or
+ // the `source` property as a convenience for inlining compiled templates.
+ result.source = source;
+ if (isError(result)) {
+ throw result;
+ }
+ return result;
+ }
+
+ /**
+ * Converts `string`, as a whole, to lower case just like
+ * [String#toLowerCase](https://mdn.io/toLowerCase).
+ *
+ * @static
+ * @memberOf _
+ * @since 4.0.0
+ * @category String
+ * @param {string} [string=''] The string to convert.
+ * @returns {string} Returns the lower cased string.
+ * @example
+ *
+ * _.toLower('--Foo-Bar--');
+ * // => '--foo-bar--'
+ *
+ * _.toLower('fooBar');
+ * // => 'foobar'
+ *
+ * _.toLower('__FOO_BAR__');
+ * // => '__foo_bar__'
+ */
+ function toLower(value) {
+ return toString(value).toLowerCase();
+ }
+
+ /**
+ * Converts `string`, as a whole, to upper case just like
+ * [String#toUpperCase](https://mdn.io/toUpperCase).
+ *
+ * @static
+ * @memberOf _
+ * @since 4.0.0
+ * @category String
+ * @param {string} [string=''] The string to convert.
+ * @returns {string} Returns the upper cased string.
+ * @example
+ *
+ * _.toUpper('--foo-bar--');
+ * // => '--FOO-BAR--'
+ *
+ * _.toUpper('fooBar');
+ * // => 'FOOBAR'
+ *
+ * _.toUpper('__foo_bar__');
+ * // => '__FOO_BAR__'
+ */
+ function toUpper(value) {
+ return toString(value).toUpperCase();
+ }
+
+ /**
+ * Removes leading and trailing whitespace or specified characters from `string`.
+ *
+ * @static
+ * @memberOf _
+ * @since 3.0.0
+ * @category String
+ * @param {string} [string=''] The string to trim.
+ * @param {string} [chars=whitespace] The characters to trim.
+ * @param- {Object} [guard] Enables use as an iteratee for methods like `_.map`.
+ * @returns {string} Returns the trimmed string.
+ * @example
+ *
+ * _.trim(' abc ');
+ * // => 'abc'
+ *
+ * _.trim('-_-abc-_-', '_-');
+ * // => 'abc'
+ *
+ * _.map([' foo ', ' bar '], _.trim);
+ * // => ['foo', 'bar']
+ */
+ function trim(string, chars, guard) {
+ string = toString(string);
+ if (string && (guard || chars === undefined)) {
+ return string.replace(reTrim, '');
+ }
+ if (!string || !(chars = baseToString(chars))) {
+ return string;
+ }
+ var strSymbols = stringToArray(string),
+ chrSymbols = stringToArray(chars),
+ start = charsStartIndex(strSymbols, chrSymbols),
+ end = charsEndIndex(strSymbols, chrSymbols) + 1;
+
+ return castSlice(strSymbols, start, end).join('');
+ }
+
+ /**
+ * Removes trailing whitespace or specified characters from `string`.
+ *
+ * @static
+ * @memberOf _
+ * @since 4.0.0
+ * @category String
+ * @param {string} [string=''] The string to trim.
+ * @param {string} [chars=whitespace] The characters to trim.
+ * @param- {Object} [guard] Enables use as an iteratee for methods like `_.map`.
+ * @returns {string} Returns the trimmed string.
+ * @example
+ *
+ * _.trimEnd(' abc ');
+ * // => ' abc'
+ *
+ * _.trimEnd('-_-abc-_-', '_-');
+ * // => '-_-abc'
+ */
+ function trimEnd(string, chars, guard) {
+ string = toString(string);
+ if (string && (guard || chars === undefined)) {
+ return string.replace(reTrimEnd, '');
+ }
+ if (!string || !(chars = baseToString(chars))) {
+ return string;
+ }
+ var strSymbols = stringToArray(string),
+ end = charsEndIndex(strSymbols, stringToArray(chars)) + 1;
+
+ return castSlice(strSymbols, 0, end).join('');
+ }
+
+ /**
+ * Removes leading whitespace or specified characters from `string`.
+ *
+ * @static
+ * @memberOf _
+ * @since 4.0.0
+ * @category String
+ * @param {string} [string=''] The string to trim.
+ * @param {string} [chars=whitespace] The characters to trim.
+ * @param- {Object} [guard] Enables use as an iteratee for methods like `_.map`.
+ * @returns {string} Returns the trimmed string.
+ * @example
+ *
+ * _.trimStart(' abc ');
+ * // => 'abc '
+ *
+ * _.trimStart('-_-abc-_-', '_-');
+ * // => 'abc-_-'
+ */
+ function trimStart(string, chars, guard) {
+ string = toString(string);
+ if (string && (guard || chars === undefined)) {
+ return string.replace(reTrimStart, '');
+ }
+ if (!string || !(chars = baseToString(chars))) {
+ return string;
+ }
+ var strSymbols = stringToArray(string),
+ start = charsStartIndex(strSymbols, stringToArray(chars));
+
+ return castSlice(strSymbols, start).join('');
+ }
+
+ /**
+ * Truncates `string` if it's longer than the given maximum string length.
+ * The last characters of the truncated string are replaced with the omission
+ * string which defaults to "...".
+ *
+ * @static
+ * @memberOf _
+ * @since 4.0.0
+ * @category String
+ * @param {string} [string=''] The string to truncate.
+ * @param {Object} [options={}] The options object.
+ * @param {number} [options.length=30] The maximum string length.
+ * @param {string} [options.omission='...'] The string to indicate text is omitted.
+ * @param {RegExp|string} [options.separator] The separator pattern to truncate to.
+ * @returns {string} Returns the truncated string.
+ * @example
+ *
+ * _.truncate('hi-diddly-ho there, neighborino');
+ * // => 'hi-diddly-ho there, neighbo...'
+ *
+ * _.truncate('hi-diddly-ho there, neighborino', {
+ * 'length': 24,
+ * 'separator': ' '
+ * });
+ * // => 'hi-diddly-ho there,...'
+ *
+ * _.truncate('hi-diddly-ho there, neighborino', {
+ * 'length': 24,
+ * 'separator': /,? +/
+ * });
+ * // => 'hi-diddly-ho there...'
+ *
+ * _.truncate('hi-diddly-ho there, neighborino', {
+ * 'omission': ' [...]'
+ * });
+ * // => 'hi-diddly-ho there, neig [...]'
+ */
+ function truncate(string, options) {
+ var length = DEFAULT_TRUNC_LENGTH,
+ omission = DEFAULT_TRUNC_OMISSION;
+
+ if (isObject(options)) {
+ var separator = 'separator' in options ? options.separator : separator;
+ length = 'length' in options ? toInteger(options.length) : length;
+ omission = 'omission' in options ? baseToString(options.omission) : omission;
+ }
+ string = toString(string);
+
+ var strLength = string.length;
+ if (hasUnicode(string)) {
+ var strSymbols = stringToArray(string);
+ strLength = strSymbols.length;
+ }
+ if (length >= strLength) {
+ return string;
+ }
+ var end = length - stringSize(omission);
+ if (end < 1) {
+ return omission;
+ }
+ var result = strSymbols
+ ? castSlice(strSymbols, 0, end).join('')
+ : string.slice(0, end);
+
+ if (separator === undefined) {
+ return result + omission;
+ }
+ if (strSymbols) {
+ end += (result.length - end);
+ }
+ if (isRegExp(separator)) {
+ if (string.slice(end).search(separator)) {
+ var match,
+ substring = result;
+
+ if (!separator.global) {
+ separator = RegExp(separator.source, toString(reFlags.exec(separator)) + 'g');
+ }
+ separator.lastIndex = 0;
+ while ((match = separator.exec(substring))) {
+ var newEnd = match.index;
+ }
+ result = result.slice(0, newEnd === undefined ? end : newEnd);
+ }
+ } else if (string.indexOf(baseToString(separator), end) != end) {
+ var index = result.lastIndexOf(separator);
+ if (index > -1) {
+ result = result.slice(0, index);
+ }
+ }
+ return result + omission;
+ }
+
+ /**
+ * The inverse of `_.escape`; this method converts the HTML entities
+ * `&amp;`, `&lt;`, `&gt;`, `&quot;`, and `&#39;` in `string` to
+ * their corresponding characters.
+ *
+ * **Note:** No other HTML entities are unescaped. To unescape additional
+ * HTML entities use a third-party library like [_he_](https://mths.be/he).
+ *
+ * @static
+ * @memberOf _
+ * @since 0.6.0
+ * @category String
+ * @param {string} [string=''] The string to unescape.
+ * @returns {string} Returns the unescaped string.
+ * @example
+ *
+ * _.unescape('fred, barney, &amp; pebbles');
+ * // => 'fred, barney, & pebbles'
+ */
+ function unescape(string) {
+ string = toString(string);
+ return (string && reHasEscapedHtml.test(string))
+ ? string.replace(reEscapedHtml, unescapeHtmlChar)
+ : string;
+ }
+
+ /**
+ * Converts `string`, as space separated words, to upper case.
+ *
+ * @static
+ * @memberOf _
+ * @since 4.0.0
+ * @category String
+ * @param {string} [string=''] The string to convert.
+ * @returns {string} Returns the upper cased string.
+ * @example
+ *
+ * _.upperCase('--foo-bar');
+ * // => 'FOO BAR'
+ *
+ * _.upperCase('fooBar');
+ * // => 'FOO BAR'
+ *
+ * _.upperCase('__foo_bar__');
+ * // => 'FOO BAR'
+ */
+ var upperCase = createCompounder(function(result, word, index) {
+ return result + (index ? ' ' : '') + word.toUpperCase();
+ });
+
+ /**
+ * Converts the first character of `string` to upper case.
+ *
+ * @static
+ * @memberOf _
+ * @since 4.0.0
+ * @category String
+ * @param {string} [string=''] The string to convert.
+ * @returns {string} Returns the converted string.
+ * @example
+ *
+ * _.upperFirst('fred');
+ * // => 'Fred'
+ *
+ * _.upperFirst('FRED');
+ * // => 'FRED'
+ */
+ var upperFirst = createCaseFirst('toUpperCase');
+
+ /**
+ * Splits `string` into an array of its words.
+ *
+ * @static
+ * @memberOf _
+ * @since 3.0.0
+ * @category String
+ * @param {string} [string=''] The string to inspect.
+ * @param {RegExp|string} [pattern] The pattern to match words.
+ * @param- {Object} [guard] Enables use as an iteratee for methods like `_.map`.
+ * @returns {Array} Returns the words of `string`.
+ * @example
+ *
+ * _.words('fred, barney, & pebbles');
+ * // => ['fred', 'barney', 'pebbles']
+ *
+ * _.words('fred, barney, & pebbles', /[^, ]+/g);
+ * // => ['fred', 'barney', '&', 'pebbles']
+ */
+ function words(string, pattern, guard) {
+ string = toString(string);
+ pattern = guard ? undefined : pattern;
+
+ if (pattern === undefined) {
+ return hasUnicodeWord(string) ? unicodeWords(string) : asciiWords(string);
+ }
+ return string.match(pattern) || [];
+ }
+
+ /*------------------------------------------------------------------------*/
+
+ /**
+ * Attempts to invoke `func`, returning either the result or the caught error
+ * object. Any additional arguments are provided to `func` when it's invoked.
+ *
+ * @static
+ * @memberOf _
+ * @since 3.0.0
+ * @category Util
+ * @param {Function} func The function to attempt.
+ * @param {...*} [args] The arguments to invoke `func` with.
+ * @returns {*} Returns the `func` result or error object.
+ * @example
+ *
+ * // Avoid throwing errors for invalid selectors.
+ * var elements = _.attempt(function(selector) {
+ * return document.querySelectorAll(selector);
+ * }, '>_>');
+ *
+ * if (_.isError(elements)) {
+ * elements = [];
+ * }
+ */
+ var attempt = baseRest(function(func, args) {
+ try {
+ return apply(func, undefined, args);
+ } catch (e) {
+ return isError(e) ? e : new Error(e);
+ }
+ });
+
+ /**
+ * Binds methods of an object to the object itself, overwriting the existing
+ * method.
+ *
+ * **Note:** This method doesn't set the "length" property of bound functions.
+ *
+ * @static
+ * @since 0.1.0
+ * @memberOf _
+ * @category Util
+ * @param {Object} object The object to bind and assign the bound methods to.
+ * @param {...(string|string[])} methodNames The object method names to bind.
+ * @returns {Object} Returns `object`.
+ * @example
+ *
+ * var view = {
+ * 'label': 'docs',
+ * 'click': function() {
+ * console.log('clicked ' + this.label);
+ * }
+ * };
+ *
+ * _.bindAll(view, ['click']);
+ * jQuery(element).on('click', view.click);
+ * // => Logs 'clicked docs' when clicked.
+ */
+ var bindAll = flatRest(function(object, methodNames) {
+ arrayEach(methodNames, function(key) {
+ key = toKey(key);
+ baseAssignValue(object, key, bind(object[key], object));
+ });
+ return object;
+ });
+
+ /**
+ * Creates a function that iterates over `pairs` and invokes the corresponding
+ * function of the first predicate to return truthy. The predicate-function
+ * pairs are invoked with the `this` binding and arguments of the created
+ * function.
+ *
+ * @static
+ * @memberOf _
+ * @since 4.0.0
+ * @category Util
+ * @param {Array} pairs The predicate-function pairs.
+ * @returns {Function} Returns the new composite function.
+ * @example
+ *
+ * var func = _.cond([
+ * [_.matches({ 'a': 1 }), _.constant('matches A')],
+ * [_.conforms({ 'b': _.isNumber }), _.constant('matches B')],
+ * [_.stubTrue, _.constant('no match')]
+ * ]);
+ *
+ * func({ 'a': 1, 'b': 2 });
+ * // => 'matches A'
+ *
+ * func({ 'a': 0, 'b': 1 });
+ * // => 'matches B'
+ *
+ * func({ 'a': '1', 'b': '2' });
+ * // => 'no match'
+ */
+ function cond(pairs) {
+ var length = pairs ? pairs.length : 0,
+ toIteratee = getIteratee();
+
+ pairs = !length ? [] : arrayMap(pairs, function(pair) {
+ if (typeof pair[1] != 'function') {
+ throw new TypeError(FUNC_ERROR_TEXT);
+ }
+ return [toIteratee(pair[0]), pair[1]];
+ });
+
+ return baseRest(function(args) {
+ var index = -1;
+ while (++index < length) {
+ var pair = pairs[index];
+ if (apply(pair[0], this, args)) {
+ return apply(pair[1], this, args);
+ }
+ }
+ });
+ }
+
+ /**
+ * Creates a function that invokes the predicate properties of `source` with
+ * the corresponding property values of a given object, returning `true` if
+ * all predicates return truthy, else `false`.
+ *
+ * **Note:** The created function is equivalent to `_.conformsTo` with
+ * `source` partially applied.
+ *
+ * @static
+ * @memberOf _
+ * @since 4.0.0
+ * @category Util
+ * @param {Object} source The object of property predicates to conform to.
+ * @returns {Function} Returns the new spec function.
+ * @example
+ *
+ * var objects = [
+ * { 'a': 2, 'b': 1 },
+ * { 'a': 1, 'b': 2 }
+ * ];
+ *
+ * _.filter(objects, _.conforms({ 'b': function(n) { return n > 1; } }));
+ * // => [{ 'a': 1, 'b': 2 }]
+ */
+ function conforms(source) {
+ return baseConforms(baseClone(source, true));
+ }
+
+ /**
+ * Creates a function that returns `value`.
+ *
+ * @static
+ * @memberOf _
+ * @since 2.4.0
+ * @category Util
+ * @param {*} value The value to return from the new function.
+ * @returns {Function} Returns the new constant function.
+ * @example
+ *
+ * var objects = _.times(2, _.constant({ 'a': 1 }));
+ *
+ * console.log(objects);
+ * // => [{ 'a': 1 }, { 'a': 1 }]
+ *
+ * console.log(objects[0] === objects[1]);
+ * // => true
+ */
+ function constant(value) {
+ return function() {
+ return value;
+ };
+ }
+
+ /**
+ * Checks `value` to determine whether a default value should be returned in
+ * its place. The `defaultValue` is returned if `value` is `NaN`, `null`,
+ * or `undefined`.
+ *
+ * @static
+ * @memberOf _
+ * @since 4.14.0
+ * @category Util
+ * @param {*} value The value to check.
+ * @param {*} defaultValue The default value.
+ * @returns {*} Returns the resolved value.
+ * @example
+ *
+ * _.defaultTo(1, 10);
+ * // => 1
+ *
+ * _.defaultTo(undefined, 10);
+ * // => 10
+ */
+ function defaultTo(value, defaultValue) {
+ return (value == null || value !== value) ? defaultValue : value;
+ }
+
+ /**
+ * Creates a function that returns the result of invoking the given functions
+ * with the `this` binding of the created function, where each successive
+ * invocation is supplied the return value of the previous.
+ *
+ * @static
+ * @memberOf _
+ * @since 3.0.0
+ * @category Util
+ * @param {...(Function|Function[])} [funcs] The functions to invoke.
+ * @returns {Function} Returns the new composite function.
+ * @see _.flowRight
+ * @example
+ *
+ * function square(n) {
+ * return n * n;
+ * }
+ *
+ * var addSquare = _.flow([_.add, square]);
+ * addSquare(1, 2);
+ * // => 9
+ */
+ var flow = createFlow();
+
+ /**
+ * This method is like `_.flow` except that it creates a function that
+ * invokes the given functions from right to left.
+ *
+ * @static
+ * @since 3.0.0
+ * @memberOf _
+ * @category Util
+ * @param {...(Function|Function[])} [funcs] The functions to invoke.
+ * @returns {Function} Returns the new composite function.
+ * @see _.flow
+ * @example
+ *
+ * function square(n) {
+ * return n * n;
+ * }
+ *
+ * var addSquare = _.flowRight([square, _.add]);
+ * addSquare(1, 2);
+ * // => 9
+ */
+ var flowRight = createFlow(true);
+
+ /**
+ * This method returns the first argument it receives.
+ *
+ * @static
+ * @since 0.1.0
+ * @memberOf _
+ * @category Util
+ * @param {*} value Any value.
+ * @returns {*} Returns `value`.
+ * @example
+ *
+ * var object = { 'a': 1 };
+ *
+ * console.log(_.identity(object) === object);
+ * // => true
+ */
+ function identity(value) {
+ return value;
+ }
+
+ /**
+ * Creates a function that invokes `func` with the arguments of the created
+ * function. If `func` is a property name, the created function returns the
+ * property value for a given element. If `func` is an array or object, the
+ * created function returns `true` for elements that contain the equivalent
+ * source properties, otherwise it returns `false`.
+ *
+ * @static
+ * @since 4.0.0
+ * @memberOf _
+ * @category Util
+ * @param {*} [func=_.identity] The value to convert to a callback.
+ * @returns {Function} Returns the callback.
+ * @example
+ *
+ * var users = [
+ * { 'user': 'barney', 'age': 36, 'active': true },
+ * { 'user': 'fred', 'age': 40, 'active': false }
+ * ];
+ *
+ * // The `_.matches` iteratee shorthand.
+ * _.filter(users, _.iteratee({ 'user': 'barney', 'active': true }));
+ * // => [{ 'user': 'barney', 'age': 36, 'active': true }]
+ *
+ * // The `_.matchesProperty` iteratee shorthand.
+ * _.filter(users, _.iteratee(['user', 'fred']));
+ * // => [{ 'user': 'fred', 'age': 40 }]
+ *
+ * // The `_.property` iteratee shorthand.
+ * _.map(users, _.iteratee('user'));
+ * // => ['barney', 'fred']
+ *
+ * // Create custom iteratee shorthands.
+ * _.iteratee = _.wrap(_.iteratee, function(iteratee, func) {
+ * return !_.isRegExp(func) ? iteratee(func) : function(string) {
+ * return func.test(string);
+ * };
+ * });
+ *
+ * _.filter(['abc', 'def'], /ef/);
+ * // => ['def']
+ */
+ function iteratee(func) {
+ return baseIteratee(typeof func == 'function' ? func : baseClone(func, true));
+ }
+
+ /**
+ * Creates a function that performs a partial deep comparison between a given
+ * object and `source`, returning `true` if the given object has equivalent
+ * property values, else `false`.
+ *
+ * **Note:** The created function is equivalent to `_.isMatch` with `source`
+ * partially applied.
+ *
+ * Partial comparisons will match empty array and empty object `source`
+ * values against any array or object value, respectively. See `_.isEqual`
+ * for a list of supported value comparisons.
+ *
+ * @static
+ * @memberOf _
+ * @since 3.0.0
+ * @category Util
+ * @param {Object} source The object of property values to match.
+ * @returns {Function} Returns the new spec function.
+ * @example
+ *
+ * var objects = [
+ * { 'a': 1, 'b': 2, 'c': 3 },
+ * { 'a': 4, 'b': 5, 'c': 6 }
+ * ];
+ *
+ * _.filter(objects, _.matches({ 'a': 4, 'c': 6 }));
+ * // => [{ 'a': 4, 'b': 5, 'c': 6 }]
+ */
+ function matches(source) {
+ return baseMatches(baseClone(source, true));
+ }
+
+ /**
+ * Creates a function that performs a partial deep comparison between the
+ * value at `path` of a given object to `srcValue`, returning `true` if the
+ * object value is equivalent, else `false`.
+ *
+ * **Note:** Partial comparisons will match empty array and empty object
+ * `srcValue` values against any array or object value, respectively. See
+ * `_.isEqual` for a list of supported value comparisons.
+ *
+ * @static
+ * @memberOf _
+ * @since 3.2.0
+ * @category Util
+ * @param {Array|string} path The path of the property to get.
+ * @param {*} srcValue The value to match.
+ * @returns {Function} Returns the new spec function.
+ * @example
+ *
+ * var objects = [
+ * { 'a': 1, 'b': 2, 'c': 3 },
+ * { 'a': 4, 'b': 5, 'c': 6 }
+ * ];
+ *
+ * _.find(objects, _.matchesProperty('a', 4));
+ * // => { 'a': 4, 'b': 5, 'c': 6 }
+ */
+ function matchesProperty(path, srcValue) {
+ return baseMatchesProperty(path, baseClone(srcValue, true));
+ }
+
+ /**
+ * Creates a function that invokes the method at `path` of a given object.
+ * Any additional arguments are provided to the invoked method.
+ *
+ * @static
+ * @memberOf _
+ * @since 3.7.0
+ * @category Util
+ * @param {Array|string} path The path of the method to invoke.
+ * @param {...*} [args] The arguments to invoke the method with.
+ * @returns {Function} Returns the new invoker function.
+ * @example
+ *
+ * var objects = [
+ * { 'a': { 'b': _.constant(2) } },
+ * { 'a': { 'b': _.constant(1) } }
+ * ];
+ *
+ * _.map(objects, _.method('a.b'));
+ * // => [2, 1]
+ *
+ * _.map(objects, _.method(['a', 'b']));
+ * // => [2, 1]
+ */
+ var method = baseRest(function(path, args) {
+ return function(object) {
+ return baseInvoke(object, path, args);
+ };
+ });
+
+ /**
+ * The opposite of `_.method`; this method creates a function that invokes
+ * the method at a given path of `object`. Any additional arguments are
+ * provided to the invoked method.
+ *
+ * @static
+ * @memberOf _
+ * @since 3.7.0
+ * @category Util
+ * @param {Object} object The object to query.
+ * @param {...*} [args] The arguments to invoke the method with.
+ * @returns {Function} Returns the new invoker function.
+ * @example
+ *
+ * var array = _.times(3, _.constant),
+ * object = { 'a': array, 'b': array, 'c': array };
+ *
+ * _.map(['a[2]', 'c[0]'], _.methodOf(object));
+ * // => [2, 0]
+ *
+ * _.map([['a', '2'], ['c', '0']], _.methodOf(object));
+ * // => [2, 0]
+ */
+ var methodOf = baseRest(function(object, args) {
+ return function(path) {
+ return baseInvoke(object, path, args);
+ };
+ });
+
+ /**
+ * Adds all own enumerable string keyed function properties of a source
+ * object to the destination object. If `object` is a function, then methods
+ * are added to its prototype as well.
+ *
+ * **Note:** Use `_.runInContext` to create a pristine `lodash` function to
+ * avoid conflicts caused by modifying the original.
+ *
+ * @static
+ * @since 0.1.0
+ * @memberOf _
+ * @category Util
+ * @param {Function|Object} [object=lodash] The destination object.
+ * @param {Object} source The object of functions to add.
+ * @param {Object} [options={}] The options object.
+ * @param {boolean} [options.chain=true] Specify whether mixins are chainable.
+ * @returns {Function|Object} Returns `object`.
+ * @example
+ *
+ * function vowels(string) {
+ * return _.filter(string, function(v) {
+ * return /[aeiou]/i.test(v);
+ * });
+ * }
+ *
+ * _.mixin({ 'vowels': vowels });
+ * _.vowels('fred');
+ * // => ['e']
+ *
+ * _('fred').vowels().value();
+ * // => ['e']
+ *
+ * _.mixin({ 'vowels': vowels }, { 'chain': false });
+ * _('fred').vowels();
+ * // => ['e']
+ */
+ function mixin(object, source, options) {
+ var props = keys(source),
+ methodNames = baseFunctions(source, props);
+
+ if (options == null &&
+ !(isObject(source) && (methodNames.length || !props.length))) {
+ options = source;
+ source = object;
+ object = this;
+ methodNames = baseFunctions(source, keys(source));
+ }
+ var chain = !(isObject(options) && 'chain' in options) || !!options.chain,
+ isFunc = isFunction(object);
+
+ arrayEach(methodNames, function(methodName) {
+ var func = source[methodName];
+ object[methodName] = func;
+ if (isFunc) {
+ object.prototype[methodName] = function() {
+ var chainAll = this.__chain__;
+ if (chain || chainAll) {
+ var result = object(this.__wrapped__),
+ actions = result.__actions__ = copyArray(this.__actions__);
+
+ actions.push({ 'func': func, 'args': arguments, 'thisArg': object });
+ result.__chain__ = chainAll;
+ return result;
+ }
+ return func.apply(object, arrayPush([this.value()], arguments));
+ };
+ }
+ });
+
+ return object;
+ }
+
+ /**
+ * Reverts the `_` variable to its previous value and returns a reference to
+ * the `lodash` function.
+ *
+ * @static
+ * @since 0.1.0
+ * @memberOf _
+ * @category Util
+ * @returns {Function} Returns the `lodash` function.
+ * @example
+ *
+ * var lodash = _.noConflict();
+ */
+ function noConflict() {
+ if (root._ === this) {
+ root._ = oldDash;
+ }
+ return this;
+ }
+
+ /**
+ * This method returns `undefined`.
+ *
+ * @static
+ * @memberOf _
+ * @since 2.3.0
+ * @category Util
+ * @example
+ *
+ * _.times(2, _.noop);
+ * // => [undefined, undefined]
+ */
+ function noop() {
+ // No operation performed.
+ }
+
+ /**
+ * Creates a function that gets the argument at index `n`. If `n` is negative,
+ * the nth argument from the end is returned.
+ *
+ * @static
+ * @memberOf _
+ * @since 4.0.0
+ * @category Util
+ * @param {number} [n=0] The index of the argument to return.
+ * @returns {Function} Returns the new pass-thru function.
+ * @example
+ *
+ * var func = _.nthArg(1);
+ * func('a', 'b', 'c', 'd');
+ * // => 'b'
+ *
+ * var func = _.nthArg(-2);
+ * func('a', 'b', 'c', 'd');
+ * // => 'c'
+ */
+ function nthArg(n) {
+ n = toInteger(n);
+ return baseRest(function(args) {
+ return baseNth(args, n);
+ });
+ }
+
+ /**
+ * Creates a function that invokes `iteratees` with the arguments it receives
+ * and returns their results.
+ *
+ * @static
+ * @memberOf _
+ * @since 4.0.0
+ * @category Util
+ * @param {...(Function|Function[])} [iteratees=[_.identity]]
+ * The iteratees to invoke.
+ * @returns {Function} Returns the new function.
+ * @example
+ *
+ * var func = _.over([Math.max, Math.min]);
+ *
+ * func(1, 2, 3, 4);
+ * // => [4, 1]
+ */
+ var over = createOver(arrayMap);
+
+ /**
+ * Creates a function that checks if **all** of the `predicates` return
+ * truthy when invoked with the arguments it receives.
+ *
+ * @static
+ * @memberOf _
+ * @since 4.0.0
+ * @category Util
+ * @param {...(Function|Function[])} [predicates=[_.identity]]
+ * The predicates to check.
+ * @returns {Function} Returns the new function.
+ * @example
+ *
+ * var func = _.overEvery([Boolean, isFinite]);
+ *
+ * func('1');
+ * // => true
+ *
+ * func(null);
+ * // => false
+ *
+ * func(NaN);
+ * // => false
+ */
+ var overEvery = createOver(arrayEvery);
+
+ /**
+ * Creates a function that checks if **any** of the `predicates` return
+ * truthy when invoked with the arguments it receives.
+ *
+ * @static
+ * @memberOf _
+ * @since 4.0.0
+ * @category Util
+ * @param {...(Function|Function[])} [predicates=[_.identity]]
+ * The predicates to check.
+ * @returns {Function} Returns the new function.
+ * @example
+ *
+ * var func = _.overSome([Boolean, isFinite]);
+ *
+ * func('1');
+ * // => true
+ *
+ * func(null);
+ * // => true
+ *
+ * func(NaN);
+ * // => false
+ */
+ var overSome = createOver(arraySome);
+
+ /**
+ * Creates a function that returns the value at `path` of a given object.
+ *
+ * @static
+ * @memberOf _
+ * @since 2.4.0
+ * @category Util
+ * @param {Array|string} path The path of the property to get.
+ * @returns {Function} Returns the new accessor function.
+ * @example
+ *
+ * var objects = [
+ * { 'a': { 'b': 2 } },
+ * { 'a': { 'b': 1 } }
+ * ];
+ *
+ * _.map(objects, _.property('a.b'));
+ * // => [2, 1]
+ *
+ * _.map(_.sortBy(objects, _.property(['a', 'b'])), 'a.b');
+ * // => [1, 2]
+ */
+ function property(path) {
+ return isKey(path) ? baseProperty(toKey(path)) : basePropertyDeep(path);
+ }
+
+ /**
+ * The opposite of `_.property`; this method creates a function that returns
+ * the value at a given path of `object`.
+ *
+ * @static
+ * @memberOf _
+ * @since 3.0.0
+ * @category Util
+ * @param {Object} object The object to query.
+ * @returns {Function} Returns the new accessor function.
+ * @example
+ *
+ * var array = [0, 1, 2],
+ * object = { 'a': array, 'b': array, 'c': array };
+ *
+ * _.map(['a[2]', 'c[0]'], _.propertyOf(object));
+ * // => [2, 0]
+ *
+ * _.map([['a', '2'], ['c', '0']], _.propertyOf(object));
+ * // => [2, 0]
+ */
+ function propertyOf(object) {
+ return function(path) {
+ return object == null ? undefined : baseGet(object, path);
+ };
+ }
+
+ /**
+ * Creates an array of numbers (positive and/or negative) progressing from
+ * `start` up to, but not including, `end`. A step of `-1` is used if a negative
+ * `start` is specified without an `end` or `step`. If `end` is not specified,
+ * it's set to `start` with `start` then set to `0`.
+ *
+ * **Note:** JavaScript follows the IEEE-754 standard for resolving
+ * floating-point values which can produce unexpected results.
+ *
+ * @static
+ * @since 0.1.0
+ * @memberOf _
+ * @category Util
+ * @param {number} [start=0] The start of the range.
+ * @param {number} end The end of the range.
+ * @param {number} [step=1] The value to increment or decrement by.
+ * @returns {Array} Returns the range of numbers.
+ * @see _.inRange, _.rangeRight
+ * @example
+ *
+ * _.range(4);
+ * // => [0, 1, 2, 3]
+ *
+ * _.range(-4);
+ * // => [0, -1, -2, -3]
+ *
+ * _.range(1, 5);
+ * // => [1, 2, 3, 4]
+ *
+ * _.range(0, 20, 5);
+ * // => [0, 5, 10, 15]
+ *
+ * _.range(0, -4, -1);
+ * // => [0, -1, -2, -3]
+ *
+ * _.range(1, 4, 0);
+ * // => [1, 1, 1]
+ *
+ * _.range(0);
+ * // => []
+ */
+ var range = createRange();
+
+ /**
+ * This method is like `_.range` except that it populates values in
+ * descending order.
+ *
+ * @static
+ * @memberOf _
+ * @since 4.0.0
+ * @category Util
+ * @param {number} [start=0] The start of the range.
+ * @param {number} end The end of the range.
+ * @param {number} [step=1] The value to increment or decrement by.
+ * @returns {Array} Returns the range of numbers.
+ * @see _.inRange, _.range
+ * @example
+ *
+ * _.rangeRight(4);
+ * // => [3, 2, 1, 0]
+ *
+ * _.rangeRight(-4);
+ * // => [-3, -2, -1, 0]
+ *
+ * _.rangeRight(1, 5);
+ * // => [4, 3, 2, 1]
+ *
+ * _.rangeRight(0, 20, 5);
+ * // => [15, 10, 5, 0]
+ *
+ * _.rangeRight(0, -4, -1);
+ * // => [-3, -2, -1, 0]
+ *
+ * _.rangeRight(1, 4, 0);
+ * // => [1, 1, 1]
+ *
+ * _.rangeRight(0);
+ * // => []
+ */
+ var rangeRight = createRange(true);
+
+ /**
+ * This method returns a new empty array.
+ *
+ * @static
+ * @memberOf _
+ * @since 4.13.0
+ * @category Util
+ * @returns {Array} Returns the new empty array.
+ * @example
+ *
+ * var arrays = _.times(2, _.stubArray);
+ *
+ * console.log(arrays);
+ * // => [[], []]
+ *
+ * console.log(arrays[0] === arrays[1]);
+ * // => false
+ */
+ function stubArray() {
+ return [];
+ }
+
+ /**
+ * This method returns `false`.
+ *
+ * @static
+ * @memberOf _
+ * @since 4.13.0
+ * @category Util
+ * @returns {boolean} Returns `false`.
+ * @example
+ *
+ * _.times(2, _.stubFalse);
+ * // => [false, false]
+ */
+ function stubFalse() {
+ return false;
+ }
+
+ /**
+ * This method returns a new empty object.
+ *
+ * @static
+ * @memberOf _
+ * @since 4.13.0
+ * @category Util
+ * @returns {Object} Returns the new empty object.
+ * @example
+ *
+ * var objects = _.times(2, _.stubObject);
+ *
+ * console.log(objects);
+ * // => [{}, {}]
+ *
+ * console.log(objects[0] === objects[1]);
+ * // => false
+ */
+ function stubObject() {
+ return {};
+ }
+
+ /**
+ * This method returns an empty string.
+ *
+ * @static
+ * @memberOf _
+ * @since 4.13.0
+ * @category Util
+ * @returns {string} Returns the empty string.
+ * @example
+ *
+ * _.times(2, _.stubString);
+ * // => ['', '']
+ */
+ function stubString() {
+ return '';
+ }
+
+ /**
+ * This method returns `true`.
+ *
+ * @static
+ * @memberOf _
+ * @since 4.13.0
+ * @category Util
+ * @returns {boolean} Returns `true`.
+ * @example
+ *
+ * _.times(2, _.stubTrue);
+ * // => [true, true]
+ */
+ function stubTrue() {
+ return true;
+ }
+
+ /**
+ * Invokes the iteratee `n` times, returning an array of the results of
+ * each invocation. The iteratee is invoked with one argument; (index).
+ *
+ * @static
+ * @since 0.1.0
+ * @memberOf _
+ * @category Util
+ * @param {number} n The number of times to invoke `iteratee`.
+ * @param {Function} [iteratee=_.identity] The function invoked per iteration.
+ * @returns {Array} Returns the array of results.
+ * @example
+ *
+ * _.times(3, String);
+ * // => ['0', '1', '2']
+ *
+ * _.times(4, _.constant(0));
+ * // => [0, 0, 0, 0]
+ */
+ function times(n, iteratee) {
+ n = toInteger(n);
+ if (n < 1 || n > MAX_SAFE_INTEGER) {
+ return [];
+ }
+ var index = MAX_ARRAY_LENGTH,
+ length = nativeMin(n, MAX_ARRAY_LENGTH);
+
+ iteratee = getIteratee(iteratee);
+ n -= MAX_ARRAY_LENGTH;
+
+ var result = baseTimes(length, iteratee);
+ while (++index < n) {
+ iteratee(index);
+ }
+ return result;
+ }
+
+ /**
+ * Converts `value` to a property path array.
+ *
+ * @static
+ * @memberOf _
+ * @since 4.0.0
+ * @category Util
+ * @param {*} value The value to convert.
+ * @returns {Array} Returns the new property path array.
+ * @example
+ *
+ * _.toPath('a.b.c');
+ * // => ['a', 'b', 'c']
+ *
+ * _.toPath('a[0].b.c');
+ * // => ['a', '0', 'b', 'c']
+ */
+ function toPath(value) {
+ if (isArray(value)) {
+ return arrayMap(value, toKey);
+ }
+ return isSymbol(value) ? [value] : copyArray(stringToPath(value));
+ }
+
+ /**
+ * Generates a unique ID. If `prefix` is given, the ID is appended to it.
+ *
+ * @static
+ * @since 0.1.0
+ * @memberOf _
+ * @category Util
+ * @param {string} [prefix=''] The value to prefix the ID with.
+ * @returns {string} Returns the unique ID.
+ * @example
+ *
+ * _.uniqueId('contact_');
+ * // => 'contact_104'
+ *
+ * _.uniqueId();
+ * // => '105'
+ */
+ function uniqueId(prefix) {
+ var id = ++idCounter;
+ return toString(prefix) + id;
+ }
+
+ /*------------------------------------------------------------------------*/
+
+ /**
+ * Adds two numbers.
+ *
+ * @static
+ * @memberOf _
+ * @since 3.4.0
+ * @category Math
+ * @param {number} augend The first number in an addition.
+ * @param {number} addend The second number in an addition.
+ * @returns {number} Returns the total.
+ * @example
+ *
+ * _.add(6, 4);
+ * // => 10
+ */
+ var add = createMathOperation(function(augend, addend) {
+ return augend + addend;
+ }, 0);
+
+ /**
+ * Computes `number` rounded up to `precision`.
+ *
+ * @static
+ * @memberOf _
+ * @since 3.10.0
+ * @category Math
+ * @param {number} number The number to round up.
+ * @param {number} [precision=0] The precision to round up to.
+ * @returns {number} Returns the rounded up number.
+ * @example
+ *
+ * _.ceil(4.006);
+ * // => 5
+ *
+ * _.ceil(6.004, 2);
+ * // => 6.01
+ *
+ * _.ceil(6040, -2);
+ * // => 6100
+ */
+ var ceil = createRound('ceil');
+
+ /**
+ * Divide two numbers.
+ *
+ * @static
+ * @memberOf _
+ * @since 4.7.0
+ * @category Math
+ * @param {number} dividend The first number in a division.
+ * @param {number} divisor The second number in a division.
+ * @returns {number} Returns the quotient.
+ * @example
+ *
+ * _.divide(6, 4);
+ * // => 1.5
+ */
+ var divide = createMathOperation(function(dividend, divisor) {
+ return dividend / divisor;
+ }, 1);
+
+ /**
+ * Computes `number` rounded down to `precision`.
+ *
+ * @static
+ * @memberOf _
+ * @since 3.10.0
+ * @category Math
+ * @param {number} number The number to round down.
+ * @param {number} [precision=0] The precision to round down to.
+ * @returns {number} Returns the rounded down number.
+ * @example
+ *
+ * _.floor(4.006);
+ * // => 4
+ *
+ * _.floor(0.046, 2);
+ * // => 0.04
+ *
+ * _.floor(4060, -2);
+ * // => 4000
+ */
+ var floor = createRound('floor');
+
+ /**
+ * Computes the maximum value of `array`. If `array` is empty or falsey,
+ * `undefined` is returned.
+ *
+ * @static
+ * @since 0.1.0
+ * @memberOf _
+ * @category Math
+ * @param {Array} array The array to iterate over.
+ * @returns {*} Returns the maximum value.
+ * @example
+ *
+ * _.max([4, 2, 8, 6]);
+ * // => 8
+ *
+ * _.max([]);
+ * // => undefined
+ */
+ function max(array) {
+ return (array && array.length)
+ ? baseExtremum(array, identity, baseGt)
+ : undefined;
+ }
+
+ /**
+ * This method is like `_.max` except that it accepts `iteratee` which is
+ * invoked for each element in `array` to generate the criterion by which
+ * the value is ranked. The iteratee is invoked with one argument: (value).
+ *
+ * @static
+ * @memberOf _
+ * @since 4.0.0
+ * @category Math
+ * @param {Array} array The array to iterate over.
+ * @param {Function} [iteratee=_.identity] The iteratee invoked per element.
+ * @returns {*} Returns the maximum value.
+ * @example
+ *
+ * var objects = [{ 'n': 1 }, { 'n': 2 }];
+ *
+ * _.maxBy(objects, function(o) { return o.n; });
+ * // => { 'n': 2 }
+ *
+ * // The `_.property` iteratee shorthand.
+ * _.maxBy(objects, 'n');
+ * // => { 'n': 2 }
+ */
+ function maxBy(array, iteratee) {
+ return (array && array.length)
+ ? baseExtremum(array, getIteratee(iteratee, 2), baseGt)
+ : undefined;
+ }
+
+ /**
+ * Computes the mean of the values in `array`.
+ *
+ * @static
+ * @memberOf _
+ * @since 4.0.0
+ * @category Math
+ * @param {Array} array The array to iterate over.
+ * @returns {number} Returns the mean.
+ * @example
+ *
+ * _.mean([4, 2, 8, 6]);
+ * // => 5
+ */
+ function mean(array) {
+ return baseMean(array, identity);
+ }
+
+ /**
+ * This method is like `_.mean` except that it accepts `iteratee` which is
+ * invoked for each element in `array` to generate the value to be averaged.
+ * The iteratee is invoked with one argument: (value).
+ *
+ * @static
+ * @memberOf _
+ * @since 4.7.0
+ * @category Math
+ * @param {Array} array The array to iterate over.
+ * @param {Function} [iteratee=_.identity] The iteratee invoked per element.
+ * @returns {number} Returns the mean.
+ * @example
+ *
+ * var objects = [{ 'n': 4 }, { 'n': 2 }, { 'n': 8 }, { 'n': 6 }];
+ *
+ * _.meanBy(objects, function(o) { return o.n; });
+ * // => 5
+ *
+ * // The `_.property` iteratee shorthand.
+ * _.meanBy(objects, 'n');
+ * // => 5
+ */
+ function meanBy(array, iteratee) {
+ return baseMean(array, getIteratee(iteratee, 2));
+ }
+
+ /**
+ * Computes the minimum value of `array`. If `array` is empty or falsey,
+ * `undefined` is returned.
+ *
+ * @static
+ * @since 0.1.0
+ * @memberOf _
+ * @category Math
+ * @param {Array} array The array to iterate over.
+ * @returns {*} Returns the minimum value.
+ * @example
+ *
+ * _.min([4, 2, 8, 6]);
+ * // => 2
+ *
+ * _.min([]);
+ * // => undefined
+ */
+ function min(array) {
+ return (array && array.length)
+ ? baseExtremum(array, identity, baseLt)
+ : undefined;
+ }
+
+ /**
+ * This method is like `_.min` except that it accepts `iteratee` which is
+ * invoked for each element in `array` to generate the criterion by which
+ * the value is ranked. The iteratee is invoked with one argument: (value).
+ *
+ * @static
+ * @memberOf _
+ * @since 4.0.0
+ * @category Math
+ * @param {Array} array The array to iterate over.
+ * @param {Function} [iteratee=_.identity] The iteratee invoked per element.
+ * @returns {*} Returns the minimum value.
+ * @example
+ *
+ * var objects = [{ 'n': 1 }, { 'n': 2 }];
+ *
+ * _.minBy(objects, function(o) { return o.n; });
+ * // => { 'n': 1 }
+ *
+ * // The `_.property` iteratee shorthand.
+ * _.minBy(objects, 'n');
+ * // => { 'n': 1 }
+ */
+ function minBy(array, iteratee) {
+ return (array && array.length)
+ ? baseExtremum(array, getIteratee(iteratee, 2), baseLt)
+ : undefined;
+ }
+
+ /**
+ * Multiply two numbers.
+ *
+ * @static
+ * @memberOf _
+ * @since 4.7.0
+ * @category Math
+ * @param {number} multiplier The first number in a multiplication.
+ * @param {number} multiplicand The second number in a multiplication.
+ * @returns {number} Returns the product.
+ * @example
+ *
+ * _.multiply(6, 4);
+ * // => 24
+ */
+ var multiply = createMathOperation(function(multiplier, multiplicand) {
+ return multiplier * multiplicand;
+ }, 1);
+
+ /**
+ * Computes `number` rounded to `precision`.
+ *
+ * @static
+ * @memberOf _
+ * @since 3.10.0
+ * @category Math
+ * @param {number} number The number to round.
+ * @param {number} [precision=0] The precision to round to.
+ * @returns {number} Returns the rounded number.
+ * @example
+ *
+ * _.round(4.006);
+ * // => 4
+ *
+ * _.round(4.006, 2);
+ * // => 4.01
+ *
+ * _.round(4060, -2);
+ * // => 4100
+ */
+ var round = createRound('round');
+
+ /**
+ * Subtract two numbers.
+ *
+ * @static
+ * @memberOf _
+ * @since 4.0.0
+ * @category Math
+ * @param {number} minuend The first number in a subtraction.
+ * @param {number} subtrahend The second number in a subtraction.
+ * @returns {number} Returns the difference.
+ * @example
+ *
+ * _.subtract(6, 4);
+ * // => 2
+ */
+ var subtract = createMathOperation(function(minuend, subtrahend) {
+ return minuend - subtrahend;
+ }, 0);
+
+ /**
+ * Computes the sum of the values in `array`.
+ *
+ * @static
+ * @memberOf _
+ * @since 3.4.0
+ * @category Math
+ * @param {Array} array The array to iterate over.
+ * @returns {number} Returns the sum.
+ * @example
+ *
+ * _.sum([4, 2, 8, 6]);
+ * // => 20
+ */
+ function sum(array) {
+ return (array && array.length)
+ ? baseSum(array, identity)
+ : 0;
+ }
+
+ /**
+ * This method is like `_.sum` except that it accepts `iteratee` which is
+ * invoked for each element in `array` to generate the value to be summed.
+ * The iteratee is invoked with one argument: (value).
+ *
+ * @static
+ * @memberOf _
+ * @since 4.0.0
+ * @category Math
+ * @param {Array} array The array to iterate over.
+ * @param {Function} [iteratee=_.identity] The iteratee invoked per element.
+ * @returns {number} Returns the sum.
+ * @example
+ *
+ * var objects = [{ 'n': 4 }, { 'n': 2 }, { 'n': 8 }, { 'n': 6 }];
+ *
+ * _.sumBy(objects, function(o) { return o.n; });
+ * // => 20
+ *
+ * // The `_.property` iteratee shorthand.
+ * _.sumBy(objects, 'n');
+ * // => 20
+ */
+ function sumBy(array, iteratee) {
+ return (array && array.length)
+ ? baseSum(array, getIteratee(iteratee, 2))
+ : 0;
+ }
+
+ /*------------------------------------------------------------------------*/
+
+ // Add methods that return wrapped values in chain sequences.
+ lodash.after = after;
+ lodash.ary = ary;
+ lodash.assign = assign;
+ lodash.assignIn = assignIn;
+ lodash.assignInWith = assignInWith;
+ lodash.assignWith = assignWith;
+ lodash.at = at;
+ lodash.before = before;
+ lodash.bind = bind;
+ lodash.bindAll = bindAll;
+ lodash.bindKey = bindKey;
+ lodash.castArray = castArray;
+ lodash.chain = chain;
+ lodash.chunk = chunk;
+ lodash.compact = compact;
+ lodash.concat = concat;
+ lodash.cond = cond;
+ lodash.conforms = conforms;
+ lodash.constant = constant;
+ lodash.countBy = countBy;
+ lodash.create = create;
+ lodash.curry = curry;
+ lodash.curryRight = curryRight;
+ lodash.debounce = debounce;
+ lodash.defaults = defaults;
+ lodash.defaultsDeep = defaultsDeep;
+ lodash.defer = defer;
+ lodash.delay = delay;
+ lodash.difference = difference;
+ lodash.differenceBy = differenceBy;
+ lodash.differenceWith = differenceWith;
+ lodash.drop = drop;
+ lodash.dropRight = dropRight;
+ lodash.dropRightWhile = dropRightWhile;
+ lodash.dropWhile = dropWhile;
+ lodash.fill = fill;
+ lodash.filter = filter;
+ lodash.flatMap = flatMap;
+ lodash.flatMapDeep = flatMapDeep;
+ lodash.flatMapDepth = flatMapDepth;
+ lodash.flatten = flatten;
+ lodash.flattenDeep = flattenDeep;
+ lodash.flattenDepth = flattenDepth;
+ lodash.flip = flip;
+ lodash.flow = flow;
+ lodash.flowRight = flowRight;
+ lodash.fromPairs = fromPairs;
+ lodash.functions = functions;
+ lodash.functionsIn = functionsIn;
+ lodash.groupBy = groupBy;
+ lodash.initial = initial;
+ lodash.intersection = intersection;
+ lodash.intersectionBy = intersectionBy;
+ lodash.intersectionWith = intersectionWith;
+ lodash.invert = invert;
+ lodash.invertBy = invertBy;
+ lodash.invokeMap = invokeMap;
+ lodash.iteratee = iteratee;
+ lodash.keyBy = keyBy;
+ lodash.keys = keys;
+ lodash.keysIn = keysIn;
+ lodash.map = map;
+ lodash.mapKeys = mapKeys;
+ lodash.mapValues = mapValues;
+ lodash.matches = matches;
+ lodash.matchesProperty = matchesProperty;
+ lodash.memoize = memoize;
+ lodash.merge = merge;
+ lodash.mergeWith = mergeWith;
+ lodash.method = method;
+ lodash.methodOf = methodOf;
+ lodash.mixin = mixin;
+ lodash.negate = negate;
+ lodash.nthArg = nthArg;
+ lodash.omit = omit;
+ lodash.omitBy = omitBy;
+ lodash.once = once;
+ lodash.orderBy = orderBy;
+ lodash.over = over;
+ lodash.overArgs = overArgs;
+ lodash.overEvery = overEvery;
+ lodash.overSome = overSome;
+ lodash.partial = partial;
+ lodash.partialRight = partialRight;
+ lodash.partition = partition;
+ lodash.pick = pick;
+ lodash.pickBy = pickBy;
+ lodash.property = property;
+ lodash.propertyOf = propertyOf;
+ lodash.pull = pull;
+ lodash.pullAll = pullAll;
+ lodash.pullAllBy = pullAllBy;
+ lodash.pullAllWith = pullAllWith;
+ lodash.pullAt = pullAt;
+ lodash.range = range;
+ lodash.rangeRight = rangeRight;
+ lodash.rearg = rearg;
+ lodash.reject = reject;
+ lodash.remove = remove;
+ lodash.rest = rest;
+ lodash.reverse = reverse;
+ lodash.sampleSize = sampleSize;
+ lodash.set = set;
+ lodash.setWith = setWith;
+ lodash.shuffle = shuffle;
+ lodash.slice = slice;
+ lodash.sortBy = sortBy;
+ lodash.sortedUniq = sortedUniq;
+ lodash.sortedUniqBy = sortedUniqBy;
+ lodash.split = split;
+ lodash.spread = spread;
+ lodash.tail = tail;
+ lodash.take = take;
+ lodash.takeRight = takeRight;
+ lodash.takeRightWhile = takeRightWhile;
+ lodash.takeWhile = takeWhile;
+ lodash.tap = tap;
+ lodash.throttle = throttle;
+ lodash.thru = thru;
+ lodash.toArray = toArray;
+ lodash.toPairs = toPairs;
+ lodash.toPairsIn = toPairsIn;
+ lodash.toPath = toPath;
+ lodash.toPlainObject = toPlainObject;
+ lodash.transform = transform;
+ lodash.unary = unary;
+ lodash.union = union;
+ lodash.unionBy = unionBy;
+ lodash.unionWith = unionWith;
+ lodash.uniq = uniq;
+ lodash.uniqBy = uniqBy;
+ lodash.uniqWith = uniqWith;
+ lodash.unset = unset;
+ lodash.unzip = unzip;
+ lodash.unzipWith = unzipWith;
+ lodash.update = update;
+ lodash.updateWith = updateWith;
+ lodash.values = values;
+ lodash.valuesIn = valuesIn;
+ lodash.without = without;
+ lodash.words = words;
+ lodash.wrap = wrap;
+ lodash.xor = xor;
+ lodash.xorBy = xorBy;
+ lodash.xorWith = xorWith;
+ lodash.zip = zip;
+ lodash.zipObject = zipObject;
+ lodash.zipObjectDeep = zipObjectDeep;
+ lodash.zipWith = zipWith;
+
+ // Add aliases.
+ lodash.entries = toPairs;
+ lodash.entriesIn = toPairsIn;
+ lodash.extend = assignIn;
+ lodash.extendWith = assignInWith;
+
+ // Add methods to `lodash.prototype`.
+ mixin(lodash, lodash);
+
+ /*------------------------------------------------------------------------*/
+
+ // Add methods that return unwrapped values in chain sequences.
+ lodash.add = add;
+ lodash.attempt = attempt;
+ lodash.camelCase = camelCase;
+ lodash.capitalize = capitalize;
+ lodash.ceil = ceil;
+ lodash.clamp = clamp;
+ lodash.clone = clone;
+ lodash.cloneDeep = cloneDeep;
+ lodash.cloneDeepWith = cloneDeepWith;
+ lodash.cloneWith = cloneWith;
+ lodash.conformsTo = conformsTo;
+ lodash.deburr = deburr;
+ lodash.defaultTo = defaultTo;
+ lodash.divide = divide;
+ lodash.endsWith = endsWith;
+ lodash.eq = eq;
+ lodash.escape = escape;
+ lodash.escapeRegExp = escapeRegExp;
+ lodash.every = every;
+ lodash.find = find;
+ lodash.findIndex = findIndex;
+ lodash.findKey = findKey;
+ lodash.findLast = findLast;
+ lodash.findLastIndex = findLastIndex;
+ lodash.findLastKey = findLastKey;
+ lodash.floor = floor;
+ lodash.forEach = forEach;
+ lodash.forEachRight = forEachRight;
+ lodash.forIn = forIn;
+ lodash.forInRight = forInRight;
+ lodash.forOwn = forOwn;
+ lodash.forOwnRight = forOwnRight;
+ lodash.get = get;
+ lodash.gt = gt;
+ lodash.gte = gte;
+ lodash.has = has;
+ lodash.hasIn = hasIn;
+ lodash.head = head;
+ lodash.identity = identity;
+ lodash.includes = includes;
+ lodash.indexOf = indexOf;
+ lodash.inRange = inRange;
+ lodash.invoke = invoke;
+ lodash.isArguments = isArguments;
+ lodash.isArray = isArray;
+ lodash.isArrayBuffer = isArrayBuffer;
+ lodash.isArrayLike = isArrayLike;
+ lodash.isArrayLikeObject = isArrayLikeObject;
+ lodash.isBoolean = isBoolean;
+ lodash.isBuffer = isBuffer;
+ lodash.isDate = isDate;
+ lodash.isElement = isElement;
+ lodash.isEmpty = isEmpty;
+ lodash.isEqual = isEqual;
+ lodash.isEqualWith = isEqualWith;
+ lodash.isError = isError;
+ lodash.isFinite = isFinite;
+ lodash.isFunction = isFunction;
+ lodash.isInteger = isInteger;
+ lodash.isLength = isLength;
+ lodash.isMap = isMap;
+ lodash.isMatch = isMatch;
+ lodash.isMatchWith = isMatchWith;
+ lodash.isNaN = isNaN;
+ lodash.isNative = isNative;
+ lodash.isNil = isNil;
+ lodash.isNull = isNull;
+ lodash.isNumber = isNumber;
+ lodash.isObject = isObject;
+ lodash.isObjectLike = isObjectLike;
+ lodash.isPlainObject = isPlainObject;
+ lodash.isRegExp = isRegExp;
+ lodash.isSafeInteger = isSafeInteger;
+ lodash.isSet = isSet;
+ lodash.isString = isString;
+ lodash.isSymbol = isSymbol;
+ lodash.isTypedArray = isTypedArray;
+ lodash.isUndefined = isUndefined;
+ lodash.isWeakMap = isWeakMap;
+ lodash.isWeakSet = isWeakSet;
+ lodash.join = join;
+ lodash.kebabCase = kebabCase;
+ lodash.last = last;
+ lodash.lastIndexOf = lastIndexOf;
+ lodash.lowerCase = lowerCase;
+ lodash.lowerFirst = lowerFirst;
+ lodash.lt = lt;
+ lodash.lte = lte;
+ lodash.max = max;
+ lodash.maxBy = maxBy;
+ lodash.mean = mean;
+ lodash.meanBy = meanBy;
+ lodash.min = min;
+ lodash.minBy = minBy;
+ lodash.stubArray = stubArray;
+ lodash.stubFalse = stubFalse;
+ lodash.stubObject = stubObject;
+ lodash.stubString = stubString;
+ lodash.stubTrue = stubTrue;
+ lodash.multiply = multiply;
+ lodash.nth = nth;
+ lodash.noConflict = noConflict;
+ lodash.noop = noop;
+ lodash.now = now;
+ lodash.pad = pad;
+ lodash.padEnd = padEnd;
+ lodash.padStart = padStart;
+ lodash.parseInt = parseInt;
+ lodash.random = random;
+ lodash.reduce = reduce;
+ lodash.reduceRight = reduceRight;
+ lodash.repeat = repeat;
+ lodash.replace = replace;
+ lodash.result = result;
+ lodash.round = round;
+ lodash.runInContext = runInContext;
+ lodash.sample = sample;
+ lodash.size = size;
+ lodash.snakeCase = snakeCase;
+ lodash.some = some;
+ lodash.sortedIndex = sortedIndex;
+ lodash.sortedIndexBy = sortedIndexBy;
+ lodash.sortedIndexOf = sortedIndexOf;
+ lodash.sortedLastIndex = sortedLastIndex;
+ lodash.sortedLastIndexBy = sortedLastIndexBy;
+ lodash.sortedLastIndexOf = sortedLastIndexOf;
+ lodash.startCase = startCase;
+ lodash.startsWith = startsWith;
+ lodash.subtract = subtract;
+ lodash.sum = sum;
+ lodash.sumBy = sumBy;
+ lodash.template = template;
+ lodash.times = times;
+ lodash.toFinite = toFinite;
+ lodash.toInteger = toInteger;
+ lodash.toLength = toLength;
+ lodash.toLower = toLower;
+ lodash.toNumber = toNumber;
+ lodash.toSafeInteger = toSafeInteger;
+ lodash.toString = toString;
+ lodash.toUpper = toUpper;
+ lodash.trim = trim;
+ lodash.trimEnd = trimEnd;
+ lodash.trimStart = trimStart;
+ lodash.truncate = truncate;
+ lodash.unescape = unescape;
+ lodash.uniqueId = uniqueId;
+ lodash.upperCase = upperCase;
+ lodash.upperFirst = upperFirst;
+
+ // Add aliases.
+ lodash.each = forEach;
+ lodash.eachRight = forEachRight;
+ lodash.first = head;
+
+ mixin(lodash, (function() {
+ var source = {};
+ baseForOwn(lodash, function(func, methodName) {
+ if (!hasOwnProperty.call(lodash.prototype, methodName)) {
+ source[methodName] = func;
+ }
+ });
+ return source;
+ }()), { 'chain': false });
+
+ /*------------------------------------------------------------------------*/
+
+ /**
+ * The semantic version number.
+ *
+ * @static
+ * @memberOf _
+ * @type {string}
+ */
+ lodash.VERSION = VERSION;
+
+ // Assign default placeholders.
+ arrayEach(['bind', 'bindKey', 'curry', 'curryRight', 'partial', 'partialRight'], function(methodName) {
+ lodash[methodName].placeholder = lodash;
+ });
+
+ // Add `LazyWrapper` methods for `_.drop` and `_.take` variants.
+ arrayEach(['drop', 'take'], function(methodName, index) {
+ LazyWrapper.prototype[methodName] = function(n) {
+ var filtered = this.__filtered__;
+ if (filtered && !index) {
+ return new LazyWrapper(this);
+ }
+ n = n === undefined ? 1 : nativeMax(toInteger(n), 0);
+
+ var result = this.clone();
+ if (filtered) {
+ result.__takeCount__ = nativeMin(n, result.__takeCount__);
+ } else {
+ result.__views__.push({
+ 'size': nativeMin(n, MAX_ARRAY_LENGTH),
+ 'type': methodName + (result.__dir__ < 0 ? 'Right' : '')
+ });
+ }
+ return result;
+ };
+
+ LazyWrapper.prototype[methodName + 'Right'] = function(n) {
+ return this.reverse()[methodName](n).reverse();
+ };
+ });
+
+ // Add `LazyWrapper` methods that accept an `iteratee` value.
+ arrayEach(['filter', 'map', 'takeWhile'], function(methodName, index) {
+ var type = index + 1,
+ isFilter = type == LAZY_FILTER_FLAG || type == LAZY_WHILE_FLAG;
+
+ LazyWrapper.prototype[methodName] = function(iteratee) {
+ var result = this.clone();
+ result.__iteratees__.push({
+ 'iteratee': getIteratee(iteratee, 3),
+ 'type': type
+ });
+ result.__filtered__ = result.__filtered__ || isFilter;
+ return result;
+ };
+ });
+
+ // Add `LazyWrapper` methods for `_.head` and `_.last`.
+ arrayEach(['head', 'last'], function(methodName, index) {
+ var takeName = 'take' + (index ? 'Right' : '');
+
+ LazyWrapper.prototype[methodName] = function() {
+ return this[takeName](1).value()[0];
+ };
+ });
+
+ // Add `LazyWrapper` methods for `_.initial` and `_.tail`.
+ arrayEach(['initial', 'tail'], function(methodName, index) {
+ var dropName = 'drop' + (index ? '' : 'Right');
+
+ LazyWrapper.prototype[methodName] = function() {
+ return this.__filtered__ ? new LazyWrapper(this) : this[dropName](1);
+ };
+ });
+
+ LazyWrapper.prototype.compact = function() {
+ return this.filter(identity);
+ };
+
+ LazyWrapper.prototype.find = function(predicate) {
+ return this.filter(predicate).head();
+ };
+
+ LazyWrapper.prototype.findLast = function(predicate) {
+ return this.reverse().find(predicate);
+ };
+
+ LazyWrapper.prototype.invokeMap = baseRest(function(path, args) {
+ if (typeof path == 'function') {
+ return new LazyWrapper(this);
+ }
+ return this.map(function(value) {
+ return baseInvoke(value, path, args);
+ });
+ });
+
+ LazyWrapper.prototype.reject = function(predicate) {
+ return this.filter(negate(getIteratee(predicate)));
+ };
+
+ LazyWrapper.prototype.slice = function(start, end) {
+ start = toInteger(start);
+
+ var result = this;
+ if (result.__filtered__ && (start > 0 || end < 0)) {
+ return new LazyWrapper(result);
+ }
+ if (start < 0) {
+ result = result.takeRight(-start);
+ } else if (start) {
+ result = result.drop(start);
+ }
+ if (end !== undefined) {
+ end = toInteger(end);
+ result = end < 0 ? result.dropRight(-end) : result.take(end - start);
+ }
+ return result;
+ };
+
+ LazyWrapper.prototype.takeRightWhile = function(predicate) {
+ return this.reverse().takeWhile(predicate).reverse();
+ };
+
+ LazyWrapper.prototype.toArray = function() {
+ return this.take(MAX_ARRAY_LENGTH);
+ };
+
+ // Add `LazyWrapper` methods to `lodash.prototype`.
+ baseForOwn(LazyWrapper.prototype, function(func, methodName) {
+ var checkIteratee = /^(?:filter|find|map|reject)|While$/.test(methodName),
+ isTaker = /^(?:head|last)$/.test(methodName),
+ lodashFunc = lodash[isTaker ? ('take' + (methodName == 'last' ? 'Right' : '')) : methodName],
+ retUnwrapped = isTaker || /^find/.test(methodName);
+
+ if (!lodashFunc) {
+ return;
+ }
+ lodash.prototype[methodName] = function() {
+ var value = this.__wrapped__,
+ args = isTaker ? [1] : arguments,
+ isLazy = value instanceof LazyWrapper,
+ iteratee = args[0],
+ useLazy = isLazy || isArray(value);
+
+ var interceptor = function(value) {
+ var result = lodashFunc.apply(lodash, arrayPush([value], args));
+ return (isTaker && chainAll) ? result[0] : result;
+ };
+
+ if (useLazy && checkIteratee && typeof iteratee == 'function' && iteratee.length != 1) {
+ // Avoid lazy use if the iteratee has a "length" value other than `1`.
+ isLazy = useLazy = false;
+ }
+ var chainAll = this.__chain__,
+ isHybrid = !!this.__actions__.length,
+ isUnwrapped = retUnwrapped && !chainAll,
+ onlyLazy = isLazy && !isHybrid;
+
+ if (!retUnwrapped && useLazy) {
+ value = onlyLazy ? value : new LazyWrapper(this);
+ var result = func.apply(value, args);
+ result.__actions__.push({ 'func': thru, 'args': [interceptor], 'thisArg': undefined });
+ return new LodashWrapper(result, chainAll);
+ }
+ if (isUnwrapped && onlyLazy) {
+ return func.apply(this, args);
+ }
+ result = this.thru(interceptor);
+ return isUnwrapped ? (isTaker ? result.value()[0] : result.value()) : result;
+ };
+ });
+
+ // Add `Array` methods to `lodash.prototype`.
+ arrayEach(['pop', 'push', 'shift', 'sort', 'splice', 'unshift'], function(methodName) {
+ var func = arrayProto[methodName],
+ chainName = /^(?:push|sort|unshift)$/.test(methodName) ? 'tap' : 'thru',
+ retUnwrapped = /^(?:pop|shift)$/.test(methodName);
+
+ lodash.prototype[methodName] = function() {
+ var args = arguments;
+ if (retUnwrapped && !this.__chain__) {
+ var value = this.value();
+ return func.apply(isArray(value) ? value : [], args);
+ }
+ return this[chainName](function(value) {
+ return func.apply(isArray(value) ? value : [], args);
+ });
+ };
+ });
+
+ // Map minified method names to their real names.
+ baseForOwn(LazyWrapper.prototype, function(func, methodName) {
+ var lodashFunc = lodash[methodName];
+ if (lodashFunc) {
+ var key = (lodashFunc.name + ''),
+ names = realNames[key] || (realNames[key] = []);
+
+ names.push({ 'name': methodName, 'func': lodashFunc });
+ }
+ });
+
+ realNames[createHybrid(undefined, BIND_KEY_FLAG).name] = [{
+ 'name': 'wrapper',
+ 'func': undefined
+ }];
+
+ // Add methods to `LazyWrapper`.
+ LazyWrapper.prototype.clone = lazyClone;
+ LazyWrapper.prototype.reverse = lazyReverse;
+ LazyWrapper.prototype.value = lazyValue;
+
+ // Add chain sequence methods to the `lodash` wrapper.
+ lodash.prototype.at = wrapperAt;
+ lodash.prototype.chain = wrapperChain;
+ lodash.prototype.commit = wrapperCommit;
+ lodash.prototype.next = wrapperNext;
+ lodash.prototype.plant = wrapperPlant;
+ lodash.prototype.reverse = wrapperReverse;
+ lodash.prototype.toJSON = lodash.prototype.valueOf = lodash.prototype.value = wrapperValue;
+
+ // Add lazy aliases.
+ lodash.prototype.first = lodash.prototype.head;
+
+ if (iteratorSymbol) {
+ lodash.prototype[iteratorSymbol] = wrapperToIterator;
+ }
+ return lodash;
+ });
+
+ /*--------------------------------------------------------------------------*/
+
+ // Export lodash.
+ var _ = runInContext();
+
+ // Some AMD build optimizers, like r.js, check for condition patterns like:
+ if (true) {
+ // Expose Lodash on the global object to prevent errors when Lodash is
+ // loaded by a script tag in the presence of an AMD loader.
+ // See http://requirejs.org/docs/errors.html#mismatch for more details.
+ // Use `_.noConflict` to remove Lodash from the global object.
+ root._ = _;
+
+ // Define as an anonymous module so, through path mapping, it can be
+ // referenced as the "underscore" module.
+ !(__WEBPACK_AMD_DEFINE_RESULT__ = function() {
+ return _;
+ }.call(exports, __webpack_require__, exports, module), __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__));
+ }
+ // Check for `exports` after `define` in case a build optimizer adds it.
+ else if (freeModule) {
+ // Export for Node.js.
+ (freeModule.exports = _)._ = _;
+ // Export for CommonJS support.
+ freeExports._ = _;
+ }
+ else {
+ // Export to the global object.
+ root._ = _;
+ }
+ }.call(this));
+
+ /* WEBPACK VAR INJECTION */}.call(exports, (function() { return this; }()), __webpack_require__(77)(module)))
+
+/***/ },
+/* 409 */
+/***/ function(module, exports) {
+
+ // removed by extract-text-webpack-plugin
+
+/***/ },
+/* 410 */,
+/* 411 */
+/***/ function(module, exports, __webpack_require__) {
+
+ var React = __webpack_require__(2);
+ var dom = React.DOM;
+
+
+ var ReactDOM = __webpack_require__(16);
+
+ function renderConditionalPanel(_ref) {
+ var condition = _ref.condition;
+ var closePanel = _ref.closePanel;
+ var setBreakpoint = _ref.setBreakpoint;
+
+ var panel = document.createElement("div");
+
+ function onKey(e) {
+ if (e.key != "Enter") {
+ return;
+ }
+
+ setBreakpoint(e.target.value);
+ closePanel();
+ }
+
+ ReactDOM.render(dom.div({ className: "conditional-breakpoint-panel" }, dom.input({
+ defaultValue: condition,
+ placeholder: "This breakpoint will pause when the expression is true",
+ onKeyPress: onKey
+ })), panel);
+
+ return panel;
+ }
+
+ module.exports = {
+ renderConditionalPanel
+ };
+
+/***/ },
+/* 412 */
+/***/ function(module, exports, __webpack_require__) {
+
+ var React = __webpack_require__(2);
+ var ReactDOM = __webpack_require__(16);
+
+ var PropTypes = React.PropTypes;
+
+ var classnames = __webpack_require__(211);
+ var Svg = __webpack_require__(310);
+
+ var breakpointSvg = document.createElement("div");
+ ReactDOM.render(Svg("breakpoint"), breakpointSvg);
+
+ function makeMarker(isDisabled) {
+ var bp = breakpointSvg.cloneNode(true);
+ bp.className = classnames("editor new-breakpoint", { "breakpoint-disabled": isDisabled });
+
+ return bp;
+ }
+
+ var Breakpoint = React.createClass({
+ propTypes: {
+ breakpoint: PropTypes.object,
+ editor: PropTypes.object
+ },
+
+ displayName: "Breakpoint",
+
+ addBreakpoint() {
+ var bp = this.props.breakpoint;
+ var line = bp.location.line - 1;
+
+ this.props.editor.setGutterMarker(line, "breakpoints", makeMarker(bp.disabled));
+ this.props.editor.addLineClass(line, "line", "new-breakpoint");
+ if (bp.condition) {
+ this.props.editor.addLineClass(line, "line", "has-condition");
+ }
+ },
+
+ shouldComponentUpdate(nextProps) {
+ return this.props.editor !== nextProps.editor || this.props.breakpoint.disabled !== nextProps.breakpoint.disabled || this.props.breakpoint.condition !== nextProps.breakpoint.condition;
+ },
+
+ componentDidMount() {
+ if (!this.props.editor) {
+ return;
+ }
+
+ this.addBreakpoint();
+ },
+
+ componentDidUpdate() {
+ this.addBreakpoint();
+ },
+
+ componentWillUnmount() {
+ if (!this.props.editor) {
+ return;
+ }
+
+ var bp = this.props.breakpoint;
+ var line = bp.location.line - 1;
+
+ this.props.editor.setGutterMarker(line, "breakpoints", null);
+ this.props.editor.removeLineClass(line, "line", "new-breakpoint");
+ this.props.editor.removeLineClass(line, "line", "has-condition");
+ },
+
+ render() {
+ return null;
+ }
+ });
+
+ module.exports = Breakpoint;
+
+/***/ },
+/* 413 */
+/***/ function(module, exports, __webpack_require__) {
+
+ var _require = __webpack_require__(28);
+
+ var Menu = _require.Menu;
+ var MenuItem = _require.MenuItem;
+
+ var _require2 = __webpack_require__(89);
+
+ var isFirefoxPanel = _require2.isFirefoxPanel;
+
+
+ function createPopup(doc) {
+ var popup = doc.createElement("menupopup");
+
+ if (popup.openPopupAtScreen) {
+ return popup;
+ }
+
+ function preventDefault(e) {
+ e.preventDefault();
+ e.returnValue = false;
+ }
+
+ var mask = document.querySelector("#contextmenu-mask");
+ if (!mask) {
+ mask = doc.createElement("div");
+ mask.id = "contextmenu-mask";
+ document.body.appendChild(mask);
+ }
+
+ mask.onclick = () => popup.hidePopup();
+
+ popup.openPopupAtScreen = function (clientX, clientY) {
+ this.style.setProperty("left", clientX + "px");
+ this.style.setProperty("top", clientY + "px");
+ mask = document.querySelector("#contextmenu-mask");
+ window.onwheel = preventDefault;
+ mask.classList.add("show");
+ this.dispatchEvent(new Event("popupshown"));
+ this.popupshown;
+ };
+
+ popup.hidePopup = function () {
+ this.remove();
+ mask = document.querySelector("#contextmenu-mask");
+ mask.classList.remove("show");
+ window.onwheel = null;
+ };
+
+ return popup;
+ }
+
+ if (!isFirefoxPanel()) {
+ Menu.prototype.createPopup = createPopup;
+ }
+
+ function onShown(menu, popup) {
+ popup.childNodes.forEach((menuitem, index) => {
+ var item = menu.items[index];
+ menuitem.onclick = () => {
+ item.click();
+ popup.hidePopup();
+ };
+ });
+ }
+
+ function showMenu(e, items) {
+ var menu = new Menu();
+ items.forEach(item => menu.append(new MenuItem(item)));
+
+ if (isFirefoxPanel()) {
+ return menu.popup(e.screenX, e.screenY, { doc: window.parent.document });
+ }
+
+ menu.on("open", (_, popup) => onShown(menu, popup));
+ return menu.popup(e.clientX, e.clientY, { doc: document });
+ }
+
+ function buildMenu(items) {
+ return items.map(itm => {
+ var hide = typeof itm.hidden === "function" ? itm.hidden() : itm.hidden;
+ return hide ? null : itm.item;
+ }).filter(itm => itm !== null);
+ }
+
+ module.exports = {
+ showMenu,
+ buildMenu
+ };
+
+/***/ },
+/* 414 */
+/***/ function(module, exports) {
+
+ // removed by extract-text-webpack-plugin
+
+/***/ },
+/* 415 */,
+/* 416 */
+/***/ function(module, exports, __webpack_require__) {
+
+ var React = __webpack_require__(2);
+ var dom = React.DOM;
+ var PropTypes = React.PropTypes;
+
+ var _require = __webpack_require__(19);
+
+ var connect = _require.connect;
+
+ var _require2 = __webpack_require__(3);
+
+ var bindActionCreators = _require2.bindActionCreators;
+
+ var _require3 = __webpack_require__(259);
+
+ var getPause = _require3.getPause;
+ var getIsWaitingOnBreak = _require3.getIsWaitingOnBreak;
+ var getBreakpointsDisabled = _require3.getBreakpointsDisabled;
+ var getShouldPauseOnExceptions = _require3.getShouldPauseOnExceptions;
+ var getShouldIgnoreCaughtExceptions = _require3.getShouldIgnoreCaughtExceptions;
+ var getBreakpoints = _require3.getBreakpoints;
+ var getBreakpointsLoading = _require3.getBreakpointsLoading;
+
+ var _require4 = __webpack_require__(89);
+
+ var isEnabled = _require4.isEnabled;
+
+ var Svg = __webpack_require__(310);
+ var ImPropTypes = __webpack_require__(235);
+
+ var _require5 = __webpack_require__(30);
+
+ var appinfo = _require5.Services.appinfo;
+
+ var shiftKey = appinfo.OS === "Darwin" ? "\u21E7" : "Shift+";
+ var ctrlKey = appinfo.OS === "Linux" ? "Ctrl+" : "";
+
+ var actions = __webpack_require__(262);
+ var Breakpoints = React.createFactory(__webpack_require__(417));
+ var Expressions = React.createFactory(__webpack_require__(420));
+ var Scopes = React.createFactory(__webpack_require__(424));
+ var Frames = React.createFactory(__webpack_require__(444));
+ var Accordion = React.createFactory(__webpack_require__(447));
+ __webpack_require__(450);
+
+ function debugBtn(onClick, type, className, tooltip) {
+ className = `${ type } ${ className }`;
+ return dom.span({ onClick, className, key: type }, Svg(type, { title: tooltip }));
+ }
+
+ var RightSidebar = React.createClass({
+ propTypes: {
+ sources: PropTypes.object,
+ selectedSource: PropTypes.object,
+ resume: PropTypes.func,
+ stepIn: PropTypes.func,
+ stepOut: PropTypes.func,
+ stepOver: PropTypes.func,
+ toggleAllBreakpoints: PropTypes.func,
+ breakOnNext: PropTypes.func,
+ pause: ImPropTypes.map,
+ pauseOnExceptions: PropTypes.func,
+ shouldPauseOnExceptions: PropTypes.bool,
+ shouldIgnoreCaughtExceptions: PropTypes.bool,
+ breakpoints: ImPropTypes.map,
+ isWaitingOnBreak: PropTypes.bool,
+ breakpointsDisabled: PropTypes.bool,
+ breakpointsLoading: PropTypes.bool,
+ evaluateExpressions: PropTypes.func
+ },
+
+ contextTypes: {
+ shortcuts: PropTypes.object
+ },
+
+ displayName: "RightSidebar",
+
+ getInitialState() {
+ return {
+ expressionInputVisibility: true
+ };
+ },
+
+ resume() {
+ if (this.props.pause) {
+ this.props.resume();
+ } else if (!this.props.isWaitingOnBreak) {
+ this.props.breakOnNext();
+ }
+ },
+
+ stepOver() {
+ if (!this.props.pause) {
+ return;
+ }
+ this.props.stepOver();
+ },
+
+ stepIn() {
+ if (!this.props.pause) {
+ return;
+ }
+ this.props.stepIn();
+ },
+
+ stepOut() {
+ if (!this.props.pause) {
+ return;
+ }
+ this.props.stepOut();
+ },
+
+ componentWillUnmount() {
+ var shortcuts = this.context.shortcuts;
+ shortcuts.off("F8", this.resume);
+ shortcuts.off("F10", this.stepOver);
+ shortcuts.off(`${ ctrlKey }F11`, this.stepIn);
+ shortcuts.off(`${ ctrlKey }Shift+F11`, this.stepOut);
+ },
+
+ componentDidMount() {
+ var shortcuts = this.context.shortcuts;
+ shortcuts.on("F8", this.resume);
+ shortcuts.on("F10", this.stepOver);
+ shortcuts.on(`${ ctrlKey }F11`, this.stepIn);
+ shortcuts.on(`${ ctrlKey }Shift+F11`, this.stepOut);
+ },
+
+ renderStepButtons() {
+ var className = this.props.pause ? "active" : "disabled";
+ return [debugBtn(this.stepOver, "stepOver", className, L10N.getStr("stepOverTooltip")), debugBtn(this.stepIn, "stepIn", className, L10N.getFormatStr("stepInTooltip", ctrlKey)), debugBtn(this.stepOut, "stepOut", className, L10N.getFormatStr("stepOutTooltip", ctrlKey + shiftKey))];
+ },
+
+ renderPauseButton() {
+ var _props = this.props;
+ var pause = _props.pause;
+ var breakOnNext = _props.breakOnNext;
+ var isWaitingOnBreak = _props.isWaitingOnBreak;
+
+
+ if (pause) {
+ return debugBtn(this.resume, "resume", "active", L10N.getStr("resumeButtonTooltip"));
+ }
+
+ if (isWaitingOnBreak) {
+ return debugBtn(null, "pause", "disabled", L10N.getStr("pausePendingButtonTooltip"));
+ }
+
+ return debugBtn(breakOnNext, "pause", "active", L10N.getStr("pauseButtonTooltip"));
+ },
+
+ /*
+ * The pause on exception button has three states in this order:
+ * 1. don't pause on exceptions [false, false]
+ * 2. pause on uncaught exceptions [true, true]
+ * 3. pause on all exceptions [true, false]
+ */
+ renderPauseOnExceptions() {
+ var _props2 = this.props;
+ var shouldPauseOnExceptions = _props2.shouldPauseOnExceptions;
+ var shouldIgnoreCaughtExceptions = _props2.shouldIgnoreCaughtExceptions;
+ var pauseOnExceptions = _props2.pauseOnExceptions;
+
+
+ if (!shouldPauseOnExceptions && !shouldIgnoreCaughtExceptions) {
+ return debugBtn(() => pauseOnExceptions(true, true), "pause-exceptions", "enabled", L10N.getStr("ignoreExceptions"));
+ }
+
+ if (shouldPauseOnExceptions && shouldIgnoreCaughtExceptions) {
+ return debugBtn(() => pauseOnExceptions(true, false), "pause-exceptions", "uncaught enabled", L10N.getStr("pauseOnUncaughtExceptions"));
+ }
+
+ return debugBtn(() => pauseOnExceptions(false, false), "pause-exceptions", "all enabled", L10N.getStr("pauseOnExceptions"));
+ },
+
+ renderDisableBreakpoints() {
+ var _props3 = this.props;
+ var toggleAllBreakpoints = _props3.toggleAllBreakpoints;
+ var breakpoints = _props3.breakpoints;
+ var breakpointsDisabled = _props3.breakpointsDisabled;
+ var breakpointsLoading = _props3.breakpointsLoading;
+
+
+ if (breakpoints.size == 0 || breakpointsLoading) {
+ return debugBtn(null, "toggleBreakpoints", "disabled", "Disable Breakpoints");
+ }
+
+ return debugBtn(() => toggleAllBreakpoints(!breakpointsDisabled), "toggleBreakpoints", breakpointsDisabled ? "breakpoints-disabled" : "", "Disable Breakpoints");
+ },
+
+ getItems() {
+ var expressionInputVisibility = this.state.expressionInputVisibility;
+
+ var items = [{ header: L10N.getStr("breakpoints.header"),
+ component: Breakpoints,
+ opened: true }, { header: L10N.getStr("callStack.header"),
+ component: Frames }, { header: L10N.getStr("scopes.header"),
+ component: Scopes }];
+ if (isEnabled("watchExpressions")) {
+ items.unshift({ header: L10N.getStr("watchExpressions.header"),
+ buttons: [debugBtn(evt => {
+ evt.stopPropagation();
+ this.props.evaluateExpressions();
+ }, "domain", "accordion-button", "Refresh"), debugBtn(evt => {
+ evt.stopPropagation();
+ this.setState({
+ expressionInputVisibility: !expressionInputVisibility
+ });
+ }, "file", "accordion-button", "Add Watch Expression")],
+ component: Expressions,
+ componentProps: { expressionInputVisibility },
+ opened: true
+ });
+ }
+ return items;
+ },
+
+ render() {
+ return dom.div({ className: "right-sidebar",
+ style: { overflowX: "hidden" } }, dom.div({ className: "command-bar" }, this.renderPauseButton(), this.renderStepButtons(), this.renderDisableBreakpoints(), this.renderPauseOnExceptions()), Accordion({
+ items: this.getItems()
+ }));
+ }
+
+ });
+
+ module.exports = connect(state => {
+ return {
+ pause: getPause(state),
+ isWaitingOnBreak: getIsWaitingOnBreak(state),
+ shouldPauseOnExceptions: getShouldPauseOnExceptions(state),
+ shouldIgnoreCaughtExceptions: getShouldIgnoreCaughtExceptions(state),
+ breakpointsDisabled: getBreakpointsDisabled(state),
+ breakpoints: getBreakpoints(state),
+ breakpointsLoading: getBreakpointsLoading(state)
+ };
+ }, dispatch => bindActionCreators(actions, dispatch))(RightSidebar);
+
+/***/ },
+/* 417 */
+/***/ function(module, exports, __webpack_require__) {
+
+ var React = __webpack_require__(2);
+
+ var _require = __webpack_require__(19);
+
+ var connect = _require.connect;
+
+ var _require2 = __webpack_require__(3);
+
+ var bindActionCreators = _require2.bindActionCreators;
+
+ var ImPropTypes = __webpack_require__(235);
+ var classnames = __webpack_require__(211);
+ var actions = __webpack_require__(262);
+
+ var _require3 = __webpack_require__(259);
+
+ var getSource = _require3.getSource;
+ var getPause = _require3.getPause;
+ var getBreakpoints = _require3.getBreakpoints;
+
+ var _require4 = __webpack_require__(255);
+
+ var makeLocationId = _require4.makeLocationId;
+
+ var _require5 = __webpack_require__(244);
+
+ var truncateStr = _require5.truncateStr;
+ var dom = React.DOM;
+ var PropTypes = React.PropTypes;
+
+ var _require6 = __webpack_require__(244);
+
+ var endTruncateStr = _require6.endTruncateStr;
+
+ var _require7 = __webpack_require__(278);
+
+ var basename = _require7.basename;
+
+ var CloseButton = __webpack_require__(336);
+
+ __webpack_require__(418);
+
+ function isCurrentlyPausedAtBreakpoint(state, breakpoint) {
+ var pause = getPause(state);
+ if (!pause || pause.get("isInterrupted")) {
+ return false;
+ }
+
+ var bpId = makeLocationId(breakpoint.location);
+ var pausedId = makeLocationId(pause.getIn(["frame", "location"]).toJS());
+
+ return bpId === pausedId;
+ }
+
+ function renderSourceLocation(source, line) {
+ var url = source.get("url") ? basename(source.get("url")) : null;
+ // const line = url !== "" ? `: ${line}` : "";
+ return url ? dom.div({ className: "location" }, `${ endTruncateStr(url, 30) }: ${ line }`) : null;
+ }
+
+ var Breakpoints = React.createClass({
+ propTypes: {
+ breakpoints: ImPropTypes.map.isRequired,
+ enableBreakpoint: PropTypes.func.isRequired,
+ disableBreakpoint: PropTypes.func.isRequired,
+ selectSource: PropTypes.func.isRequired,
+ removeBreakpoint: PropTypes.func.isRequired
+ },
+
+ displayName: "Breakpoints",
+
+ handleCheckbox(breakpoint) {
+ if (breakpoint.loading) {
+ return;
+ }
+
+ if (breakpoint.disabled) {
+ this.props.enableBreakpoint(breakpoint.location);
+ } else {
+ this.props.disableBreakpoint(breakpoint.location);
+ }
+ },
+
+ selectBreakpoint(breakpoint) {
+ var sourceId = breakpoint.location.sourceId;
+ var line = breakpoint.location.line;
+ this.props.selectSource(sourceId, { line });
+ },
+
+ removeBreakpoint(event, breakpoint) {
+ event.stopPropagation();
+ this.props.removeBreakpoint(breakpoint.location);
+ },
+
+ renderBreakpoint(breakpoint) {
+ var snippet = truncateStr(breakpoint.text || "", 30);
+ var locationId = breakpoint.locationId;
+ var line = breakpoint.location.line;
+ var isCurrentlyPaused = breakpoint.isCurrentlyPaused;
+ var isDisabled = breakpoint.disabled;
+
+ return dom.div({
+ className: classnames({
+ breakpoint,
+ paused: isCurrentlyPaused,
+ disabled: isDisabled
+ }),
+ key: locationId,
+ onClick: () => this.selectBreakpoint(breakpoint)
+ }, dom.input({
+ type: "checkbox",
+ className: "breakpoint-checkbox",
+ checked: !isDisabled,
+ onChange: () => this.handleCheckbox(breakpoint),
+ // Prevent clicking on the checkbox from triggering the onClick of
+ // the surrounding div
+ onClick: ev => ev.stopPropagation()
+ }), dom.div({ className: "breakpoint-label", title: breakpoint.text }, dom.div({}, renderSourceLocation(breakpoint.location.source, line))), dom.div({ className: "breakpoint-snippet" }, snippet), CloseButton({
+ handleClick: ev => this.removeBreakpoint(ev, breakpoint)
+ }));
+ },
+
+ render() {
+ var breakpoints = this.props.breakpoints;
+
+ return dom.div({ className: "pane breakpoints-list" }, breakpoints.size === 0 ? dom.div({ className: "pane-info" }, "No Breakpoints") : breakpoints.valueSeq().map(bp => {
+ return this.renderBreakpoint(bp);
+ }));
+ }
+ });
+
+ function _getBreakpoints(state) {
+ return getBreakpoints(state).map(bp => {
+ var source = getSource(state, bp.location.sourceId);
+ var isCurrentlyPaused = isCurrentlyPausedAtBreakpoint(state, bp);
+ var locationId = makeLocationId(bp.location);
+
+ bp = Object.assign({}, bp);
+ bp.location.source = source;
+ bp.locationId = locationId;
+ bp.isCurrentlyPaused = isCurrentlyPaused;
+ return bp;
+ }).filter(bp => bp.location.source);
+ }
+
+ module.exports = connect((state, props) => ({
+ breakpoints: _getBreakpoints(state)
+ }), dispatch => bindActionCreators(actions, dispatch))(Breakpoints);
+
+/***/ },
+/* 418 */
+/***/ function(module, exports) {
+
+ // removed by extract-text-webpack-plugin
+
+/***/ },
+/* 419 */,
+/* 420 */
+/***/ function(module, exports, __webpack_require__) {
+
+ var React = __webpack_require__(2);
+
+ var _require = __webpack_require__(19);
+
+ var connect = _require.connect;
+
+ var _require2 = __webpack_require__(3);
+
+ var bindActionCreators = _require2.bindActionCreators;
+
+ var ImPropTypes = __webpack_require__(235);
+ var actions = __webpack_require__(262);
+
+ var _require3 = __webpack_require__(259);
+
+ var getExpressions = _require3.getExpressions;
+ var getPause = _require3.getPause;
+
+ var Rep = React.createFactory(__webpack_require__(421));
+ var CloseButton = React.createFactory(__webpack_require__(336));
+ var dom = React.DOM;
+ var PropTypes = React.PropTypes;
+
+
+ __webpack_require__(422);
+
+ var Expressions = React.createClass({
+ propTypes: {
+ expressions: ImPropTypes.list,
+ addExpression: PropTypes.func,
+ updateExpression: PropTypes.func,
+ deleteExpression: PropTypes.func,
+ expressionInputVisibility: PropTypes.bool,
+ loadObjectProperties: PropTypes.func,
+ loadedObjects: ImPropTypes.map
+ },
+
+ displayName: "Expressions",
+
+ inputKeyPress(e, _ref) {
+ var id = _ref.id;
+
+ if (e.key !== "Enter") {
+ return;
+ }
+ var addExpression = this.props.addExpression;
+
+ var expression = {
+ input: e.target.value
+ };
+ if (id !== undefined) {
+ expression.id = id;
+ }
+ e.target.value = "";
+ addExpression(expression);
+ },
+
+ updateExpression(e, _ref2) {
+ var id = _ref2.id;
+
+ e.stopPropagation();
+ var updateExpression = this.props.updateExpression;
+
+ var expression = {
+ id,
+ input: e.target.textContent
+ };
+ updateExpression(expression);
+ },
+
+ renderExpressionValue(value) {
+ if (!value) {
+ return dom.span({ className: "expression-error" }, "<not available>");
+ }
+ if (value.exception) {
+ return Rep({ object: value.exception });
+ }
+ return Rep({ object: value.result });
+ },
+
+ deleteExpression(e, expression) {
+ e.stopPropagation();
+ var deleteExpression = this.props.deleteExpression;
+
+ deleteExpression(expression);
+ },
+
+ renderExpressionUpdating(expression) {
+ return dom.span({ className: "expression-input-container" }, dom.input({ type: "text",
+ className: "input-expression",
+ onKeyPress: e => this.inputKeyPress(e, expression),
+ defaultValue: expression.input,
+ ref: c => {
+ this._input = c;
+ }
+ }));
+ },
+
+ renderExpression(expression) {
+ return dom.span({ className: "expression-output-container",
+ key: expression.id }, dom.span({ className: "expression-input",
+ onClick: e => this.updateExpression(e, expression) }, expression.input), dom.span({ className: "expression-seperator" }, ": "), dom.span({ className: "expression-value" }, this.renderExpressionValue(expression.value)), CloseButton({ handleClick: e => this.deleteExpression(e, expression) }));
+ },
+
+ renderExpressionContainer(expression) {
+ return dom.div({ className: "expression-container",
+ key: expression.id + expression.input }, expression.updating ? this.renderExpressionUpdating(expression) : this.renderExpression(expression));
+ },
+
+ componentDidUpdate() {
+ if (this._input) {
+ this._input.focus();
+ }
+ },
+
+ render() {
+ var expressions = this.props.expressions;
+
+ return dom.span({ className: "pane expressions-list" }, this.props.expressionInputVisibility ? dom.input({ type: "text",
+ className: "input-expression",
+ placeholder: "Add Watch Expression",
+ onKeyPress: e => this.inputKeyPress(e, {}) }) : null, expressions.toSeq().map(expression => this.renderExpressionContainer(expression)));
+ }
+ });
+
+ module.exports = connect(state => ({ pauseInfo: getPause(state),
+ expressions: getExpressions(state)
+ }), dispatch => bindActionCreators(actions, dispatch))(Expressions);
+
+/***/ },
+/* 421 */
+/***/ function(module, exports, __webpack_require__) {
+
+ var React = __webpack_require__(2);
+
+ var _require = __webpack_require__(30);
+
+ var rep = _require.rep;
+ var Grip = _require.Grip;
+
+ var Rep = React.createFactory(rep);
+
+ function renderRep(_ref) {
+ var object = _ref.object;
+ var mode = _ref.mode;
+
+ return Rep({ object, defaultRep: Grip, mode });
+ }
+
+ module.exports = renderRep;
+
+/***/ },
+/* 422 */
+/***/ function(module, exports) {
+
+ // removed by extract-text-webpack-plugin
+
+/***/ },
+/* 423 */,
+/* 424 */
+/***/ function(module, exports, __webpack_require__) {
+
+ var React = __webpack_require__(2);
+
+ var _require = __webpack_require__(3);
+
+ var bindActionCreators = _require.bindActionCreators;
+
+ var _require2 = __webpack_require__(19);
+
+ var connect = _require2.connect;
+
+ var ImPropTypes = __webpack_require__(235);
+ var actions = __webpack_require__(262);
+
+ var _require3 = __webpack_require__(259);
+
+ var getSelectedFrame = _require3.getSelectedFrame;
+ var getLoadedObjects = _require3.getLoadedObjects;
+ var getPause = _require3.getPause;
+
+ var ObjectInspector = React.createFactory(__webpack_require__(425));
+ var dom = React.DOM;
+ var PropTypes = React.PropTypes;
+
+ var toPairs = __webpack_require__(428);
+
+ __webpack_require__(442);
+
+ function info(text) {
+ return dom.div({ className: "pane-info" }, text);
+ }
+
+ // Create the tree nodes representing all the variables and arguments
+ // for the bindings from a scope.
+ function getBindingVariables(bindings, parentName) {
+ var args = bindings.arguments.map(arg => toPairs(arg)[0]);
+ var variables = toPairs(bindings.variables);
+
+ return args.concat(variables).filter(binding => !(binding[1].value.missingArguments || binding[1].value.optimizedOut)).map(binding => ({
+ name: binding[0],
+ path: parentName + "/" + binding[0],
+ contents: binding[1]
+ }));
+ }
+
+ function getSpecialVariables(pauseInfo, path) {
+ var thrown = pauseInfo.getIn(["why", "frameFinished", "throw"]);
+ var returned = pauseInfo.getIn(["why", "frameFinished", "return"]);
+ var vars = [];
+
+ if (thrown) {
+ // handle dehydrating exception strings and errors.
+ thrown = thrown.toJS ? thrown.toJS() : thrown;
+
+ vars.push({
+ name: "<exception>",
+ path: path + "/<exception>",
+ contents: { value: thrown }
+ });
+ }
+
+ if (returned) {
+ vars.push({
+ name: "<return>",
+ path: path + "/<return>",
+ contents: { value: returned.toJS() }
+ });
+ }
+
+ return vars;
+ }
+
+ function getThisVariable(frame, path) {
+ var this_ = frame.this;
+
+ if (!this_) {
+ return null;
+ }
+
+ return {
+ name: "<this>",
+ path: path + "/<this>",
+ contents: { value: this_ }
+ };
+ }
+
+ function getScopes(pauseInfo, selectedFrame) {
+ if (!pauseInfo || !selectedFrame) {
+ return null;
+ }
+
+ var selectedScope = selectedFrame.scope;
+
+ if (!selectedScope) {
+ return null;
+ }
+
+ var scopes = [];
+
+ var scope = selectedScope;
+ var pausedScopeActor = pauseInfo.getIn(["frame", "scope"]).get("actor");
+
+ do {
+ var type = scope.type;
+ var key = scope.actor;
+ if (type === "function" || type === "block") {
+ var bindings = scope.bindings;
+ var title = void 0;
+ if (type === "function") {
+ title = scope.function.displayName || "(anonymous)";
+ } else {
+ title = "Block";
+ }
+
+ var vars = getBindingVariables(bindings, title);
+
+ // show exception, return, and this variables in innermost scope
+ if (scope.actor === pausedScopeActor) {
+ vars = vars.concat(getSpecialVariables(pauseInfo, key));
+ }
+
+ if (scope.actor === selectedScope.actor) {
+ var this_ = getThisVariable(selectedFrame, key);
+
+ if (this_) {
+ vars.push(this_);
+ }
+ }
+
+ if (vars && vars.length) {
+ vars.sort((a, b) => a.name.localeCompare(b.name));
+ scopes.push({ name: title, path: key, contents: vars });
+ }
+ } else if (type === "object") {
+ var value = scope.object;
+ // If this is the global window scope, mark it as such so that it will
+ // preview Window: Global instead of Window: Window
+ if (value.class === "Window") {
+ value = Object.assign({}, scope.object, { isGlobal: true });
+ }
+ scopes.push({
+ name: scope.object.class,
+ path: key,
+ contents: { value }
+ });
+ }
+ } while (scope = scope.parent); // eslint-disable-line no-cond-assign
+
+ return scopes;
+ }
+
+ var Scopes = React.createClass({
+ propTypes: {
+ pauseInfo: ImPropTypes.map,
+ loadedObjects: ImPropTypes.map,
+ loadObjectProperties: PropTypes.func,
+ selectedFrame: PropTypes.object
+ },
+
+ displayName: "Scopes",
+
+ getInitialState() {
+ var _props = this.props;
+ var pauseInfo = _props.pauseInfo;
+ var selectedFrame = _props.selectedFrame;
+
+ return { scopes: getScopes(pauseInfo, selectedFrame) };
+ },
+
+ componentWillReceiveProps(nextProps) {
+ var _props2 = this.props;
+ var pauseInfo = _props2.pauseInfo;
+ var selectedFrame = _props2.selectedFrame;
+
+ var pauseInfoChanged = pauseInfo !== nextProps.pauseInfo;
+ var selectedFrameChange = selectedFrame !== nextProps.selectedFrame;
+
+ if (pauseInfoChanged || selectedFrameChange) {
+ this.setState({
+ scopes: getScopes(nextProps.pauseInfo, nextProps.selectedFrame)
+ });
+ }
+ },
+
+ render() {
+ var _props3 = this.props;
+ var pauseInfo = _props3.pauseInfo;
+ var loadObjectProperties = _props3.loadObjectProperties;
+ var loadedObjects = _props3.loadedObjects;
+ var scopes = this.state.scopes;
+
+
+ var scopeInspector = info(L10N.getStr("scopes.notAvailable"));
+ if (scopes) {
+ scopeInspector = ObjectInspector({
+ roots: scopes,
+ getObjectProperties: id => loadedObjects.get(id),
+ loadObjectProperties: loadObjectProperties
+ });
+ }
+
+ return dom.div({ className: "pane scopes-list" }, pauseInfo ? scopeInspector : info(L10N.getStr("scopes.notPaused")));
+ }
+ });
+
+ module.exports = connect(state => ({
+ pauseInfo: getPause(state),
+ selectedFrame: getSelectedFrame(state),
+ loadedObjects: getLoadedObjects(state)
+ }), dispatch => bindActionCreators(actions, dispatch))(Scopes);
+
+/***/ },
+/* 425 */
+/***/ function(module, exports, __webpack_require__) {
+
+ var React = __webpack_require__(2);
+ var classnames = __webpack_require__(211);
+ var ManagedTree = React.createFactory(__webpack_require__(394));
+ var Svg = __webpack_require__(310);
+ var Rep = __webpack_require__(421);
+ var dom = React.DOM;
+ var PropTypes = React.PropTypes;
+
+
+ __webpack_require__(426);
+
+ // This implements a component that renders an interactive inspector
+ // for looking at JavaScript objects. It expects descriptions of
+ // objects from the protocol, and will dynamically fetch child
+ // properties as objects are expanded.
+ //
+ // If you want to inspect a single object, pass the name and the
+ // protocol descriptor of it:
+ //
+ // ObjectInspector({
+ // name: "foo",
+ // desc: { writable: true, ..., { value: { actor: "1", ... }}},
+ // ...
+ // })
+ //
+ // If you want multiple top-level objects (like scopes), you can pass
+ // an array of manually constructed nodes as `roots`:
+ //
+ // ObjectInspector({
+ // roots: [{ name: ... }, ...],
+ // ...
+ // });
+
+ // There are 3 types of nodes: a simple node with a children array, an
+ // object that has properties that should be children when they are
+ // fetched, and a primitive value that should be displayed with no
+ // children.
+
+ function nodeHasChildren(item) {
+ return Array.isArray(item.contents);
+ }
+
+ function nodeHasProperties(item) {
+ return !nodeHasChildren(item) && item.contents.value.type === "object";
+ }
+
+ function nodeIsPrimitive(item) {
+ return !nodeHasChildren(item) && !nodeHasProperties(item);
+ }
+
+ function createNode(name, path, contents) {
+ // The path is important to uniquely identify the item in the entire
+ // tree. This helps debugging & optimizes React's rendering of large
+ // lists. The path will be separated by property name,
+ // i.e. `{ foo: { bar: { baz: 5 }}}` will have a path of `foo/bar/baz`
+ // for the inner object.
+ return { name, path, contents };
+ }
+
+ var ObjectInspector = React.createClass({
+ propTypes: {
+ name: PropTypes.string,
+ desc: PropTypes.object,
+ roots: PropTypes.array,
+ getObjectProperties: PropTypes.func.isRequired,
+ loadObjectProperties: PropTypes.func.isRequired
+ },
+
+ displayName: "ObjectInspector",
+
+ getInitialState() {
+ // Cache of dynamically built nodes. We shouldn't need to clear
+ // this out ever, since we don't ever "switch out" the object
+ // being inspected.
+ this.actorCache = {};
+ return {};
+ },
+
+ makeNodesForProperties(objProps, parentPath) {
+ var ownProperties = objProps.ownProperties;
+ var prototype = objProps.prototype;
+
+
+ var nodes = Object.keys(ownProperties).filter(name => {
+ // Ignore non-concrete values like getters and setters
+ // for now by making sure we have a value.
+ return "value" in ownProperties[name];
+ }).map(name => {
+ return createNode(name, parentPath + "/" + name, ownProperties[name]);
+ });
+
+ // Add the prototype if it exists and is not null
+ if (prototype && prototype.type !== "null") {
+ nodes.push(createNode("__proto__", parentPath + "/__proto__", { value: prototype }));
+ }
+
+ return nodes;
+ },
+
+ getChildren(item) {
+ var getObjectProperties = this.props.getObjectProperties;
+
+ var obj = item.contents;
+
+ // Nodes can either have children already, or be an object with
+ // properties that we need to go and fetch.
+ if (nodeHasChildren(item)) {
+ return item.contents;
+ } else if (nodeHasProperties(item)) {
+ var actor = obj.value.actor;
+
+ // Because we are dynamically creating the tree as the user
+ // expands it (not precalcuated tree structure), we cache child
+ // arrays. This not only helps performance, but is necessary
+ // because the expanded state depends on instances of nodes
+ // being the same across renders. If we didn't do this, each
+ // node would be a new instance every render.
+ var key = item.path;
+ if (this.actorCache[key]) {
+ return this.actorCache[key];
+ }
+
+ var loadedProps = getObjectProperties(actor);
+ if (loadedProps) {
+ var children = this.makeNodesForProperties(loadedProps, item.path);
+ this.actorCache[actor] = children;
+ return children;
+ }
+ return [];
+ }
+ return [];
+ },
+
+ renderItem(item, depth, focused, _, expanded, _ref) {
+ var setExpanded = _ref.setExpanded;
+
+ var objectValue = void 0;
+ if (nodeHasProperties(item) || nodeIsPrimitive(item)) {
+ var object = item.contents.value;
+ objectValue = Rep({ object, mode: "tiny" });
+ }
+
+ return dom.div({ className: classnames("node", { focused }),
+ style: { marginLeft: depth * 15 },
+ onClick: e => {
+ e.stopPropagation();
+ setExpanded(item, !expanded);
+ }
+ }, Svg("arrow", {
+ className: classnames({
+ expanded: expanded,
+ hidden: nodeIsPrimitive(item)
+ })
+ }), dom.span({ className: "object-label" }, item.name), dom.span({ className: "object-delimiter" }, objectValue ? ": " : ""), dom.span({ className: "object-value" }, objectValue || ""));
+ },
+
+ render() {
+ var _props = this.props;
+ var name = _props.name;
+ var desc = _props.desc;
+ var loadObjectProperties = _props.loadObjectProperties;
+
+
+ var roots = this.props.roots;
+ if (!roots) {
+ roots = [createNode(name, name, desc)];
+ }
+
+ return ManagedTree({
+ itemHeight: 20,
+ getParent: item => null,
+ getChildren: this.getChildren,
+ getRoots: () => roots,
+ getKey: item => item.path,
+ autoExpand: 0,
+ disabledFocus: true,
+ onExpand: item => {
+ if (nodeHasProperties(item)) {
+ loadObjectProperties(item.contents.value);
+ }
+ },
+
+ renderItem: this.renderItem
+ });
+ }
+ });
+
+ module.exports = ObjectInspector;
+
+/***/ },
+/* 426 */
+/***/ function(module, exports) {
+
+ // removed by extract-text-webpack-plugin
+
+/***/ },
+/* 427 */,
+/* 428 */
+/***/ function(module, exports, __webpack_require__) {
+
+ var createToPairs = __webpack_require__(429),
+ keys = __webpack_require__(439);
+
+ /**
+ * Creates an array of own enumerable string keyed-value pairs for `object`
+ * which can be consumed by `_.fromPairs`. If `object` is a map or set, its
+ * entries are returned.
+ *
+ * @static
+ * @memberOf _
+ * @since 4.0.0
+ * @alias entries
+ * @category Object
+ * @param {Object} object The object to query.
+ * @returns {Array} Returns the key-value pairs.
+ * @example
+ *
+ * function Foo() {
+ * this.a = 1;
+ * this.b = 2;
+ * }
+ *
+ * Foo.prototype.c = 3;
+ *
+ * _.toPairs(new Foo);
+ * // => [['a', 1], ['b', 2]] (iteration order is not guaranteed)
+ */
+ var toPairs = createToPairs(keys);
+
+ module.exports = toPairs;
+
+
+/***/ },
+/* 429 */
+/***/ function(module, exports, __webpack_require__) {
+
+ var baseToPairs = __webpack_require__(430),
+ getTag = __webpack_require__(431),
+ mapToArray = __webpack_require__(437),
+ setToPairs = __webpack_require__(438);
+
+ /** `Object#toString` result references. */
+ var mapTag = '[object Map]',
+ setTag = '[object Set]';
+
+ /**
+ * Creates a `_.toPairs` or `_.toPairsIn` function.
+ *
+ * @private
+ * @param {Function} keysFunc The function to get the keys of a given object.
+ * @returns {Function} Returns the new pairs function.
+ */
+ function createToPairs(keysFunc) {
+ return function(object) {
+ var tag = getTag(object);
+ if (tag == mapTag) {
+ return mapToArray(object);
+ }
+ if (tag == setTag) {
+ return setToPairs(object);
+ }
+ return baseToPairs(object, keysFunc(object));
+ };
+ }
+
+ module.exports = createToPairs;
+
+
+/***/ },
+/* 430 */
+/***/ function(module, exports, __webpack_require__) {
+
+ var arrayMap = __webpack_require__(135);
+
+ /**
+ * The base implementation of `_.toPairs` and `_.toPairsIn` which creates an array
+ * of key-value pairs for `object` corresponding to the property names of `props`.
+ *
+ * @private
+ * @param {Object} object The object to query.
+ * @param {Array} props The property names to get values for.
+ * @returns {Object} Returns the key-value pairs.
+ */
+ function baseToPairs(object, props) {
+ return arrayMap(props, function(key) {
+ return [key, object[key]];
+ });
+ }
+
+ module.exports = baseToPairs;
+
+
+/***/ },
+/* 431 */
+/***/ function(module, exports, __webpack_require__) {
+
+ var DataView = __webpack_require__(432),
+ Map = __webpack_require__(125),
+ Promise = __webpack_require__(433),
+ Set = __webpack_require__(434),
+ WeakMap = __webpack_require__(435),
+ baseGetTag = __webpack_require__(436),
+ toSource = __webpack_require__(111);
+
+ /** `Object#toString` result references. */
+ var mapTag = '[object Map]',
+ objectTag = '[object Object]',
+ promiseTag = '[object Promise]',
+ setTag = '[object Set]',
+ weakMapTag = '[object WeakMap]';
+
+ var dataViewTag = '[object DataView]';
+
+ /** Used for built-in method references. */
+ var objectProto = Object.prototype;
+
+ /**
+ * Used to resolve the
+ * [`toStringTag`](http://ecma-international.org/ecma-262/7.0/#sec-object.prototype.tostring)
+ * of values.
+ */
+ var objectToString = objectProto.toString;
+
+ /** Used to detect maps, sets, and weakmaps. */
+ var dataViewCtorString = toSource(DataView),
+ mapCtorString = toSource(Map),
+ promiseCtorString = toSource(Promise),
+ setCtorString = toSource(Set),
+ weakMapCtorString = toSource(WeakMap);
+
+ /**
+ * Gets the `toStringTag` of `value`.
+ *
+ * @private
+ * @param {*} value The value to query.
+ * @returns {string} Returns the `toStringTag`.
+ */
+ var getTag = baseGetTag;
+
+ // Fallback for data views, maps, sets, and weak maps in IE 11 and promises in Node.js < 6.
+ if ((DataView && getTag(new DataView(new ArrayBuffer(1))) != dataViewTag) ||
+ (Map && getTag(new Map) != mapTag) ||
+ (Promise && getTag(Promise.resolve()) != promiseTag) ||
+ (Set && getTag(new Set) != setTag) ||
+ (WeakMap && getTag(new WeakMap) != weakMapTag)) {
+ getTag = function(value) {
+ var result = objectToString.call(value),
+ Ctor = result == objectTag ? value.constructor : undefined,
+ ctorString = Ctor ? toSource(Ctor) : undefined;
+
+ if (ctorString) {
+ switch (ctorString) {
+ case dataViewCtorString: return dataViewTag;
+ case mapCtorString: return mapTag;
+ case promiseCtorString: return promiseTag;
+ case setCtorString: return setTag;
+ case weakMapCtorString: return weakMapTag;
+ }
+ }
+ return result;
+ };
+ }
+
+ module.exports = getTag;
+
+
+/***/ },
+/* 432 */
+/***/ function(module, exports, __webpack_require__) {
+
+ var getNative = __webpack_require__(103),
+ root = __webpack_require__(109);
+
+ /* Built-in method references that are verified to be native. */
+ var DataView = getNative(root, 'DataView');
+
+ module.exports = DataView;
+
+
+/***/ },
+/* 433 */
+/***/ function(module, exports, __webpack_require__) {
+
+ var getNative = __webpack_require__(103),
+ root = __webpack_require__(109);
+
+ /* Built-in method references that are verified to be native. */
+ var Promise = getNative(root, 'Promise');
+
+ module.exports = Promise;
+
+
+/***/ },
+/* 434 */
+/***/ function(module, exports, __webpack_require__) {
+
+ var getNative = __webpack_require__(103),
+ root = __webpack_require__(109);
+
+ /* Built-in method references that are verified to be native. */
+ var Set = getNative(root, 'Set');
+
+ module.exports = Set;
+
+
+/***/ },
+/* 435 */
+/***/ function(module, exports, __webpack_require__) {
+
+ var getNative = __webpack_require__(103),
+ root = __webpack_require__(109);
+
+ /* Built-in method references that are verified to be native. */
+ var WeakMap = getNative(root, 'WeakMap');
+
+ module.exports = WeakMap;
+
+
+/***/ },
+/* 436 */
+/***/ function(module, exports) {
+
+ /** Used for built-in method references. */
+ var objectProto = Object.prototype;
+
+ /**
+ * Used to resolve the
+ * [`toStringTag`](http://ecma-international.org/ecma-262/7.0/#sec-object.prototype.tostring)
+ * of values.
+ */
+ var objectToString = objectProto.toString;
+
+ /**
+ * The base implementation of `getTag`.
+ *
+ * @private
+ * @param {*} value The value to query.
+ * @returns {string} Returns the `toStringTag`.
+ */
+ function baseGetTag(value) {
+ return objectToString.call(value);
+ }
+
+ module.exports = baseGetTag;
+
+
+/***/ },
+/* 437 */
+/***/ function(module, exports) {
+
+ /**
+ * Converts `map` to its key-value pairs.
+ *
+ * @private
+ * @param {Object} map The map to convert.
+ * @returns {Array} Returns the key-value pairs.
+ */
+ function mapToArray(map) {
+ var index = -1,
+ result = Array(map.size);
+
+ map.forEach(function(value, key) {
+ result[++index] = [key, value];
+ });
+ return result;
+ }
+
+ module.exports = mapToArray;
+
+
+/***/ },
+/* 438 */
+/***/ function(module, exports) {
+
+ /**
+ * Converts `set` to its value-value pairs.
+ *
+ * @private
+ * @param {Object} set The set to convert.
+ * @returns {Array} Returns the value-value pairs.
+ */
+ function setToPairs(set) {
+ var index = -1,
+ result = Array(set.size);
+
+ set.forEach(function(value) {
+ result[++index] = [value, value];
+ });
+ return result;
+ }
+
+ module.exports = setToPairs;
+
+
+/***/ },
+/* 439 */
+/***/ function(module, exports, __webpack_require__) {
+
+ var arrayLikeKeys = __webpack_require__(379),
+ baseKeys = __webpack_require__(440),
+ isArrayLike = __webpack_require__(367);
+
+ /**
+ * Creates an array of the own enumerable property names of `object`.
+ *
+ * **Note:** Non-object values are coerced to objects. See the
+ * [ES spec](http://ecma-international.org/ecma-262/7.0/#sec-object.keys)
+ * for more details.
+ *
+ * @static
+ * @since 0.1.0
+ * @memberOf _
+ * @category Object
+ * @param {Object} object The object to query.
+ * @returns {Array} Returns the array of property names.
+ * @example
+ *
+ * function Foo() {
+ * this.a = 1;
+ * this.b = 2;
+ * }
+ *
+ * Foo.prototype.c = 3;
+ *
+ * _.keys(new Foo);
+ * // => ['a', 'b'] (iteration order is not guaranteed)
+ *
+ * _.keys('hi');
+ * // => ['0', '1']
+ */
+ function keys(object) {
+ return isArrayLike(object) ? arrayLikeKeys(object) : baseKeys(object);
+ }
+
+ module.exports = keys;
+
+
+/***/ },
+/* 440 */
+/***/ function(module, exports, __webpack_require__) {
+
+ var isPrototype = __webpack_require__(363),
+ nativeKeys = __webpack_require__(441);
+
+ /** Used for built-in method references. */
+ var objectProto = Object.prototype;
+
+ /** Used to check objects for own properties. */
+ var hasOwnProperty = objectProto.hasOwnProperty;
+
+ /**
+ * The base implementation of `_.keys` which doesn't treat sparse arrays as dense.
+ *
+ * @private
+ * @param {Object} object The object to query.
+ * @returns {Array} Returns the array of property names.
+ */
+ function baseKeys(object) {
+ if (!isPrototype(object)) {
+ return nativeKeys(object);
+ }
+ var result = [];
+ for (var key in Object(object)) {
+ if (hasOwnProperty.call(object, key) && key != 'constructor') {
+ result.push(key);
+ }
+ }
+ return result;
+ }
+
+ module.exports = baseKeys;
+
+
+/***/ },
+/* 441 */
+/***/ function(module, exports, __webpack_require__) {
+
+ var overArg = __webpack_require__(7);
+
+ /* Built-in method references for those with the same name as other `lodash` methods. */
+ var nativeKeys = overArg(Object.keys, Object);
+
+ module.exports = nativeKeys;
+
+
+/***/ },
+/* 442 */
+/***/ function(module, exports) {
+
+ // removed by extract-text-webpack-plugin
+
+/***/ },
+/* 443 */,
+/* 444 */
+/***/ function(module, exports, __webpack_require__) {
+
+ var React = __webpack_require__(2);
+ var dom = React.DOM;
+ var PropTypes = React.PropTypes;
+ var div = dom.div;
+
+ var _require = __webpack_require__(3);
+
+ var bindActionCreators = _require.bindActionCreators;
+
+ var _require2 = __webpack_require__(19);
+
+ var connect = _require2.connect;
+
+ var ImPropTypes = __webpack_require__(235);
+ var actions = __webpack_require__(262);
+
+ var _require3 = __webpack_require__(244);
+
+ var endTruncateStr = _require3.endTruncateStr;
+
+ var _require4 = __webpack_require__(277);
+
+ var getFilename = _require4.getFilename;
+
+ var _require5 = __webpack_require__(259);
+
+ var getFrames = _require5.getFrames;
+ var getSelectedFrame = _require5.getSelectedFrame;
+ var getSource = _require5.getSource;
+
+
+ if (typeof window == "object") {
+ __webpack_require__(445);
+ }
+
+ var NUM_FRAMES_SHOWN = 7;
+
+ function renderFrameTitle(frame) {
+ return div({ className: "title" }, endTruncateStr(frame.displayName, 40));
+ }
+
+ function renderFrameLocation(frame) {
+ var filename = getFilename(frame.source);
+ return div({ className: "location" }, `${ filename }: ${ frame.location.line }`);
+ }
+
+ var Frames = React.createClass({
+ propTypes: {
+ frames: ImPropTypes.list,
+ selectedFrame: PropTypes.object,
+ selectFrame: PropTypes.func.isRequired
+ },
+
+ displayName: "Frames",
+
+ getInitialState() {
+ return { showAllFrames: false };
+ },
+
+ toggleFramesDisplay() {
+ this.setState({
+ showAllFrames: !this.state.showAllFrames
+ });
+ },
+
+ renderFrame(frame) {
+ var _props = this.props;
+ var selectedFrame = _props.selectedFrame;
+ var selectFrame = _props.selectFrame;
+
+
+ var selectedClass = selectedFrame && (selectedFrame.id === frame.id ? "selected" : "");
+
+ return dom.li({ key: frame.id,
+ className: `frame ${ selectedClass }`,
+ onMouseDown: () => selectFrame(frame),
+ tabIndex: 0
+ }, renderFrameTitle(frame), renderFrameLocation(frame));
+ },
+
+ renderFrames() {
+ var frames = this.props.frames;
+
+ if (!frames) {
+ return null;
+ }
+
+ var numFramesToShow = this.state.showAllFrames ? frames.size : NUM_FRAMES_SHOWN;
+ frames = frames.slice(0, numFramesToShow);
+
+ return dom.ul({}, frames.map(frame => this.renderFrame(frame)));
+ },
+
+ renderToggleButton() {
+ var frames = this.props.frames;
+
+ var buttonMessage = this.state.showAllFrames ? L10N.getStr("callStack.collapse") : L10N.getStr("callStack.expand");
+
+ if (frames.size < NUM_FRAMES_SHOWN) {
+ return null;
+ }
+
+ return dom.div({ className: "show-more", onClick: this.toggleFramesDisplay }, buttonMessage);
+ },
+
+ render() {
+ var frames = this.props.frames;
+
+
+ if (!frames) {
+ return div({ className: "pane frames" }, div({ className: "pane-info empty" }, L10N.getStr("callStack.notPaused")));
+ }
+
+ return div({ className: "pane frames" }, this.renderFrames(), this.renderToggleButton());
+ }
+ });
+
+ function getAndProcessFrames(state) {
+ var frames = getFrames(state);
+ if (!frames) {
+ return null;
+ }
+ return frames.filter(frame => getSource(state, frame.location.sourceId)).map(frame => Object.assign({}, frame, {
+ source: getSource(state, frame.location.sourceId).toJS()
+ }));
+ }
+
+ module.exports = connect(state => ({
+ frames: getAndProcessFrames(state),
+ selectedFrame: getSelectedFrame(state)
+ }), dispatch => bindActionCreators(actions, dispatch))(Frames);
+
+/***/ },
+/* 445 */
+/***/ function(module, exports) {
+
+ // removed by extract-text-webpack-plugin
+
+/***/ },
+/* 446 */,
+/* 447 */
+/***/ function(module, exports, __webpack_require__) {
+
+ function _toConsumableArray(arr) { if (Array.isArray(arr)) { for (var i = 0, arr2 = Array(arr.length); i < arr.length; i++) { arr2[i] = arr[i]; } return arr2; } else { return Array.from(arr); } }
+
+ var React = __webpack_require__(2);
+ var dom = React.DOM;
+ var PropTypes = React.PropTypes;
+ var div = dom.div;
+ var span = dom.span;
+
+ var Svg = __webpack_require__(310);
+
+ __webpack_require__(448);
+
+ var Accordion = React.createClass({
+ propTypes: {
+ items: PropTypes.array
+ },
+
+ displayName: "Accordion",
+
+ getInitialState: function () {
+ return { opened: this.props.items.map(item => item.opened),
+ created: [] };
+ },
+
+ handleHeaderClick: function (i) {
+ var opened = [].concat(_toConsumableArray(this.state.opened));
+ var created = [].concat(_toConsumableArray(this.state.created));
+ var item = this.props.items[i];
+
+ opened[i] = !opened[i];
+ created[i] = true;
+
+ if (opened[i] && item.onOpened) {
+ item.onOpened();
+ }
+
+ this.setState({ opened, created });
+ },
+
+ renderContainer: function (item, i) {
+ var _state = this.state;
+ var opened = _state.opened;
+ var created = _state.created;
+
+ var containerClassName = item.header.toLowerCase().replace(/\s/g, "-") + "-pane";
+
+ return div({ className: containerClassName, key: i }, div({ className: "_header",
+ onClick: () => this.handleHeaderClick(i) }, Svg("arrow", { className: opened[i] ? "expanded" : "" }), item.header, item.buttons ? dom.span({ className: "header-buttons" }, item.buttons.map((button, id) => span({ key: id }, button))) : null), created[i] || opened[i] ? div({ className: "_content",
+ style: { display: opened[i] ? "block" : "none" }
+ }, React.createElement(item.component, item.componentProps || {})) : null);
+ },
+
+ render: function () {
+ return div({ className: "accordion" }, this.props.items.map(this.renderContainer));
+ }
+ });
+
+ module.exports = Accordion;
+
+/***/ },
+/* 448 */
+/***/ function(module, exports) {
+
+ // removed by extract-text-webpack-plugin
+
+/***/ },
+/* 449 */,
+/* 450 */
+/***/ function(module, exports) {
+
+ // removed by extract-text-webpack-plugin
+
+/***/ },
+/* 451 */,
+/* 452 */
+/***/ function(module, exports, __webpack_require__) {
+
+ function _toConsumableArray(arr) { if (Array.isArray(arr)) { for (var i = 0, arr2 = Array(arr.length); i < arr.length; i++) { arr2[i] = arr[i]; } return arr2; } else { return Array.from(arr); } }
+
+ var React = __webpack_require__(2);
+ var dom = React.DOM;
+ var PropTypes = React.PropTypes;
+
+ var ImPropTypes = __webpack_require__(235);
+
+ var _require = __webpack_require__(19);
+
+ var connect = _require.connect;
+
+ var _require2 = __webpack_require__(3);
+
+ var bindActionCreators = _require2.bindActionCreators;
+
+ var _require3 = __webpack_require__(259);
+
+ var getSelectedSource = _require3.getSelectedSource;
+ var getSourceTabs = _require3.getSourceTabs;
+ var getFileSearchState = _require3.getFileSearchState;
+
+ var _require4 = __webpack_require__(277);
+
+ var getFilename = _require4.getFilename;
+
+ var classnames = __webpack_require__(211);
+ var actions = __webpack_require__(262);
+
+ var _require5 = __webpack_require__(89);
+
+ var isEnabled = _require5.isEnabled;
+
+ var CloseButton = __webpack_require__(336);
+ var Svg = __webpack_require__(310);
+ var Dropdown = React.createFactory(__webpack_require__(453));
+
+ var _require6 = __webpack_require__(413);
+
+ var showMenu = _require6.showMenu;
+ var buildMenu = _require6.buildMenu;
+
+
+ __webpack_require__(454);
+ __webpack_require__(456);
+
+ /*
+ * Finds the hidden tabs by comparing the tabs' top offset.
+ * hidden tabs will have a great top offset.
+ *
+ * @param sourceTabs Immutable.list
+ * @param sourceTabEls HTMLCollection
+ *
+ * @returns Immutable.list
+ */
+ function getHiddenTabs(sourceTabs, sourceTabEls) {
+ sourceTabEls = [].slice.call(sourceTabEls);
+ function getTopOffset() {
+ var topOffsets = sourceTabEls.map(t => t.getBoundingClientRect().top);
+ return Math.min.apply(Math, _toConsumableArray(topOffsets));
+ }
+
+ var tabTopOffset = getTopOffset();
+ return sourceTabs.filter((tab, index) => {
+ return sourceTabEls[index].getBoundingClientRect().top > tabTopOffset;
+ });
+ }
+
+ var SourceTabs = React.createClass({
+ propTypes: {
+ sourceTabs: ImPropTypes.list,
+ selectedSource: ImPropTypes.map,
+ selectSource: PropTypes.func.isRequired,
+ closeTab: PropTypes.func.isRequired,
+ toggleFileSearch: PropTypes.func.isRequired
+ },
+
+ displayName: "SourceTabs",
+
+ getInitialState() {
+ return {
+ dropdownShown: false,
+ hiddenSourceTabs: null
+ };
+ },
+
+ componentDidUpdate() {
+ this.updateHiddenSourceTabs(this.props.sourceTabs);
+ },
+
+ onTabContextMenu(event, tab) {
+ event.preventDefault();
+ this.showContextMenu(event, tab);
+ },
+
+ showContextMenu(e, tab) {
+ var _props = this.props;
+ var closeTab = _props.closeTab;
+ var sourceTabs = _props.sourceTabs;
+
+
+ var closeTabLabel = L10N.getStr("sourceTabs.closeTab");
+ var closeOtherTabsLabel = L10N.getStr("sourceTabs.closeOtherTabs");
+ var closeTabsToRightLabel = L10N.getStr("sourceTabs.closeTabsToRight");
+ var closeAllTabsLabel = L10N.getStr("sourceTabs.closeAllTabs");
+
+ var tabs = sourceTabs.map(t => t.get("id"));
+
+ var closeTabMenuItem = {
+ id: "node-menu-close-tab",
+ label: closeTabLabel,
+ accesskey: "C",
+ disabled: false,
+ click: () => closeTab(tab)
+ };
+
+ var closeOtherTabsMenuItem = {
+ id: "node-menu-close-other-tabs",
+ label: closeOtherTabsLabel,
+ accesskey: "O",
+ disabled: false,
+ click: () => {
+ tabs.forEach(t => {
+ if (t !== tab) {
+ closeTab(t);
+ }
+ });
+ }
+ };
+
+ var closeTabsToRightMenuItem = {
+ id: "node-menu-close-tabs-to-right",
+ label: closeTabsToRightLabel,
+ accesskey: "R",
+ disabled: false,
+ click: () => {
+ tabs.reverse().every(t => {
+ if (t === tab) {
+ return false;
+ }
+ closeTab(t);
+ return true;
+ });
+ }
+ };
+
+ var closeAllTabsMenuItem = {
+ id: "node-menu-close-all-tabs",
+ label: closeAllTabsLabel,
+ accesskey: "A",
+ disabled: false,
+ click: () => tabs.forEach(closeTab)
+ };
+
+ showMenu(e, buildMenu([{ item: closeTabMenuItem }, { item: closeOtherTabsMenuItem, hidden: () => tabs.size === 1 }, { item: closeTabsToRightMenuItem, hidden: () => tabs.some((t, i) => t === tab && tabs.size - 1 === i) }, { item: closeAllTabsMenuItem }]));
+ },
+
+ /*
+ * Updates the hiddenSourceTabs state, by
+ * finding the source tabs who have wrapped and are not on the top row.
+ */
+ updateHiddenSourceTabs(sourceTabs) {
+ if (!this.refs.sourceTabs) {
+ return;
+ }
+
+ var sourceTabEls = this.refs.sourceTabs.children;
+ var hiddenSourceTabs = getHiddenTabs(sourceTabs, sourceTabEls);
+
+ if (!hiddenSourceTabs.equals(this.state.hiddenSourceTabs)) {
+ this.setState({ hiddenSourceTabs });
+ }
+ },
+
+ toggleSourcesDropdown(e) {
+ this.setState({
+ dropdownShown: !this.state.dropdownShown
+ });
+ },
+
+ renderDropdownSource(source) {
+ var selectSource = this.props.selectSource;
+
+ var filename = getFilename(source.toJS());
+
+ return dom.li({
+ key: source.get("id"),
+ onClick: () => {
+ // const tabIndex = getLastVisibleTabIndex(sourceTabs, sourceTabEls);
+ var tabIndex = 0;
+ selectSource(source.get("id"), { tabIndex });
+ }
+ }, filename);
+ },
+
+ renderTabs() {
+ var sourceTabs = this.props.sourceTabs;
+ return dom.div({ className: "source-tabs", ref: "sourceTabs" }, sourceTabs.map(this.renderTab));
+ },
+
+ renderTab(source) {
+ var _props2 = this.props;
+ var selectedSource = _props2.selectedSource;
+ var selectSource = _props2.selectSource;
+ var closeTab = _props2.closeTab;
+
+ var filename = getFilename(source.toJS());
+ var active = source.get("id") == selectedSource.get("id");
+
+ function onClickClose(ev) {
+ ev.stopPropagation();
+ closeTab(source.get("id"));
+ }
+
+ return dom.div({
+ className: classnames("source-tab", { active }),
+ key: source.get("id"),
+ onClick: () => selectSource(source.get("id")),
+ onContextMenu: e => this.onTabContextMenu(e, source.get("id")),
+ title: source.get("url")
+ }, dom.div({ className: "filename" }, filename), CloseButton({ handleClick: onClickClose }));
+ },
+
+ renderNewButton() {
+ return dom.div({
+ className: "new-tab-btn",
+ onClick: () => this.props.toggleFileSearch(true)
+ }, Svg("plus"));
+ },
+
+ renderDropdown() {
+ var hiddenSourceTabs = this.state.hiddenSourceTabs;
+ if (!hiddenSourceTabs || hiddenSourceTabs.size == 0) {
+ return dom.div({});
+ }
+
+ return Dropdown({
+ panel: dom.ul({}, this.state.hiddenSourceTabs.map(this.renderDropdownSource))
+ });
+ },
+
+ render() {
+ if (!isEnabled("tabs")) {
+ return dom.div({ className: "source-header" });
+ }
+
+ return dom.div({ className: "source-header" }, this.renderTabs(), this.renderNewButton(), this.renderDropdown());
+ }
+ });
+
+ module.exports = connect(state => ({
+ selectedSource: getSelectedSource(state),
+ sourceTabs: getSourceTabs(state),
+ searchOn: getFileSearchState(state)
+ }), dispatch => bindActionCreators(actions, dispatch))(SourceTabs);
+
+/***/ },
+/* 453 */
+/***/ function(module, exports, __webpack_require__) {
+
+ var React = __webpack_require__(2);
+ var dom = React.DOM;
+ var PropTypes = React.PropTypes;
+
+
+ var Dropdown = React.createClass({
+ propTypes: {
+ panel: PropTypes.object
+ },
+
+ displayName: "Dropdown",
+
+ getInitialState() {
+ return {
+ dropdownShown: false
+ };
+ },
+
+ toggleDropdown(e) {
+ this.setState({
+ dropdownShown: !this.state.dropdownShown
+ });
+ },
+
+ renderPanel() {
+ return dom.div({
+ className: "dropdown",
+ onClick: this.toggleDropdown,
+ style: { display: this.state.dropdownShown ? "block" : "none" }
+ }, this.props.panel);
+ },
+
+ renderButton() {
+ return dom.span({
+ className: "dropdown-button",
+ onClick: this.toggleDropdown
+ }, "»");
+ },
+
+ renderMask() {
+ return dom.div({
+ className: "dropdown-mask",
+ onClick: this.toggleDropdown,
+ style: { display: this.state.dropdownShown ? "block" : "none" }
+ });
+ },
+
+ render() {
+ return dom.div({}, this.renderPanel(), this.renderButton(), this.renderMask());
+ }
+
+ });
+
+ module.exports = Dropdown;
+
+/***/ },
+/* 454 */
+/***/ function(module, exports) {
+
+ // removed by extract-text-webpack-plugin
+
+/***/ },
+/* 455 */,
+/* 456 */
+/***/ function(module, exports) {
+
+ // removed by extract-text-webpack-plugin
+
+/***/ },
+/* 457 */,
+/* 458 */
+/***/ function(module, exports) {
+
+ module.exports = {
+ "breakpoints.header": "Breakpoints",
+ "callStack.header": "Call Stack",
+ "callStack.notPaused": "Not Paused",
+ "callStack.collapse": "Collapse Rows",
+ "callStack.expand": "Expand Rows",
+ "editor.searchResults": "%d of %d results",
+ "editor.noResults": "no results",
+ "editor.addBreakpoint": "Add Breakpoint",
+ "editor.removeBreakpoint": "Remove Breakpoint",
+ "editor.editBreakpoint": "Edit Breakpoint",
+ "editor.addConditionalBreakpoint": "Add Conditional Breakpoint",
+ "scopes.header": "Scopes",
+ "scopes.notAvailable": "Scopes Unavailable",
+ "scopes.notPaused": "Not Paused",
+ "sources.header": "Sources",
+ "sources.search": "%S to search",
+ "watchExpressions.header": "Watch Expressions",
+ "welcome.search": "%S to search for files",
+ "sourceSearch.search": "Search...",
+ "sourceSearch.resultsSummary": "%d instances of \"%S\"",
+ "sourceSearch.noResults": "No files matching %S found",
+ "ignoreExceptions": "Ignore exceptions. Click to pause on uncaught exceptions",
+ "pauseOnUncaughtExceptions": "Pause on uncaught exceptions. Click to pause on all exceptions",
+ "pauseOnExceptions": "Pause on all exceptions. Click to ignore exceptions",
+ "stepOutTooltip": "Step Out (%SF11)",
+ "stepInTooltip": "Step In (%SF11)",
+ "stepOverTooltip": "Step Over (F10)",
+ "resumeButtonTooltip": "Click to resume (F8)",
+ "pausePendingButtonTooltip": "Waiting for next execution",
+ "pauseButtonTooltip": "Click to pause (F8)",
+ "sourceTabs.closeTab": "Close tab",
+ "sourceTabs.closeOtherTabs": "Close others",
+ "sourceTabs.closeTabsToRight": "Close tabs to the right",
+ "sourceTabs.closeAllTabs": "Close all tabs"
+ };
+
+/***/ },
+/* 459 */
+/***/ function(module, exports, __webpack_require__) {
+
+ function _toConsumableArray(arr) { if (Array.isArray(arr)) { for (var i = 0, arr2 = Array(arr.length); i < arr.length; i++) { arr2[i] = arr[i]; } return arr2; } else { return Array.from(arr); } }
+
+ var _require = __webpack_require__(30);
+
+ var sprintf = _require.sprintf;
+
+ var strings = {};
+
+ function setBundle(bundle) {
+ strings = bundle;
+ }
+
+ function getStr(key) {
+ if (!strings[key]) {
+ throw new Error(`L10N key ${ key } cannot be found.`);
+ }
+ return strings[key];
+ }
+
+ function getFormatStr(name) {
+ for (var _len = arguments.length, args = Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) {
+ args[_key - 1] = arguments[_key];
+ }
+
+ return sprintf.apply(undefined, [getStr(name)].concat(_toConsumableArray(args)));
+ }
+
+ module.exports = {
+ getStr,
+ getFormatStr,
+ setBundle
+ };
+
+/***/ }
+/******/ ]);
+//# sourceMappingURL=bundle.js.map
diff --git a/devtools/client/debugger/new/images/Icons.js b/devtools/client/debugger/new/images/Icons.js
new file mode 100644
index 000000000..bda48ef93
--- /dev/null
+++ b/devtools/client/debugger/new/images/Icons.js
@@ -0,0 +1,46 @@
+const React = require("react");
+const InlineSVG = require("svg-inline-react");
+const { DOM: dom } = React;
+
+const DomainIcon = props => {
+ return dom.span(
+ props,
+ React.createElement(InlineSVG, {
+ src: require("./domain.svg")
+ })
+ );
+};
+
+const FileIcon = props => {
+ return dom.span(
+ props,
+ React.createElement(InlineSVG, {
+ src: require("./file.svg")
+ })
+ );
+};
+
+const FolderIcon = props => {
+ return dom.span(
+ props,
+ React.createElement(InlineSVG, {
+ src: require("./folder.svg")
+ })
+ );
+};
+
+const WorkerIcon = props => {
+ return dom.span(
+ props,
+ React.createElement(InlineSVG, {
+ src: require("./worker.svg")
+ })
+ );
+};
+
+module.exports = {
+ DomainIcon,
+ FileIcon,
+ FolderIcon,
+ WorkerIcon
+};
diff --git a/devtools/client/debugger/new/images/Svg.js b/devtools/client/debugger/new/images/Svg.js
new file mode 100644
index 000000000..775aecfc0
--- /dev/null
+++ b/devtools/client/debugger/new/images/Svg.js
@@ -0,0 +1,43 @@
+const React = require("react");
+const InlineSVG = require("svg-inline-react");
+
+const svg = {
+ "angle-brackets": require("./angle-brackets.svg"),
+ "arrow": require("./arrow.svg"),
+ "blackBox": require("./blackBox.svg"),
+ "breakpoint": require("./breakpoint.svg"),
+ "close": require("./close.svg"),
+ "domain": require("./domain.svg"),
+ "file": require("./file.svg"),
+ "folder": require("./folder.svg"),
+ "globe": require("./globe.svg"),
+ "magnifying-glass": require("./magnifying-glass.svg"),
+ "pause": require("./pause.svg"),
+ "pause-exceptions": require("./pause-exceptions.svg"),
+ "plus": require("./plus.svg"),
+ "prettyPrint": require("./prettyPrint.svg"),
+ "resume": require("./resume.svg"),
+ "settings": require("./settings.svg"),
+ "stepIn": require("./stepIn.svg"),
+ "stepOut": require("./stepOut.svg"),
+ "stepOver": require("./stepOver.svg"),
+ "subSettings": require("./subSettings.svg"),
+ "toggleBreakpoints": require("./toggle-breakpoints.svg"),
+ "worker": require("./worker.svg"),
+ "sad-face": require("./sad-face.svg")
+};
+
+module.exports = function(name, props) { // eslint-disable-line
+ if (!svg[name]) {
+ throw new Error("Unknown SVG: " + name);
+ }
+ let className = name;
+ if (props && props.className) {
+ className = `${name} ${props.className}`;
+ }
+ if (name === "subSettings") {
+ className = "";
+ }
+ props = Object.assign({}, props, { className, src: svg[name] });
+ return React.createElement(InlineSVG, props);
+};
diff --git a/devtools/client/debugger/new/images/angle-brackets.svg b/devtools/client/debugger/new/images/angle-brackets.svg
new file mode 100644
index 000000000..b353bee9e
--- /dev/null
+++ b/devtools/client/debugger/new/images/angle-brackets.svg
@@ -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/. -->
+<svg width="16px" height="11px" viewBox="-1 73 16 11" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+ <g id="Shape-Copy-3-+-Shape-Copy-4" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" transform="translate(0.000000, 74.000000)">
+ <path d="M0.749321284,4.16081709 L4.43130681,0.242526751 C4.66815444,-0.00952143591 5.06030999,-0.0211407611 5.30721074,0.216574262 C5.55411149,0.454289284 5.56226116,0.851320812 5.32541353,1.103369 L1.95384971,4.69131519 L5.48809879,8.09407556 C5.73499955,8.33179058 5.74314922,8.72882211 5.50630159,8.9808703 C5.26945396,9.23291849 4.87729841,9.24453781 4.63039766,9.00682279 L0.827097345,5.34502101 C0.749816996,5.31670099 0.677016974,5.27216098 0.613753508,5.21125118 C0.427367989,5.03179997 0.377040713,4.7615583 0.465458792,4.53143559 C0.492371834,4.43667624 0.541703274,4.34676528 0.613628034,4.27022448 C0.654709457,4.22650651 0.70046335,4.19002189 0.749321284,4.16081709 Z" id="Shape-Copy-3" stroke="#FFFFFF" stroke-width="0.05" fill="#DDE1E4"></path>
+ <path d="M13.7119065,5.44453032 L9.77062746,9.09174784 C9.51677479,9.3266604 9.12476399,9.31089603 8.89504684,9.05653714 C8.66532968,8.80217826 8.68489539,8.40554539 8.93874806,8.17063283 L12.5546008,4.82456128 L9.26827469,1.18571135 C9.03855754,0.931352463 9.05812324,0.534719593 9.31197591,0.299807038 C9.56582858,0.0648944831 9.95783938,0.0806588502 10.1875565,0.335017737 L13.72891,4.25625178 C13.8013755,4.28980469 13.8684335,4.3382578 13.9254821,4.40142604 C14.0883019,4.58171146 14.1258883,4.83347168 14.0435812,5.04846202 C14.0126705,5.15680232 13.9526426,5.2583679 13.8641331,5.34027361 C13.8174417,5.38348136 13.7660763,5.41820853 13.7119065,5.44453032 Z" id="Shape-Copy-4" stroke="#FFFFFF" stroke-width="0.05" fill="#DDE1E4"></path>
+ </g>
+</svg>
diff --git a/devtools/client/debugger/new/images/arrow.svg b/devtools/client/debugger/new/images/arrow.svg
new file mode 100644
index 000000000..33a107797
--- /dev/null
+++ b/devtools/client/debugger/new/images/arrow.svg
@@ -0,0 +1,6 @@
+<!-- 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/. -->
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 16 16">
+ <path d="M8 13.4c-.5 0-.9-.2-1.2-.6L.4 5.2C0 4.7-.1 4.3.2 3.7S1 3 1.6 3h12.8c.6 0 1.2.1 1.4.7.3.6.2 1.1-.2 1.6l-6.4 7.6c-.3.4-.7.5-1.2.5z"/>
+</svg> \ No newline at end of file
diff --git a/devtools/client/debugger/new/images/blackBox.svg b/devtools/client/debugger/new/images/blackBox.svg
new file mode 100644
index 000000000..b98d62f13
--- /dev/null
+++ b/devtools/client/debugger/new/images/blackBox.svg
@@ -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/. -->
+<svg width="16" height="16" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg">
+ <g fill-rule="evenodd">
+ <circle cx="8" cy="8.5" r="1.5"/>
+ <path d="M15.498 8.28l-.001-.03v-.002-.004l-.002-.018-.004-.031c0-.002 0-.002 0 0l-.004-.035.006.082c-.037-.296-.133-.501-.28-.661-.4-.522-.915-1.042-1.562-1.604-1.36-1.182-2.74-1.975-4.178-2.309a6.544 6.544 0 0 0-2.755-.042c-.78.153-1.565.462-2.369.91C3.252 5.147 2.207 6 1.252 7.035c-.216.233-.36.398-.499.577-.338.437-.338 1 0 1.437.428.552.941 1.072 1.59 1.635 1.359 1.181 2.739 1.975 4.177 2.308.907.21 1.829.223 2.756.043.78-.153 1.564-.462 2.369-.91 1.097-.612 2.141-1.464 3.097-2.499.217-.235.36-.398.498-.578.12-.128.216-.334.248-.554 0 .01 0 .01-.008.04l.013-.079-.001.011.003-.031.001-.017v.005l.001-.02v.008l.002-.03.001-.05-.001-.044v-.004-.004zm-.954.045v.007l.001.004V8.33v.012l-.001.01v-.005-.005l.002-.015-.001.008c-.002.014-.002.014 0 0l-.007.084c.003-.057-.004-.041-.014-.031-.143.182-.27.327-.468.543-.89.963-1.856 1.752-2.86 2.311-.724.404-1.419.677-2.095.81a5.63 5.63 0 0 1-2.374-.036c-1.273-.295-2.523-1.014-3.774-2.101-.604-.525-1.075-1.001-1.457-1.496-.054-.07-.054-.107 0-.177.117-.152.244-.298.442-.512.89-.963 1.856-1.752 2.86-2.311.724-.404 1.419-.678 2.095-.81a5.631 5.631 0 0 1 2.374.036c1.272.295 2.523 1.014 3.774 2.101.603.524 1.074 1 1.457 1.496.035.041.043.057.046.076 0 .01 0 .01.008.043l-.009-.047.003.02-.002-.013v-.008.016c0-.004 0-.004 0 0v-.004z"/>
+ </g>
+</svg>
diff --git a/devtools/client/debugger/new/images/breakpoint.svg b/devtools/client/debugger/new/images/breakpoint.svg
new file mode 100644
index 000000000..f0e5de106
--- /dev/null
+++ b/devtools/client/debugger/new/images/breakpoint.svg
@@ -0,0 +1,6 @@
+<!-- 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/. -->
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 60 12">
+ <path id="base-path" d="M53.9,0H1C0.4,0,0,0.4,0,1v10c0,0.6,0.4,1,1,1h52.9c0.6,0,1.2-0.3,1.5-0.7L60,6l-4.4-5.3C55,0.3,54.5,0,53.9,0z"/>
+</svg>
diff --git a/devtools/client/debugger/new/images/close.svg b/devtools/client/debugger/new/images/close.svg
new file mode 100644
index 000000000..7efd07f80
--- /dev/null
+++ b/devtools/client/debugger/new/images/close.svg
@@ -0,0 +1,7 @@
+<!-- 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/. -->
+<svg width="16px" height="16px" viewBox="0 0 6 6" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+ <path d="M1.35191454,5.27895256 L5.31214367,1.35518468 C5.50830675,1.16082764 5.50977084,0.844248536 5.3154138,0.648085456 C5.12105677,0.451922377 4.80447766,0.450458288 4.60831458,0.644815324 L0.648085456,4.56858321 C0.451922377,4.76294025 0.450458288,5.07951935 0.644815324,5.27568243 C0.83917236,5.47184551 1.15575146,5.4733096 1.35191454,5.27895256 L1.35191454,5.27895256 Z" id="Line" stroke="none" fill="#696969" fill-rule="evenodd"></path>
+ <path d="M5.31214367,4.56858321 L1.35191454,0.644815324 C1.15575146,0.450458288 0.83917236,0.451922377 0.644815324,0.648085456 C0.450458288,0.844248536 0.451922377,1.16082764 0.648085456,1.35518468 L4.60831458,5.27895256 C4.80447766,5.4733096 5.12105677,5.47184551 5.3154138,5.27568243 C5.50977084,5.07951935 5.50830675,4.76294025 5.31214367,4.56858321 L5.31214367,4.56858321 Z" id="Line-Copy-2" stroke="none" fill="#696969" fill-rule="evenodd"></path>
+</svg>
diff --git a/devtools/client/debugger/new/images/disableBreakpoints.svg b/devtools/client/debugger/new/images/disableBreakpoints.svg
new file mode 100644
index 000000000..bdf28ffcf
--- /dev/null
+++ b/devtools/client/debugger/new/images/disableBreakpoints.svg
@@ -0,0 +1,8 @@
+<!-- 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/. -->
+<svg width="16" height="16" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg" fill="##4A464C">
+ <g fill-rule="evenodd">
+ <path d="M3.233 11.25l-.417 1H1.712C.763 12.25 0 11.574 0 10.747V6.503C0 5.675.755 5 1.712 5h4.127l-.417 1H1.597C1.257 6 1 6.225 1 6.503v4.244c0 .277.267.503.597.503h1.636zM7.405 11.27L7 12.306c.865.01 2.212-.024 2.315-.04.112-.016.112-.016.185-.035.075-.02.156-.046.251-.082.152-.056.349-.138.592-.244.415-.182.962-.435 1.612-.744l.138-.066a179.35 179.35 0 0 0 2.255-1.094c1.191-.546 1.191-2.074-.025-2.632l-.737-.34a3547.554 3547.554 0 0 0-3.854-1.78c-.029.11-.065.222-.11.336l-.232.596c.894.408 4.56 2.107 4.56 2.107.458.21.458.596 0 .806L9.197 11.27H7.405zM4.462 14.692l5-12a.5.5 0 1 0-.924-.384l-5 12a.5.5 0 1 0 .924.384z"/>
+ </g>
+</svg>
diff --git a/devtools/client/debugger/new/images/domain.svg b/devtools/client/debugger/new/images/domain.svg
new file mode 100644
index 000000000..f00c9b37d
--- /dev/null
+++ b/devtools/client/debugger/new/images/domain.svg
@@ -0,0 +1,7 @@
+<!-- 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/. -->
+<svg width="16" height="16" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg">
+ <path d="M9.05 4.634l-2.144.003-.116.116v1.445l.92.965.492.034.116-.116v-.617L9.13 5.7l.035-.95M12.482 10.38l-1.505-1.462H9.362l-.564.516-.034 1.108.72.768 1.323.034-.117-.116v1.2l.972 1.02.315.034.116-.116v-1.154l.422-.374.034-.927-.117.117h.26l.408-.36V10.5l-.125-.124-.575-.033"/>
+ <path d="M8.47 15.073c-3.088 0-5.6-2.513-5.6-5.602V9.4v-.003c0-.018 0-.018.002-.034l.182-.088.724.587.49.033.497.543-.034.9.317.383h.47l.114.096-.032 1.9.524.553h.105l.025-.338 1.004-.95.054-.474.53-.462v-.888l-.588-.038-1.118-1.155H4.48l-.154-.09V9.01l.155-.1h1.164v-.273l.12-.115.7.033.494-.443.034-.746-.624-.655h-.724v.28l-.11.07H4.64l-.114-.09.025-.64.48-.43v-.244h-.382c-.102 0-.152-.128-.08-.2 1.04-1.01 2.428-1.59 3.903-1.59 1.374 0 2.672.5 3.688 1.39.08.068.03.198-.075.198l-1.144-.034-.81.803.52.523v.16l-.382.388h-.158l-.176-.177v-.16l.076-.074-.252-.252-.37.362.53.53c.072.072.005.194-.096.194l-.752-.005v.844h.783L9.885 8l.16-.143h.16l.62.61v.267l.58.027.003.002V8.76l.18-.03 1.234 1.24.753-.708h.382l.116.108c0 .02.003.016.003.036v.065c0 3.09-2.515 5.603-5.605 5.603M8.47 3C4.904 3 2 5.903 2 9.47c0 3.57 2.903 6.472 6.47 6.472 3.57 0 6.472-2.903 6.472-6.47C14.942 5.9 12.04 3 8.472 3"/>
+</svg>
diff --git a/devtools/client/debugger/new/images/favicon.png b/devtools/client/debugger/new/images/favicon.png
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/devtools/client/debugger/new/images/favicon.png
diff --git a/devtools/client/debugger/new/images/file.svg b/devtools/client/debugger/new/images/file.svg
new file mode 100644
index 000000000..7f5a70855
--- /dev/null
+++ b/devtools/client/debugger/new/images/file.svg
@@ -0,0 +1,7 @@
+<!-- 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/. -->
+<svg width="16" height="16" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg">
+ <path d="M4 2v12h9V4.775L9.888 2H4zm0-1h5.888c.246 0 .483.09.666.254l3.112 2.774c.212.19.334.462.334.747V14c0 .552-.448 1-1 1H4c-.552 0-1-.448-1-1V2c0-.552.448-1 1-1z"/>
+ <path d="M9 1.5v4c0 .325.306.564.62.485l4-1c.27-.067.432-.338.365-.606-.067-.27-.338-.432-.606-.365l-4 1L10 5.5v-4c0-.276-.224-.5-.5-.5s-.5.224-.5.5z"/>
+</svg>
diff --git a/devtools/client/debugger/new/images/folder.svg b/devtools/client/debugger/new/images/folder.svg
new file mode 100644
index 000000000..6b8ef6ac3
--- /dev/null
+++ b/devtools/client/debugger/new/images/folder.svg
@@ -0,0 +1,6 @@
+<!-- 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/. -->
+<svg width="16" height="16" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg">
+ <path d="M2 5.193v7.652c0 .003-.002 0 .007 0H14v-7.69c0-.003.002 0-.007 0h-7.53v-2.15c0-.002-.004-.005-.01-.005H2.01C2 3 2 3 2 3.005V5.193zm-1 0V3.005C1 2.45 1.444 2 2.01 2h4.442c.558 0 1.01.45 1.01 1.005v1.15h6.53c.557 0 1.008.44 1.008 1v7.69c0 .553-.45 1-1.007 1H2.007c-.556 0-1.007-.44-1.007-1V5.193zM6.08 4.15H2v1h4.46v-1h-.38z" fill-rule="evenodd"/>
+</svg>
diff --git a/devtools/client/debugger/new/images/globe.svg b/devtools/client/debugger/new/images/globe.svg
new file mode 100644
index 000000000..d513a659f
--- /dev/null
+++ b/devtools/client/debugger/new/images/globe.svg
@@ -0,0 +1,10 @@
+<!-- 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/. -->
+<svg width="13px" height="12px" viewBox="14 6 13 12" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+ <g id="world" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" transform="translate(14.000000, 6.000000)" stroke-linecap="round" stroke-linejoin="round">
+ <path d="M6.35076107,0.354 C3.25095418,0.354 0.729,2.87582735 0.729,5.9758879 C0.729,9.07544113 3.25082735,11.5972685 6.35076107,11.5972685 C9.45044113,11.5972685 11.9723953,9.07544113 11.9723953,5.97576107 C11.9723953,2.87582735 9.45044113,0.354 6.35076107,0.354 L6.35076107,0.354 Z M6.35076107,10.8289121 C3.67445071,10.8289121 1.49722956,8.65181776 1.49722956,5.97576107 C1.49722956,5.9443064 1.49900522,5.91335907 1.49976622,5.88215806 L2.20090094,6.4213266 L2.56313696,6.4213266 L2.97268183,6.8306178 L2.97268183,7.68217686 L3.32324919,8.03287105 L3.73926255,8.03287105 L3.73926255,9.79940584 L4.27386509,10.3361645 L4.4591686,10.3361645 L4.4591686,10.000183 L5.37655417,9.08343163 L5.37655417,8.73400577 L5.85585737,8.25203907 L5.85585737,7.37206934 L5.32518666,7.37206934 L4.28439226,6.33140176 L2.82225748,6.33140176 L2.82225748,5.56938704 L3.96286973,5.56938704 L3.96286973,5.23949352 L4.65068695,5.23949352 L5.11477015,4.77667865 L5.11477015,4.03001076 L4.49087694,3.40662489 L3.75359472,3.40662489 L3.75359472,3.78725175 L2.96228149,3.78725175 L2.96228149,3.28385021 L3.42217919,2.82319151 L3.42217919,2.49786399 L2.97001833,2.49786399 C3.84466106,1.64744643 5.03714814,1.12222956 6.35063424,1.12222956 C7.57292716,1.12222956 8.69020207,1.57730759 9.54442463,2.32587797 L8.46164839,2.32587797 L7.680355,3.10666403 L8.21508437,3.64088607 L7.87238068,3.98257509 L7.7165025,3.82669692 L7.85297518,3.68946324 L7.78930484,3.62566607 L7.78943167,3.62566607 L7.56011699,3.39559038 L7.55986332,3.39571722 L7.49758815,3.33318838 L7.01904595,3.78585658 L7.55910232,4.32654712 L6.8069806,4.32198112 L6.8069806,5.25864535 L7.66716433,5.25864535 L7.6723645,4.72112565 L7.81289584,4.57996014 L8.31819988,5.08653251 L8.31819988,5.41921636 L9.00703176,5.41921636 L9.03366676,5.39321553 L9.03430093,5.39194719 L10.195587,6.55259911 L10.8637451,5.88520206 L11.2018828,5.88520206 C11.2023901,5.9153884 11.2041658,5.94532107 11.2041658,5.97563424 C11.2040389,8.65181776 9.0269446,10.8289121 6.35076107,10.8289121 L6.35076107,10.8289121 Z" id="Shape" stroke="#DDE1E5" stroke-width="0.25" fill="#DDE1E5"></path>
+ <polygon id="Shape" stroke="#DDE1E5" stroke-width="0.25" fill="#DDE1E5" points="6.50676608 1.61523076 4.52892694 1.61789426 4.52892694 2.95192735 5.34560683 3.76733891 5.72496536 3.76733891 5.72496536 3.1967157 6.50676608 2.41592965"></polygon>
+ <polygon id="Shape" stroke="#DDE1E5" stroke-width="0.25" fill="#DDE1E5" points="9.59959714 6.88718547 8.28623788 5.57268471 8.28623788 5.57002121 6.79607294 5.57002121 6.35101474 6.01469891 6.35101474 6.96201714 6.98429362 7.59466185 8.12909136 7.59466185 8.12909136 8.70343893 8.99434843 9.56882283 9.20971144 9.56882283 9.20971144 8.50329592 9.63029081 8.08271655 9.63029081 7.3026915 9.87025949 7.3026915 10.1711082 7.00082814 10.0558167 6.88718547"></polygon>
+ </g>
+</svg>
diff --git a/devtools/client/debugger/new/images/magnifying-glass.svg b/devtools/client/debugger/new/images/magnifying-glass.svg
new file mode 100644
index 000000000..856013283
--- /dev/null
+++ b/devtools/client/debugger/new/images/magnifying-glass.svg
@@ -0,0 +1,4 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16">
+ <path class="st0" d="M9 9.3l3.6 3.6"/>
+ <ellipse fill="transparent" cx="5.9" cy="6.2" rx="4.5" ry="4.5"/>
+</svg>
diff --git a/devtools/client/debugger/new/images/pause-circle.svg b/devtools/client/debugger/new/images/pause-circle.svg
new file mode 100644
index 000000000..2d4c116e5
--- /dev/null
+++ b/devtools/client/debugger/new/images/pause-circle.svg
@@ -0,0 +1,10 @@
+<!-- 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/. -->
+<svg width="16px" height="15px" viewBox="975 569 11 11" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+ <g id="Pause-circle" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" transform="translate(976.000000, 570.000000)">
+ <path d="M4.5,0.538639227 C2.3152037,0.538639227 0.538639227,2.31614868 0.538639227,4.5 C0.538639227,6.6847963 2.3152037,8.46136077 4.5,8.46136077 C6.6847963,8.46136077 8.46136077,6.6847963 8.46136077,4.5 C8.46136077,2.31614868 6.6847963,0.538639227 4.5,0.538639227 M4.5,9 C2.01847963,9 0,6.98152037 0,4.5 C0,2.01847963 2.01847963,0 4.5,0 C6.98152037,0 9,2.01847963 9,4.5 C9,6.98152037 6.98152037,9 4.5,9" id="Fill-1-Copy" stroke="#4990E2" stroke-width="0.5" fill="#4990E2"></path>
+ <path d="M3,3 L3,6.5" id="Line" stroke="#4990E2" stroke-width="1.15" stroke-linecap="round"></path>
+ <path d="M6,3 L6,6.5" id="Line" stroke="#4990E2" stroke-width="1.15" stroke-linecap="round"></path>
+ </g>
+</svg>
diff --git a/devtools/client/debugger/new/images/pause-exceptions.svg b/devtools/client/debugger/new/images/pause-exceptions.svg
new file mode 100644
index 000000000..8a0eb2c83
--- /dev/null
+++ b/devtools/client/debugger/new/images/pause-exceptions.svg
@@ -0,0 +1,7 @@
+<!-- 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/. -->
+<svg width="16" height="16" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg">
+ <path d="M10.483 13.995H5.517l-3.512-3.512V5.516l3.512-3.512h4.966l3.512 3.512v4.967l-3.512 3.512zm4.37-9.042l-3.807-3.805A.503.503 0 0 0 10.691 1H5.309a.503.503 0 0 0-.356.148L1.147 4.953A.502.502 0 0 0 1 5.308v5.383c0 .134.053.262.147.356l3.806 3.806a.503.503 0 0 0 .356.147h5.382a.503.503 0 0 0 .355-.147l3.806-3.806A.502.502 0 0 0 15 10.69V5.308a.502.502 0 0 0-.147-.355z"/>
+ <path d="M10 10.5a.5.5 0 1 0 1 0v-5a.5.5 0 1 0-1 0v5zM5 10.5a.5.5 0 1 0 1 0v-5a.5.5 0 0 0-1 0v5z"/>
+</svg>
diff --git a/devtools/client/debugger/new/images/pause.svg b/devtools/client/debugger/new/images/pause.svg
new file mode 100644
index 000000000..b27bf2a85
--- /dev/null
+++ b/devtools/client/debugger/new/images/pause.svg
@@ -0,0 +1,8 @@
+<!-- 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/. -->
+<svg width="16" height="16" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg">
+ <g fill-rule="evenodd">
+ <path d="M6.5 12.003l.052-9a.5.5 0 1 0-1-.006l-.052 9a.5.5 0 1 0 1 .006zM13 11.997l-.05-9a.488.488 0 0 0-.477-.497.488.488 0 0 0-.473.503l.05 9a.488.488 0 0 0 .477.497.488.488 0 0 0 .473-.503z"/>
+ </g>
+</svg>
diff --git a/devtools/client/debugger/new/images/play.svg b/devtools/client/debugger/new/images/play.svg
new file mode 100644
index 000000000..21ffb0fe5
--- /dev/null
+++ b/devtools/client/debugger/new/images/play.svg
@@ -0,0 +1,6 @@
+<!-- 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/. -->
+<svg width="16" height="16" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg" fill="#696969">
+ <path d="M4 13l7.778-5L4 3v10zm-1 0V3a1 1 0 0 1 1.54-.841l7.779 5a1 1 0 0 1 0 1.682l-7.778 5A1 1 0 0 1 3 13z" fill-rule="evenodd"/>
+</svg>
diff --git a/devtools/client/debugger/new/images/plus.svg b/devtools/client/debugger/new/images/plus.svg
new file mode 100644
index 000000000..ae7a69dfd
--- /dev/null
+++ b/devtools/client/debugger/new/images/plus.svg
@@ -0,0 +1,6 @@
+<!-- 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/. -->
+<svg width="16" height="16" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg">
+ <path d="M8.5 8.5V14a.5.5 0 1 1-1 0V8.5H2a.5.5 0 0 1 0-1h5.5V2a.5.5 0 0 1 1 0v5.5H14a.5.5 0 1 1 0 1H8.5z" fill-rule="evenodd"/>
+</svg>
diff --git a/devtools/client/debugger/new/images/prettyPrint.svg b/devtools/client/debugger/new/images/prettyPrint.svg
new file mode 100644
index 000000000..62e2707f9
--- /dev/null
+++ b/devtools/client/debugger/new/images/prettyPrint.svg
@@ -0,0 +1,6 @@
+<!-- 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/. -->
+<svg width="16" height="16" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg">
+ <path d="M4.525 13.21h-.472c-.574 0-.987-.154-1.24-.463-.253-.31-.38-.882-.38-1.719v-.573c0-.746-.097-1.265-.292-1.557-.196-.293-.51-.44-.945-.44v-.974c.435 0 .75-.146.945-.44.195-.292.293-.811.293-1.556v-.58c0-.833.126-1.404.379-1.712.253-.31.666-.464 1.24-.464h.472v.783h-.179c-.37 0-.628.08-.774.24-.145.159-.218.54-.218 1.141v.383c0 .824-.096 1.432-.287 1.823-.191.39-.516.679-.974.866.458.191.783.482.974.873.191.39.287.998.287 1.823v.382c0 .602.073.982.218 1.142.146.16.404.239.774.239h.18v.783zm9.502-4.752c-.43 0-.744.147-.942.44-.197.292-.296.811-.296 1.557v.573c0 .837-.125 1.41-.376 1.719-.251.309-.664.463-1.237.463h-.478v-.783h.185c.37 0 .628-.08.774-.24.145-.159.218-.539.218-1.14v-.383c0-.825.096-1.433.287-1.823.191-.39.516-.682.974-.873-.458-.187-.783-.476-.974-.866-.191-.391-.287-.999-.287-1.823v-.383c0-.602-.073-.982-.218-1.142-.146-.159-.404-.239-.774-.239h-.185v-.783h.478c.573 0 .986.155 1.237.464.25.308.376.88.376 1.712v.58c0 .673.088 1.174.263 1.503.176.329.5.493.975.493v.974z" fill-rule="evenodd"/>
+</svg>
diff --git a/devtools/client/debugger/new/images/resume.svg b/devtools/client/debugger/new/images/resume.svg
new file mode 100644
index 000000000..4a8b7fcd4
--- /dev/null
+++ b/devtools/client/debugger/new/images/resume.svg
@@ -0,0 +1,6 @@
+<!-- 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/. -->
+<svg width="16" height="16" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg">
+ <path d="M6.925 12.5l7.4-5-7.4-5v10zM6 12.5v-10c0-.785.8-1.264 1.415-.848l7.4 5c.58.392.58 1.304 0 1.696l-7.4 5C6.8 13.764 6 13.285 6 12.5z" fill-rule="evenodd"/>
+</svg>
diff --git a/devtools/client/debugger/new/images/sad-face.svg b/devtools/client/debugger/new/images/sad-face.svg
new file mode 100644
index 000000000..6c42ca43b
--- /dev/null
+++ b/devtools/client/debugger/new/images/sad-face.svg
@@ -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/. -->
+<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16" fill="#D92215">
+ <path d="M8 14.5c-3.6 0-6.5-2.9-6.5-6.5S4.4 1.5 8 1.5s6.5 2.9 6.5 6.5-2.9 6.5-6.5 6.5zm0-12C5 2.5 2.5 5 2.5 8S5 13.5 8 13.5 13.5 11 13.5 8 11 2.5 8 2.5z"/>
+ <circle cx="5" cy="6" r="1" transform="translate(1 1)"/>
+ <circle cx="9" cy="6" r="1" transform="translate(1 1)"/>
+ <path d="M5.5 11c-.1 0-.2 0-.3-.1-.2-.1-.3-.4-.1-.7C6 9 7 8.5 8.1 8.5c1.7.1 2.8 1.7 2.8 1.8.2.2.1.5-.1.7-.2.1-.6 0-.7-.2 0 0-.9-1.3-2-1.3-.7 0-1.4.4-2.1 1.3-.2.2-.4.2-.5.2z"/>
+</svg>
diff --git a/devtools/client/debugger/new/images/settings.svg b/devtools/client/debugger/new/images/settings.svg
new file mode 100644
index 000000000..310438f7e
--- /dev/null
+++ b/devtools/client/debugger/new/images/settings.svg
@@ -0,0 +1,6 @@
+<!-- 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/. -->
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="33" height="12" viewBox="0 0 33 12">
+ <path id="base-path" d="M27.1,0H1C0.4,0,0,0.4,0,1v10c0,0.6,0.4,1,1,1h26.1 c0.6,0,1.2-0.3,1.5-0.7L33,6l-4.4-5.3C28.2,0.3,27.7,0,27.1,0z"/>
+</svg>
diff --git a/devtools/client/debugger/new/images/stepIn.svg b/devtools/client/debugger/new/images/stepIn.svg
new file mode 100644
index 000000000..eff11c0c9
--- /dev/null
+++ b/devtools/client/debugger/new/images/stepIn.svg
@@ -0,0 +1,8 @@
+<!-- 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/. -->
+<svg width="16" height="16" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg">
+ <g fill-rule="evenodd">
+ <path d="M1.5 14.042h4.095a.5.5 0 0 0 0-1H1.5a.5.5 0 1 0 0 1zM7.983 2a.5.5 0 0 1 .517.5v7.483l3.136-3.326a.5.5 0 1 1 .728.686l-4 4.243a.499.499 0 0 1-.73-.004L3.635 7.343a.5.5 0 0 1 .728-.686L7.5 9.983V3H1.536C1.24 3 1 2.776 1 2.5s.24-.5.536-.5h6.447zM10.5 14.042h4.095a.5.5 0 0 0 0-1H10.5a.5.5 0 1 0 0 1z"/>
+ </g>
+</svg>
diff --git a/devtools/client/debugger/new/images/stepOut.svg b/devtools/client/debugger/new/images/stepOut.svg
new file mode 100644
index 000000000..4e5457141
--- /dev/null
+++ b/devtools/client/debugger/new/images/stepOut.svg
@@ -0,0 +1,8 @@
+<!-- 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/. -->
+<svg width="16" height="16" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg">
+ <g fill-rule="evenodd">
+ <path d="M5 13.5H1a.5.5 0 1 0 0 1h4a.5.5 0 1 0 0-1zM12 13.5H8a.5.5 0 1 0 0 1h4a.5.5 0 1 0 0-1zM6.11 5.012A.427.427 0 0 1 6.21 5h7.083L9.646 1.354a.5.5 0 1 1 .708-.708l4.5 4.5a.498.498 0 0 1 0 .708l-4.5 4.5a.5.5 0 0 1-.708-.708L13.293 6H6.5v5.5a.5.5 0 1 1-1 0v-6a.5.5 0 0 1 .61-.488z"/>
+ </g>
+</svg>
diff --git a/devtools/client/debugger/new/images/stepOver.svg b/devtools/client/debugger/new/images/stepOver.svg
new file mode 100644
index 000000000..c1d30c051
--- /dev/null
+++ b/devtools/client/debugger/new/images/stepOver.svg
@@ -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/. -->
+<svg width="16" height="16" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg">
+ <g fill-rule="evenodd">
+ <path d="M13.297 6.912C12.595 4.39 10.167 2.5 7.398 2.5A5.898 5.898 0 0 0 1.5 8.398a.5.5 0 0 0 1 0A4.898 4.898 0 0 1 7.398 3.5c2.75 0 5.102 2.236 5.102 4.898v.004L8.669 7.029a.5.5 0 0 0-.338.942l4.462 1.598a.5.5 0 0 0 .651-.34.506.506 0 0 0 .02-.043l2-5a.5.5 0 1 0-.928-.372l-1.24 3.098z"/>
+ <circle cx="7" cy="12" r="1"/>
+ </g>
+</svg>
diff --git a/devtools/client/debugger/new/images/subSettings.svg b/devtools/client/debugger/new/images/subSettings.svg
new file mode 100644
index 000000000..6b2355584
--- /dev/null
+++ b/devtools/client/debugger/new/images/subSettings.svg
@@ -0,0 +1,6 @@
+<!-- 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/. -->
+<svg width="16" height="16" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg">
+ <path d="M12.219 7c.345 0 .635.117.869.352.234.234.351.524.351.869 0 .351-.118.652-.356.903-.238.25-.526.376-.864.376-.332 0-.615-.125-.85-.376a1.276 1.276 0 0 1-.351-.903A1.185 1.185 0 0 1 12.218 7zM8.234 7c.345 0 .635.117.87.352.234.234.351.524.351.869 0 .351-.119.652-.356.903-.238.25-.526.376-.865.376-.332 0-.613-.125-.844-.376a1.286 1.286 0 0 1-.347-.903c0-.352.114-.643.342-.874.228-.231.51-.347.85-.347zM4.201 7c.339 0 .627.117.864.352.238.234.357.524.357.869 0 .351-.119.652-.357.903-.237.25-.525.376-.864.376-.338 0-.623-.125-.854-.376A1.286 1.286 0 0 1 3 8.221 1.185 1.185 0 0 1 4.201 7z" fill-rule="evenodd"/>
+</svg>
diff --git a/devtools/client/debugger/new/images/toggle-breakpoints.svg b/devtools/client/debugger/new/images/toggle-breakpoints.svg
new file mode 100644
index 000000000..9b2cccf82
--- /dev/null
+++ b/devtools/client/debugger/new/images/toggle-breakpoints.svg
@@ -0,0 +1,8 @@
+<!-- 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/. -->
+<svg width="16" height="16" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg">
+ <g fill-rule="evenodd">
+ <path d="M3.233 11.25l-.417 1H1.712C.763 12.25 0 11.574 0 10.747V6.503C0 5.675.755 5 1.712 5h4.127l-.417 1H1.597C1.257 6 1 6.225 1 6.503v4.244c0 .277.267.503.597.503h1.636zM7.405 11.27L7 12.306c.865.01 2.212-.024 2.315-.04.112-.016.112-.016.185-.035.075-.02.156-.046.251-.082.152-.056.349-.138.592-.244.415-.182.962-.435 1.612-.744l.138-.066a179.35 179.35 0 0 0 2.255-1.094c1.191-.546 1.191-2.074-.025-2.632l-.737-.34a3547.554 3547.554 0 0 0-3.854-1.78c-.029.11-.065.222-.11.336l-.232.596c.894.408 4.56 2.107 4.56 2.107.458.21.458.596 0 .806L9.197 11.27H7.405zM4.462 14.692l5-12a.5.5 0 1 0-.924-.384l-5 12a.5.5 0 1 0 .924.384z"/>
+ </g>
+</svg>
diff --git a/devtools/client/debugger/new/images/worker.svg b/devtools/client/debugger/new/images/worker.svg
new file mode 100644
index 000000000..4a9874efb
--- /dev/null
+++ b/devtools/client/debugger/new/images/worker.svg
@@ -0,0 +1,6 @@
+<!-- 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/. -->
+<svg width="16" height="16" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg">
+ <path fill-rule="evenodd" d="M8.5 8.793L5.854 6.146l-.04-.035L7.5 4.426c.2-.2.3-.4.3-.6 0-.2-.1-.4-.2-.6l-1-1c-.4-.3-.9-.3-1.2 0l-4.1 4.1c-.2.2-.3.4-.3.6 0 .2.1.4.2.6l1 1c.3.3.9.3 1.2 0l1.71-1.71.036.04L7.793 9.5l-3.647 3.646c-.195.196-.195.512 0 .708.196.195.512.195.708 0L8.5 10.207l3.646 3.647c.196.195.512.195.708 0 .195-.196.195-.512 0-.708L9.207 9.5l2.565-2.565L13.3 8.5c.1.1 2.3 1.1 2.7.7.4-.4-.3-2.7-.5-2.9l-1.1-1.1c.1-.1.2-.4.2-.6 0-.2-.1-.4-.2-.6l-.4-.4c-.3-.3-.8-.3-1.1 0l-1.5-1.4c-.2-.2-.3-.2-.5-.2s-.3.1-.5.2L9.2 3.4c-.2.1-.2.2-.2.4s.1.4.2.5l1.874 1.92L8.5 8.792z"/>
+</svg>
diff --git a/devtools/client/debugger/new/index.html b/devtools/client/debugger/new/index.html
new file mode 100644
index 000000000..ed4c976fc
--- /dev/null
+++ b/devtools/client/debugger/new/index.html
@@ -0,0 +1,31 @@
+<!-- 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/. -->
+<!DOCTYPE html>
+<html dir="">
+ <head>
+ <link rel="stylesheet"
+ type="text/css"
+ href="chrome://devtools/content/sourceeditor/codemirror/lib/codemirror.css" />
+ <link rel="stylesheet"
+ type="text/css"
+ href="chrome://devtools/content/sourceeditor/codemirror/addon/dialog/dialog.css" />
+ <link rel="stylesheet"
+ type="text/css"
+ href="chrome://devtools/content/sourceeditor/codemirror/mozilla.css" />
+ <link rel="stylesheet" type="text/css" href="resource://devtools/client/debugger/new/styles.css" />
+ </head>
+ <body>
+ <div id="mount"></div>
+ <script type="application/javascript;version=1.8"
+ src="chrome://devtools/content/shared/theme-switching.js"></script>
+ <script type="text/javascript">
+ const { BrowserLoader } = Components.utils.import("resource://devtools/client/shared/browser-loader.js", {});
+ const { require: devtoolsRequire } = BrowserLoader({
+ baseURI: "resource://devtools/client/debugger/new/",
+ window,
+ });
+ </script>
+ <script type="text/javascript" src="resource://devtools/client/debugger/new/bundle.js"></script>
+ </body>
+</html>
diff --git a/devtools/client/debugger/new/moz.build b/devtools/client/debugger/new/moz.build
new file mode 100644
index 000000000..09d1f908e
--- /dev/null
+++ b/devtools/client/debugger/new/moz.build
@@ -0,0 +1,12 @@
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+DevToolsModules(
+ 'bundle.js',
+ 'panel.js',
+ 'pretty-print-worker.js',
+ 'source-map-worker.js',
+ 'styles.css'
+)
diff --git a/devtools/client/debugger/new/panel.js b/devtools/client/debugger/new/panel.js
new file mode 100644
index 000000000..62d4a9f4f
--- /dev/null
+++ b/devtools/client/debugger/new/panel.js
@@ -0,0 +1,77 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+"use strict";
+
+const { Task } = require("devtools/shared/task");
+var {LocalizationHelper} = require("devtools/shared/l10n");
+
+const DBG_STRINGS_URI = "devtools/client/locales/debugger.properties";
+var L10N = new LocalizationHelper(DBG_STRINGS_URI);
+
+function DebuggerPanel(iframeWindow, toolbox) {
+ this.panelWin = iframeWindow;
+ this.panelWin.L10N = L10N;
+ this.toolbox = toolbox;
+}
+
+DebuggerPanel.prototype = {
+ open: Task.async(function* () {
+ if (!this.toolbox.target.isRemote) {
+ yield this.toolbox.target.makeRemote();
+ }
+
+ yield this.panelWin.Debugger.bootstrap({
+ threadClient: this.toolbox.threadClient,
+ tabTarget: this.toolbox.target
+ });
+
+ this.isReady = true;
+ return this;
+ }),
+
+ _store: function () {
+ return this.panelWin.Debugger.store;
+ },
+
+ _getState: function () {
+ return this._store().getState();
+ },
+
+ _actions: function () {
+ return this.panelWin.Debugger.actions;
+ },
+
+ _selectors: function () {
+ return this.panelWin.Debugger.selectors;
+ },
+
+ getFrames: function () {
+ let frames = this._selectors().getFrames(this._getState());
+
+ // Frames is null when the debugger is not paused.
+ if (!frames) {
+ return {
+ frames: [],
+ selected: -1
+ };
+ }
+
+ frames = frames.toJS();
+ const selectedFrame = this._selectors().getSelectedFrame(this._getState());
+ const selected = frames.findIndex(frame => frame.id == selectedFrame.id);
+
+ frames.forEach(frame => {
+ frame.actor = frame.id;
+ });
+
+ return { frames, selected };
+ },
+
+ destroy: function () {
+ this.panelWin.Debugger.destroy();
+ this.emit("destroyed");
+ }
+};
+
+exports.DebuggerPanel = DebuggerPanel;
diff --git a/devtools/client/debugger/new/pretty-print-worker.js b/devtools/client/debugger/new/pretty-print-worker.js
new file mode 100644
index 000000000..4fa735e16
--- /dev/null
+++ b/devtools/client/debugger/new/pretty-print-worker.js
@@ -0,0 +1,5904 @@
+var Debugger =
+/******/ (function(modules) { // webpackBootstrap
+/******/ // The module cache
+/******/ var installedModules = {};
+/******/
+/******/ // The require function
+/******/ function __webpack_require__(moduleId) {
+/******/
+/******/ // Check if module is in cache
+/******/ if(installedModules[moduleId])
+/******/ return installedModules[moduleId].exports;
+/******/
+/******/ // Create a new module (and put it into the cache)
+/******/ var module = installedModules[moduleId] = {
+/******/ exports: {},
+/******/ id: moduleId,
+/******/ loaded: false
+/******/ };
+/******/
+/******/ // Execute the module function
+/******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
+/******/
+/******/ // Flag the module as loaded
+/******/ module.loaded = true;
+/******/
+/******/ // Return the exports of the module
+/******/ return module.exports;
+/******/ }
+/******/
+/******/
+/******/ // expose the modules object (__webpack_modules__)
+/******/ __webpack_require__.m = modules;
+/******/
+/******/ // expose the module cache
+/******/ __webpack_require__.c = installedModules;
+/******/
+/******/ // __webpack_public_path__
+/******/ __webpack_require__.p = "/public/build";
+/******/
+/******/ // Load entry module and return exports
+/******/ return __webpack_require__(0);
+/******/ })
+/************************************************************************/
+/******/ ({
+
+/***/ 0:
+/***/ function(module, exports, __webpack_require__) {
+
+ var prettyFast = __webpack_require__(460);
+ var assert = __webpack_require__(247);
+
+ function prettyPrint(_ref) {
+ var url = _ref.url;
+ var indent = _ref.indent;
+ var source = _ref.source;
+
+ try {
+ var prettified = prettyFast(source, {
+ url: url,
+ indent: " ".repeat(indent)
+ });
+
+ return {
+ code: prettified.code,
+ mappings: prettified.map._mappings
+ };
+ } catch (e) {
+ return new Error(e.message + "\n" + e.stack);
+ }
+ }
+
+ function invertMappings(mappings) {
+ return mappings._array.map(m => {
+ var mapping = {
+ generated: {
+ line: m.originalLine,
+ column: m.originalColumn
+ }
+ };
+ if (m.source) {
+ mapping.source = m.source;
+ mapping.original = {
+ line: m.generatedLine,
+ column: m.generatedColumn
+ };
+ mapping.name = m.name;
+ }
+ return mapping;
+ });
+ }
+
+ self.onmessage = function (msg) {
+ var _msg$data = msg.data;
+ var id = _msg$data.id;
+ var args = _msg$data.args;
+
+ assert(msg.data.method === "prettyPrint", "Method must be `prettyPrint`");
+
+ try {
+ var _prettyPrint = prettyPrint(args[0]);
+
+ var code = _prettyPrint.code;
+ var mappings = _prettyPrint.mappings;
+
+ self.postMessage({ id, response: {
+ code, mappings: invertMappings(mappings)
+ } });
+ } catch (e) {
+ self.postMessage({ id, error: e });
+ }
+ };
+
+/***/ },
+
+/***/ 247:
+/***/ function(module, exports) {
+
+ function assert(condition, message) {
+ if (!condition) {
+ throw new Error("Assertion failure: " + message);
+ }
+ }
+
+ module.exports = assert;
+
+/***/ },
+
+/***/ 460:
+/***/ function(module, exports, __webpack_require__) {
+
+ var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_RESULT__;/* -*- indent-tabs-mode: nil; js-indent-level: 2; fill-column: 80 -*- */
+ /*
+ * Copyright 2013 Mozilla Foundation and contributors
+ * Licensed under the New BSD license. See LICENSE.md or:
+ * http://opensource.org/licenses/BSD-2-Clause
+ */
+ (function (root, factory) {
+ "use strict";
+
+ if (true) {
+ !(__WEBPACK_AMD_DEFINE_FACTORY__ = (factory), __WEBPACK_AMD_DEFINE_RESULT__ = (typeof __WEBPACK_AMD_DEFINE_FACTORY__ === 'function' ? (__WEBPACK_AMD_DEFINE_FACTORY__.call(exports, __webpack_require__, exports, module)) : __WEBPACK_AMD_DEFINE_FACTORY__), __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__));
+ } else if (typeof exports === "object") {
+ module.exports = factory();
+ } else {
+ root.prettyFast = factory();
+ }
+ }(this, function () {
+ "use strict";
+
+ var acorn = this.acorn || __webpack_require__(461);
+ var sourceMap = this.sourceMap || __webpack_require__(462);
+ var SourceNode = sourceMap.SourceNode;
+
+ // If any of these tokens are seen before a "[" token, we know that "[" token
+ // is the start of an array literal, rather than a property access.
+ //
+ // The only exception is "}", which would need to be disambiguated by
+ // parsing. The majority of the time, an open bracket following a closing
+ // curly is going to be an array literal, so we brush the complication under
+ // the rug, and handle the ambiguity by always assuming that it will be an
+ // array literal.
+ var PRE_ARRAY_LITERAL_TOKENS = {
+ "typeof": true,
+ "void": true,
+ "delete": true,
+ "case": true,
+ "do": true,
+ "=": true,
+ "in": true,
+ "{": true,
+ "*": true,
+ "/": true,
+ "%": true,
+ "else": true,
+ ";": true,
+ "++": true,
+ "--": true,
+ "+": true,
+ "-": true,
+ "~": true,
+ "!": true,
+ ":": true,
+ "?": true,
+ ">>": true,
+ ">>>": true,
+ "<<": true,
+ "||": true,
+ "&&": true,
+ "<": true,
+ ">": true,
+ "<=": true,
+ ">=": true,
+ "instanceof": true,
+ "&": true,
+ "^": true,
+ "|": true,
+ "==": true,
+ "!=": true,
+ "===": true,
+ "!==": true,
+ ",": true,
+
+ "}": true
+ };
+
+ /**
+ * Determines if we think that the given token starts an array literal.
+ *
+ * @param Object token
+ * The token we want to determine if it is an array literal.
+ * @param Object lastToken
+ * The last token we added to the pretty printed results.
+ *
+ * @returns Boolean
+ * True if we believe it is an array literal, false otherwise.
+ */
+ function isArrayLiteral(token, lastToken) {
+ if (token.type.type != "[") {
+ return false;
+ }
+ if (!lastToken) {
+ return true;
+ }
+ if (lastToken.type.isAssign) {
+ return true;
+ }
+ return !!PRE_ARRAY_LITERAL_TOKENS[
+ lastToken.type.keyword || lastToken.type.type
+ ];
+ }
+
+ // If any of these tokens are followed by a token on a new line, we know that
+ // ASI cannot happen.
+ var PREVENT_ASI_AFTER_TOKENS = {
+ // Binary operators
+ "*": true,
+ "/": true,
+ "%": true,
+ "+": true,
+ "-": true,
+ "<<": true,
+ ">>": true,
+ ">>>": true,
+ "<": true,
+ ">": true,
+ "<=": true,
+ ">=": true,
+ "instanceof": true,
+ "in": true,
+ "==": true,
+ "!=": true,
+ "===": true,
+ "!==": true,
+ "&": true,
+ "^": true,
+ "|": true,
+ "&&": true,
+ "||": true,
+ ",": true,
+ ".": true,
+ "=": true,
+ "*=": true,
+ "/=": true,
+ "%=": true,
+ "+=": true,
+ "-=": true,
+ "<<=": true,
+ ">>=": true,
+ ">>>=": true,
+ "&=": true,
+ "^=": true,
+ "|=": true,
+ // Unary operators
+ "delete": true,
+ "void": true,
+ "typeof": true,
+ "~": true,
+ "!": true,
+ "new": true,
+ // Function calls and grouped expressions
+ "(": true
+ };
+
+ // If any of these tokens are on a line after the token before it, we know
+ // that ASI cannot happen.
+ var PREVENT_ASI_BEFORE_TOKENS = {
+ // Binary operators
+ "*": true,
+ "/": true,
+ "%": true,
+ "<<": true,
+ ">>": true,
+ ">>>": true,
+ "<": true,
+ ">": true,
+ "<=": true,
+ ">=": true,
+ "instanceof": true,
+ "in": true,
+ "==": true,
+ "!=": true,
+ "===": true,
+ "!==": true,
+ "&": true,
+ "^": true,
+ "|": true,
+ "&&": true,
+ "||": true,
+ ",": true,
+ ".": true,
+ "=": true,
+ "*=": true,
+ "/=": true,
+ "%=": true,
+ "+=": true,
+ "-=": true,
+ "<<=": true,
+ ">>=": true,
+ ">>>=": true,
+ "&=": true,
+ "^=": true,
+ "|=": true,
+ // Function calls
+ "(": true
+ };
+
+ /**
+ * Determines if Automatic Semicolon Insertion (ASI) occurs between these
+ * tokens.
+ *
+ * @param Object token
+ * The current token.
+ * @param Object lastToken
+ * The last token we added to the pretty printed results.
+ *
+ * @returns Boolean
+ * True if we believe ASI occurs.
+ */
+ function isASI(token, lastToken) {
+ if (!lastToken) {
+ return false;
+ }
+ if (token.startLoc.line === lastToken.startLoc.line) {
+ return false;
+ }
+ if (PREVENT_ASI_AFTER_TOKENS[
+ lastToken.type.type || lastToken.type.keyword
+ ]) {
+ return false;
+ }
+ if (PREVENT_ASI_BEFORE_TOKENS[token.type.type || token.type.keyword]) {
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Determine if we have encountered a getter or setter.
+ *
+ * @param Object token
+ * The current token. If this is a getter or setter, it would be the
+ * property name.
+ * @param Object lastToken
+ * The last token we added to the pretty printed results. If this is a
+ * getter or setter, it would be the `get` or `set` keyword
+ * respectively.
+ * @param Array stack
+ * The stack of open parens/curlies/brackets/etc.
+ *
+ * @returns Boolean
+ * True if this is a getter or setter.
+ */
+ function isGetterOrSetter(token, lastToken, stack) {
+ return stack[stack.length - 1] == "{"
+ && lastToken
+ && lastToken.type.type == "name"
+ && (lastToken.value == "get" || lastToken.value == "set")
+ && token.type.type == "name";
+ }
+
+ /**
+ * Determine if we should add a newline after the given token.
+ *
+ * @param Object token
+ * The token we are looking at.
+ * @param Array stack
+ * The stack of open parens/curlies/brackets/etc.
+ *
+ * @returns Boolean
+ * True if we should add a newline.
+ */
+ function isLineDelimiter(token, stack) {
+ if (token.isArrayLiteral) {
+ return true;
+ }
+ var ttt = token.type.type;
+ var top = stack[stack.length - 1];
+ return ttt == ";" && top != "("
+ || ttt == "{"
+ || ttt == "," && top != "("
+ || ttt == ":" && (top == "case" || top == "default");
+ }
+
+ /**
+ * Append the necessary whitespace to the result after we have added the given
+ * token.
+ *
+ * @param Object token
+ * The token that was just added to the result.
+ * @param Function write
+ * The function to write to the pretty printed results.
+ * @param Array stack
+ * The stack of open parens/curlies/brackets/etc.
+ *
+ * @returns Boolean
+ * Returns true if we added a newline to result, false in all other
+ * cases.
+ */
+ function appendNewline(token, write, stack) {
+ if (isLineDelimiter(token, stack)) {
+ write("\n", token.startLoc.line, token.startLoc.column);
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Determines if we need to add a space between the last token we added and
+ * the token we are about to add.
+ *
+ * @param Object token
+ * The token we are about to add to the pretty printed code.
+ * @param Object lastToken
+ * The last token added to the pretty printed code.
+ */
+ function needsSpaceAfter(token, lastToken) {
+ if (lastToken) {
+ if (lastToken.type.isLoop) {
+ return true;
+ }
+ if (lastToken.type.isAssign) {
+ return true;
+ }
+ if (lastToken.type.binop != null) {
+ return true;
+ }
+
+ var ltt = lastToken.type.type;
+ if (ltt == "?") {
+ return true;
+ }
+ if (ltt == ":") {
+ return true;
+ }
+ if (ltt == ",") {
+ return true;
+ }
+ if (ltt == ";") {
+ return true;
+ }
+
+ var ltk = lastToken.type.keyword;
+ if (ltk != null) {
+ if (ltk == "break" || ltk == "continue" || ltk == "return") {
+ return token.type.type != ";";
+ }
+ if (ltk != "debugger"
+ && ltk != "null"
+ && ltk != "true"
+ && ltk != "false"
+ && ltk != "this"
+ && ltk != "default") {
+ return true;
+ }
+ }
+
+ if (ltt == ")" && (token.type.type != ")"
+ && token.type.type != "]"
+ && token.type.type != ";"
+ && token.type.type != ","
+ && token.type.type != ".")) {
+ return true;
+ }
+ }
+
+ if (token.type.isAssign) {
+ return true;
+ }
+ if (token.type.binop != null) {
+ return true;
+ }
+ if (token.type.type == "?") {
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * Add the required whitespace before this token, whether that is a single
+ * space, newline, and/or the indent on fresh lines.
+ *
+ * @param Object token
+ * The token we are about to add to the pretty printed code.
+ * @param Object lastToken
+ * The last token we added to the pretty printed code.
+ * @param Boolean addedNewline
+ * Whether we added a newline after adding the last token to the pretty
+ * printed code.
+ * @param Function write
+ * The function to write pretty printed code to the result SourceNode.
+ * @param Object options
+ * The options object.
+ * @param Number indentLevel
+ * The number of indents deep we are.
+ * @param Array stack
+ * The stack of open curlies, brackets, etc.
+ */
+ function prependWhiteSpace(token, lastToken, addedNewline, write, options,
+ indentLevel, stack) {
+ var ttk = token.type.keyword;
+ var ttt = token.type.type;
+ var newlineAdded = addedNewline;
+ var ltt = lastToken ? lastToken.type.type : null;
+
+ // Handle whitespace and newlines after "}" here instead of in
+ // `isLineDelimiter` because it is only a line delimiter some of the
+ // time. For example, we don't want to put "else if" on a new line after
+ // the first if's block.
+ if (lastToken && ltt == "}") {
+ if (ttk == "while" && stack[stack.length - 1] == "do") {
+ write(" ",
+ lastToken.startLoc.line,
+ lastToken.startLoc.column);
+ } else if (ttk == "else" ||
+ ttk == "catch" ||
+ ttk == "finally") {
+ write(" ",
+ lastToken.startLoc.line,
+ lastToken.startLoc.column);
+ } else if (ttt != "(" &&
+ ttt != ";" &&
+ ttt != "," &&
+ ttt != ")" &&
+ ttt != ".") {
+ write("\n",
+ lastToken.startLoc.line,
+ lastToken.startLoc.column);
+ newlineAdded = true;
+ }
+ }
+
+ if (isGetterOrSetter(token, lastToken, stack)) {
+ write(" ",
+ lastToken.startLoc.line,
+ lastToken.startLoc.column);
+ }
+
+ if (ttt == ":" && stack[stack.length - 1] == "?") {
+ write(" ",
+ lastToken.startLoc.line,
+ lastToken.startLoc.column);
+ }
+
+ if (lastToken && ltt != "}" && ttk == "else") {
+ write(" ",
+ lastToken.startLoc.line,
+ lastToken.startLoc.column);
+ }
+
+ function ensureNewline() {
+ if (!newlineAdded) {
+ write("\n",
+ lastToken.startLoc.line,
+ lastToken.startLoc.column);
+ newlineAdded = true;
+ }
+ }
+
+ if (isASI(token, lastToken)) {
+ ensureNewline();
+ }
+
+ if (decrementsIndent(ttt, stack)) {
+ ensureNewline();
+ }
+
+ if (newlineAdded) {
+ if (ttk == "case" || ttk == "default") {
+ write(repeat(options.indent, indentLevel - 1),
+ token.startLoc.line,
+ token.startLoc.column);
+ } else {
+ write(repeat(options.indent, indentLevel),
+ token.startLoc.line,
+ token.startLoc.column);
+ }
+ } else if (needsSpaceAfter(token, lastToken)) {
+ write(" ",
+ lastToken.startLoc.line,
+ lastToken.startLoc.column);
+ }
+ }
+
+ /**
+ * Repeat the `str` string `n` times.
+ *
+ * @param String str
+ * The string to be repeated.
+ * @param Number n
+ * The number of times to repeat the string.
+ *
+ * @returns String
+ * The repeated string.
+ */
+ function repeat(str, n) {
+ var result = "";
+ while (n > 0) {
+ if (n & 1) {
+ result += str;
+ }
+ n >>= 1;
+ str += str;
+ }
+ return result;
+ }
+
+ /**
+ * Make sure that we output the escaped character combination inside string
+ * literals instead of various problematic characters.
+ */
+ var sanitize = (function () {
+ var escapeCharacters = {
+ // Backslash
+ "\\": "\\\\",
+ // Newlines
+ "\n": "\\n",
+ // Carriage return
+ "\r": "\\r",
+ // Tab
+ "\t": "\\t",
+ // Vertical tab
+ "\v": "\\v",
+ // Form feed
+ "\f": "\\f",
+ // Null character
+ "\0": "\\0",
+ // Single quotes
+ "'": "\\'"
+ };
+
+ var regExpString = "("
+ + Object.keys(escapeCharacters)
+ .map(function (c) { return escapeCharacters[c]; })
+ .join("|")
+ + ")";
+ var escapeCharactersRegExp = new RegExp(regExpString, "g");
+
+ return function (str) {
+ return str.replace(escapeCharactersRegExp, function (_, c) {
+ return escapeCharacters[c];
+ });
+ };
+ }());
+ /**
+ * Add the given token to the pretty printed results.
+ *
+ * @param Object token
+ * The token to add.
+ * @param Function write
+ * The function to write pretty printed code to the result SourceNode.
+ */
+ function addToken(token, write) {
+ if (token.type.type == "string") {
+ write("'" + sanitize(token.value) + "'",
+ token.startLoc.line,
+ token.startLoc.column);
+ } else if (token.type.type == "regexp") {
+ write(String(token.value.value),
+ token.startLoc.line,
+ token.startLoc.column);
+ } else {
+ write(String(token.value != null ? token.value : token.type.type),
+ token.startLoc.line,
+ token.startLoc.column);
+ }
+ }
+
+ /**
+ * Returns true if the given token type belongs on the stack.
+ */
+ function belongsOnStack(token) {
+ var ttt = token.type.type;
+ var ttk = token.type.keyword;
+ return ttt == "{"
+ || ttt == "("
+ || ttt == "["
+ || ttt == "?"
+ || ttk == "do"
+ || ttk == "switch"
+ || ttk == "case"
+ || ttk == "default";
+ }
+
+ /**
+ * Returns true if the given token should cause us to pop the stack.
+ */
+ function shouldStackPop(token, stack) {
+ var ttt = token.type.type;
+ var ttk = token.type.keyword;
+ var top = stack[stack.length - 1];
+ return ttt == "]"
+ || ttt == ")"
+ || ttt == "}"
+ || (ttt == ":" && (top == "case" || top == "default" || top == "?"))
+ || (ttk == "while" && top == "do");
+ }
+
+ /**
+ * Returns true if the given token type should cause us to decrement the
+ * indent level.
+ */
+ function decrementsIndent(tokenType, stack) {
+ return tokenType == "}"
+ || (tokenType == "]" && stack[stack.length - 1] == "[\n");
+ }
+
+ /**
+ * Returns true if the given token should cause us to increment the indent
+ * level.
+ */
+ function incrementsIndent(token) {
+ return token.type.type == "{"
+ || token.isArrayLiteral
+ || token.type.keyword == "switch";
+ }
+
+ /**
+ * Add a comment to the pretty printed code.
+ *
+ * @param Function write
+ * The function to write pretty printed code to the result SourceNode.
+ * @param Number indentLevel
+ * The number of indents deep we are.
+ * @param Object options
+ * The options object.
+ * @param Boolean block
+ * True if the comment is a multiline block style comment.
+ * @param String text
+ * The text of the comment.
+ * @param Number line
+ * The line number to comment appeared on.
+ * @param Number column
+ * The column number the comment appeared on.
+ */
+ function addComment(write, indentLevel, options, block, text, line, column) {
+ var indentString = repeat(options.indent, indentLevel);
+
+ write(indentString, line, column);
+ if (block) {
+ write("/*");
+ write(text
+ .split(new RegExp("/\n" + indentString + "/", "g"))
+ .join("\n" + indentString));
+ write("*/");
+ } else {
+ write("//");
+ write(text);
+ }
+ write("\n");
+ }
+
+ /**
+ * The main function.
+ *
+ * @param String input
+ * The ugly JS code we want to pretty print.
+ * @param Object options
+ * The options object. Provides configurability of the pretty
+ * printing. Properties:
+ * - url: The URL string of the ugly JS code.
+ * - indent: The string to indent code by.
+ *
+ * @returns Object
+ * An object with the following properties:
+ * - code: The pretty printed code string.
+ * - map: A SourceMapGenerator instance.
+ */
+ return function prettyFast(input, options) {
+ // The level of indents deep we are.
+ var indentLevel = 0;
+
+ // We will accumulate the pretty printed code in this SourceNode.
+ var result = new SourceNode();
+
+ /**
+ * Write a pretty printed string to the result SourceNode.
+ *
+ * We buffer our writes so that we only create one mapping for each line in
+ * the source map. This enhances performance by avoiding extraneous mapping
+ * serialization, and flattening the tree that
+ * `SourceNode#toStringWithSourceMap` will have to recursively walk. When
+ * timing how long it takes to pretty print jQuery, this optimization
+ * brought the time down from ~390 ms to ~190ms!
+ *
+ * @param String str
+ * The string to be added to the result.
+ * @param Number line
+ * The line number the string came from in the ugly source.
+ * @param Number column
+ * The column number the string came from in the ugly source.
+ */
+ var write = (function () {
+ var buffer = [];
+ var bufferLine = -1;
+ var bufferColumn = -1;
+ return function write(str, line, column) {
+ if (line != null && bufferLine === -1) {
+ bufferLine = line;
+ }
+ if (column != null && bufferColumn === -1) {
+ bufferColumn = column;
+ }
+ buffer.push(str);
+
+ if (str == "\n") {
+ var lineStr = "";
+ for (var i = 0, len = buffer.length; i < len; i++) {
+ lineStr += buffer[i];
+ }
+ result.add(new SourceNode(bufferLine, bufferColumn, options.url,
+ lineStr));
+ buffer.splice(0, buffer.length);
+ bufferLine = -1;
+ bufferColumn = -1;
+ }
+ };
+ }());
+
+ // Whether or not we added a newline on after we added the last token.
+ var addedNewline = false;
+
+ // The current token we will be adding to the pretty printed code.
+ var token;
+
+ // Shorthand for token.type.type, so we don't have to repeatedly access
+ // properties.
+ var ttt;
+
+ // Shorthand for token.type.keyword, so we don't have to repeatedly access
+ // properties.
+ var ttk;
+
+ // The last token we added to the pretty printed code.
+ var lastToken;
+
+ // Stack of token types/keywords that can affect whether we want to add a
+ // newline or a space. We can make that decision based on what token type is
+ // on the top of the stack. For example, a comma in a parameter list should
+ // be followed by a space, while a comma in an object literal should be
+ // followed by a newline.
+ //
+ // Strings that go on the stack:
+ //
+ // - "{"
+ // - "("
+ // - "["
+ // - "[\n"
+ // - "do"
+ // - "?"
+ // - "switch"
+ // - "case"
+ // - "default"
+ //
+ // The difference between "[" and "[\n" is that "[\n" is used when we are
+ // treating "[" and "]" tokens as line delimiters and should increment and
+ // decrement the indent level when we find them.
+ var stack = [];
+
+ // Acorn's tokenizer will always yield comments *before* the token they
+ // follow (unless the very first thing in the source is a comment), so we
+ // have to queue the comments in order to pretty print them in the correct
+ // location. For example, the source file:
+ //
+ // foo
+ // // a
+ // // b
+ // bar
+ //
+ // When tokenized by acorn, gives us the following token stream:
+ //
+ // [ '// a', '// b', foo, bar ]
+ var commentQueue = [];
+
+ var getToken = acorn.tokenize(input, {
+ locations: true,
+ sourceFile: options.url,
+ onComment: function (block, text, start, end, startLoc, endLoc) {
+ if (lastToken) {
+ commentQueue.push({
+ block: block,
+ text: text,
+ line: startLoc.line,
+ column: startLoc.column,
+ trailing: lastToken.endLoc.line == startLoc.line
+ });
+ } else {
+ addComment(write, indentLevel, options, block, text, startLoc.line,
+ startLoc.column);
+ addedNewline = true;
+ }
+ }
+ });
+
+ for (;;) {
+ token = getToken();
+
+ ttk = token.type.keyword;
+ ttt = token.type.type;
+
+ if (ttt == "eof") {
+ if (!addedNewline) {
+ write("\n");
+ }
+ break;
+ }
+
+ token.isArrayLiteral = isArrayLiteral(token, lastToken);
+
+ if (belongsOnStack(token)) {
+ if (token.isArrayLiteral) {
+ stack.push("[\n");
+ } else {
+ stack.push(ttt || ttk);
+ }
+ }
+
+ if (decrementsIndent(ttt, stack)) {
+ indentLevel--;
+ if (ttt == "}"
+ && stack.length > 1
+ && stack[stack.length - 2] == "switch") {
+ indentLevel--;
+ }
+ }
+
+ prependWhiteSpace(token, lastToken, addedNewline, write, options,
+ indentLevel, stack);
+ addToken(token, write);
+ if (commentQueue.length === 0 || !commentQueue[0].trailing) {
+ addedNewline = appendNewline(token, write, stack);
+ }
+
+ if (shouldStackPop(token, stack)) {
+ stack.pop();
+ if (token == "}" && stack.length
+ && stack[stack.length - 1] == "switch") {
+ stack.pop();
+ }
+ }
+
+ if (incrementsIndent(token)) {
+ indentLevel++;
+ }
+
+ // Acorn's tokenizer re-uses tokens, so we have to copy the last token on
+ // every iteration. We follow acorn's lead here, and reuse the lastToken
+ // object the same way that acorn reuses the token object. This allows us
+ // to avoid allocations and minimize GC pauses.
+ if (!lastToken) {
+ lastToken = { startLoc: {}, endLoc: {} };
+ }
+ lastToken.start = token.start;
+ lastToken.end = token.end;
+ lastToken.startLoc.line = token.startLoc.line;
+ lastToken.startLoc.column = token.startLoc.column;
+ lastToken.endLoc.line = token.endLoc.line;
+ lastToken.endLoc.column = token.endLoc.column;
+ lastToken.type = token.type;
+ lastToken.value = token.value;
+ lastToken.isArrayLiteral = token.isArrayLiteral;
+
+ // Apply all the comments that have been queued up.
+ if (commentQueue.length) {
+ if (!addedNewline && !commentQueue[0].trailing) {
+ write("\n");
+ }
+ if (commentQueue[0].trailing) {
+ write(" ");
+ }
+ for (var i = 0, n = commentQueue.length; i < n; i++) {
+ var comment = commentQueue[i];
+ var commentIndentLevel = commentQueue[i].trailing ? 0 : indentLevel;
+ addComment(write, commentIndentLevel, options, comment.block,
+ comment.text, comment.line, comment.column);
+ }
+ addedNewline = true;
+ commentQueue.splice(0, commentQueue.length);
+ }
+ }
+
+ return result.toStringWithSourceMap({ file: options.url });
+ };
+
+ }.bind(this)));
+
+
+/***/ },
+
+/***/ 461:
+/***/ function(module, exports, __webpack_require__) {
+
+ var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;// Acorn is a tiny, fast JavaScript parser written in JavaScript.
+ //
+ // Acorn was written by Marijn Haverbeke and various contributors and
+ // released under an MIT license. The Unicode regexps (for identifiers
+ // and whitespace) were taken from [Esprima](http://esprima.org) by
+ // Ariya Hidayat.
+ //
+ // Git repositories for Acorn are available at
+ //
+ // http://marijnhaverbeke.nl/git/acorn
+ // https://github.com/marijnh/acorn.git
+ //
+ // Please use the [github bug tracker][ghbt] to report issues.
+ //
+ // [ghbt]: https://github.com/marijnh/acorn/issues
+ //
+ // This file defines the main parser interface. The library also comes
+ // with a [error-tolerant parser][dammit] and an
+ // [abstract syntax tree walker][walk], defined in other files.
+ //
+ // [dammit]: acorn_loose.js
+ // [walk]: util/walk.js
+
+ (function(root, mod) {
+ if (true) return mod(exports); // CommonJS
+ if (true) return !(__WEBPACK_AMD_DEFINE_ARRAY__ = [exports], __WEBPACK_AMD_DEFINE_FACTORY__ = (mod), __WEBPACK_AMD_DEFINE_RESULT__ = (typeof __WEBPACK_AMD_DEFINE_FACTORY__ === 'function' ? (__WEBPACK_AMD_DEFINE_FACTORY__.apply(exports, __WEBPACK_AMD_DEFINE_ARRAY__)) : __WEBPACK_AMD_DEFINE_FACTORY__), __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__)); // AMD
+ mod(root.acorn || (root.acorn = {})); // Plain browser env
+ })(this, function(exports) {
+ "use strict";
+
+ exports.version = "0.11.0";
+
+ // The main exported interface (under `self.acorn` when in the
+ // browser) is a `parse` function that takes a code string and
+ // returns an abstract syntax tree as specified by [Mozilla parser
+ // API][api], with the caveat that inline XML is not recognized.
+ //
+ // [api]: https://developer.mozilla.org/en-US/docs/SpiderMonkey/Parser_API
+
+ var options, input, inputLen, sourceFile;
+
+ exports.parse = function(inpt, opts) {
+ input = String(inpt); inputLen = input.length;
+ setOptions(opts);
+ initTokenState();
+ var startPos = options.locations ? [tokPos, curPosition()] : tokPos;
+ initParserState();
+ return parseTopLevel(options.program || startNodeAt(startPos));
+ };
+
+ // A second optional argument can be given to further configure
+ // the parser process. These options are recognized:
+
+ var defaultOptions = exports.defaultOptions = {
+ // `ecmaVersion` indicates the ECMAScript version to parse. Must
+ // be either 3, or 5, or 6. This influences support for strict
+ // mode, the set of reserved words, support for getters and
+ // setters and other features.
+ ecmaVersion: 5,
+ // Turn on `strictSemicolons` to prevent the parser from doing
+ // automatic semicolon insertion.
+ strictSemicolons: false,
+ // When `allowTrailingCommas` is false, the parser will not allow
+ // trailing commas in array and object literals.
+ allowTrailingCommas: true,
+ // By default, reserved words are not enforced. Enable
+ // `forbidReserved` to enforce them. When this option has the
+ // value "everywhere", reserved words and keywords can also not be
+ // used as property names.
+ forbidReserved: false,
+ // When enabled, a return at the top level is not considered an
+ // error.
+ allowReturnOutsideFunction: false,
+ // When enabled, import/export statements are not constrained to
+ // appearing at the top of the program.
+ allowImportExportEverywhere: false,
+ // When `locations` is on, `loc` properties holding objects with
+ // `start` and `end` properties in `{line, column}` form (with
+ // line being 1-based and column 0-based) will be attached to the
+ // nodes.
+ locations: false,
+ // A function can be passed as `onToken` option, which will
+ // cause Acorn to call that function with object in the same
+ // format as tokenize() returns. Note that you are not
+ // allowed to call the parser from the callback—that will
+ // corrupt its internal state.
+ onToken: null,
+ // A function can be passed as `onComment` option, which will
+ // cause Acorn to call that function with `(block, text, start,
+ // end)` parameters whenever a comment is skipped. `block` is a
+ // boolean indicating whether this is a block (`/* */`) comment,
+ // `text` is the content of the comment, and `start` and `end` are
+ // character offsets that denote the start and end of the comment.
+ // When the `locations` option is on, two more parameters are
+ // passed, the full `{line, column}` locations of the start and
+ // end of the comments. Note that you are not allowed to call the
+ // parser from the callback—that will corrupt its internal state.
+ onComment: null,
+ // Nodes have their start and end characters offsets recorded in
+ // `start` and `end` properties (directly on the node, rather than
+ // the `loc` object, which holds line/column data. To also add a
+ // [semi-standardized][range] `range` property holding a `[start,
+ // end]` array with the same numbers, set the `ranges` option to
+ // `true`.
+ //
+ // [range]: https://bugzilla.mozilla.org/show_bug.cgi?id=745678
+ ranges: false,
+ // It is possible to parse multiple files into a single AST by
+ // passing the tree produced by parsing the first file as
+ // `program` option in subsequent parses. This will add the
+ // toplevel forms of the parsed file to the `Program` (top) node
+ // of an existing parse tree.
+ program: null,
+ // When `locations` is on, you can pass this to record the source
+ // file in every node's `loc` object.
+ sourceFile: null,
+ // This value, if given, is stored in every node, whether
+ // `locations` is on or off.
+ directSourceFile: null,
+ // When enabled, parenthesized expressions are represented by
+ // (non-standard) ParenthesizedExpression nodes
+ preserveParens: false
+ };
+
+ // This function tries to parse a single expression at a given
+ // offset in a string. Useful for parsing mixed-language formats
+ // that embed JavaScript expressions.
+
+ exports.parseExpressionAt = function(inpt, pos, opts) {
+ input = String(inpt); inputLen = input.length;
+ setOptions(opts);
+ initTokenState(pos);
+ initParserState();
+ return parseExpression();
+ };
+
+ var isArray = function (obj) {
+ return Object.prototype.toString.call(obj) === "[object Array]";
+ };
+
+ function setOptions(opts) {
+ options = {};
+ for (var opt in defaultOptions)
+ options[opt] = opts && has(opts, opt) ? opts[opt] : defaultOptions[opt];
+ sourceFile = options.sourceFile || null;
+ if (isArray(options.onToken)) {
+ var tokens = options.onToken;
+ options.onToken = function (token) {
+ tokens.push(token);
+ };
+ }
+ if (isArray(options.onComment)) {
+ var comments = options.onComment;
+ options.onComment = function (block, text, start, end, startLoc, endLoc) {
+ var comment = {
+ type: block ? 'Block' : 'Line',
+ value: text,
+ start: start,
+ end: end
+ };
+ if (options.locations) {
+ comment.loc = new SourceLocation();
+ comment.loc.start = startLoc;
+ comment.loc.end = endLoc;
+ }
+ if (options.ranges)
+ comment.range = [start, end];
+ comments.push(comment);
+ };
+ }
+ isKeyword = options.ecmaVersion >= 6 ? isEcma6Keyword : isEcma5AndLessKeyword;
+ }
+
+ // The `getLineInfo` function is mostly useful when the
+ // `locations` option is off (for performance reasons) and you
+ // want to find the line/column position for a given character
+ // offset. `input` should be the code string that the offset refers
+ // into.
+
+ var getLineInfo = exports.getLineInfo = function(input, offset) {
+ for (var line = 1, cur = 0;;) {
+ lineBreak.lastIndex = cur;
+ var match = lineBreak.exec(input);
+ if (match && match.index < offset) {
+ ++line;
+ cur = match.index + match[0].length;
+ } else break;
+ }
+ return {line: line, column: offset - cur};
+ };
+
+ function Token() {
+ this.type = tokType;
+ this.value = tokVal;
+ this.start = tokStart;
+ this.end = tokEnd;
+ if (options.locations) {
+ this.loc = new SourceLocation();
+ this.loc.end = tokEndLoc;
+ // TODO: remove in next major release
+ this.startLoc = tokStartLoc;
+ this.endLoc = tokEndLoc;
+ }
+ if (options.ranges)
+ this.range = [tokStart, tokEnd];
+ }
+
+ exports.Token = Token;
+
+ // Acorn is organized as a tokenizer and a recursive-descent parser.
+ // The `tokenize` export provides an interface to the tokenizer.
+ // Because the tokenizer is optimized for being efficiently used by
+ // the Acorn parser itself, this interface is somewhat crude and not
+ // very modular. Performing another parse or call to `tokenize` will
+ // reset the internal state, and invalidate existing tokenizers.
+
+ exports.tokenize = function(inpt, opts) {
+ input = String(inpt); inputLen = input.length;
+ setOptions(opts);
+ initTokenState();
+ skipSpace();
+
+ function getToken(forceRegexp) {
+ lastEnd = tokEnd;
+ readToken(forceRegexp);
+ return new Token();
+ }
+ getToken.jumpTo = function(pos, reAllowed) {
+ tokPos = pos;
+ if (options.locations) {
+ tokCurLine = 1;
+ tokLineStart = lineBreak.lastIndex = 0;
+ var match;
+ while ((match = lineBreak.exec(input)) && match.index < pos) {
+ ++tokCurLine;
+ tokLineStart = match.index + match[0].length;
+ }
+ }
+ tokRegexpAllowed = reAllowed;
+ skipSpace();
+ };
+ getToken.noRegexp = function() {
+ tokRegexpAllowed = false;
+ };
+ getToken.options = options;
+ return getToken;
+ };
+
+ // State is kept in (closure-)global variables. We already saw the
+ // `options`, `input`, and `inputLen` variables above.
+
+ // The current position of the tokenizer in the input.
+
+ var tokPos;
+
+ // The start and end offsets of the current token.
+
+ var tokStart, tokEnd;
+
+ // When `options.locations` is true, these hold objects
+ // containing the tokens start and end line/column pairs.
+
+ var tokStartLoc, tokEndLoc;
+
+ // The type and value of the current token. Token types are objects,
+ // named by variables against which they can be compared, and
+ // holding properties that describe them (indicating, for example,
+ // the precedence of an infix operator, and the original name of a
+ // keyword token). The kind of value that's held in `tokVal` depends
+ // on the type of the token. For literals, it is the literal value,
+ // for operators, the operator name, and so on.
+
+ var tokType, tokVal;
+
+ // Internal state for the tokenizer. To distinguish between division
+ // operators and regular expressions, it remembers whether the last
+ // token was one that is allowed to be followed by an expression.
+ // (If it is, a slash is probably a regexp, if it isn't it's a
+ // division operator. See the `parseStatement` function for a
+ // caveat.)
+
+ var tokRegexpAllowed;
+
+ // When `options.locations` is true, these are used to keep
+ // track of the current line, and know when a new line has been
+ // entered.
+
+ var tokCurLine, tokLineStart;
+
+ // These store the position of the previous token, which is useful
+ // when finishing a node and assigning its `end` position.
+
+ var lastStart, lastEnd, lastEndLoc;
+
+ // This is the parser's state. `inFunction` is used to reject
+ // `return` statements outside of functions, `inGenerator` to
+ // reject `yield`s outside of generators, `labels` to verify
+ // that `break` and `continue` have somewhere to jump to, and
+ // `strict` indicates whether strict mode is on.
+
+ var inFunction, inGenerator, labels, strict;
+
+ // This counter is used for checking that arrow expressions did
+ // not contain nested parentheses in argument list.
+
+ var metParenL;
+
+ // This is used by the tokenizer to track the template strings it is
+ // inside, and count the amount of open braces seen inside them, to
+ // be able to switch back to a template token when the } to match ${
+ // is encountered. It will hold an array of integers.
+
+ var templates;
+
+ function initParserState() {
+ lastStart = lastEnd = tokPos;
+ if (options.locations) lastEndLoc = curPosition();
+ inFunction = inGenerator = strict = false;
+ labels = [];
+ skipSpace();
+ readToken();
+ }
+
+ // This function is used to raise exceptions on parse errors. It
+ // takes an offset integer (into the current `input`) to indicate
+ // the location of the error, attaches the position to the end
+ // of the error message, and then raises a `SyntaxError` with that
+ // message.
+
+ function raise(pos, message) {
+ var loc = getLineInfo(input, pos);
+ message += " (" + loc.line + ":" + loc.column + ")";
+ var err = new SyntaxError(message);
+ err.pos = pos; err.loc = loc; err.raisedAt = tokPos;
+ throw err;
+ }
+
+ // Reused empty array added for node fields that are always empty.
+
+ var empty = [];
+
+ // ## Token types
+
+ // The assignment of fine-grained, information-carrying type objects
+ // allows the tokenizer to store the information it has about a
+ // token in a way that is very cheap for the parser to look up.
+
+ // All token type variables start with an underscore, to make them
+ // easy to recognize.
+
+ // These are the general types. The `type` property is only used to
+ // make them recognizeable when debugging.
+
+ var _num = {type: "num"}, _regexp = {type: "regexp"}, _string = {type: "string"};
+ var _name = {type: "name"}, _eof = {type: "eof"};
+
+ // Keyword tokens. The `keyword` property (also used in keyword-like
+ // operators) indicates that the token originated from an
+ // identifier-like word, which is used when parsing property names.
+ //
+ // The `beforeExpr` property is used to disambiguate between regular
+ // expressions and divisions. It is set on all token types that can
+ // be followed by an expression (thus, a slash after them would be a
+ // regular expression).
+ //
+ // `isLoop` marks a keyword as starting a loop, which is important
+ // to know when parsing a label, in order to allow or disallow
+ // continue jumps to that label.
+
+ var _break = {keyword: "break"}, _case = {keyword: "case", beforeExpr: true}, _catch = {keyword: "catch"};
+ var _continue = {keyword: "continue"}, _debugger = {keyword: "debugger"}, _default = {keyword: "default"};
+ var _do = {keyword: "do", isLoop: true}, _else = {keyword: "else", beforeExpr: true};
+ var _finally = {keyword: "finally"}, _for = {keyword: "for", isLoop: true}, _function = {keyword: "function"};
+ var _if = {keyword: "if"}, _return = {keyword: "return", beforeExpr: true}, _switch = {keyword: "switch"};
+ var _throw = {keyword: "throw", beforeExpr: true}, _try = {keyword: "try"}, _var = {keyword: "var"};
+ var _let = {keyword: "let"}, _const = {keyword: "const"};
+ var _while = {keyword: "while", isLoop: true}, _with = {keyword: "with"}, _new = {keyword: "new", beforeExpr: true};
+ var _this = {keyword: "this"};
+ var _class = {keyword: "class"}, _extends = {keyword: "extends", beforeExpr: true};
+ var _export = {keyword: "export"}, _import = {keyword: "import"};
+ var _yield = {keyword: "yield", beforeExpr: true};
+
+ // The keywords that denote values.
+
+ var _null = {keyword: "null", atomValue: null}, _true = {keyword: "true", atomValue: true};
+ var _false = {keyword: "false", atomValue: false};
+
+ // Some keywords are treated as regular operators. `in` sometimes
+ // (when parsing `for`) needs to be tested against specifically, so
+ // we assign a variable name to it for quick comparing.
+
+ var _in = {keyword: "in", binop: 7, beforeExpr: true};
+
+ // Map keyword names to token types.
+
+ var keywordTypes = {"break": _break, "case": _case, "catch": _catch,
+ "continue": _continue, "debugger": _debugger, "default": _default,
+ "do": _do, "else": _else, "finally": _finally, "for": _for,
+ "function": _function, "if": _if, "return": _return, "switch": _switch,
+ "throw": _throw, "try": _try, "var": _var, "let": _let, "const": _const,
+ "while": _while, "with": _with,
+ "null": _null, "true": _true, "false": _false, "new": _new, "in": _in,
+ "instanceof": {keyword: "instanceof", binop: 7, beforeExpr: true}, "this": _this,
+ "typeof": {keyword: "typeof", prefix: true, beforeExpr: true},
+ "void": {keyword: "void", prefix: true, beforeExpr: true},
+ "delete": {keyword: "delete", prefix: true, beforeExpr: true},
+ "class": _class, "extends": _extends,
+ "export": _export, "import": _import, "yield": _yield};
+
+ // Punctuation token types. Again, the `type` property is purely for debugging.
+
+ var _bracketL = {type: "[", beforeExpr: true}, _bracketR = {type: "]"}, _braceL = {type: "{", beforeExpr: true};
+ var _braceR = {type: "}"}, _parenL = {type: "(", beforeExpr: true}, _parenR = {type: ")"};
+ var _comma = {type: ",", beforeExpr: true}, _semi = {type: ";", beforeExpr: true};
+ var _colon = {type: ":", beforeExpr: true}, _dot = {type: "."}, _question = {type: "?", beforeExpr: true};
+ var _arrow = {type: "=>", beforeExpr: true}, _template = {type: "template"}, _templateContinued = {type: "templateContinued"};
+ var _ellipsis = {type: "...", prefix: true, beforeExpr: true};
+
+ // Operators. These carry several kinds of properties to help the
+ // parser use them properly (the presence of these properties is
+ // what categorizes them as operators).
+ //
+ // `binop`, when present, specifies that this operator is a binary
+ // operator, and will refer to its precedence.
+ //
+ // `prefix` and `postfix` mark the operator as a prefix or postfix
+ // unary operator. `isUpdate` specifies that the node produced by
+ // the operator should be of type UpdateExpression rather than
+ // simply UnaryExpression (`++` and `--`).
+ //
+ // `isAssign` marks all of `=`, `+=`, `-=` etcetera, which act as
+ // binary operators with a very low precedence, that should result
+ // in AssignmentExpression nodes.
+
+ var _slash = {binop: 10, beforeExpr: true}, _eq = {isAssign: true, beforeExpr: true};
+ var _assign = {isAssign: true, beforeExpr: true};
+ var _incDec = {postfix: true, prefix: true, isUpdate: true}, _prefix = {prefix: true, beforeExpr: true};
+ var _logicalOR = {binop: 1, beforeExpr: true};
+ var _logicalAND = {binop: 2, beforeExpr: true};
+ var _bitwiseOR = {binop: 3, beforeExpr: true};
+ var _bitwiseXOR = {binop: 4, beforeExpr: true};
+ var _bitwiseAND = {binop: 5, beforeExpr: true};
+ var _equality = {binop: 6, beforeExpr: true};
+ var _relational = {binop: 7, beforeExpr: true};
+ var _bitShift = {binop: 8, beforeExpr: true};
+ var _plusMin = {binop: 9, prefix: true, beforeExpr: true};
+ var _modulo = {binop: 10, beforeExpr: true};
+
+ // '*' may be multiply or have special meaning in ES6
+ var _star = {binop: 10, beforeExpr: true};
+
+ // Provide access to the token types for external users of the
+ // tokenizer.
+
+ exports.tokTypes = {bracketL: _bracketL, bracketR: _bracketR, braceL: _braceL, braceR: _braceR,
+ parenL: _parenL, parenR: _parenR, comma: _comma, semi: _semi, colon: _colon,
+ dot: _dot, ellipsis: _ellipsis, question: _question, slash: _slash, eq: _eq,
+ name: _name, eof: _eof, num: _num, regexp: _regexp, string: _string,
+ arrow: _arrow, template: _template, templateContinued: _templateContinued, star: _star,
+ assign: _assign};
+ for (var kw in keywordTypes) exports.tokTypes["_" + kw] = keywordTypes[kw];
+
+ // This is a trick taken from Esprima. It turns out that, on
+ // non-Chrome browsers, to check whether a string is in a set, a
+ // predicate containing a big ugly `switch` statement is faster than
+ // a regular expression, and on Chrome the two are about on par.
+ // This function uses `eval` (non-lexical) to produce such a
+ // predicate from a space-separated string of words.
+ //
+ // It starts by sorting the words by length.
+
+ function makePredicate(words) {
+ words = words.split(" ");
+ var f = "", cats = [];
+ out: for (var i = 0; i < words.length; ++i) {
+ for (var j = 0; j < cats.length; ++j)
+ if (cats[j][0].length == words[i].length) {
+ cats[j].push(words[i]);
+ continue out;
+ }
+ cats.push([words[i]]);
+ }
+ function compareTo(arr) {
+ if (arr.length == 1) return f += "return str === " + JSON.stringify(arr[0]) + ";";
+ f += "switch(str){";
+ for (var i = 0; i < arr.length; ++i) f += "case " + JSON.stringify(arr[i]) + ":";
+ f += "return true}return false;";
+ }
+
+ // When there are more than three length categories, an outer
+ // switch first dispatches on the lengths, to save on comparisons.
+
+ if (cats.length > 3) {
+ cats.sort(function(a, b) {return b.length - a.length;});
+ f += "switch(str.length){";
+ for (var i = 0; i < cats.length; ++i) {
+ var cat = cats[i];
+ f += "case " + cat[0].length + ":";
+ compareTo(cat);
+ }
+ f += "}";
+
+ // Otherwise, simply generate a flat `switch` statement.
+
+ } else {
+ compareTo(words);
+ }
+ return new Function("str", f);
+ }
+
+ // The ECMAScript 3 reserved word list.
+
+ var isReservedWord3 = makePredicate("abstract boolean byte char class double enum export extends final float goto implements import int interface long native package private protected public short static super synchronized throws transient volatile");
+
+ // ECMAScript 5 reserved words.
+
+ var isReservedWord5 = makePredicate("class enum extends super const export import");
+
+ // The additional reserved words in strict mode.
+
+ var isStrictReservedWord = makePredicate("implements interface let package private protected public static yield");
+
+ // The forbidden variable names in strict mode.
+
+ var isStrictBadIdWord = makePredicate("eval arguments");
+
+ // And the keywords.
+
+ var ecma5AndLessKeywords = "break case catch continue debugger default do else finally for function if return switch throw try var while with null true false instanceof typeof void delete new in this";
+
+ var isEcma5AndLessKeyword = makePredicate(ecma5AndLessKeywords);
+
+ var isEcma6Keyword = makePredicate(ecma5AndLessKeywords + " let const class extends export import yield");
+
+ var isKeyword = isEcma5AndLessKeyword;
+
+ // ## Character categories
+
+ // Big ugly regular expressions that match characters in the
+ // whitespace, identifier, and identifier-start categories. These
+ // are only applied when a character is found to actually have a
+ // code point above 128.
+ // Generated by `tools/generate-identifier-regex.js`.
+
+ var nonASCIIwhitespace = /[\u1680\u180e\u2000-\u200a\u202f\u205f\u3000\ufeff]/;
+ var nonASCIIidentifierStartChars = "\xAA\xB5\xBA\xC0-\xD6\xD8-\xF6\xF8-\u02C1\u02C6-\u02D1\u02E0-\u02E4\u02EC\u02EE\u0370-\u0374\u0376\u0377\u037A-\u037D\u037F\u0386\u0388-\u038A\u038C\u038E-\u03A1\u03A3-\u03F5\u03F7-\u0481\u048A-\u052F\u0531-\u0556\u0559\u0561-\u0587\u05D0-\u05EA\u05F0-\u05F2\u0620-\u064A\u066E\u066F\u0671-\u06D3\u06D5\u06E5\u06E6\u06EE\u06EF\u06FA-\u06FC\u06FF\u0710\u0712-\u072F\u074D-\u07A5\u07B1\u07CA-\u07EA\u07F4\u07F5\u07FA\u0800-\u0815\u081A\u0824\u0828\u0840-\u0858\u08A0-\u08B2\u0904-\u0939\u093D\u0950\u0958-\u0961\u0971-\u0980\u0985-\u098C\u098F\u0990\u0993-\u09A8\u09AA-\u09B0\u09B2\u09B6-\u09B9\u09BD\u09CE\u09DC\u09DD\u09DF-\u09E1\u09F0\u09F1\u0A05-\u0A0A\u0A0F\u0A10\u0A13-\u0A28\u0A2A-\u0A30\u0A32\u0A33\u0A35\u0A36\u0A38\u0A39\u0A59-\u0A5C\u0A5E\u0A72-\u0A74\u0A85-\u0A8D\u0A8F-\u0A91\u0A93-\u0AA8\u0AAA-\u0AB0\u0AB2\u0AB3\u0AB5-\u0AB9\u0ABD\u0AD0\u0AE0\u0AE1\u0B05-\u0B0C\u0B0F\u0B10\u0B13-\u0B28\u0B2A-\u0B30\u0B32\u0B33\u0B35-\u0B39\u0B3D\u0B5C\u0B5D\u0B5F-\u0B61\u0B71\u0B83\u0B85-\u0B8A\u0B8E-\u0B90\u0B92-\u0B95\u0B99\u0B9A\u0B9C\u0B9E\u0B9F\u0BA3\u0BA4\u0BA8-\u0BAA\u0BAE-\u0BB9\u0BD0\u0C05-\u0C0C\u0C0E-\u0C10\u0C12-\u0C28\u0C2A-\u0C39\u0C3D\u0C58\u0C59\u0C60\u0C61\u0C85-\u0C8C\u0C8E-\u0C90\u0C92-\u0CA8\u0CAA-\u0CB3\u0CB5-\u0CB9\u0CBD\u0CDE\u0CE0\u0CE1\u0CF1\u0CF2\u0D05-\u0D0C\u0D0E-\u0D10\u0D12-\u0D3A\u0D3D\u0D4E\u0D60\u0D61\u0D7A-\u0D7F\u0D85-\u0D96\u0D9A-\u0DB1\u0DB3-\u0DBB\u0DBD\u0DC0-\u0DC6\u0E01-\u0E30\u0E32\u0E33\u0E40-\u0E46\u0E81\u0E82\u0E84\u0E87\u0E88\u0E8A\u0E8D\u0E94-\u0E97\u0E99-\u0E9F\u0EA1-\u0EA3\u0EA5\u0EA7\u0EAA\u0EAB\u0EAD-\u0EB0\u0EB2\u0EB3\u0EBD\u0EC0-\u0EC4\u0EC6\u0EDC-\u0EDF\u0F00\u0F40-\u0F47\u0F49-\u0F6C\u0F88-\u0F8C\u1000-\u102A\u103F\u1050-\u1055\u105A-\u105D\u1061\u1065\u1066\u106E-\u1070\u1075-\u1081\u108E\u10A0-\u10C5\u10C7\u10CD\u10D0-\u10FA\u10FC-\u1248\u124A-\u124D\u1250-\u1256\u1258\u125A-\u125D\u1260-\u1288\u128A-\u128D\u1290-\u12B0\u12B2-\u12B5\u12B8-\u12BE\u12C0\u12C2-\u12C5\u12C8-\u12D6\u12D8-\u1310\u1312-\u1315\u1318-\u135A\u1380-\u138F\u13A0-\u13F4\u1401-\u166C\u166F-\u167F\u1681-\u169A\u16A0-\u16EA\u16EE-\u16F8\u1700-\u170C\u170E-\u1711\u1720-\u1731\u1740-\u1751\u1760-\u176C\u176E-\u1770\u1780-\u17B3\u17D7\u17DC\u1820-\u1877\u1880-\u18A8\u18AA\u18B0-\u18F5\u1900-\u191E\u1950-\u196D\u1970-\u1974\u1980-\u19AB\u19C1-\u19C7\u1A00-\u1A16\u1A20-\u1A54\u1AA7\u1B05-\u1B33\u1B45-\u1B4B\u1B83-\u1BA0\u1BAE\u1BAF\u1BBA-\u1BE5\u1C00-\u1C23\u1C4D-\u1C4F\u1C5A-\u1C7D\u1CE9-\u1CEC\u1CEE-\u1CF1\u1CF5\u1CF6\u1D00-\u1DBF\u1E00-\u1F15\u1F18-\u1F1D\u1F20-\u1F45\u1F48-\u1F4D\u1F50-\u1F57\u1F59\u1F5B\u1F5D\u1F5F-\u1F7D\u1F80-\u1FB4\u1FB6-\u1FBC\u1FBE\u1FC2-\u1FC4\u1FC6-\u1FCC\u1FD0-\u1FD3\u1FD6-\u1FDB\u1FE0-\u1FEC\u1FF2-\u1FF4\u1FF6-\u1FFC\u2071\u207F\u2090-\u209C\u2102\u2107\u210A-\u2113\u2115\u2119-\u211D\u2124\u2126\u2128\u212A-\u212D\u212F-\u2139\u213C-\u213F\u2145-\u2149\u214E\u2160-\u2188\u2C00-\u2C2E\u2C30-\u2C5E\u2C60-\u2CE4\u2CEB-\u2CEE\u2CF2\u2CF3\u2D00-\u2D25\u2D27\u2D2D\u2D30-\u2D67\u2D6F\u2D80-\u2D96\u2DA0-\u2DA6\u2DA8-\u2DAE\u2DB0-\u2DB6\u2DB8-\u2DBE\u2DC0-\u2DC6\u2DC8-\u2DCE\u2DD0-\u2DD6\u2DD8-\u2DDE\u2E2F\u3005-\u3007\u3021-\u3029\u3031-\u3035\u3038-\u303C\u3041-\u3096\u309D-\u309F\u30A1-\u30FA\u30FC-\u30FF\u3105-\u312D\u3131-\u318E\u31A0-\u31BA\u31F0-\u31FF\u3400-\u4DB5\u4E00-\u9FCC\uA000-\uA48C\uA4D0-\uA4FD\uA500-\uA60C\uA610-\uA61F\uA62A\uA62B\uA640-\uA66E\uA67F-\uA69D\uA6A0-\uA6EF\uA717-\uA71F\uA722-\uA788\uA78B-\uA78E\uA790-\uA7AD\uA7B0\uA7B1\uA7F7-\uA801\uA803-\uA805\uA807-\uA80A\uA80C-\uA822\uA840-\uA873\uA882-\uA8B3\uA8F2-\uA8F7\uA8FB\uA90A-\uA925\uA930-\uA946\uA960-\uA97C\uA984-\uA9B2\uA9CF\uA9E0-\uA9E4\uA9E6-\uA9EF\uA9FA-\uA9FE\uAA00-\uAA28\uAA40-\uAA42\uAA44-\uAA4B\uAA60-\uAA76\uAA7A\uAA7E-\uAAAF\uAAB1\uAAB5\uAAB6\uAAB9-\uAABD\uAAC0\uAAC2\uAADB-\uAADD\uAAE0-\uAAEA\uAAF2-\uAAF4\uAB01-\uAB06\uAB09-\uAB0E\uAB11-\uAB16\uAB20-\uAB26\uAB28-\uAB2E\uAB30-\uAB5A\uAB5C-\uAB5F\uAB64\uAB65\uABC0-\uABE2\uAC00-\uD7A3\uD7B0-\uD7C6\uD7CB-\uD7FB\uF900-\uFA6D\uFA70-\uFAD9\uFB00-\uFB06\uFB13-\uFB17\uFB1D\uFB1F-\uFB28\uFB2A-\uFB36\uFB38-\uFB3C\uFB3E\uFB40\uFB41\uFB43\uFB44\uFB46-\uFBB1\uFBD3-\uFD3D\uFD50-\uFD8F\uFD92-\uFDC7\uFDF0-\uFDFB\uFE70-\uFE74\uFE76-\uFEFC\uFF21-\uFF3A\uFF41-\uFF5A\uFF66-\uFFBE\uFFC2-\uFFC7\uFFCA-\uFFCF\uFFD2-\uFFD7\uFFDA-\uFFDC";
+ var nonASCIIidentifierChars = "\u0300-\u036F\u0483-\u0487\u0591-\u05BD\u05BF\u05C1\u05C2\u05C4\u05C5\u05C7\u0610-\u061A\u064B-\u0669\u0670\u06D6-\u06DC\u06DF-\u06E4\u06E7\u06E8\u06EA-\u06ED\u06F0-\u06F9\u0711\u0730-\u074A\u07A6-\u07B0\u07C0-\u07C9\u07EB-\u07F3\u0816-\u0819\u081B-\u0823\u0825-\u0827\u0829-\u082D\u0859-\u085B\u08E4-\u0903\u093A-\u093C\u093E-\u094F\u0951-\u0957\u0962\u0963\u0966-\u096F\u0981-\u0983\u09BC\u09BE-\u09C4\u09C7\u09C8\u09CB-\u09CD\u09D7\u09E2\u09E3\u09E6-\u09EF\u0A01-\u0A03\u0A3C\u0A3E-\u0A42\u0A47\u0A48\u0A4B-\u0A4D\u0A51\u0A66-\u0A71\u0A75\u0A81-\u0A83\u0ABC\u0ABE-\u0AC5\u0AC7-\u0AC9\u0ACB-\u0ACD\u0AE2\u0AE3\u0AE6-\u0AEF\u0B01-\u0B03\u0B3C\u0B3E-\u0B44\u0B47\u0B48\u0B4B-\u0B4D\u0B56\u0B57\u0B62\u0B63\u0B66-\u0B6F\u0B82\u0BBE-\u0BC2\u0BC6-\u0BC8\u0BCA-\u0BCD\u0BD7\u0BE6-\u0BEF\u0C00-\u0C03\u0C3E-\u0C44\u0C46-\u0C48\u0C4A-\u0C4D\u0C55\u0C56\u0C62\u0C63\u0C66-\u0C6F\u0C81-\u0C83\u0CBC\u0CBE-\u0CC4\u0CC6-\u0CC8\u0CCA-\u0CCD\u0CD5\u0CD6\u0CE2\u0CE3\u0CE6-\u0CEF\u0D01-\u0D03\u0D3E-\u0D44\u0D46-\u0D48\u0D4A-\u0D4D\u0D57\u0D62\u0D63\u0D66-\u0D6F\u0D82\u0D83\u0DCA\u0DCF-\u0DD4\u0DD6\u0DD8-\u0DDF\u0DE6-\u0DEF\u0DF2\u0DF3\u0E31\u0E34-\u0E3A\u0E47-\u0E4E\u0E50-\u0E59\u0EB1\u0EB4-\u0EB9\u0EBB\u0EBC\u0EC8-\u0ECD\u0ED0-\u0ED9\u0F18\u0F19\u0F20-\u0F29\u0F35\u0F37\u0F39\u0F3E\u0F3F\u0F71-\u0F84\u0F86\u0F87\u0F8D-\u0F97\u0F99-\u0FBC\u0FC6\u102B-\u103E\u1040-\u1049\u1056-\u1059\u105E-\u1060\u1062-\u1064\u1067-\u106D\u1071-\u1074\u1082-\u108D\u108F-\u109D\u135D-\u135F\u1712-\u1714\u1732-\u1734\u1752\u1753\u1772\u1773\u17B4-\u17D3\u17DD\u17E0-\u17E9\u180B-\u180D\u1810-\u1819\u18A9\u1920-\u192B\u1930-\u193B\u1946-\u194F\u19B0-\u19C0\u19C8\u19C9\u19D0-\u19D9\u1A17-\u1A1B\u1A55-\u1A5E\u1A60-\u1A7C\u1A7F-\u1A89\u1A90-\u1A99\u1AB0-\u1ABD\u1B00-\u1B04\u1B34-\u1B44\u1B50-\u1B59\u1B6B-\u1B73\u1B80-\u1B82\u1BA1-\u1BAD\u1BB0-\u1BB9\u1BE6-\u1BF3\u1C24-\u1C37\u1C40-\u1C49\u1C50-\u1C59\u1CD0-\u1CD2\u1CD4-\u1CE8\u1CED\u1CF2-\u1CF4\u1CF8\u1CF9\u1DC0-\u1DF5\u1DFC-\u1DFF\u200C\u200D\u203F\u2040\u2054\u20D0-\u20DC\u20E1\u20E5-\u20F0\u2CEF-\u2CF1\u2D7F\u2DE0-\u2DFF\u302A-\u302F\u3099\u309A\uA620-\uA629\uA66F\uA674-\uA67D\uA69F\uA6F0\uA6F1\uA802\uA806\uA80B\uA823-\uA827\uA880\uA881\uA8B4-\uA8C4\uA8D0-\uA8D9\uA8E0-\uA8F1\uA900-\uA909\uA926-\uA92D\uA947-\uA953\uA980-\uA983\uA9B3-\uA9C0\uA9D0-\uA9D9\uA9E5\uA9F0-\uA9F9\uAA29-\uAA36\uAA43\uAA4C\uAA4D\uAA50-\uAA59\uAA7B-\uAA7D\uAAB0\uAAB2-\uAAB4\uAAB7\uAAB8\uAABE\uAABF\uAAC1\uAAEB-\uAAEF\uAAF5\uAAF6\uABE3-\uABEA\uABEC\uABED\uABF0-\uABF9\uFB1E\uFE00-\uFE0F\uFE20-\uFE2D\uFE33\uFE34\uFE4D-\uFE4F\uFF10-\uFF19\uFF3F";
+ var nonASCIIidentifierStart = new RegExp("[" + nonASCIIidentifierStartChars + "]");
+ var nonASCIIidentifier = new RegExp("[" + nonASCIIidentifierStartChars + nonASCIIidentifierChars + "]");
+
+ // Whether a single character denotes a newline.
+
+ var newline = /[\n\r\u2028\u2029]/;
+
+ function isNewLine(code) {
+ return code === 10 || code === 13 || code === 0x2028 || code == 0x2029;
+ }
+
+ // Matches a whole line break (where CRLF is considered a single
+ // line break). Used to count lines.
+
+ var lineBreak = /\r\n|[\n\r\u2028\u2029]/g;
+
+ // Test whether a given character code starts an identifier.
+
+ var isIdentifierStart = exports.isIdentifierStart = function(code) {
+ if (code < 65) return code === 36;
+ if (code < 91) return true;
+ if (code < 97) return code === 95;
+ if (code < 123)return true;
+ return code >= 0xaa && nonASCIIidentifierStart.test(String.fromCharCode(code));
+ };
+
+ // Test whether a given character is part of an identifier.
+
+ var isIdentifierChar = exports.isIdentifierChar = function(code) {
+ if (code < 48) return code === 36;
+ if (code < 58) return true;
+ if (code < 65) return false;
+ if (code < 91) return true;
+ if (code < 97) return code === 95;
+ if (code < 123)return true;
+ return code >= 0xaa && nonASCIIidentifier.test(String.fromCharCode(code));
+ };
+
+ // ## Tokenizer
+
+ // These are used when `options.locations` is on, for the
+ // `tokStartLoc` and `tokEndLoc` properties.
+
+ function Position(line, col) {
+ this.line = line;
+ this.column = col;
+ }
+
+ Position.prototype.offset = function(n) {
+ return new Position(this.line, this.column + n);
+ }
+
+ function curPosition() {
+ return new Position(tokCurLine, tokPos - tokLineStart);
+ }
+
+ // Reset the token state. Used at the start of a parse.
+
+ function initTokenState(pos) {
+ if (pos) {
+ tokPos = pos;
+ tokLineStart = Math.max(0, input.lastIndexOf("\n", pos));
+ tokCurLine = input.slice(0, tokLineStart).split(newline).length;
+ } else {
+ tokCurLine = 1;
+ tokPos = tokLineStart = 0;
+ }
+ tokRegexpAllowed = true;
+ metParenL = 0;
+ templates = [];
+ }
+
+ // Called at the end of every token. Sets `tokEnd`, `tokVal`, and
+ // `tokRegexpAllowed`, and skips the space after the token, so that
+ // the next one's `tokStart` will point at the right position.
+
+ function finishToken(type, val, shouldSkipSpace) {
+ tokEnd = tokPos;
+ if (options.locations) tokEndLoc = curPosition();
+ tokType = type;
+ if (shouldSkipSpace !== false) skipSpace();
+ tokVal = val;
+ tokRegexpAllowed = type.beforeExpr;
+ if (options.onToken) {
+ options.onToken(new Token());
+ }
+ }
+
+ function skipBlockComment() {
+ var startLoc = options.onComment && options.locations && curPosition();
+ var start = tokPos, end = input.indexOf("*/", tokPos += 2);
+ if (end === -1) raise(tokPos - 2, "Unterminated comment");
+ tokPos = end + 2;
+ if (options.locations) {
+ lineBreak.lastIndex = start;
+ var match;
+ while ((match = lineBreak.exec(input)) && match.index < tokPos) {
+ ++tokCurLine;
+ tokLineStart = match.index + match[0].length;
+ }
+ }
+ if (options.onComment)
+ options.onComment(true, input.slice(start + 2, end), start, tokPos,
+ startLoc, options.locations && curPosition());
+ }
+
+ function skipLineComment(startSkip) {
+ var start = tokPos;
+ var startLoc = options.onComment && options.locations && curPosition();
+ var ch = input.charCodeAt(tokPos+=startSkip);
+ while (tokPos < inputLen && ch !== 10 && ch !== 13 && ch !== 8232 && ch !== 8233) {
+ ++tokPos;
+ ch = input.charCodeAt(tokPos);
+ }
+ if (options.onComment)
+ options.onComment(false, input.slice(start + startSkip, tokPos), start, tokPos,
+ startLoc, options.locations && curPosition());
+ }
+
+ // Called at the start of the parse and after every token. Skips
+ // whitespace and comments, and.
+
+ function skipSpace() {
+ while (tokPos < inputLen) {
+ var ch = input.charCodeAt(tokPos);
+ if (ch === 32) { // ' '
+ ++tokPos;
+ } else if (ch === 13) {
+ ++tokPos;
+ var next = input.charCodeAt(tokPos);
+ if (next === 10) {
+ ++tokPos;
+ }
+ if (options.locations) {
+ ++tokCurLine;
+ tokLineStart = tokPos;
+ }
+ } else if (ch === 10 || ch === 8232 || ch === 8233) {
+ ++tokPos;
+ if (options.locations) {
+ ++tokCurLine;
+ tokLineStart = tokPos;
+ }
+ } else if (ch > 8 && ch < 14) {
+ ++tokPos;
+ } else if (ch === 47) { // '/'
+ var next = input.charCodeAt(tokPos + 1);
+ if (next === 42) { // '*'
+ skipBlockComment();
+ } else if (next === 47) { // '/'
+ skipLineComment(2);
+ } else break;
+ } else if (ch === 160) { // '\xa0'
+ ++tokPos;
+ } else if (ch >= 5760 && nonASCIIwhitespace.test(String.fromCharCode(ch))) {
+ ++tokPos;
+ } else {
+ break;
+ }
+ }
+ }
+
+ // ### Token reading
+
+ // This is the function that is called to fetch the next token. It
+ // is somewhat obscure, because it works in character codes rather
+ // than characters, and because operator parsing has been inlined
+ // into it.
+ //
+ // All in the name of speed.
+ //
+ // The `forceRegexp` parameter is used in the one case where the
+ // `tokRegexpAllowed` trick does not work. See `parseStatement`.
+
+ function readToken_dot() {
+ var next = input.charCodeAt(tokPos + 1);
+ if (next >= 48 && next <= 57) return readNumber(true);
+ var next2 = input.charCodeAt(tokPos + 2);
+ if (options.ecmaVersion >= 6 && next === 46 && next2 === 46) { // 46 = dot '.'
+ tokPos += 3;
+ return finishToken(_ellipsis);
+ } else {
+ ++tokPos;
+ return finishToken(_dot);
+ }
+ }
+
+ function readToken_slash() { // '/'
+ var next = input.charCodeAt(tokPos + 1);
+ if (tokRegexpAllowed) {++tokPos; return readRegexp();}
+ if (next === 61) return finishOp(_assign, 2);
+ return finishOp(_slash, 1);
+ }
+
+ function readToken_mult_modulo(code) { // '%*'
+ var next = input.charCodeAt(tokPos + 1);
+ if (next === 61) return finishOp(_assign, 2);
+ return finishOp(code === 42 ? _star : _modulo, 1);
+ }
+
+ function readToken_pipe_amp(code) { // '|&'
+ var next = input.charCodeAt(tokPos + 1);
+ if (next === code) return finishOp(code === 124 ? _logicalOR : _logicalAND, 2);
+ if (next === 61) return finishOp(_assign, 2);
+ return finishOp(code === 124 ? _bitwiseOR : _bitwiseAND, 1);
+ }
+
+ function readToken_caret() { // '^'
+ var next = input.charCodeAt(tokPos + 1);
+ if (next === 61) return finishOp(_assign, 2);
+ return finishOp(_bitwiseXOR, 1);
+ }
+
+ function readToken_plus_min(code) { // '+-'
+ var next = input.charCodeAt(tokPos + 1);
+ if (next === code) {
+ if (next == 45 && input.charCodeAt(tokPos + 2) == 62 &&
+ newline.test(input.slice(lastEnd, tokPos))) {
+ // A `-->` line comment
+ skipLineComment(3);
+ skipSpace();
+ return readToken();
+ }
+ return finishOp(_incDec, 2);
+ }
+ if (next === 61) return finishOp(_assign, 2);
+ return finishOp(_plusMin, 1);
+ }
+
+ function readToken_lt_gt(code) { // '<>'
+ var next = input.charCodeAt(tokPos + 1);
+ var size = 1;
+ if (next === code) {
+ size = code === 62 && input.charCodeAt(tokPos + 2) === 62 ? 3 : 2;
+ if (input.charCodeAt(tokPos + size) === 61) return finishOp(_assign, size + 1);
+ return finishOp(_bitShift, size);
+ }
+ if (next == 33 && code == 60 && input.charCodeAt(tokPos + 2) == 45 &&
+ input.charCodeAt(tokPos + 3) == 45) {
+ // `<!--`, an XML-style comment that should be interpreted as a line comment
+ skipLineComment(4);
+ skipSpace();
+ return readToken();
+ }
+ if (next === 61)
+ size = input.charCodeAt(tokPos + 2) === 61 ? 3 : 2;
+ return finishOp(_relational, size);
+ }
+
+ function readToken_eq_excl(code) { // '=!', '=>'
+ var next = input.charCodeAt(tokPos + 1);
+ if (next === 61) return finishOp(_equality, input.charCodeAt(tokPos + 2) === 61 ? 3 : 2);
+ if (code === 61 && next === 62 && options.ecmaVersion >= 6) { // '=>'
+ tokPos += 2;
+ return finishToken(_arrow);
+ }
+ return finishOp(code === 61 ? _eq : _prefix, 1);
+ }
+
+ function getTokenFromCode(code) {
+ switch (code) {
+ // The interpretation of a dot depends on whether it is followed
+ // by a digit or another two dots.
+ case 46: // '.'
+ return readToken_dot();
+
+ // Punctuation tokens.
+ case 40: ++tokPos; return finishToken(_parenL);
+ case 41: ++tokPos; return finishToken(_parenR);
+ case 59: ++tokPos; return finishToken(_semi);
+ case 44: ++tokPos; return finishToken(_comma);
+ case 91: ++tokPos; return finishToken(_bracketL);
+ case 93: ++tokPos; return finishToken(_bracketR);
+ case 123:
+ ++tokPos;
+ if (templates.length) ++templates[templates.length - 1];
+ return finishToken(_braceL);
+ case 125:
+ ++tokPos;
+ if (templates.length && --templates[templates.length - 1] === 0)
+ return readTemplateString(_templateContinued);
+ else
+ return finishToken(_braceR);
+ case 58: ++tokPos; return finishToken(_colon);
+ case 63: ++tokPos; return finishToken(_question);
+
+ case 96: // '`'
+ if (options.ecmaVersion >= 6) {
+ ++tokPos;
+ return readTemplateString(_template);
+ }
+
+ case 48: // '0'
+ var next = input.charCodeAt(tokPos + 1);
+ if (next === 120 || next === 88) return readRadixNumber(16); // '0x', '0X' - hex number
+ if (options.ecmaVersion >= 6) {
+ if (next === 111 || next === 79) return readRadixNumber(8); // '0o', '0O' - octal number
+ if (next === 98 || next === 66) return readRadixNumber(2); // '0b', '0B' - binary number
+ }
+ // Anything else beginning with a digit is an integer, octal
+ // number, or float.
+ case 49: case 50: case 51: case 52: case 53: case 54: case 55: case 56: case 57: // 1-9
+ return readNumber(false);
+
+ // Quotes produce strings.
+ case 34: case 39: // '"', "'"
+ return readString(code);
+
+ // Operators are parsed inline in tiny state machines. '=' (61) is
+ // often referred to. `finishOp` simply skips the amount of
+ // characters it is given as second argument, and returns a token
+ // of the type given by its first argument.
+
+ case 47: // '/'
+ return readToken_slash();
+
+ case 37: case 42: // '%*'
+ return readToken_mult_modulo(code);
+
+ case 124: case 38: // '|&'
+ return readToken_pipe_amp(code);
+
+ case 94: // '^'
+ return readToken_caret();
+
+ case 43: case 45: // '+-'
+ return readToken_plus_min(code);
+
+ case 60: case 62: // '<>'
+ return readToken_lt_gt(code);
+
+ case 61: case 33: // '=!'
+ return readToken_eq_excl(code);
+
+ case 126: // '~'
+ return finishOp(_prefix, 1);
+ }
+
+ return false;
+ }
+
+ function readToken(forceRegexp) {
+ if (!forceRegexp) tokStart = tokPos;
+ else tokPos = tokStart + 1;
+ if (options.locations) tokStartLoc = curPosition();
+ if (forceRegexp) return readRegexp();
+ if (tokPos >= inputLen) return finishToken(_eof);
+
+ var code = input.charCodeAt(tokPos);
+
+ // Identifier or keyword. '\uXXXX' sequences are allowed in
+ // identifiers, so '\' also dispatches to that.
+ if (isIdentifierStart(code) || code === 92 /* '\' */) return readWord();
+
+ var tok = getTokenFromCode(code);
+
+ if (tok === false) {
+ // If we are here, we either found a non-ASCII identifier
+ // character, or something that's entirely disallowed.
+ var ch = String.fromCharCode(code);
+ if (ch === "\\" || nonASCIIidentifierStart.test(ch)) return readWord();
+ raise(tokPos, "Unexpected character '" + ch + "'");
+ }
+ return tok;
+ }
+
+ function finishOp(type, size) {
+ var str = input.slice(tokPos, tokPos + size);
+ tokPos += size;
+ finishToken(type, str);
+ }
+
+ var regexpUnicodeSupport = false;
+ try { new RegExp("\uffff", "u"); regexpUnicodeSupport = true; }
+ catch(e) {}
+
+ // Parse a regular expression. Some context-awareness is necessary,
+ // since a '/' inside a '[]' set does not end the expression.
+
+ function readRegexp() {
+ var content = "", escaped, inClass, start = tokPos;
+ for (;;) {
+ if (tokPos >= inputLen) raise(start, "Unterminated regular expression");
+ var ch = input.charAt(tokPos);
+ if (newline.test(ch)) raise(start, "Unterminated regular expression");
+ if (!escaped) {
+ if (ch === "[") inClass = true;
+ else if (ch === "]" && inClass) inClass = false;
+ else if (ch === "/" && !inClass) break;
+ escaped = ch === "\\";
+ } else escaped = false;
+ ++tokPos;
+ }
+ var content = input.slice(start, tokPos);
+ ++tokPos;
+ // Need to use `readWord1` because '\uXXXX' sequences are allowed
+ // here (don't ask).
+ var mods = readWord1();
+ var tmp = content;
+ if (mods) {
+ var validFlags = /^[gmsiy]*$/;
+ if (options.ecmaVersion >= 6) validFlags = /^[gmsiyu]*$/;
+ if (!validFlags.test(mods)) raise(start, "Invalid regular expression flag");
+ if (mods.indexOf('u') >= 0 && !regexpUnicodeSupport) {
+ // Replace each astral symbol and every Unicode code point
+ // escape sequence that represents such a symbol with a single
+ // ASCII symbol to avoid throwing on regular expressions that
+ // are only valid in combination with the `/u` flag.
+ tmp = tmp
+ .replace(/\\u\{([0-9a-fA-F]{5,6})\}/g, "x")
+ .replace(/[\uD800-\uDBFF][\uDC00-\uDFFF]/g, "x");
+ }
+ }
+ // Detect invalid regular expressions.
+ try {
+ new RegExp(tmp);
+ } catch (e) {
+ if (e instanceof SyntaxError) raise(start, "Error parsing regular expression: " + e.message);
+ raise(e);
+ }
+ // Get a regular expression object for this pattern-flag pair, or `null` in
+ // case the current environment doesn't support the flags it uses.
+ try {
+ var value = new RegExp(content, mods);
+ } catch (err) {
+ value = null;
+ }
+ return finishToken(_regexp, {pattern: content, flags: mods, value: value});
+ }
+
+ // Read an integer in the given radix. Return null if zero digits
+ // were read, the integer value otherwise. When `len` is given, this
+ // will return `null` unless the integer has exactly `len` digits.
+
+ function readInt(radix, len) {
+ var start = tokPos, total = 0;
+ for (var i = 0, e = len == null ? Infinity : len; i < e; ++i) {
+ var code = input.charCodeAt(tokPos), val;
+ if (code >= 97) val = code - 97 + 10; // a
+ else if (code >= 65) val = code - 65 + 10; // A
+ else if (code >= 48 && code <= 57) val = code - 48; // 0-9
+ else val = Infinity;
+ if (val >= radix) break;
+ ++tokPos;
+ total = total * radix + val;
+ }
+ if (tokPos === start || len != null && tokPos - start !== len) return null;
+
+ return total;
+ }
+
+ function readRadixNumber(radix) {
+ tokPos += 2; // 0x
+ var val = readInt(radix);
+ if (val == null) raise(tokStart + 2, "Expected number in radix " + radix);
+ if (isIdentifierStart(input.charCodeAt(tokPos))) raise(tokPos, "Identifier directly after number");
+ return finishToken(_num, val);
+ }
+
+ // Read an integer, octal integer, or floating-point number.
+
+ function readNumber(startsWithDot) {
+ var start = tokPos, isFloat = false, octal = input.charCodeAt(tokPos) === 48;
+ if (!startsWithDot && readInt(10) === null) raise(start, "Invalid number");
+ if (input.charCodeAt(tokPos) === 46) {
+ ++tokPos;
+ readInt(10);
+ isFloat = true;
+ }
+ var next = input.charCodeAt(tokPos);
+ if (next === 69 || next === 101) { // 'eE'
+ next = input.charCodeAt(++tokPos);
+ if (next === 43 || next === 45) ++tokPos; // '+-'
+ if (readInt(10) === null) raise(start, "Invalid number");
+ isFloat = true;
+ }
+ if (isIdentifierStart(input.charCodeAt(tokPos))) raise(tokPos, "Identifier directly after number");
+
+ var str = input.slice(start, tokPos), val;
+ if (isFloat) val = parseFloat(str);
+ else if (!octal || str.length === 1) val = parseInt(str, 10);
+ else if (/[89]/.test(str) || strict) raise(start, "Invalid number");
+ else val = parseInt(str, 8);
+ return finishToken(_num, val);
+ }
+
+ // Read a string value, interpreting backslash-escapes.
+
+ function readCodePoint() {
+ var ch = input.charCodeAt(tokPos), code;
+
+ if (ch === 123) {
+ if (options.ecmaVersion < 6) unexpected();
+ ++tokPos;
+ code = readHexChar(input.indexOf('}', tokPos) - tokPos);
+ ++tokPos;
+ if (code > 0x10FFFF) unexpected();
+ } else {
+ code = readHexChar(4);
+ }
+
+ // UTF-16 Encoding
+ if (code <= 0xFFFF) {
+ return String.fromCharCode(code);
+ }
+ var cu1 = ((code - 0x10000) >> 10) + 0xD800;
+ var cu2 = ((code - 0x10000) & 1023) + 0xDC00;
+ return String.fromCharCode(cu1, cu2);
+ }
+
+ function readString(quote) {
+ ++tokPos;
+ var out = "";
+ for (;;) {
+ if (tokPos >= inputLen) raise(tokStart, "Unterminated string constant");
+ var ch = input.charCodeAt(tokPos);
+ if (ch === quote) {
+ ++tokPos;
+ return finishToken(_string, out);
+ }
+ if (ch === 92) { // '\'
+ out += readEscapedChar();
+ } else {
+ ++tokPos;
+ if (newline.test(String.fromCharCode(ch))) {
+ raise(tokStart, "Unterminated string constant");
+ }
+ out += String.fromCharCode(ch); // '\'
+ }
+ }
+ }
+
+ function readTemplateString(type) {
+ if (type == _templateContinued) templates.pop();
+ var out = "", start = tokPos;;
+ for (;;) {
+ if (tokPos >= inputLen) raise(tokStart, "Unterminated template");
+ var ch = input.charAt(tokPos);
+ if (ch === "`" || ch === "$" && input.charCodeAt(tokPos + 1) === 123) { // '`', '${'
+ var raw = input.slice(start, tokPos);
+ ++tokPos;
+ if (ch == "$") { ++tokPos; templates.push(1); }
+ return finishToken(type, {cooked: out, raw: raw});
+ }
+
+ if (ch === "\\") { // '\'
+ out += readEscapedChar();
+ } else {
+ ++tokPos;
+ if (newline.test(ch)) {
+ if (ch === "\r" && input.charCodeAt(tokPos) === 10) {
+ ++tokPos;
+ ch = "\n";
+ }
+ if (options.locations) {
+ ++tokCurLine;
+ tokLineStart = tokPos;
+ }
+ }
+ out += ch;
+ }
+ }
+ }
+
+ // Used to read escaped characters
+
+ function readEscapedChar() {
+ var ch = input.charCodeAt(++tokPos);
+ var octal = /^[0-7]+/.exec(input.slice(tokPos, tokPos + 3));
+ if (octal) octal = octal[0];
+ while (octal && parseInt(octal, 8) > 255) octal = octal.slice(0, -1);
+ if (octal === "0") octal = null;
+ ++tokPos;
+ if (octal) {
+ if (strict) raise(tokPos - 2, "Octal literal in strict mode");
+ tokPos += octal.length - 1;
+ return String.fromCharCode(parseInt(octal, 8));
+ } else {
+ switch (ch) {
+ case 110: return "\n"; // 'n' -> '\n'
+ case 114: return "\r"; // 'r' -> '\r'
+ case 120: return String.fromCharCode(readHexChar(2)); // 'x'
+ case 117: return readCodePoint(); // 'u'
+ case 116: return "\t"; // 't' -> '\t'
+ case 98: return "\b"; // 'b' -> '\b'
+ case 118: return "\u000b"; // 'v' -> '\u000b'
+ case 102: return "\f"; // 'f' -> '\f'
+ case 48: return "\0"; // 0 -> '\0'
+ case 13: if (input.charCodeAt(tokPos) === 10) ++tokPos; // '\r\n'
+ case 10: // ' \n'
+ if (options.locations) { tokLineStart = tokPos; ++tokCurLine; }
+ return "";
+ default: return String.fromCharCode(ch);
+ }
+ }
+ }
+
+ // Used to read character escape sequences ('\x', '\u', '\U').
+
+ function readHexChar(len) {
+ var n = readInt(16, len);
+ if (n === null) raise(tokStart, "Bad character escape sequence");
+ return n;
+ }
+
+ // Used to signal to callers of `readWord1` whether the word
+ // contained any escape sequences. This is needed because words with
+ // escape sequences must not be interpreted as keywords.
+
+ var containsEsc;
+
+ // Read an identifier, and return it as a string. Sets `containsEsc`
+ // to whether the word contained a '\u' escape.
+ //
+ // Only builds up the word character-by-character when it actually
+ // containeds an escape, as a micro-optimization.
+
+ function readWord1() {
+ containsEsc = false;
+ var word, first = true, start = tokPos;
+ for (;;) {
+ var ch = input.charCodeAt(tokPos);
+ if (isIdentifierChar(ch)) {
+ if (containsEsc) word += input.charAt(tokPos);
+ ++tokPos;
+ } else if (ch === 92) { // "\"
+ if (!containsEsc) word = input.slice(start, tokPos);
+ containsEsc = true;
+ if (input.charCodeAt(++tokPos) != 117) // "u"
+ raise(tokPos, "Expecting Unicode escape sequence \\uXXXX");
+ ++tokPos;
+ var esc = readHexChar(4);
+ var escStr = String.fromCharCode(esc);
+ if (!escStr) raise(tokPos - 1, "Invalid Unicode escape");
+ if (!(first ? isIdentifierStart(esc) : isIdentifierChar(esc)))
+ raise(tokPos - 4, "Invalid Unicode escape");
+ word += escStr;
+ } else {
+ break;
+ }
+ first = false;
+ }
+ return containsEsc ? word : input.slice(start, tokPos);
+ }
+
+ // Read an identifier or keyword token. Will check for reserved
+ // words when necessary.
+
+ function readWord() {
+ var word = readWord1();
+ var type = _name;
+ if (!containsEsc && isKeyword(word))
+ type = keywordTypes[word];
+ return finishToken(type, word);
+ }
+
+ // ## Parser
+
+ // A recursive descent parser operates by defining functions for all
+ // syntactic elements, and recursively calling those, each function
+ // advancing the input stream and returning an AST node. Precedence
+ // of constructs (for example, the fact that `!x[1]` means `!(x[1])`
+ // instead of `(!x)[1]` is handled by the fact that the parser
+ // function that parses unary prefix operators is called first, and
+ // in turn calls the function that parses `[]` subscripts — that
+ // way, it'll receive the node for `x[1]` already parsed, and wraps
+ // *that* in the unary operator node.
+ //
+ // Acorn uses an [operator precedence parser][opp] to handle binary
+ // operator precedence, because it is much more compact than using
+ // the technique outlined above, which uses different, nesting
+ // functions to specify precedence, for all of the ten binary
+ // precedence levels that JavaScript defines.
+ //
+ // [opp]: http://en.wikipedia.org/wiki/Operator-precedence_parser
+
+ // ### Parser utilities
+
+ // Continue to the next token.
+
+ function next() {
+ lastStart = tokStart;
+ lastEnd = tokEnd;
+ lastEndLoc = tokEndLoc;
+ readToken();
+ }
+
+ // Enter strict mode. Re-reads the next token to please pedantic
+ // tests ("use strict"; 010; -- should fail).
+
+ function setStrict(strct) {
+ strict = strct;
+ tokPos = tokStart;
+ if (options.locations) {
+ while (tokPos < tokLineStart) {
+ tokLineStart = input.lastIndexOf("\n", tokLineStart - 2) + 1;
+ --tokCurLine;
+ }
+ }
+ skipSpace();
+ readToken();
+ }
+
+ // Start an AST node, attaching a start offset.
+
+ function Node() {
+ this.type = null;
+ this.start = tokStart;
+ this.end = null;
+ }
+
+ exports.Node = Node;
+
+ function SourceLocation() {
+ this.start = tokStartLoc;
+ this.end = null;
+ if (sourceFile !== null) this.source = sourceFile;
+ }
+
+ function startNode() {
+ var node = new Node();
+ if (options.locations)
+ node.loc = new SourceLocation();
+ if (options.directSourceFile)
+ node.sourceFile = options.directSourceFile;
+ if (options.ranges)
+ node.range = [tokStart, 0];
+ return node;
+ }
+
+ // Sometimes, a node is only started *after* the token stream passed
+ // its start position. The functions below help storing a position
+ // and creating a node from a previous position.
+
+ function storeCurrentPos() {
+ return options.locations ? [tokStart, tokStartLoc] : tokStart;
+ }
+
+ function startNodeAt(pos) {
+ var node = new Node(), start = pos;
+ if (options.locations) {
+ node.loc = new SourceLocation();
+ node.loc.start = start[1];
+ start = pos[0];
+ }
+ node.start = start;
+ if (options.directSourceFile)
+ node.sourceFile = options.directSourceFile;
+ if (options.ranges)
+ node.range = [start, 0];
+
+ return node;
+ }
+
+ // Finish an AST node, adding `type` and `end` properties.
+
+ function finishNode(node, type) {
+ node.type = type;
+ node.end = lastEnd;
+ if (options.locations)
+ node.loc.end = lastEndLoc;
+ if (options.ranges)
+ node.range[1] = lastEnd;
+ return node;
+ }
+
+ function finishNodeAt(node, type, pos) {
+ if (options.locations) { node.loc.end = pos[1]; pos = pos[0]; }
+ node.type = type;
+ node.end = pos;
+ if (options.ranges)
+ node.range[1] = pos;
+ return node;
+ }
+
+ // Test whether a statement node is the string literal `"use strict"`.
+
+ function isUseStrict(stmt) {
+ return options.ecmaVersion >= 5 && stmt.type === "ExpressionStatement" &&
+ stmt.expression.type === "Literal" && stmt.expression.value === "use strict";
+ }
+
+ // Predicate that tests whether the next token is of the given
+ // type, and if yes, consumes it as a side effect.
+
+ function eat(type) {
+ if (tokType === type) {
+ next();
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ // Test whether a semicolon can be inserted at the current position.
+
+ function canInsertSemicolon() {
+ return !options.strictSemicolons &&
+ (tokType === _eof || tokType === _braceR || newline.test(input.slice(lastEnd, tokStart)));
+ }
+
+ // Consume a semicolon, or, failing that, see if we are allowed to
+ // pretend that there is a semicolon at this position.
+
+ function semicolon() {
+ if (!eat(_semi) && !canInsertSemicolon()) unexpected();
+ }
+
+ // Expect a token of a given type. If found, consume it, otherwise,
+ // raise an unexpected token error.
+
+ function expect(type) {
+ eat(type) || unexpected();
+ }
+
+ // Raise an unexpected token error.
+
+ function unexpected(pos) {
+ raise(pos != null ? pos : tokStart, "Unexpected token");
+ }
+
+ // Checks if hash object has a property.
+
+ function has(obj, propName) {
+ return Object.prototype.hasOwnProperty.call(obj, propName);
+ }
+ // Convert existing expression atom to assignable pattern
+ // if possible.
+
+ function toAssignable(node, allowSpread, checkType) {
+ if (options.ecmaVersion >= 6 && node) {
+ switch (node.type) {
+ case "Identifier":
+ case "MemberExpression":
+ break;
+
+ case "ObjectExpression":
+ node.type = "ObjectPattern";
+ for (var i = 0; i < node.properties.length; i++) {
+ var prop = node.properties[i];
+ if (prop.kind !== "init") unexpected(prop.key.start);
+ toAssignable(prop.value, false, checkType);
+ }
+ break;
+
+ case "ArrayExpression":
+ node.type = "ArrayPattern";
+ for (var i = 0, lastI = node.elements.length - 1; i <= lastI; i++) {
+ toAssignable(node.elements[i], i === lastI, checkType);
+ }
+ break;
+
+ case "SpreadElement":
+ if (allowSpread) {
+ toAssignable(node.argument, false, checkType);
+ checkSpreadAssign(node.argument);
+ } else {
+ unexpected(node.start);
+ }
+ break;
+
+ default:
+ if (checkType) unexpected(node.start);
+ }
+ }
+ return node;
+ }
+
+ // Checks if node can be assignable spread argument.
+
+ function checkSpreadAssign(node) {
+ if (node.type !== "Identifier" && node.type !== "ArrayPattern")
+ unexpected(node.start);
+ }
+
+ // Verify that argument names are not repeated, and it does not
+ // try to bind the words `eval` or `arguments`.
+
+ function checkFunctionParam(param, nameHash) {
+ switch (param.type) {
+ case "Identifier":
+ if (isStrictReservedWord(param.name) || isStrictBadIdWord(param.name))
+ raise(param.start, "Defining '" + param.name + "' in strict mode");
+ if (has(nameHash, param.name))
+ raise(param.start, "Argument name clash in strict mode");
+ nameHash[param.name] = true;
+ break;
+
+ case "ObjectPattern":
+ for (var i = 0; i < param.properties.length; i++)
+ checkFunctionParam(param.properties[i].value, nameHash);
+ break;
+
+ case "ArrayPattern":
+ for (var i = 0; i < param.elements.length; i++) {
+ var elem = param.elements[i];
+ if (elem) checkFunctionParam(elem, nameHash);
+ }
+ break;
+ }
+ }
+
+ // Check if property name clashes with already added.
+ // Object/class getters and setters are not allowed to clash —
+ // either with each other or with an init property — and in
+ // strict mode, init properties are also not allowed to be repeated.
+
+ function checkPropClash(prop, propHash) {
+ if (options.ecmaVersion >= 6) return;
+ var key = prop.key, name;
+ switch (key.type) {
+ case "Identifier": name = key.name; break;
+ case "Literal": name = String(key.value); break;
+ default: return;
+ }
+ var kind = prop.kind || "init", other;
+ if (has(propHash, name)) {
+ other = propHash[name];
+ var isGetSet = kind !== "init";
+ if ((strict || isGetSet) && other[kind] || !(isGetSet ^ other.init))
+ raise(key.start, "Redefinition of property");
+ } else {
+ other = propHash[name] = {
+ init: false,
+ get: false,
+ set: false
+ };
+ }
+ other[kind] = true;
+ }
+
+ // Verify that a node is an lval — something that can be assigned
+ // to.
+
+ function checkLVal(expr, isBinding) {
+ switch (expr.type) {
+ case "Identifier":
+ if (strict && (isStrictBadIdWord(expr.name) || isStrictReservedWord(expr.name)))
+ raise(expr.start, isBinding
+ ? "Binding " + expr.name + " in strict mode"
+ : "Assigning to " + expr.name + " in strict mode"
+ );
+ break;
+
+ case "MemberExpression":
+ if (!isBinding) break;
+
+ case "ObjectPattern":
+ for (var i = 0; i < expr.properties.length; i++)
+ checkLVal(expr.properties[i].value, isBinding);
+ break;
+
+ case "ArrayPattern":
+ for (var i = 0; i < expr.elements.length; i++) {
+ var elem = expr.elements[i];
+ if (elem) checkLVal(elem, isBinding);
+ }
+ break;
+
+ case "SpreadElement":
+ break;
+
+ default:
+ raise(expr.start, "Assigning to rvalue");
+ }
+ }
+
+ // ### Statement parsing
+
+ // Parse a program. Initializes the parser, reads any number of
+ // statements, and wraps them in a Program node. Optionally takes a
+ // `program` argument. If present, the statements will be appended
+ // to its body instead of creating a new node.
+
+ function parseTopLevel(node) {
+ var first = true;
+ if (!node.body) node.body = [];
+ while (tokType !== _eof) {
+ var stmt = parseStatement(true);
+ node.body.push(stmt);
+ if (first && isUseStrict(stmt)) setStrict(true);
+ first = false;
+ }
+
+ lastStart = tokStart;
+ lastEnd = tokEnd;
+ lastEndLoc = tokEndLoc;
+ return finishNode(node, "Program");
+ }
+
+ var loopLabel = {kind: "loop"}, switchLabel = {kind: "switch"};
+
+ // Parse a single statement.
+ //
+ // If expecting a statement and finding a slash operator, parse a
+ // regular expression literal. This is to handle cases like
+ // `if (foo) /blah/.exec(foo);`, where looking at the previous token
+ // does not help.
+
+ function parseStatement(topLevel) {
+ if (tokType === _slash || tokType === _assign && tokVal == "/=")
+ readToken(true);
+
+ var starttype = tokType, node = startNode();
+
+ // Most types of statements are recognized by the keyword they
+ // start with. Many are trivial to parse, some require a bit of
+ // complexity.
+
+ switch (starttype) {
+ case _break: case _continue: return parseBreakContinueStatement(node, starttype.keyword);
+ case _debugger: return parseDebuggerStatement(node);
+ case _do: return parseDoStatement(node);
+ case _for: return parseForStatement(node);
+ case _function: return parseFunctionStatement(node);
+ case _class: return parseClass(node, true);
+ case _if: return parseIfStatement(node);
+ case _return: return parseReturnStatement(node);
+ case _switch: return parseSwitchStatement(node);
+ case _throw: return parseThrowStatement(node);
+ case _try: return parseTryStatement(node);
+ case _var: case _let: case _const: return parseVarStatement(node, starttype.keyword);
+ case _while: return parseWhileStatement(node);
+ case _with: return parseWithStatement(node);
+ case _braceL: return parseBlock(); // no point creating a function for this
+ case _semi: return parseEmptyStatement(node);
+ case _export:
+ case _import:
+ if (!topLevel && !options.allowImportExportEverywhere)
+ raise(tokStart, "'import' and 'export' may only appear at the top level");
+ return starttype === _import ? parseImport(node) : parseExport(node);
+
+ // If the statement does not start with a statement keyword or a
+ // brace, it's an ExpressionStatement or LabeledStatement. We
+ // simply start parsing an expression, and afterwards, if the
+ // next token is a colon and the expression was a simple
+ // Identifier node, we switch to interpreting it as a label.
+ default:
+ var maybeName = tokVal, expr = parseExpression();
+ if (starttype === _name && expr.type === "Identifier" && eat(_colon))
+ return parseLabeledStatement(node, maybeName, expr);
+ else return parseExpressionStatement(node, expr);
+ }
+ }
+
+ function parseBreakContinueStatement(node, keyword) {
+ var isBreak = keyword == "break";
+ next();
+ if (eat(_semi) || canInsertSemicolon()) node.label = null;
+ else if (tokType !== _name) unexpected();
+ else {
+ node.label = parseIdent();
+ semicolon();
+ }
+
+ // Verify that there is an actual destination to break or
+ // continue to.
+ for (var i = 0; i < labels.length; ++i) {
+ var lab = labels[i];
+ if (node.label == null || lab.name === node.label.name) {
+ if (lab.kind != null && (isBreak || lab.kind === "loop")) break;
+ if (node.label && isBreak) break;
+ }
+ }
+ if (i === labels.length) raise(node.start, "Unsyntactic " + keyword);
+ return finishNode(node, isBreak ? "BreakStatement" : "ContinueStatement");
+ }
+
+ function parseDebuggerStatement(node) {
+ next();
+ semicolon();
+ return finishNode(node, "DebuggerStatement");
+ }
+
+ function parseDoStatement(node) {
+ next();
+ labels.push(loopLabel);
+ node.body = parseStatement();
+ labels.pop();
+ expect(_while);
+ node.test = parseParenExpression();
+ if (options.ecmaVersion >= 6)
+ eat(_semi);
+ else
+ semicolon();
+ return finishNode(node, "DoWhileStatement");
+ }
+
+ // Disambiguating between a `for` and a `for`/`in` or `for`/`of`
+ // loop is non-trivial. Basically, we have to parse the init `var`
+ // statement or expression, disallowing the `in` operator (see
+ // the second parameter to `parseExpression`), and then check
+ // whether the next token is `in` or `of`. When there is no init
+ // part (semicolon immediately after the opening parenthesis), it
+ // is a regular `for` loop.
+
+ function parseForStatement(node) {
+ next();
+ labels.push(loopLabel);
+ expect(_parenL);
+ if (tokType === _semi) return parseFor(node, null);
+ if (tokType === _var || tokType === _let) {
+ var init = startNode(), varKind = tokType.keyword, isLet = tokType === _let;
+ next();
+ parseVar(init, true, varKind);
+ finishNode(init, "VariableDeclaration");
+ if ((tokType === _in || (options.ecmaVersion >= 6 && tokType === _name && tokVal === "of")) && init.declarations.length === 1 &&
+ !(isLet && init.declarations[0].init))
+ return parseForIn(node, init);
+ return parseFor(node, init);
+ }
+ var init = parseExpression(false, true);
+ if (tokType === _in || (options.ecmaVersion >= 6 && tokType === _name && tokVal === "of")) {
+ checkLVal(init);
+ return parseForIn(node, init);
+ }
+ return parseFor(node, init);
+ }
+
+ function parseFunctionStatement(node) {
+ next();
+ return parseFunction(node, true);
+ }
+
+ function parseIfStatement(node) {
+ next();
+ node.test = parseParenExpression();
+ node.consequent = parseStatement();
+ node.alternate = eat(_else) ? parseStatement() : null;
+ return finishNode(node, "IfStatement");
+ }
+
+ function parseReturnStatement(node) {
+ if (!inFunction && !options.allowReturnOutsideFunction)
+ raise(tokStart, "'return' outside of function");
+ next();
+
+ // In `return` (and `break`/`continue`), the keywords with
+ // optional arguments, we eagerly look for a semicolon or the
+ // possibility to insert one.
+
+ if (eat(_semi) || canInsertSemicolon()) node.argument = null;
+ else { node.argument = parseExpression(); semicolon(); }
+ return finishNode(node, "ReturnStatement");
+ }
+
+ function parseSwitchStatement(node) {
+ next();
+ node.discriminant = parseParenExpression();
+ node.cases = [];
+ expect(_braceL);
+ labels.push(switchLabel);
+
+ // Statements under must be grouped (by label) in SwitchCase
+ // nodes. `cur` is used to keep the node that we are currently
+ // adding statements to.
+
+ for (var cur, sawDefault; tokType != _braceR;) {
+ if (tokType === _case || tokType === _default) {
+ var isCase = tokType === _case;
+ if (cur) finishNode(cur, "SwitchCase");
+ node.cases.push(cur = startNode());
+ cur.consequent = [];
+ next();
+ if (isCase) cur.test = parseExpression();
+ else {
+ if (sawDefault) raise(lastStart, "Multiple default clauses"); sawDefault = true;
+ cur.test = null;
+ }
+ expect(_colon);
+ } else {
+ if (!cur) unexpected();
+ cur.consequent.push(parseStatement());
+ }
+ }
+ if (cur) finishNode(cur, "SwitchCase");
+ next(); // Closing brace
+ labels.pop();
+ return finishNode(node, "SwitchStatement");
+ }
+
+ function parseThrowStatement(node) {
+ next();
+ if (newline.test(input.slice(lastEnd, tokStart)))
+ raise(lastEnd, "Illegal newline after throw");
+ node.argument = parseExpression();
+ semicolon();
+ return finishNode(node, "ThrowStatement");
+ }
+
+ function parseTryStatement(node) {
+ next();
+ node.block = parseBlock();
+ node.handler = null;
+ if (tokType === _catch) {
+ var clause = startNode();
+ next();
+ expect(_parenL);
+ clause.param = parseIdent();
+ if (strict && isStrictBadIdWord(clause.param.name))
+ raise(clause.param.start, "Binding " + clause.param.name + " in strict mode");
+ expect(_parenR);
+ clause.guard = null;
+ clause.body = parseBlock();
+ node.handler = finishNode(clause, "CatchClause");
+ }
+ node.guardedHandlers = empty;
+ node.finalizer = eat(_finally) ? parseBlock() : null;
+ if (!node.handler && !node.finalizer)
+ raise(node.start, "Missing catch or finally clause");
+ return finishNode(node, "TryStatement");
+ }
+
+ function parseVarStatement(node, kind) {
+ next();
+ parseVar(node, false, kind);
+ semicolon();
+ return finishNode(node, "VariableDeclaration");
+ }
+
+ function parseWhileStatement(node) {
+ next();
+ node.test = parseParenExpression();
+ labels.push(loopLabel);
+ node.body = parseStatement();
+ labels.pop();
+ return finishNode(node, "WhileStatement");
+ }
+
+ function parseWithStatement(node) {
+ if (strict) raise(tokStart, "'with' in strict mode");
+ next();
+ node.object = parseParenExpression();
+ node.body = parseStatement();
+ return finishNode(node, "WithStatement");
+ }
+
+ function parseEmptyStatement(node) {
+ next();
+ return finishNode(node, "EmptyStatement");
+ }
+
+ function parseLabeledStatement(node, maybeName, expr) {
+ for (var i = 0; i < labels.length; ++i)
+ if (labels[i].name === maybeName) raise(expr.start, "Label '" + maybeName + "' is already declared");
+ var kind = tokType.isLoop ? "loop" : tokType === _switch ? "switch" : null;
+ labels.push({name: maybeName, kind: kind});
+ node.body = parseStatement();
+ labels.pop();
+ node.label = expr;
+ return finishNode(node, "LabeledStatement");
+ }
+
+ function parseExpressionStatement(node, expr) {
+ node.expression = expr;
+ semicolon();
+ return finishNode(node, "ExpressionStatement");
+ }
+
+ // Used for constructs like `switch` and `if` that insist on
+ // parentheses around their expression.
+
+ function parseParenExpression() {
+ expect(_parenL);
+ var val = parseExpression();
+ expect(_parenR);
+ return val;
+ }
+
+ // Parse a semicolon-enclosed block of statements, handling `"use
+ // strict"` declarations when `allowStrict` is true (used for
+ // function bodies).
+
+ function parseBlock(allowStrict) {
+ var node = startNode(), first = true, oldStrict;
+ node.body = [];
+ expect(_braceL);
+ while (!eat(_braceR)) {
+ var stmt = parseStatement();
+ node.body.push(stmt);
+ if (first && allowStrict && isUseStrict(stmt)) {
+ oldStrict = strict;
+ setStrict(strict = true);
+ }
+ first = false;
+ }
+ if (oldStrict === false) setStrict(false);
+ return finishNode(node, "BlockStatement");
+ }
+
+ // Parse a regular `for` loop. The disambiguation code in
+ // `parseStatement` will already have parsed the init statement or
+ // expression.
+
+ function parseFor(node, init) {
+ node.init = init;
+ expect(_semi);
+ node.test = tokType === _semi ? null : parseExpression();
+ expect(_semi);
+ node.update = tokType === _parenR ? null : parseExpression();
+ expect(_parenR);
+ node.body = parseStatement();
+ labels.pop();
+ return finishNode(node, "ForStatement");
+ }
+
+ // Parse a `for`/`in` and `for`/`of` loop, which are almost
+ // same from parser's perspective.
+
+ function parseForIn(node, init) {
+ var type = tokType === _in ? "ForInStatement" : "ForOfStatement";
+ next();
+ node.left = init;
+ node.right = parseExpression();
+ expect(_parenR);
+ node.body = parseStatement();
+ labels.pop();
+ return finishNode(node, type);
+ }
+
+ // Parse a list of variable declarations.
+
+ function parseVar(node, noIn, kind) {
+ node.declarations = [];
+ node.kind = kind;
+ for (;;) {
+ var decl = startNode();
+ decl.id = options.ecmaVersion >= 6 ? toAssignable(parseExprAtom()) : parseIdent();
+ checkLVal(decl.id, true);
+ decl.init = eat(_eq) ? parseExpression(true, noIn) : (kind === _const.keyword ? unexpected() : null);
+ node.declarations.push(finishNode(decl, "VariableDeclarator"));
+ if (!eat(_comma)) break;
+ }
+ return node;
+ }
+
+ // ### Expression parsing
+
+ // These nest, from the most general expression type at the top to
+ // 'atomic', nondivisible expression types at the bottom. Most of
+ // the functions will simply let the function(s) below them parse,
+ // and, *if* the syntactic construct they handle is present, wrap
+ // the AST node that the inner parser gave them in another node.
+
+ // Parse a full expression. The arguments are used to forbid comma
+ // sequences (in argument lists, array literals, or object literals)
+ // or the `in` operator (in for loops initalization expressions).
+
+ function parseExpression(noComma, noIn) {
+ var start = storeCurrentPos();
+ var expr = parseMaybeAssign(noIn);
+ if (!noComma && tokType === _comma) {
+ var node = startNodeAt(start);
+ node.expressions = [expr];
+ while (eat(_comma)) node.expressions.push(parseMaybeAssign(noIn));
+ return finishNode(node, "SequenceExpression");
+ }
+ return expr;
+ }
+
+ // Parse an assignment expression. This includes applications of
+ // operators like `+=`.
+
+ function parseMaybeAssign(noIn) {
+ var start = storeCurrentPos();
+ var left = parseMaybeConditional(noIn);
+ if (tokType.isAssign) {
+ var node = startNodeAt(start);
+ node.operator = tokVal;
+ node.left = tokType === _eq ? toAssignable(left) : left;
+ checkLVal(left);
+ next();
+ node.right = parseMaybeAssign(noIn);
+ return finishNode(node, "AssignmentExpression");
+ }
+ return left;
+ }
+
+ // Parse a ternary conditional (`?:`) operator.
+
+ function parseMaybeConditional(noIn) {
+ var start = storeCurrentPos();
+ var expr = parseExprOps(noIn);
+ if (eat(_question)) {
+ var node = startNodeAt(start);
+ node.test = expr;
+ node.consequent = parseExpression(true);
+ expect(_colon);
+ node.alternate = parseExpression(true, noIn);
+ return finishNode(node, "ConditionalExpression");
+ }
+ return expr;
+ }
+
+ // Start the precedence parser.
+
+ function parseExprOps(noIn) {
+ var start = storeCurrentPos();
+ return parseExprOp(parseMaybeUnary(), start, -1, noIn);
+ }
+
+ // Parse binary operators with the operator precedence parsing
+ // algorithm. `left` is the left-hand side of the operator.
+ // `minPrec` provides context that allows the function to stop and
+ // defer further parser to one of its callers when it encounters an
+ // operator that has a lower precedence than the set it is parsing.
+
+ function parseExprOp(left, leftStart, minPrec, noIn) {
+ var prec = tokType.binop;
+ if (prec != null && (!noIn || tokType !== _in)) {
+ if (prec > minPrec) {
+ var node = startNodeAt(leftStart);
+ node.left = left;
+ node.operator = tokVal;
+ var op = tokType;
+ next();
+ var start = storeCurrentPos();
+ node.right = parseExprOp(parseMaybeUnary(), start, prec, noIn);
+ finishNode(node, (op === _logicalOR || op === _logicalAND) ? "LogicalExpression" : "BinaryExpression");
+ return parseExprOp(node, leftStart, minPrec, noIn);
+ }
+ }
+ return left;
+ }
+
+ // Parse unary operators, both prefix and postfix.
+
+ function parseMaybeUnary() {
+ if (tokType.prefix) {
+ var node = startNode(), update = tokType.isUpdate, nodeType;
+ if (tokType === _ellipsis) {
+ nodeType = "SpreadElement";
+ } else {
+ nodeType = update ? "UpdateExpression" : "UnaryExpression";
+ node.operator = tokVal;
+ node.prefix = true;
+ }
+ tokRegexpAllowed = true;
+ next();
+ node.argument = parseMaybeUnary();
+ if (update) checkLVal(node.argument);
+ else if (strict && node.operator === "delete" &&
+ node.argument.type === "Identifier")
+ raise(node.start, "Deleting local variable in strict mode");
+ return finishNode(node, nodeType);
+ }
+ var start = storeCurrentPos();
+ var expr = parseExprSubscripts();
+ while (tokType.postfix && !canInsertSemicolon()) {
+ var node = startNodeAt(start);
+ node.operator = tokVal;
+ node.prefix = false;
+ node.argument = expr;
+ checkLVal(expr);
+ next();
+ expr = finishNode(node, "UpdateExpression");
+ }
+ return expr;
+ }
+
+ // Parse call, dot, and `[]`-subscript expressions.
+
+ function parseExprSubscripts() {
+ var start = storeCurrentPos();
+ return parseSubscripts(parseExprAtom(), start);
+ }
+
+ function parseSubscripts(base, start, noCalls) {
+ if (eat(_dot)) {
+ var node = startNodeAt(start);
+ node.object = base;
+ node.property = parseIdent(true);
+ node.computed = false;
+ return parseSubscripts(finishNode(node, "MemberExpression"), start, noCalls);
+ } else if (eat(_bracketL)) {
+ var node = startNodeAt(start);
+ node.object = base;
+ node.property = parseExpression();
+ node.computed = true;
+ expect(_bracketR);
+ return parseSubscripts(finishNode(node, "MemberExpression"), start, noCalls);
+ } else if (!noCalls && eat(_parenL)) {
+ var node = startNodeAt(start);
+ node.callee = base;
+ node.arguments = parseExprList(_parenR, false);
+ return parseSubscripts(finishNode(node, "CallExpression"), start, noCalls);
+ } else if (tokType === _template) {
+ var node = startNodeAt(start);
+ node.tag = base;
+ node.quasi = parseTemplate();
+ return parseSubscripts(finishNode(node, "TaggedTemplateExpression"), start, noCalls);
+ } return base;
+ }
+
+ // Parse an atomic expression — either a single token that is an
+ // expression, an expression started by a keyword like `function` or
+ // `new`, or an expression wrapped in punctuation like `()`, `[]`,
+ // or `{}`.
+
+ function parseExprAtom() {
+ switch (tokType) {
+ case _this:
+ var node = startNode();
+ next();
+ return finishNode(node, "ThisExpression");
+
+ case _yield:
+ if (inGenerator) return parseYield();
+
+ case _name:
+ var start = storeCurrentPos();
+ var id = parseIdent(tokType !== _name);
+ if (eat(_arrow)) {
+ return parseArrowExpression(startNodeAt(start), [id]);
+ }
+ return id;
+
+ case _regexp:
+ var node = startNode();
+ node.regex = {pattern: tokVal.pattern, flags: tokVal.flags};
+ node.value = tokVal.value;
+ node.raw = input.slice(tokStart, tokEnd);
+ next();
+ return finishNode(node, "Literal");
+
+ case _num: case _string:
+ var node = startNode();
+ node.value = tokVal;
+ node.raw = input.slice(tokStart, tokEnd);
+ next();
+ return finishNode(node, "Literal");
+
+ case _null: case _true: case _false:
+ var node = startNode();
+ node.value = tokType.atomValue;
+ node.raw = tokType.keyword;
+ next();
+ return finishNode(node, "Literal");
+
+ case _parenL:
+ var start = storeCurrentPos();
+ var val, exprList;
+ next();
+ // check whether this is generator comprehension or regular expression
+ if (options.ecmaVersion >= 7 && tokType === _for) {
+ val = parseComprehension(startNodeAt(start), true);
+ } else {
+ var oldParenL = ++metParenL;
+ if (tokType !== _parenR) {
+ val = parseExpression();
+ exprList = val.type === "SequenceExpression" ? val.expressions : [val];
+ } else {
+ exprList = [];
+ }
+ expect(_parenR);
+ // if '=>' follows '(...)', convert contents to arguments
+ if (metParenL === oldParenL && eat(_arrow)) {
+ val = parseArrowExpression(startNodeAt(start), exprList);
+ } else {
+ // forbid '()' before everything but '=>'
+ if (!val) unexpected(lastStart);
+ // forbid '...' in sequence expressions
+ if (options.ecmaVersion >= 6) {
+ for (var i = 0; i < exprList.length; i++) {
+ if (exprList[i].type === "SpreadElement") unexpected();
+ }
+ }
+
+ if (options.preserveParens) {
+ var par = startNodeAt(start);
+ par.expression = val;
+ val = finishNode(par, "ParenthesizedExpression");
+ }
+ }
+ }
+ return val;
+
+ case _bracketL:
+ var node = startNode();
+ next();
+ // check whether this is array comprehension or regular array
+ if (options.ecmaVersion >= 7 && tokType === _for) {
+ return parseComprehension(node, false);
+ }
+ node.elements = parseExprList(_bracketR, true, true);
+ return finishNode(node, "ArrayExpression");
+
+ case _braceL:
+ return parseObj();
+
+ case _function:
+ var node = startNode();
+ next();
+ return parseFunction(node, false);
+
+ case _class:
+ return parseClass(startNode(), false);
+
+ case _new:
+ return parseNew();
+
+ case _template:
+ return parseTemplate();
+
+ default:
+ unexpected();
+ }
+ }
+
+ // New's precedence is slightly tricky. It must allow its argument
+ // to be a `[]` or dot subscript expression, but not a call — at
+ // least, not without wrapping it in parentheses. Thus, it uses the
+
+ function parseNew() {
+ var node = startNode();
+ next();
+ var start = storeCurrentPos();
+ node.callee = parseSubscripts(parseExprAtom(), start, true);
+ if (eat(_parenL)) node.arguments = parseExprList(_parenR, false);
+ else node.arguments = empty;
+ return finishNode(node, "NewExpression");
+ }
+
+ // Parse template expression.
+
+ function parseTemplateElement() {
+ var elem = startNodeAt(options.locations ? [tokStart + 1, tokStartLoc.offset(1)] : tokStart + 1);
+ elem.value = tokVal;
+ elem.tail = input.charCodeAt(tokEnd - 1) !== 123; // '{'
+ next();
+ var endOff = elem.tail ? 1 : 2;
+ return finishNodeAt(elem, "TemplateElement", options.locations ? [lastEnd - endOff, lastEndLoc.offset(-endOff)] : lastEnd - endOff);
+ }
+
+ function parseTemplate() {
+ var node = startNode();
+ node.expressions = [];
+ var curElt = parseTemplateElement();
+ node.quasis = [curElt];
+ while (!curElt.tail) {
+ node.expressions.push(parseExpression());
+ if (tokType !== _templateContinued) unexpected();
+ node.quasis.push(curElt = parseTemplateElement());
+ }
+ return finishNode(node, "TemplateLiteral");
+ }
+
+ // Parse an object literal.
+
+ function parseObj() {
+ var node = startNode(), first = true, propHash = {};
+ node.properties = [];
+ next();
+ while (!eat(_braceR)) {
+ if (!first) {
+ expect(_comma);
+ if (options.allowTrailingCommas && eat(_braceR)) break;
+ } else first = false;
+
+ var prop = startNode(), isGenerator;
+ if (options.ecmaVersion >= 6) {
+ prop.method = false;
+ prop.shorthand = false;
+ isGenerator = eat(_star);
+ }
+ parsePropertyName(prop);
+ if (eat(_colon)) {
+ prop.value = parseExpression(true);
+ prop.kind = "init";
+ } else if (options.ecmaVersion >= 6 && tokType === _parenL) {
+ prop.kind = "init";
+ prop.method = true;
+ prop.value = parseMethod(isGenerator);
+ } else if (options.ecmaVersion >= 5 && !prop.computed && prop.key.type === "Identifier" &&
+ (prop.key.name === "get" || prop.key.name === "set")) {
+ if (isGenerator) unexpected();
+ prop.kind = prop.key.name;
+ parsePropertyName(prop);
+ prop.value = parseMethod(false);
+ } else if (options.ecmaVersion >= 6 && !prop.computed && prop.key.type === "Identifier") {
+ prop.kind = "init";
+ prop.value = prop.key;
+ prop.shorthand = true;
+ } else unexpected();
+
+ checkPropClash(prop, propHash);
+ node.properties.push(finishNode(prop, "Property"));
+ }
+ return finishNode(node, "ObjectExpression");
+ }
+
+ function parsePropertyName(prop) {
+ if (options.ecmaVersion >= 6) {
+ if (eat(_bracketL)) {
+ prop.computed = true;
+ prop.key = parseExpression();
+ expect(_bracketR);
+ return;
+ } else {
+ prop.computed = false;
+ }
+ }
+ prop.key = (tokType === _num || tokType === _string) ? parseExprAtom() : parseIdent(true);
+ }
+
+ // Initialize empty function node.
+
+ function initFunction(node) {
+ node.id = null;
+ node.params = [];
+ if (options.ecmaVersion >= 6) {
+ node.defaults = [];
+ node.rest = null;
+ node.generator = false;
+ }
+ }
+
+ // Parse a function declaration or literal (depending on the
+ // `isStatement` parameter).
+
+ function parseFunction(node, isStatement, allowExpressionBody) {
+ initFunction(node);
+ if (options.ecmaVersion >= 6) {
+ node.generator = eat(_star);
+ }
+ if (isStatement || tokType === _name) {
+ node.id = parseIdent();
+ }
+ parseFunctionParams(node);
+ parseFunctionBody(node, allowExpressionBody);
+ return finishNode(node, isStatement ? "FunctionDeclaration" : "FunctionExpression");
+ }
+
+ // Parse object or class method.
+
+ function parseMethod(isGenerator) {
+ var node = startNode();
+ initFunction(node);
+ parseFunctionParams(node);
+ var allowExpressionBody;
+ if (options.ecmaVersion >= 6) {
+ node.generator = isGenerator;
+ allowExpressionBody = true;
+ } else {
+ allowExpressionBody = false;
+ }
+ parseFunctionBody(node, allowExpressionBody);
+ return finishNode(node, "FunctionExpression");
+ }
+
+ // Parse arrow function expression with given parameters.
+
+ function parseArrowExpression(node, params) {
+ initFunction(node);
+
+ var defaults = node.defaults, hasDefaults = false;
+
+ for (var i = 0, lastI = params.length - 1; i <= lastI; i++) {
+ var param = params[i];
+
+ if (param.type === "AssignmentExpression" && param.operator === "=") {
+ hasDefaults = true;
+ params[i] = param.left;
+ defaults.push(param.right);
+ } else {
+ toAssignable(param, i === lastI, true);
+ defaults.push(null);
+ if (param.type === "SpreadElement") {
+ params.length--;
+ node.rest = param.argument;
+ break;
+ }
+ }
+ }
+
+ node.params = params;
+ if (!hasDefaults) node.defaults = [];
+
+ parseFunctionBody(node, true);
+ return finishNode(node, "ArrowFunctionExpression");
+ }
+
+ // Parse function parameters.
+
+ function parseFunctionParams(node) {
+ var defaults = [], hasDefaults = false;
+
+ expect(_parenL);
+ for (;;) {
+ if (eat(_parenR)) {
+ break;
+ } else if (options.ecmaVersion >= 6 && eat(_ellipsis)) {
+ node.rest = toAssignable(parseExprAtom(), false, true);
+ checkSpreadAssign(node.rest);
+ expect(_parenR);
+ defaults.push(null);
+ break;
+ } else {
+ node.params.push(options.ecmaVersion >= 6 ? toAssignable(parseExprAtom(), false, true) : parseIdent());
+ if (options.ecmaVersion >= 6) {
+ if (eat(_eq)) {
+ hasDefaults = true;
+ defaults.push(parseExpression(true));
+ } else {
+ defaults.push(null);
+ }
+ }
+ if (!eat(_comma)) {
+ expect(_parenR);
+ break;
+ }
+ }
+ }
+
+ if (hasDefaults) node.defaults = defaults;
+ }
+
+ // Parse function body and check parameters.
+
+ function parseFunctionBody(node, allowExpression) {
+ var isExpression = allowExpression && tokType !== _braceL;
+
+ if (isExpression) {
+ node.body = parseExpression(true);
+ node.expression = true;
+ } else {
+ // Start a new scope with regard to labels and the `inFunction`
+ // flag (restore them to their old value afterwards).
+ var oldInFunc = inFunction, oldInGen = inGenerator, oldLabels = labels;
+ inFunction = true; inGenerator = node.generator; labels = [];
+ node.body = parseBlock(true);
+ node.expression = false;
+ inFunction = oldInFunc; inGenerator = oldInGen; labels = oldLabels;
+ }
+
+ // If this is a strict mode function, verify that argument names
+ // are not repeated, and it does not try to bind the words `eval`
+ // or `arguments`.
+ if (strict || !isExpression && node.body.body.length && isUseStrict(node.body.body[0])) {
+ var nameHash = {};
+ if (node.id)
+ checkFunctionParam(node.id, {});
+ for (var i = 0; i < node.params.length; i++)
+ checkFunctionParam(node.params[i], nameHash);
+ if (node.rest)
+ checkFunctionParam(node.rest, nameHash);
+ }
+ }
+
+ // Parse a class declaration or literal (depending on the
+ // `isStatement` parameter).
+
+ function parseClass(node, isStatement) {
+ next();
+ node.id = tokType === _name ? parseIdent() : isStatement ? unexpected() : null;
+ node.superClass = eat(_extends) ? parseExpression() : null;
+ var classBody = startNode();
+ classBody.body = [];
+ expect(_braceL);
+ while (!eat(_braceR)) {
+ var method = startNode();
+ if (tokType === _name && tokVal === "static") {
+ next();
+ method['static'] = true;
+ } else {
+ method['static'] = false;
+ }
+ var isGenerator = eat(_star);
+ parsePropertyName(method);
+ if (tokType !== _parenL && !method.computed && method.key.type === "Identifier" &&
+ (method.key.name === "get" || method.key.name === "set")) {
+ if (isGenerator) unexpected();
+ method.kind = method.key.name;
+ parsePropertyName(method);
+ } else {
+ method.kind = "";
+ }
+ method.value = parseMethod(isGenerator);
+ classBody.body.push(finishNode(method, "MethodDefinition"));
+ eat(_semi);
+ }
+ node.body = finishNode(classBody, "ClassBody");
+ return finishNode(node, isStatement ? "ClassDeclaration" : "ClassExpression");
+ }
+
+ // Parses a comma-separated list of expressions, and returns them as
+ // an array. `close` is the token type that ends the list, and
+ // `allowEmpty` can be turned on to allow subsequent commas with
+ // nothing in between them to be parsed as `null` (which is needed
+ // for array literals).
+
+ function parseExprList(close, allowTrailingComma, allowEmpty) {
+ var elts = [], first = true;
+ while (!eat(close)) {
+ if (!first) {
+ expect(_comma);
+ if (allowTrailingComma && options.allowTrailingCommas && eat(close)) break;
+ } else first = false;
+
+ if (allowEmpty && tokType === _comma) elts.push(null);
+ else elts.push(parseExpression(true));
+ }
+ return elts;
+ }
+
+ // Parse the next token as an identifier. If `liberal` is true (used
+ // when parsing properties), it will also convert keywords into
+ // identifiers.
+
+ function parseIdent(liberal) {
+ var node = startNode();
+ if (liberal && options.forbidReserved == "everywhere") liberal = false;
+ if (tokType === _name) {
+ if (!liberal &&
+ (options.forbidReserved &&
+ (options.ecmaVersion === 3 ? isReservedWord3 : isReservedWord5)(tokVal) ||
+ strict && isStrictReservedWord(tokVal)) &&
+ input.slice(tokStart, tokEnd).indexOf("\\") == -1)
+ raise(tokStart, "The keyword '" + tokVal + "' is reserved");
+ node.name = tokVal;
+ } else if (liberal && tokType.keyword) {
+ node.name = tokType.keyword;
+ } else {
+ unexpected();
+ }
+ tokRegexpAllowed = false;
+ next();
+ return finishNode(node, "Identifier");
+ }
+
+ // Parses module export declaration.
+
+ function parseExport(node) {
+ next();
+ // export var|const|let|function|class ...;
+ if (tokType === _var || tokType === _const || tokType === _let || tokType === _function || tokType === _class) {
+ node.declaration = parseStatement();
+ node['default'] = false;
+ node.specifiers = null;
+ node.source = null;
+ } else
+ // export default ...;
+ if (eat(_default)) {
+ node.declaration = parseExpression(true);
+ node['default'] = true;
+ node.specifiers = null;
+ node.source = null;
+ semicolon();
+ } else {
+ // export * from '...';
+ // export { x, y as z } [from '...'];
+ var isBatch = tokType === _star;
+ node.declaration = null;
+ node['default'] = false;
+ node.specifiers = parseExportSpecifiers();
+ if (tokType === _name && tokVal === "from") {
+ next();
+ node.source = tokType === _string ? parseExprAtom() : unexpected();
+ } else {
+ if (isBatch) unexpected();
+ node.source = null;
+ }
+ semicolon();
+ }
+ return finishNode(node, "ExportDeclaration");
+ }
+
+ // Parses a comma-separated list of module exports.
+
+ function parseExportSpecifiers() {
+ var nodes = [], first = true;
+ if (tokType === _star) {
+ // export * from '...'
+ var node = startNode();
+ next();
+ nodes.push(finishNode(node, "ExportBatchSpecifier"));
+ } else {
+ // export { x, y as z } [from '...']
+ expect(_braceL);
+ while (!eat(_braceR)) {
+ if (!first) {
+ expect(_comma);
+ if (options.allowTrailingCommas && eat(_braceR)) break;
+ } else first = false;
+
+ var node = startNode();
+ node.id = parseIdent(tokType === _default);
+ if (tokType === _name && tokVal === "as") {
+ next();
+ node.name = parseIdent(true);
+ } else {
+ node.name = null;
+ }
+ nodes.push(finishNode(node, "ExportSpecifier"));
+ }
+ }
+ return nodes;
+ }
+
+ // Parses import declaration.
+
+ function parseImport(node) {
+ next();
+ // import '...';
+ if (tokType === _string) {
+ node.specifiers = [];
+ node.source = parseExprAtom();
+ node.kind = "";
+ } else {
+ node.specifiers = parseImportSpecifiers();
+ if (tokType !== _name || tokVal !== "from") unexpected();
+ next();
+ node.source = tokType === _string ? parseExprAtom() : unexpected();
+ }
+ semicolon();
+ return finishNode(node, "ImportDeclaration");
+ }
+
+ // Parses a comma-separated list of module imports.
+
+ function parseImportSpecifiers() {
+ var nodes = [], first = true;
+ if (tokType === _name) {
+ // import defaultObj, { x, y as z } from '...'
+ var node = startNode();
+ node.id = parseIdent();
+ checkLVal(node.id, true);
+ node.name = null;
+ node['default'] = true;
+ nodes.push(finishNode(node, "ImportSpecifier"));
+ if (!eat(_comma)) return nodes;
+ }
+ if (tokType === _star) {
+ var node = startNode();
+ next();
+ if (tokType !== _name || tokVal !== "as") unexpected();
+ next();
+ node.name = parseIdent();
+ checkLVal(node.name, true);
+ nodes.push(finishNode(node, "ImportBatchSpecifier"));
+ return nodes;
+ }
+ expect(_braceL);
+ while (!eat(_braceR)) {
+ if (!first) {
+ expect(_comma);
+ if (options.allowTrailingCommas && eat(_braceR)) break;
+ } else first = false;
+
+ var node = startNode();
+ node.id = parseIdent(true);
+ if (tokType === _name && tokVal === "as") {
+ next();
+ node.name = parseIdent();
+ } else {
+ node.name = null;
+ }
+ checkLVal(node.name || node.id, true);
+ node['default'] = false;
+ nodes.push(finishNode(node, "ImportSpecifier"));
+ }
+ return nodes;
+ }
+
+ // Parses yield expression inside generator.
+
+ function parseYield() {
+ var node = startNode();
+ next();
+ if (eat(_semi) || canInsertSemicolon()) {
+ node.delegate = false;
+ node.argument = null;
+ } else {
+ node.delegate = eat(_star);
+ node.argument = parseExpression(true);
+ }
+ return finishNode(node, "YieldExpression");
+ }
+
+ // Parses array and generator comprehensions.
+
+ function parseComprehension(node, isGenerator) {
+ node.blocks = [];
+ while (tokType === _for) {
+ var block = startNode();
+ next();
+ expect(_parenL);
+ block.left = toAssignable(parseExprAtom());
+ checkLVal(block.left, true);
+ if (tokType !== _name || tokVal !== "of") unexpected();
+ next();
+ // `of` property is here for compatibility with Esprima's AST
+ // which also supports deprecated [for (... in ...) expr]
+ block.of = true;
+ block.right = parseExpression();
+ expect(_parenR);
+ node.blocks.push(finishNode(block, "ComprehensionBlock"));
+ }
+ node.filter = eat(_if) ? parseParenExpression() : null;
+ node.body = parseExpression();
+ expect(isGenerator ? _parenR : _bracketR);
+ node.generator = isGenerator;
+ return finishNode(node, "ComprehensionExpression");
+ }
+
+ });
+
+
+/***/ },
+
+/***/ 462:
+/***/ function(module, exports, __webpack_require__) {
+
+ /*
+ * Copyright 2009-2011 Mozilla Foundation and contributors
+ * Licensed under the New BSD license. See LICENSE.txt or:
+ * http://opensource.org/licenses/BSD-3-Clause
+ */
+ exports.SourceMapGenerator = __webpack_require__(463).SourceMapGenerator;
+ exports.SourceMapConsumer = __webpack_require__(469).SourceMapConsumer;
+ exports.SourceNode = __webpack_require__(471).SourceNode;
+
+
+/***/ },
+
+/***/ 463:
+/***/ function(module, exports, __webpack_require__) {
+
+ var __WEBPACK_AMD_DEFINE_RESULT__;/* -*- Mode: js; js-indent-level: 2; -*- */
+ /*
+ * Copyright 2011 Mozilla Foundation and contributors
+ * Licensed under the New BSD license. See LICENSE or:
+ * http://opensource.org/licenses/BSD-3-Clause
+ */
+ if (false) {
+ var define = require('amdefine')(module, require);
+ }
+ !(__WEBPACK_AMD_DEFINE_RESULT__ = function (require, exports, module) {
+
+ var base64VLQ = __webpack_require__(464);
+ var util = __webpack_require__(466);
+ var ArraySet = __webpack_require__(467).ArraySet;
+ var MappingList = __webpack_require__(468).MappingList;
+
+ /**
+ * An instance of the SourceMapGenerator represents a source map which is
+ * being built incrementally. You may pass an object with the following
+ * properties:
+ *
+ * - file: The filename of the generated source.
+ * - sourceRoot: A root for all relative URLs in this source map.
+ */
+ function SourceMapGenerator(aArgs) {
+ if (!aArgs) {
+ aArgs = {};
+ }
+ this._file = util.getArg(aArgs, 'file', null);
+ this._sourceRoot = util.getArg(aArgs, 'sourceRoot', null);
+ this._skipValidation = util.getArg(aArgs, 'skipValidation', false);
+ this._sources = new ArraySet();
+ this._names = new ArraySet();
+ this._mappings = new MappingList();
+ this._sourcesContents = null;
+ }
+
+ SourceMapGenerator.prototype._version = 3;
+
+ /**
+ * Creates a new SourceMapGenerator based on a SourceMapConsumer
+ *
+ * @param aSourceMapConsumer The SourceMap.
+ */
+ SourceMapGenerator.fromSourceMap =
+ function SourceMapGenerator_fromSourceMap(aSourceMapConsumer) {
+ var sourceRoot = aSourceMapConsumer.sourceRoot;
+ var generator = new SourceMapGenerator({
+ file: aSourceMapConsumer.file,
+ sourceRoot: sourceRoot
+ });
+ aSourceMapConsumer.eachMapping(function (mapping) {
+ var newMapping = {
+ generated: {
+ line: mapping.generatedLine,
+ column: mapping.generatedColumn
+ }
+ };
+
+ if (mapping.source != null) {
+ newMapping.source = mapping.source;
+ if (sourceRoot != null) {
+ newMapping.source = util.relative(sourceRoot, newMapping.source);
+ }
+
+ newMapping.original = {
+ line: mapping.originalLine,
+ column: mapping.originalColumn
+ };
+
+ if (mapping.name != null) {
+ newMapping.name = mapping.name;
+ }
+ }
+
+ generator.addMapping(newMapping);
+ });
+ aSourceMapConsumer.sources.forEach(function (sourceFile) {
+ var content = aSourceMapConsumer.sourceContentFor(sourceFile);
+ if (content != null) {
+ generator.setSourceContent(sourceFile, content);
+ }
+ });
+ return generator;
+ };
+
+ /**
+ * Add a single mapping from original source line and column to the generated
+ * source's line and column for this source map being created. The mapping
+ * object should have the following properties:
+ *
+ * - generated: An object with the generated line and column positions.
+ * - original: An object with the original line and column positions.
+ * - source: The original source file (relative to the sourceRoot).
+ * - name: An optional original token name for this mapping.
+ */
+ SourceMapGenerator.prototype.addMapping =
+ function SourceMapGenerator_addMapping(aArgs) {
+ var generated = util.getArg(aArgs, 'generated');
+ var original = util.getArg(aArgs, 'original', null);
+ var source = util.getArg(aArgs, 'source', null);
+ var name = util.getArg(aArgs, 'name', null);
+
+ if (!this._skipValidation) {
+ this._validateMapping(generated, original, source, name);
+ }
+
+ if (source != null && !this._sources.has(source)) {
+ this._sources.add(source);
+ }
+
+ if (name != null && !this._names.has(name)) {
+ this._names.add(name);
+ }
+
+ this._mappings.add({
+ generatedLine: generated.line,
+ generatedColumn: generated.column,
+ originalLine: original != null && original.line,
+ originalColumn: original != null && original.column,
+ source: source,
+ name: name
+ });
+ };
+
+ /**
+ * Set the source content for a source file.
+ */
+ SourceMapGenerator.prototype.setSourceContent =
+ function SourceMapGenerator_setSourceContent(aSourceFile, aSourceContent) {
+ var source = aSourceFile;
+ if (this._sourceRoot != null) {
+ source = util.relative(this._sourceRoot, source);
+ }
+
+ if (aSourceContent != null) {
+ // Add the source content to the _sourcesContents map.
+ // Create a new _sourcesContents map if the property is null.
+ if (!this._sourcesContents) {
+ this._sourcesContents = {};
+ }
+ this._sourcesContents[util.toSetString(source)] = aSourceContent;
+ } else if (this._sourcesContents) {
+ // Remove the source file from the _sourcesContents map.
+ // If the _sourcesContents map is empty, set the property to null.
+ delete this._sourcesContents[util.toSetString(source)];
+ if (Object.keys(this._sourcesContents).length === 0) {
+ this._sourcesContents = null;
+ }
+ }
+ };
+
+ /**
+ * Applies the mappings of a sub-source-map for a specific source file to the
+ * source map being generated. Each mapping to the supplied source file is
+ * rewritten using the supplied source map. Note: The resolution for the
+ * resulting mappings is the minimium of this map and the supplied map.
+ *
+ * @param aSourceMapConsumer The source map to be applied.
+ * @param aSourceFile Optional. The filename of the source file.
+ * If omitted, SourceMapConsumer's file property will be used.
+ * @param aSourceMapPath Optional. The dirname of the path to the source map
+ * to be applied. If relative, it is relative to the SourceMapConsumer.
+ * This parameter is needed when the two source maps aren't in the same
+ * directory, and the source map to be applied contains relative source
+ * paths. If so, those relative source paths need to be rewritten
+ * relative to the SourceMapGenerator.
+ */
+ SourceMapGenerator.prototype.applySourceMap =
+ function SourceMapGenerator_applySourceMap(aSourceMapConsumer, aSourceFile, aSourceMapPath) {
+ var sourceFile = aSourceFile;
+ // If aSourceFile is omitted, we will use the file property of the SourceMap
+ if (aSourceFile == null) {
+ if (aSourceMapConsumer.file == null) {
+ throw new Error(
+ 'SourceMapGenerator.prototype.applySourceMap requires either an explicit source file, ' +
+ 'or the source map\'s "file" property. Both were omitted.'
+ );
+ }
+ sourceFile = aSourceMapConsumer.file;
+ }
+ var sourceRoot = this._sourceRoot;
+ // Make "sourceFile" relative if an absolute Url is passed.
+ if (sourceRoot != null) {
+ sourceFile = util.relative(sourceRoot, sourceFile);
+ }
+ // Applying the SourceMap can add and remove items from the sources and
+ // the names array.
+ var newSources = new ArraySet();
+ var newNames = new ArraySet();
+
+ // Find mappings for the "sourceFile"
+ this._mappings.unsortedForEach(function (mapping) {
+ if (mapping.source === sourceFile && mapping.originalLine != null) {
+ // Check if it can be mapped by the source map, then update the mapping.
+ var original = aSourceMapConsumer.originalPositionFor({
+ line: mapping.originalLine,
+ column: mapping.originalColumn
+ });
+ if (original.source != null) {
+ // Copy mapping
+ mapping.source = original.source;
+ if (aSourceMapPath != null) {
+ mapping.source = util.join(aSourceMapPath, mapping.source)
+ }
+ if (sourceRoot != null) {
+ mapping.source = util.relative(sourceRoot, mapping.source);
+ }
+ mapping.originalLine = original.line;
+ mapping.originalColumn = original.column;
+ if (original.name != null) {
+ mapping.name = original.name;
+ }
+ }
+ }
+
+ var source = mapping.source;
+ if (source != null && !newSources.has(source)) {
+ newSources.add(source);
+ }
+
+ var name = mapping.name;
+ if (name != null && !newNames.has(name)) {
+ newNames.add(name);
+ }
+
+ }, this);
+ this._sources = newSources;
+ this._names = newNames;
+
+ // Copy sourcesContents of applied map.
+ aSourceMapConsumer.sources.forEach(function (sourceFile) {
+ var content = aSourceMapConsumer.sourceContentFor(sourceFile);
+ if (content != null) {
+ if (aSourceMapPath != null) {
+ sourceFile = util.join(aSourceMapPath, sourceFile);
+ }
+ if (sourceRoot != null) {
+ sourceFile = util.relative(sourceRoot, sourceFile);
+ }
+ this.setSourceContent(sourceFile, content);
+ }
+ }, this);
+ };
+
+ /**
+ * A mapping can have one of the three levels of data:
+ *
+ * 1. Just the generated position.
+ * 2. The Generated position, original position, and original source.
+ * 3. Generated and original position, original source, as well as a name
+ * token.
+ *
+ * To maintain consistency, we validate that any new mapping being added falls
+ * in to one of these categories.
+ */
+ SourceMapGenerator.prototype._validateMapping =
+ function SourceMapGenerator_validateMapping(aGenerated, aOriginal, aSource,
+ aName) {
+ if (aGenerated && 'line' in aGenerated && 'column' in aGenerated
+ && aGenerated.line > 0 && aGenerated.column >= 0
+ && !aOriginal && !aSource && !aName) {
+ // Case 1.
+ return;
+ }
+ else if (aGenerated && 'line' in aGenerated && 'column' in aGenerated
+ && aOriginal && 'line' in aOriginal && 'column' in aOriginal
+ && aGenerated.line > 0 && aGenerated.column >= 0
+ && aOriginal.line > 0 && aOriginal.column >= 0
+ && aSource) {
+ // Cases 2 and 3.
+ return;
+ }
+ else {
+ throw new Error('Invalid mapping: ' + JSON.stringify({
+ generated: aGenerated,
+ source: aSource,
+ original: aOriginal,
+ name: aName
+ }));
+ }
+ };
+
+ /**
+ * Serialize the accumulated mappings in to the stream of base 64 VLQs
+ * specified by the source map format.
+ */
+ SourceMapGenerator.prototype._serializeMappings =
+ function SourceMapGenerator_serializeMappings() {
+ var previousGeneratedColumn = 0;
+ var previousGeneratedLine = 1;
+ var previousOriginalColumn = 0;
+ var previousOriginalLine = 0;
+ var previousName = 0;
+ var previousSource = 0;
+ var result = '';
+ var mapping;
+
+ var mappings = this._mappings.toArray();
+
+ for (var i = 0, len = mappings.length; i < len; i++) {
+ mapping = mappings[i];
+
+ if (mapping.generatedLine !== previousGeneratedLine) {
+ previousGeneratedColumn = 0;
+ while (mapping.generatedLine !== previousGeneratedLine) {
+ result += ';';
+ previousGeneratedLine++;
+ }
+ }
+ else {
+ if (i > 0) {
+ if (!util.compareByGeneratedPositions(mapping, mappings[i - 1])) {
+ continue;
+ }
+ result += ',';
+ }
+ }
+
+ result += base64VLQ.encode(mapping.generatedColumn
+ - previousGeneratedColumn);
+ previousGeneratedColumn = mapping.generatedColumn;
+
+ if (mapping.source != null) {
+ result += base64VLQ.encode(this._sources.indexOf(mapping.source)
+ - previousSource);
+ previousSource = this._sources.indexOf(mapping.source);
+
+ // lines are stored 0-based in SourceMap spec version 3
+ result += base64VLQ.encode(mapping.originalLine - 1
+ - previousOriginalLine);
+ previousOriginalLine = mapping.originalLine - 1;
+
+ result += base64VLQ.encode(mapping.originalColumn
+ - previousOriginalColumn);
+ previousOriginalColumn = mapping.originalColumn;
+
+ if (mapping.name != null) {
+ result += base64VLQ.encode(this._names.indexOf(mapping.name)
+ - previousName);
+ previousName = this._names.indexOf(mapping.name);
+ }
+ }
+ }
+
+ return result;
+ };
+
+ SourceMapGenerator.prototype._generateSourcesContent =
+ function SourceMapGenerator_generateSourcesContent(aSources, aSourceRoot) {
+ return aSources.map(function (source) {
+ if (!this._sourcesContents) {
+ return null;
+ }
+ if (aSourceRoot != null) {
+ source = util.relative(aSourceRoot, source);
+ }
+ var key = util.toSetString(source);
+ return Object.prototype.hasOwnProperty.call(this._sourcesContents,
+ key)
+ ? this._sourcesContents[key]
+ : null;
+ }, this);
+ };
+
+ /**
+ * Externalize the source map.
+ */
+ SourceMapGenerator.prototype.toJSON =
+ function SourceMapGenerator_toJSON() {
+ var map = {
+ version: this._version,
+ sources: this._sources.toArray(),
+ names: this._names.toArray(),
+ mappings: this._serializeMappings()
+ };
+ if (this._file != null) {
+ map.file = this._file;
+ }
+ if (this._sourceRoot != null) {
+ map.sourceRoot = this._sourceRoot;
+ }
+ if (this._sourcesContents) {
+ map.sourcesContent = this._generateSourcesContent(map.sources, map.sourceRoot);
+ }
+
+ return map;
+ };
+
+ /**
+ * Render the source map being generated to a string.
+ */
+ SourceMapGenerator.prototype.toString =
+ function SourceMapGenerator_toString() {
+ return JSON.stringify(this);
+ };
+
+ exports.SourceMapGenerator = SourceMapGenerator;
+
+ }.call(exports, __webpack_require__, exports, module), __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__));
+
+
+/***/ },
+
+/***/ 464:
+/***/ function(module, exports, __webpack_require__) {
+
+ var __WEBPACK_AMD_DEFINE_RESULT__;/* -*- Mode: js; js-indent-level: 2; -*- */
+ /*
+ * Copyright 2011 Mozilla Foundation and contributors
+ * Licensed under the New BSD license. See LICENSE or:
+ * http://opensource.org/licenses/BSD-3-Clause
+ *
+ * Based on the Base 64 VLQ implementation in Closure Compiler:
+ * https://code.google.com/p/closure-compiler/source/browse/trunk/src/com/google/debugging/sourcemap/Base64VLQ.java
+ *
+ * Copyright 2011 The Closure Compiler Authors. All rights reserved.
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ * * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+ if (false) {
+ var define = require('amdefine')(module, require);
+ }
+ !(__WEBPACK_AMD_DEFINE_RESULT__ = function (require, exports, module) {
+
+ var base64 = __webpack_require__(465);
+
+ // A single base 64 digit can contain 6 bits of data. For the base 64 variable
+ // length quantities we use in the source map spec, the first bit is the sign,
+ // the next four bits are the actual value, and the 6th bit is the
+ // continuation bit. The continuation bit tells us whether there are more
+ // digits in this value following this digit.
+ //
+ // Continuation
+ // | Sign
+ // | |
+ // V V
+ // 101011
+
+ var VLQ_BASE_SHIFT = 5;
+
+ // binary: 100000
+ var VLQ_BASE = 1 << VLQ_BASE_SHIFT;
+
+ // binary: 011111
+ var VLQ_BASE_MASK = VLQ_BASE - 1;
+
+ // binary: 100000
+ var VLQ_CONTINUATION_BIT = VLQ_BASE;
+
+ /**
+ * Converts from a two-complement value to a value where the sign bit is
+ * placed in the least significant bit. For example, as decimals:
+ * 1 becomes 2 (10 binary), -1 becomes 3 (11 binary)
+ * 2 becomes 4 (100 binary), -2 becomes 5 (101 binary)
+ */
+ function toVLQSigned(aValue) {
+ return aValue < 0
+ ? ((-aValue) << 1) + 1
+ : (aValue << 1) + 0;
+ }
+
+ /**
+ * Converts to a two-complement value from a value where the sign bit is
+ * placed in the least significant bit. For example, as decimals:
+ * 2 (10 binary) becomes 1, 3 (11 binary) becomes -1
+ * 4 (100 binary) becomes 2, 5 (101 binary) becomes -2
+ */
+ function fromVLQSigned(aValue) {
+ var isNegative = (aValue & 1) === 1;
+ var shifted = aValue >> 1;
+ return isNegative
+ ? -shifted
+ : shifted;
+ }
+
+ /**
+ * Returns the base 64 VLQ encoded value.
+ */
+ exports.encode = function base64VLQ_encode(aValue) {
+ var encoded = "";
+ var digit;
+
+ var vlq = toVLQSigned(aValue);
+
+ do {
+ digit = vlq & VLQ_BASE_MASK;
+ vlq >>>= VLQ_BASE_SHIFT;
+ if (vlq > 0) {
+ // There are still more digits in this value, so we must make sure the
+ // continuation bit is marked.
+ digit |= VLQ_CONTINUATION_BIT;
+ }
+ encoded += base64.encode(digit);
+ } while (vlq > 0);
+
+ return encoded;
+ };
+
+ /**
+ * Decodes the next base 64 VLQ value from the given string and returns the
+ * value and the rest of the string via the out parameter.
+ */
+ exports.decode = function base64VLQ_decode(aStr, aOutParam) {
+ var i = 0;
+ var strLen = aStr.length;
+ var result = 0;
+ var shift = 0;
+ var continuation, digit;
+
+ do {
+ if (i >= strLen) {
+ throw new Error("Expected more digits in base 64 VLQ value.");
+ }
+ digit = base64.decode(aStr.charAt(i++));
+ continuation = !!(digit & VLQ_CONTINUATION_BIT);
+ digit &= VLQ_BASE_MASK;
+ result = result + (digit << shift);
+ shift += VLQ_BASE_SHIFT;
+ } while (continuation);
+
+ aOutParam.value = fromVLQSigned(result);
+ aOutParam.rest = aStr.slice(i);
+ };
+
+ }.call(exports, __webpack_require__, exports, module), __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__));
+
+
+/***/ },
+
+/***/ 465:
+/***/ function(module, exports, __webpack_require__) {
+
+ var __WEBPACK_AMD_DEFINE_RESULT__;/* -*- Mode: js; js-indent-level: 2; -*- */
+ /*
+ * Copyright 2011 Mozilla Foundation and contributors
+ * Licensed under the New BSD license. See LICENSE or:
+ * http://opensource.org/licenses/BSD-3-Clause
+ */
+ if (false) {
+ var define = require('amdefine')(module, require);
+ }
+ !(__WEBPACK_AMD_DEFINE_RESULT__ = function (require, exports, module) {
+
+ var charToIntMap = {};
+ var intToCharMap = {};
+
+ 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'
+ .split('')
+ .forEach(function (ch, index) {
+ charToIntMap[ch] = index;
+ intToCharMap[index] = ch;
+ });
+
+ /**
+ * Encode an integer in the range of 0 to 63 to a single base 64 digit.
+ */
+ exports.encode = function base64_encode(aNumber) {
+ if (aNumber in intToCharMap) {
+ return intToCharMap[aNumber];
+ }
+ throw new TypeError("Must be between 0 and 63: " + aNumber);
+ };
+
+ /**
+ * Decode a single base 64 digit to an integer.
+ */
+ exports.decode = function base64_decode(aChar) {
+ if (aChar in charToIntMap) {
+ return charToIntMap[aChar];
+ }
+ throw new TypeError("Not a valid base 64 digit: " + aChar);
+ };
+
+ }.call(exports, __webpack_require__, exports, module), __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__));
+
+
+/***/ },
+
+/***/ 466:
+/***/ function(module, exports, __webpack_require__) {
+
+ var __WEBPACK_AMD_DEFINE_RESULT__;/* -*- Mode: js; js-indent-level: 2; -*- */
+ /*
+ * Copyright 2011 Mozilla Foundation and contributors
+ * Licensed under the New BSD license. See LICENSE or:
+ * http://opensource.org/licenses/BSD-3-Clause
+ */
+ if (false) {
+ var define = require('amdefine')(module, require);
+ }
+ !(__WEBPACK_AMD_DEFINE_RESULT__ = function (require, exports, module) {
+
+ /**
+ * This is a helper function for getting values from parameter/options
+ * objects.
+ *
+ * @param args The object we are extracting values from
+ * @param name The name of the property we are getting.
+ * @param defaultValue An optional value to return if the property is missing
+ * from the object. If this is not specified and the property is missing, an
+ * error will be thrown.
+ */
+ function getArg(aArgs, aName, aDefaultValue) {
+ if (aName in aArgs) {
+ return aArgs[aName];
+ } else if (arguments.length === 3) {
+ return aDefaultValue;
+ } else {
+ throw new Error('"' + aName + '" is a required argument.');
+ }
+ }
+ exports.getArg = getArg;
+
+ var urlRegexp = /^(?:([\w+\-.]+):)?\/\/(?:(\w+:\w+)@)?([\w.]*)(?::(\d+))?(\S*)$/;
+ var dataUrlRegexp = /^data:.+\,.+$/;
+
+ function urlParse(aUrl) {
+ var match = aUrl.match(urlRegexp);
+ if (!match) {
+ return null;
+ }
+ return {
+ scheme: match[1],
+ auth: match[2],
+ host: match[3],
+ port: match[4],
+ path: match[5]
+ };
+ }
+ exports.urlParse = urlParse;
+
+ function urlGenerate(aParsedUrl) {
+ var url = '';
+ if (aParsedUrl.scheme) {
+ url += aParsedUrl.scheme + ':';
+ }
+ url += '//';
+ if (aParsedUrl.auth) {
+ url += aParsedUrl.auth + '@';
+ }
+ if (aParsedUrl.host) {
+ url += aParsedUrl.host;
+ }
+ if (aParsedUrl.port) {
+ url += ":" + aParsedUrl.port
+ }
+ if (aParsedUrl.path) {
+ url += aParsedUrl.path;
+ }
+ return url;
+ }
+ exports.urlGenerate = urlGenerate;
+
+ /**
+ * Normalizes a path, or the path portion of a URL:
+ *
+ * - Replaces consequtive slashes with one slash.
+ * - Removes unnecessary '.' parts.
+ * - Removes unnecessary '<dir>/..' parts.
+ *
+ * Based on code in the Node.js 'path' core module.
+ *
+ * @param aPath The path or url to normalize.
+ */
+ function normalize(aPath) {
+ var path = aPath;
+ var url = urlParse(aPath);
+ if (url) {
+ if (!url.path) {
+ return aPath;
+ }
+ path = url.path;
+ }
+ var isAbsolute = (path.charAt(0) === '/');
+
+ var parts = path.split(/\/+/);
+ for (var part, up = 0, i = parts.length - 1; i >= 0; i--) {
+ part = parts[i];
+ if (part === '.') {
+ parts.splice(i, 1);
+ } else if (part === '..') {
+ up++;
+ } else if (up > 0) {
+ if (part === '') {
+ // The first part is blank if the path is absolute. Trying to go
+ // above the root is a no-op. Therefore we can remove all '..' parts
+ // directly after the root.
+ parts.splice(i + 1, up);
+ up = 0;
+ } else {
+ parts.splice(i, 2);
+ up--;
+ }
+ }
+ }
+ path = parts.join('/');
+
+ if (path === '') {
+ path = isAbsolute ? '/' : '.';
+ }
+
+ if (url) {
+ url.path = path;
+ return urlGenerate(url);
+ }
+ return path;
+ }
+ exports.normalize = normalize;
+
+ /**
+ * Joins two paths/URLs.
+ *
+ * @param aRoot The root path or URL.
+ * @param aPath The path or URL to be joined with the root.
+ *
+ * - If aPath is a URL or a data URI, aPath is returned, unless aPath is a
+ * scheme-relative URL: Then the scheme of aRoot, if any, is prepended
+ * first.
+ * - Otherwise aPath is a path. If aRoot is a URL, then its path portion
+ * is updated with the result and aRoot is returned. Otherwise the result
+ * is returned.
+ * - If aPath is absolute, the result is aPath.
+ * - Otherwise the two paths are joined with a slash.
+ * - Joining for example 'http://' and 'www.example.com' is also supported.
+ */
+ function join(aRoot, aPath) {
+ if (aRoot === "") {
+ aRoot = ".";
+ }
+ if (aPath === "") {
+ aPath = ".";
+ }
+ var aPathUrl = urlParse(aPath);
+ var aRootUrl = urlParse(aRoot);
+ if (aRootUrl) {
+ aRoot = aRootUrl.path || '/';
+ }
+
+ // `join(foo, '//www.example.org')`
+ if (aPathUrl && !aPathUrl.scheme) {
+ if (aRootUrl) {
+ aPathUrl.scheme = aRootUrl.scheme;
+ }
+ return urlGenerate(aPathUrl);
+ }
+
+ if (aPathUrl || aPath.match(dataUrlRegexp)) {
+ return aPath;
+ }
+
+ // `join('http://', 'www.example.com')`
+ if (aRootUrl && !aRootUrl.host && !aRootUrl.path) {
+ aRootUrl.host = aPath;
+ return urlGenerate(aRootUrl);
+ }
+
+ var joined = aPath.charAt(0) === '/'
+ ? aPath
+ : normalize(aRoot.replace(/\/+$/, '') + '/' + aPath);
+
+ if (aRootUrl) {
+ aRootUrl.path = joined;
+ return urlGenerate(aRootUrl);
+ }
+ return joined;
+ }
+ exports.join = join;
+
+ /**
+ * Make a path relative to a URL or another path.
+ *
+ * @param aRoot The root path or URL.
+ * @param aPath The path or URL to be made relative to aRoot.
+ */
+ function relative(aRoot, aPath) {
+ if (aRoot === "") {
+ aRoot = ".";
+ }
+
+ aRoot = aRoot.replace(/\/$/, '');
+
+ // XXX: It is possible to remove this block, and the tests still pass!
+ var url = urlParse(aRoot);
+ if (aPath.charAt(0) == "/" && url && url.path == "/") {
+ return aPath.slice(1);
+ }
+
+ return aPath.indexOf(aRoot + '/') === 0
+ ? aPath.substr(aRoot.length + 1)
+ : aPath;
+ }
+ exports.relative = relative;
+
+ /**
+ * Because behavior goes wacky when you set `__proto__` on objects, we
+ * have to prefix all the strings in our set with an arbitrary character.
+ *
+ * See https://github.com/mozilla/source-map/pull/31 and
+ * https://github.com/mozilla/source-map/issues/30
+ *
+ * @param String aStr
+ */
+ function toSetString(aStr) {
+ return '$' + aStr;
+ }
+ exports.toSetString = toSetString;
+
+ function fromSetString(aStr) {
+ return aStr.substr(1);
+ }
+ exports.fromSetString = fromSetString;
+
+ function strcmp(aStr1, aStr2) {
+ var s1 = aStr1 || "";
+ var s2 = aStr2 || "";
+ return (s1 > s2) - (s1 < s2);
+ }
+
+ /**
+ * Comparator between two mappings where the original positions are compared.
+ *
+ * Optionally pass in `true` as `onlyCompareGenerated` to consider two
+ * mappings with the same original source/line/column, but different generated
+ * line and column the same. Useful when searching for a mapping with a
+ * stubbed out mapping.
+ */
+ function compareByOriginalPositions(mappingA, mappingB, onlyCompareOriginal) {
+ var cmp;
+
+ cmp = strcmp(mappingA.source, mappingB.source);
+ if (cmp) {
+ return cmp;
+ }
+
+ cmp = mappingA.originalLine - mappingB.originalLine;
+ if (cmp) {
+ return cmp;
+ }
+
+ cmp = mappingA.originalColumn - mappingB.originalColumn;
+ if (cmp || onlyCompareOriginal) {
+ return cmp;
+ }
+
+ cmp = strcmp(mappingA.name, mappingB.name);
+ if (cmp) {
+ return cmp;
+ }
+
+ cmp = mappingA.generatedLine - mappingB.generatedLine;
+ if (cmp) {
+ return cmp;
+ }
+
+ return mappingA.generatedColumn - mappingB.generatedColumn;
+ };
+ exports.compareByOriginalPositions = compareByOriginalPositions;
+
+ /**
+ * Comparator between two mappings where the generated positions are
+ * compared.
+ *
+ * Optionally pass in `true` as `onlyCompareGenerated` to consider two
+ * mappings with the same generated line and column, but different
+ * source/name/original line and column the same. Useful when searching for a
+ * mapping with a stubbed out mapping.
+ */
+ function compareByGeneratedPositions(mappingA, mappingB, onlyCompareGenerated) {
+ var cmp;
+
+ cmp = mappingA.generatedLine - mappingB.generatedLine;
+ if (cmp) {
+ return cmp;
+ }
+
+ cmp = mappingA.generatedColumn - mappingB.generatedColumn;
+ if (cmp || onlyCompareGenerated) {
+ return cmp;
+ }
+
+ cmp = strcmp(mappingA.source, mappingB.source);
+ if (cmp) {
+ return cmp;
+ }
+
+ cmp = mappingA.originalLine - mappingB.originalLine;
+ if (cmp) {
+ return cmp;
+ }
+
+ cmp = mappingA.originalColumn - mappingB.originalColumn;
+ if (cmp) {
+ return cmp;
+ }
+
+ return strcmp(mappingA.name, mappingB.name);
+ };
+ exports.compareByGeneratedPositions = compareByGeneratedPositions;
+
+ }.call(exports, __webpack_require__, exports, module), __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__));
+
+
+/***/ },
+
+/***/ 467:
+/***/ function(module, exports, __webpack_require__) {
+
+ var __WEBPACK_AMD_DEFINE_RESULT__;/* -*- Mode: js; js-indent-level: 2; -*- */
+ /*
+ * Copyright 2011 Mozilla Foundation and contributors
+ * Licensed under the New BSD license. See LICENSE or:
+ * http://opensource.org/licenses/BSD-3-Clause
+ */
+ if (false) {
+ var define = require('amdefine')(module, require);
+ }
+ !(__WEBPACK_AMD_DEFINE_RESULT__ = function (require, exports, module) {
+
+ var util = __webpack_require__(466);
+
+ /**
+ * A data structure which is a combination of an array and a set. Adding a new
+ * member is O(1), testing for membership is O(1), and finding the index of an
+ * element is O(1). Removing elements from the set is not supported. Only
+ * strings are supported for membership.
+ */
+ function ArraySet() {
+ this._array = [];
+ this._set = {};
+ }
+
+ /**
+ * Static method for creating ArraySet instances from an existing array.
+ */
+ ArraySet.fromArray = function ArraySet_fromArray(aArray, aAllowDuplicates) {
+ var set = new ArraySet();
+ for (var i = 0, len = aArray.length; i < len; i++) {
+ set.add(aArray[i], aAllowDuplicates);
+ }
+ return set;
+ };
+
+ /**
+ * Add the given string to this set.
+ *
+ * @param String aStr
+ */
+ ArraySet.prototype.add = function ArraySet_add(aStr, aAllowDuplicates) {
+ var isDuplicate = this.has(aStr);
+ var idx = this._array.length;
+ if (!isDuplicate || aAllowDuplicates) {
+ this._array.push(aStr);
+ }
+ if (!isDuplicate) {
+ this._set[util.toSetString(aStr)] = idx;
+ }
+ };
+
+ /**
+ * Is the given string a member of this set?
+ *
+ * @param String aStr
+ */
+ ArraySet.prototype.has = function ArraySet_has(aStr) {
+ return Object.prototype.hasOwnProperty.call(this._set,
+ util.toSetString(aStr));
+ };
+
+ /**
+ * What is the index of the given string in the array?
+ *
+ * @param String aStr
+ */
+ ArraySet.prototype.indexOf = function ArraySet_indexOf(aStr) {
+ if (this.has(aStr)) {
+ return this._set[util.toSetString(aStr)];
+ }
+ throw new Error('"' + aStr + '" is not in the set.');
+ };
+
+ /**
+ * What is the element at the given index?
+ *
+ * @param Number aIdx
+ */
+ ArraySet.prototype.at = function ArraySet_at(aIdx) {
+ if (aIdx >= 0 && aIdx < this._array.length) {
+ return this._array[aIdx];
+ }
+ throw new Error('No element indexed by ' + aIdx);
+ };
+
+ /**
+ * Returns the array representation of this set (which has the proper indices
+ * indicated by indexOf). Note that this is a copy of the internal array used
+ * for storing the members so that no one can mess with internal state.
+ */
+ ArraySet.prototype.toArray = function ArraySet_toArray() {
+ return this._array.slice();
+ };
+
+ exports.ArraySet = ArraySet;
+
+ }.call(exports, __webpack_require__, exports, module), __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__));
+
+
+/***/ },
+
+/***/ 468:
+/***/ function(module, exports, __webpack_require__) {
+
+ var __WEBPACK_AMD_DEFINE_RESULT__;/* -*- Mode: js; js-indent-level: 2; -*- */
+ /*
+ * Copyright 2014 Mozilla Foundation and contributors
+ * Licensed under the New BSD license. See LICENSE or:
+ * http://opensource.org/licenses/BSD-3-Clause
+ */
+ if (false) {
+ var define = require('amdefine')(module, require);
+ }
+ !(__WEBPACK_AMD_DEFINE_RESULT__ = function (require, exports, module) {
+
+ var util = __webpack_require__(466);
+
+ /**
+ * Determine whether mappingB is after mappingA with respect to generated
+ * position.
+ */
+ function generatedPositionAfter(mappingA, mappingB) {
+ // Optimized for most common case
+ var lineA = mappingA.generatedLine;
+ var lineB = mappingB.generatedLine;
+ var columnA = mappingA.generatedColumn;
+ var columnB = mappingB.generatedColumn;
+ return lineB > lineA || lineB == lineA && columnB >= columnA ||
+ util.compareByGeneratedPositions(mappingA, mappingB) <= 0;
+ }
+
+ /**
+ * A data structure to provide a sorted view of accumulated mappings in a
+ * performance conscious manner. It trades a neglibable overhead in general
+ * case for a large speedup in case of mappings being added in order.
+ */
+ function MappingList() {
+ this._array = [];
+ this._sorted = true;
+ // Serves as infimum
+ this._last = {generatedLine: -1, generatedColumn: 0};
+ }
+
+ /**
+ * Iterate through internal items. This method takes the same arguments that
+ * `Array.prototype.forEach` takes.
+ *
+ * NOTE: The order of the mappings is NOT guaranteed.
+ */
+ MappingList.prototype.unsortedForEach =
+ function MappingList_forEach(aCallback, aThisArg) {
+ this._array.forEach(aCallback, aThisArg);
+ };
+
+ /**
+ * Add the given source mapping.
+ *
+ * @param Object aMapping
+ */
+ MappingList.prototype.add = function MappingList_add(aMapping) {
+ var mapping;
+ if (generatedPositionAfter(this._last, aMapping)) {
+ this._last = aMapping;
+ this._array.push(aMapping);
+ } else {
+ this._sorted = false;
+ this._array.push(aMapping);
+ }
+ };
+
+ /**
+ * Returns the flat, sorted array of mappings. The mappings are sorted by
+ * generated position.
+ *
+ * WARNING: This method returns internal data without copying, for
+ * performance. The return value must NOT be mutated, and should be treated as
+ * an immutable borrow. If you want to take ownership, you must make your own
+ * copy.
+ */
+ MappingList.prototype.toArray = function MappingList_toArray() {
+ if (!this._sorted) {
+ this._array.sort(util.compareByGeneratedPositions);
+ this._sorted = true;
+ }
+ return this._array;
+ };
+
+ exports.MappingList = MappingList;
+
+ }.call(exports, __webpack_require__, exports, module), __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__));
+
+
+/***/ },
+
+/***/ 469:
+/***/ function(module, exports, __webpack_require__) {
+
+ var __WEBPACK_AMD_DEFINE_RESULT__;/* -*- Mode: js; js-indent-level: 2; -*- */
+ /*
+ * Copyright 2011 Mozilla Foundation and contributors
+ * Licensed under the New BSD license. See LICENSE or:
+ * http://opensource.org/licenses/BSD-3-Clause
+ */
+ if (false) {
+ var define = require('amdefine')(module, require);
+ }
+ !(__WEBPACK_AMD_DEFINE_RESULT__ = function (require, exports, module) {
+
+ var util = __webpack_require__(466);
+ var binarySearch = __webpack_require__(470);
+ var ArraySet = __webpack_require__(467).ArraySet;
+ var base64VLQ = __webpack_require__(464);
+
+ /**
+ * A SourceMapConsumer instance represents a parsed source map which we can
+ * query for information about the original file positions by giving it a file
+ * position in the generated source.
+ *
+ * The only parameter is the raw source map (either as a JSON string, or
+ * already parsed to an object). According to the spec, source maps have the
+ * following attributes:
+ *
+ * - version: Which version of the source map spec this map is following.
+ * - sources: An array of URLs to the original source files.
+ * - names: An array of identifiers which can be referrenced by individual mappings.
+ * - sourceRoot: Optional. The URL root from which all sources are relative.
+ * - sourcesContent: Optional. An array of contents of the original source files.
+ * - mappings: A string of base64 VLQs which contain the actual mappings.
+ * - file: Optional. The generated file this source map is associated with.
+ *
+ * Here is an example source map, taken from the source map spec[0]:
+ *
+ * {
+ * version : 3,
+ * file: "out.js",
+ * sourceRoot : "",
+ * sources: ["foo.js", "bar.js"],
+ * names: ["src", "maps", "are", "fun"],
+ * mappings: "AA,AB;;ABCDE;"
+ * }
+ *
+ * [0]: https://docs.google.com/document/d/1U1RGAehQwRypUTovF1KRlpiOFze0b-_2gc6fAH0KY0k/edit?pli=1#
+ */
+ function SourceMapConsumer(aSourceMap) {
+ var sourceMap = aSourceMap;
+ if (typeof aSourceMap === 'string') {
+ sourceMap = JSON.parse(aSourceMap.replace(/^\)\]\}'/, ''));
+ }
+
+ var version = util.getArg(sourceMap, 'version');
+ var sources = util.getArg(sourceMap, 'sources');
+ // Sass 3.3 leaves out the 'names' array, so we deviate from the spec (which
+ // requires the array) to play nice here.
+ var names = util.getArg(sourceMap, 'names', []);
+ var sourceRoot = util.getArg(sourceMap, 'sourceRoot', null);
+ var sourcesContent = util.getArg(sourceMap, 'sourcesContent', null);
+ var mappings = util.getArg(sourceMap, 'mappings');
+ var file = util.getArg(sourceMap, 'file', null);
+
+ // Once again, Sass deviates from the spec and supplies the version as a
+ // string rather than a number, so we use loose equality checking here.
+ if (version != this._version) {
+ throw new Error('Unsupported version: ' + version);
+ }
+
+ // Some source maps produce relative source paths like "./foo.js" instead of
+ // "foo.js". Normalize these first so that future comparisons will succeed.
+ // See bugzil.la/1090768.
+ sources = sources.map(util.normalize);
+
+ // Pass `true` below to allow duplicate names and sources. While source maps
+ // are intended to be compressed and deduplicated, the TypeScript compiler
+ // sometimes generates source maps with duplicates in them. See Github issue
+ // #72 and bugzil.la/889492.
+ this._names = ArraySet.fromArray(names, true);
+ this._sources = ArraySet.fromArray(sources, true);
+
+ this.sourceRoot = sourceRoot;
+ this.sourcesContent = sourcesContent;
+ this._mappings = mappings;
+ this.file = file;
+ }
+
+ /**
+ * Create a SourceMapConsumer from a SourceMapGenerator.
+ *
+ * @param SourceMapGenerator aSourceMap
+ * The source map that will be consumed.
+ * @returns SourceMapConsumer
+ */
+ SourceMapConsumer.fromSourceMap =
+ function SourceMapConsumer_fromSourceMap(aSourceMap) {
+ var smc = Object.create(SourceMapConsumer.prototype);
+
+ smc._names = ArraySet.fromArray(aSourceMap._names.toArray(), true);
+ smc._sources = ArraySet.fromArray(aSourceMap._sources.toArray(), true);
+ smc.sourceRoot = aSourceMap._sourceRoot;
+ smc.sourcesContent = aSourceMap._generateSourcesContent(smc._sources.toArray(),
+ smc.sourceRoot);
+ smc.file = aSourceMap._file;
+
+ smc.__generatedMappings = aSourceMap._mappings.toArray().slice();
+ smc.__originalMappings = aSourceMap._mappings.toArray().slice()
+ .sort(util.compareByOriginalPositions);
+
+ return smc;
+ };
+
+ /**
+ * The version of the source mapping spec that we are consuming.
+ */
+ SourceMapConsumer.prototype._version = 3;
+
+ /**
+ * The list of original sources.
+ */
+ Object.defineProperty(SourceMapConsumer.prototype, 'sources', {
+ get: function () {
+ return this._sources.toArray().map(function (s) {
+ return this.sourceRoot != null ? util.join(this.sourceRoot, s) : s;
+ }, this);
+ }
+ });
+
+ // `__generatedMappings` and `__originalMappings` are arrays that hold the
+ // parsed mapping coordinates from the source map's "mappings" attribute. They
+ // are lazily instantiated, accessed via the `_generatedMappings` and
+ // `_originalMappings` getters respectively, and we only parse the mappings
+ // and create these arrays once queried for a source location. We jump through
+ // these hoops because there can be many thousands of mappings, and parsing
+ // them is expensive, so we only want to do it if we must.
+ //
+ // Each object in the arrays is of the form:
+ //
+ // {
+ // generatedLine: The line number in the generated code,
+ // generatedColumn: The column number in the generated code,
+ // source: The path to the original source file that generated this
+ // chunk of code,
+ // originalLine: The line number in the original source that
+ // corresponds to this chunk of generated code,
+ // originalColumn: The column number in the original source that
+ // corresponds to this chunk of generated code,
+ // name: The name of the original symbol which generated this chunk of
+ // code.
+ // }
+ //
+ // All properties except for `generatedLine` and `generatedColumn` can be
+ // `null`.
+ //
+ // `_generatedMappings` is ordered by the generated positions.
+ //
+ // `_originalMappings` is ordered by the original positions.
+
+ SourceMapConsumer.prototype.__generatedMappings = null;
+ Object.defineProperty(SourceMapConsumer.prototype, '_generatedMappings', {
+ get: function () {
+ if (!this.__generatedMappings) {
+ this.__generatedMappings = [];
+ this.__originalMappings = [];
+ this._parseMappings(this._mappings, this.sourceRoot);
+ }
+
+ return this.__generatedMappings;
+ }
+ });
+
+ SourceMapConsumer.prototype.__originalMappings = null;
+ Object.defineProperty(SourceMapConsumer.prototype, '_originalMappings', {
+ get: function () {
+ if (!this.__originalMappings) {
+ this.__generatedMappings = [];
+ this.__originalMappings = [];
+ this._parseMappings(this._mappings, this.sourceRoot);
+ }
+
+ return this.__originalMappings;
+ }
+ });
+
+ SourceMapConsumer.prototype._nextCharIsMappingSeparator =
+ function SourceMapConsumer_nextCharIsMappingSeparator(aStr) {
+ var c = aStr.charAt(0);
+ return c === ";" || c === ",";
+ };
+
+ /**
+ * Parse the mappings in a string in to a data structure which we can easily
+ * query (the ordered arrays in the `this.__generatedMappings` and
+ * `this.__originalMappings` properties).
+ */
+ SourceMapConsumer.prototype._parseMappings =
+ function SourceMapConsumer_parseMappings(aStr, aSourceRoot) {
+ var generatedLine = 1;
+ var previousGeneratedColumn = 0;
+ var previousOriginalLine = 0;
+ var previousOriginalColumn = 0;
+ var previousSource = 0;
+ var previousName = 0;
+ var str = aStr;
+ var temp = {};
+ var mapping;
+
+ while (str.length > 0) {
+ if (str.charAt(0) === ';') {
+ generatedLine++;
+ str = str.slice(1);
+ previousGeneratedColumn = 0;
+ }
+ else if (str.charAt(0) === ',') {
+ str = str.slice(1);
+ }
+ else {
+ mapping = {};
+ mapping.generatedLine = generatedLine;
+
+ // Generated column.
+ base64VLQ.decode(str, temp);
+ mapping.generatedColumn = previousGeneratedColumn + temp.value;
+ previousGeneratedColumn = mapping.generatedColumn;
+ str = temp.rest;
+
+ if (str.length > 0 && !this._nextCharIsMappingSeparator(str)) {
+ // Original source.
+ base64VLQ.decode(str, temp);
+ mapping.source = this._sources.at(previousSource + temp.value);
+ previousSource += temp.value;
+ str = temp.rest;
+ if (str.length === 0 || this._nextCharIsMappingSeparator(str)) {
+ throw new Error('Found a source, but no line and column');
+ }
+
+ // Original line.
+ base64VLQ.decode(str, temp);
+ mapping.originalLine = previousOriginalLine + temp.value;
+ previousOriginalLine = mapping.originalLine;
+ // Lines are stored 0-based
+ mapping.originalLine += 1;
+ str = temp.rest;
+ if (str.length === 0 || this._nextCharIsMappingSeparator(str)) {
+ throw new Error('Found a source and line, but no column');
+ }
+
+ // Original column.
+ base64VLQ.decode(str, temp);
+ mapping.originalColumn = previousOriginalColumn + temp.value;
+ previousOriginalColumn = mapping.originalColumn;
+ str = temp.rest;
+
+ if (str.length > 0 && !this._nextCharIsMappingSeparator(str)) {
+ // Original name.
+ base64VLQ.decode(str, temp);
+ mapping.name = this._names.at(previousName + temp.value);
+ previousName += temp.value;
+ str = temp.rest;
+ }
+ }
+
+ this.__generatedMappings.push(mapping);
+ if (typeof mapping.originalLine === 'number') {
+ this.__originalMappings.push(mapping);
+ }
+ }
+ }
+
+ this.__generatedMappings.sort(util.compareByGeneratedPositions);
+ this.__originalMappings.sort(util.compareByOriginalPositions);
+ };
+
+ /**
+ * Find the mapping that best matches the hypothetical "needle" mapping that
+ * we are searching for in the given "haystack" of mappings.
+ */
+ SourceMapConsumer.prototype._findMapping =
+ function SourceMapConsumer_findMapping(aNeedle, aMappings, aLineName,
+ aColumnName, aComparator) {
+ // To return the position we are searching for, we must first find the
+ // mapping for the given position and then return the opposite position it
+ // points to. Because the mappings are sorted, we can use binary search to
+ // find the best mapping.
+
+ if (aNeedle[aLineName] <= 0) {
+ throw new TypeError('Line must be greater than or equal to 1, got '
+ + aNeedle[aLineName]);
+ }
+ if (aNeedle[aColumnName] < 0) {
+ throw new TypeError('Column must be greater than or equal to 0, got '
+ + aNeedle[aColumnName]);
+ }
+
+ return binarySearch.search(aNeedle, aMappings, aComparator);
+ };
+
+ /**
+ * Compute the last column for each generated mapping. The last column is
+ * inclusive.
+ */
+ SourceMapConsumer.prototype.computeColumnSpans =
+ function SourceMapConsumer_computeColumnSpans() {
+ for (var index = 0; index < this._generatedMappings.length; ++index) {
+ var mapping = this._generatedMappings[index];
+
+ // Mappings do not contain a field for the last generated columnt. We
+ // can come up with an optimistic estimate, however, by assuming that
+ // mappings are contiguous (i.e. given two consecutive mappings, the
+ // first mapping ends where the second one starts).
+ if (index + 1 < this._generatedMappings.length) {
+ var nextMapping = this._generatedMappings[index + 1];
+
+ if (mapping.generatedLine === nextMapping.generatedLine) {
+ mapping.lastGeneratedColumn = nextMapping.generatedColumn - 1;
+ continue;
+ }
+ }
+
+ // The last mapping for each line spans the entire line.
+ mapping.lastGeneratedColumn = Infinity;
+ }
+ };
+
+ /**
+ * Returns the original source, line, and column information for the generated
+ * source's line and column positions provided. The only argument is an object
+ * with the following properties:
+ *
+ * - line: The line number in the generated source.
+ * - column: The column number in the generated source.
+ *
+ * and an object is returned with the following properties:
+ *
+ * - source: The original source file, or null.
+ * - line: The line number in the original source, or null.
+ * - column: The column number in the original source, or null.
+ * - name: The original identifier, or null.
+ */
+ SourceMapConsumer.prototype.originalPositionFor =
+ function SourceMapConsumer_originalPositionFor(aArgs) {
+ var needle = {
+ generatedLine: util.getArg(aArgs, 'line'),
+ generatedColumn: util.getArg(aArgs, 'column')
+ };
+
+ var index = this._findMapping(needle,
+ this._generatedMappings,
+ "generatedLine",
+ "generatedColumn",
+ util.compareByGeneratedPositions);
+
+ if (index >= 0) {
+ var mapping = this._generatedMappings[index];
+
+ if (mapping.generatedLine === needle.generatedLine) {
+ var source = util.getArg(mapping, 'source', null);
+ if (source != null && this.sourceRoot != null) {
+ source = util.join(this.sourceRoot, source);
+ }
+ return {
+ source: source,
+ line: util.getArg(mapping, 'originalLine', null),
+ column: util.getArg(mapping, 'originalColumn', null),
+ name: util.getArg(mapping, 'name', null)
+ };
+ }
+ }
+
+ return {
+ source: null,
+ line: null,
+ column: null,
+ name: null
+ };
+ };
+
+ /**
+ * Returns the original source content. The only argument is the url of the
+ * original source file. Returns null if no original source content is
+ * availible.
+ */
+ SourceMapConsumer.prototype.sourceContentFor =
+ function SourceMapConsumer_sourceContentFor(aSource) {
+ if (!this.sourcesContent) {
+ return null;
+ }
+
+ if (this.sourceRoot != null) {
+ aSource = util.relative(this.sourceRoot, aSource);
+ }
+
+ if (this._sources.has(aSource)) {
+ return this.sourcesContent[this._sources.indexOf(aSource)];
+ }
+
+ var url;
+ if (this.sourceRoot != null
+ && (url = util.urlParse(this.sourceRoot))) {
+ // XXX: file:// URIs and absolute paths lead to unexpected behavior for
+ // many users. We can help them out when they expect file:// URIs to
+ // behave like it would if they were running a local HTTP server. See
+ // https://bugzilla.mozilla.org/show_bug.cgi?id=885597.
+ var fileUriAbsPath = aSource.replace(/^file:\/\//, "");
+ if (url.scheme == "file"
+ && this._sources.has(fileUriAbsPath)) {
+ return this.sourcesContent[this._sources.indexOf(fileUriAbsPath)]
+ }
+
+ if ((!url.path || url.path == "/")
+ && this._sources.has("/" + aSource)) {
+ return this.sourcesContent[this._sources.indexOf("/" + aSource)];
+ }
+ }
+
+ throw new Error('"' + aSource + '" is not in the SourceMap.');
+ };
+
+ /**
+ * Returns the generated line and column information for the original source,
+ * line, and column positions provided. The only argument is an object with
+ * the following properties:
+ *
+ * - source: The filename of the original source.
+ * - line: The line number in the original source.
+ * - column: The column number in the original source.
+ *
+ * and an object is returned with the following properties:
+ *
+ * - line: The line number in the generated source, or null.
+ * - column: The column number in the generated source, or null.
+ */
+ SourceMapConsumer.prototype.generatedPositionFor =
+ function SourceMapConsumer_generatedPositionFor(aArgs) {
+ var needle = {
+ source: util.getArg(aArgs, 'source'),
+ originalLine: util.getArg(aArgs, 'line'),
+ originalColumn: util.getArg(aArgs, 'column')
+ };
+
+ if (this.sourceRoot != null) {
+ needle.source = util.relative(this.sourceRoot, needle.source);
+ }
+
+ var index = this._findMapping(needle,
+ this._originalMappings,
+ "originalLine",
+ "originalColumn",
+ util.compareByOriginalPositions);
+
+ if (index >= 0) {
+ var mapping = this._originalMappings[index];
+
+ return {
+ line: util.getArg(mapping, 'generatedLine', null),
+ column: util.getArg(mapping, 'generatedColumn', null),
+ lastColumn: util.getArg(mapping, 'lastGeneratedColumn', null)
+ };
+ }
+
+ return {
+ line: null,
+ column: null,
+ lastColumn: null
+ };
+ };
+
+ /**
+ * Returns all generated line and column information for the original source
+ * and line provided. The only argument is an object with the following
+ * properties:
+ *
+ * - source: The filename of the original source.
+ * - line: The line number in the original source.
+ *
+ * and an array of objects is returned, each with the following properties:
+ *
+ * - line: The line number in the generated source, or null.
+ * - column: The column number in the generated source, or null.
+ */
+ SourceMapConsumer.prototype.allGeneratedPositionsFor =
+ function SourceMapConsumer_allGeneratedPositionsFor(aArgs) {
+ // When there is no exact match, SourceMapConsumer.prototype._findMapping
+ // returns the index of the closest mapping less than the needle. By
+ // setting needle.originalColumn to Infinity, we thus find the last
+ // mapping for the given line, provided such a mapping exists.
+ var needle = {
+ source: util.getArg(aArgs, 'source'),
+ originalLine: util.getArg(aArgs, 'line'),
+ originalColumn: Infinity
+ };
+
+ if (this.sourceRoot != null) {
+ needle.source = util.relative(this.sourceRoot, needle.source);
+ }
+
+ var mappings = [];
+
+ var index = this._findMapping(needle,
+ this._originalMappings,
+ "originalLine",
+ "originalColumn",
+ util.compareByOriginalPositions);
+ if (index >= 0) {
+ var mapping = this._originalMappings[index];
+
+ while (mapping && mapping.originalLine === needle.originalLine) {
+ mappings.push({
+ line: util.getArg(mapping, 'generatedLine', null),
+ column: util.getArg(mapping, 'generatedColumn', null),
+ lastColumn: util.getArg(mapping, 'lastGeneratedColumn', null)
+ });
+
+ mapping = this._originalMappings[--index];
+ }
+ }
+
+ return mappings.reverse();
+ };
+
+ SourceMapConsumer.GENERATED_ORDER = 1;
+ SourceMapConsumer.ORIGINAL_ORDER = 2;
+
+ /**
+ * Iterate over each mapping between an original source/line/column and a
+ * generated line/column in this source map.
+ *
+ * @param Function aCallback
+ * The function that is called with each mapping.
+ * @param Object aContext
+ * Optional. If specified, this object will be the value of `this` every
+ * time that `aCallback` is called.
+ * @param aOrder
+ * Either `SourceMapConsumer.GENERATED_ORDER` or
+ * `SourceMapConsumer.ORIGINAL_ORDER`. Specifies whether you want to
+ * iterate over the mappings sorted by the generated file's line/column
+ * order or the original's source/line/column order, respectively. Defaults to
+ * `SourceMapConsumer.GENERATED_ORDER`.
+ */
+ SourceMapConsumer.prototype.eachMapping =
+ function SourceMapConsumer_eachMapping(aCallback, aContext, aOrder) {
+ var context = aContext || null;
+ var order = aOrder || SourceMapConsumer.GENERATED_ORDER;
+
+ var mappings;
+ switch (order) {
+ case SourceMapConsumer.GENERATED_ORDER:
+ mappings = this._generatedMappings;
+ break;
+ case SourceMapConsumer.ORIGINAL_ORDER:
+ mappings = this._originalMappings;
+ break;
+ default:
+ throw new Error("Unknown order of iteration.");
+ }
+
+ var sourceRoot = this.sourceRoot;
+ mappings.map(function (mapping) {
+ var source = mapping.source;
+ if (source != null && sourceRoot != null) {
+ source = util.join(sourceRoot, source);
+ }
+ return {
+ source: source,
+ generatedLine: mapping.generatedLine,
+ generatedColumn: mapping.generatedColumn,
+ originalLine: mapping.originalLine,
+ originalColumn: mapping.originalColumn,
+ name: mapping.name
+ };
+ }).forEach(aCallback, context);
+ };
+
+ exports.SourceMapConsumer = SourceMapConsumer;
+
+ }.call(exports, __webpack_require__, exports, module), __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__));
+
+
+/***/ },
+
+/***/ 470:
+/***/ function(module, exports, __webpack_require__) {
+
+ var __WEBPACK_AMD_DEFINE_RESULT__;/* -*- Mode: js; js-indent-level: 2; -*- */
+ /*
+ * Copyright 2011 Mozilla Foundation and contributors
+ * Licensed under the New BSD license. See LICENSE or:
+ * http://opensource.org/licenses/BSD-3-Clause
+ */
+ if (false) {
+ var define = require('amdefine')(module, require);
+ }
+ !(__WEBPACK_AMD_DEFINE_RESULT__ = function (require, exports, module) {
+
+ /**
+ * Recursive implementation of binary search.
+ *
+ * @param aLow Indices here and lower do not contain the needle.
+ * @param aHigh Indices here and higher do not contain the needle.
+ * @param aNeedle The element being searched for.
+ * @param aHaystack The non-empty array being searched.
+ * @param aCompare Function which takes two elements and returns -1, 0, or 1.
+ */
+ function recursiveSearch(aLow, aHigh, aNeedle, aHaystack, aCompare) {
+ // This function terminates when one of the following is true:
+ //
+ // 1. We find the exact element we are looking for.
+ //
+ // 2. We did not find the exact element, but we can return the index of
+ // the next closest element that is less than that element.
+ //
+ // 3. We did not find the exact element, and there is no next-closest
+ // element which is less than the one we are searching for, so we
+ // return -1.
+ var mid = Math.floor((aHigh - aLow) / 2) + aLow;
+ var cmp = aCompare(aNeedle, aHaystack[mid], true);
+ if (cmp === 0) {
+ // Found the element we are looking for.
+ return mid;
+ }
+ else if (cmp > 0) {
+ // aHaystack[mid] is greater than our needle.
+ if (aHigh - mid > 1) {
+ // The element is in the upper half.
+ return recursiveSearch(mid, aHigh, aNeedle, aHaystack, aCompare);
+ }
+ // We did not find an exact match, return the next closest one
+ // (termination case 2).
+ return mid;
+ }
+ else {
+ // aHaystack[mid] is less than our needle.
+ if (mid - aLow > 1) {
+ // The element is in the lower half.
+ return recursiveSearch(aLow, mid, aNeedle, aHaystack, aCompare);
+ }
+ // The exact needle element was not found in this haystack. Determine if
+ // we are in termination case (2) or (3) and return the appropriate thing.
+ return aLow < 0 ? -1 : aLow;
+ }
+ }
+
+ /**
+ * This is an implementation of binary search which will always try and return
+ * the index of next lowest value checked if there is no exact hit. This is
+ * because mappings between original and generated line/col pairs are single
+ * points, and there is an implicit region between each of them, so a miss
+ * just means that you aren't on the very start of a region.
+ *
+ * @param aNeedle The element you are looking for.
+ * @param aHaystack The array that is being searched.
+ * @param aCompare A function which takes the needle and an element in the
+ * array and returns -1, 0, or 1 depending on whether the needle is less
+ * than, equal to, or greater than the element, respectively.
+ */
+ exports.search = function search(aNeedle, aHaystack, aCompare) {
+ if (aHaystack.length === 0) {
+ return -1;
+ }
+ return recursiveSearch(-1, aHaystack.length, aNeedle, aHaystack, aCompare)
+ };
+
+ }.call(exports, __webpack_require__, exports, module), __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__));
+
+
+/***/ },
+
+/***/ 471:
+/***/ function(module, exports, __webpack_require__) {
+
+ var __WEBPACK_AMD_DEFINE_RESULT__;/* -*- Mode: js; js-indent-level: 2; -*- */
+ /*
+ * Copyright 2011 Mozilla Foundation and contributors
+ * Licensed under the New BSD license. See LICENSE or:
+ * http://opensource.org/licenses/BSD-3-Clause
+ */
+ if (false) {
+ var define = require('amdefine')(module, require);
+ }
+ !(__WEBPACK_AMD_DEFINE_RESULT__ = function (require, exports, module) {
+
+ var SourceMapGenerator = __webpack_require__(463).SourceMapGenerator;
+ var util = __webpack_require__(466);
+
+ // Matches a Windows-style `\r\n` newline or a `\n` newline used by all other
+ // operating systems these days (capturing the result).
+ var REGEX_NEWLINE = /(\r?\n)/;
+
+ // Newline character code for charCodeAt() comparisons
+ var NEWLINE_CODE = 10;
+
+ // Private symbol for identifying `SourceNode`s when multiple versions of
+ // the source-map library are loaded. This MUST NOT CHANGE across
+ // versions!
+ var isSourceNode = "$$$isSourceNode$$$";
+
+ /**
+ * SourceNodes provide a way to abstract over interpolating/concatenating
+ * snippets of generated JavaScript source code while maintaining the line and
+ * column information associated with the original source code.
+ *
+ * @param aLine The original line number.
+ * @param aColumn The original column number.
+ * @param aSource The original source's filename.
+ * @param aChunks Optional. An array of strings which are snippets of
+ * generated JS, or other SourceNodes.
+ * @param aName The original identifier.
+ */
+ function SourceNode(aLine, aColumn, aSource, aChunks, aName) {
+ this.children = [];
+ this.sourceContents = {};
+ this.line = aLine == null ? null : aLine;
+ this.column = aColumn == null ? null : aColumn;
+ this.source = aSource == null ? null : aSource;
+ this.name = aName == null ? null : aName;
+ this[isSourceNode] = true;
+ if (aChunks != null) this.add(aChunks);
+ }
+
+ /**
+ * Creates a SourceNode from generated code and a SourceMapConsumer.
+ *
+ * @param aGeneratedCode The generated code
+ * @param aSourceMapConsumer The SourceMap for the generated code
+ * @param aRelativePath Optional. The path that relative sources in the
+ * SourceMapConsumer should be relative to.
+ */
+ SourceNode.fromStringWithSourceMap =
+ function SourceNode_fromStringWithSourceMap(aGeneratedCode, aSourceMapConsumer, aRelativePath) {
+ // The SourceNode we want to fill with the generated code
+ // and the SourceMap
+ var node = new SourceNode();
+
+ // All even indices of this array are one line of the generated code,
+ // while all odd indices are the newlines between two adjacent lines
+ // (since `REGEX_NEWLINE` captures its match).
+ // Processed fragments are removed from this array, by calling `shiftNextLine`.
+ var remainingLines = aGeneratedCode.split(REGEX_NEWLINE);
+ var shiftNextLine = function() {
+ var lineContents = remainingLines.shift();
+ // The last line of a file might not have a newline.
+ var newLine = remainingLines.shift() || "";
+ return lineContents + newLine;
+ };
+
+ // We need to remember the position of "remainingLines"
+ var lastGeneratedLine = 1, lastGeneratedColumn = 0;
+
+ // The generate SourceNodes we need a code range.
+ // To extract it current and last mapping is used.
+ // Here we store the last mapping.
+ var lastMapping = null;
+
+ aSourceMapConsumer.eachMapping(function (mapping) {
+ if (lastMapping !== null) {
+ // We add the code from "lastMapping" to "mapping":
+ // First check if there is a new line in between.
+ if (lastGeneratedLine < mapping.generatedLine) {
+ var code = "";
+ // Associate first line with "lastMapping"
+ addMappingWithCode(lastMapping, shiftNextLine());
+ lastGeneratedLine++;
+ lastGeneratedColumn = 0;
+ // The remaining code is added without mapping
+ } else {
+ // There is no new line in between.
+ // Associate the code between "lastGeneratedColumn" and
+ // "mapping.generatedColumn" with "lastMapping"
+ var nextLine = remainingLines[0];
+ var code = nextLine.substr(0, mapping.generatedColumn -
+ lastGeneratedColumn);
+ remainingLines[0] = nextLine.substr(mapping.generatedColumn -
+ lastGeneratedColumn);
+ lastGeneratedColumn = mapping.generatedColumn;
+ addMappingWithCode(lastMapping, code);
+ // No more remaining code, continue
+ lastMapping = mapping;
+ return;
+ }
+ }
+ // We add the generated code until the first mapping
+ // to the SourceNode without any mapping.
+ // Each line is added as separate string.
+ while (lastGeneratedLine < mapping.generatedLine) {
+ node.add(shiftNextLine());
+ lastGeneratedLine++;
+ }
+ if (lastGeneratedColumn < mapping.generatedColumn) {
+ var nextLine = remainingLines[0];
+ node.add(nextLine.substr(0, mapping.generatedColumn));
+ remainingLines[0] = nextLine.substr(mapping.generatedColumn);
+ lastGeneratedColumn = mapping.generatedColumn;
+ }
+ lastMapping = mapping;
+ }, this);
+ // We have processed all mappings.
+ if (remainingLines.length > 0) {
+ if (lastMapping) {
+ // Associate the remaining code in the current line with "lastMapping"
+ addMappingWithCode(lastMapping, shiftNextLine());
+ }
+ // and add the remaining lines without any mapping
+ node.add(remainingLines.join(""));
+ }
+
+ // Copy sourcesContent into SourceNode
+ aSourceMapConsumer.sources.forEach(function (sourceFile) {
+ var content = aSourceMapConsumer.sourceContentFor(sourceFile);
+ if (content != null) {
+ if (aRelativePath != null) {
+ sourceFile = util.join(aRelativePath, sourceFile);
+ }
+ node.setSourceContent(sourceFile, content);
+ }
+ });
+
+ return node;
+
+ function addMappingWithCode(mapping, code) {
+ if (mapping === null || mapping.source === undefined) {
+ node.add(code);
+ } else {
+ var source = aRelativePath
+ ? util.join(aRelativePath, mapping.source)
+ : mapping.source;
+ node.add(new SourceNode(mapping.originalLine,
+ mapping.originalColumn,
+ source,
+ code,
+ mapping.name));
+ }
+ }
+ };
+
+ /**
+ * Add a chunk of generated JS to this source node.
+ *
+ * @param aChunk A string snippet of generated JS code, another instance of
+ * SourceNode, or an array where each member is one of those things.
+ */
+ SourceNode.prototype.add = function SourceNode_add(aChunk) {
+ if (Array.isArray(aChunk)) {
+ aChunk.forEach(function (chunk) {
+ this.add(chunk);
+ }, this);
+ }
+ else if (aChunk[isSourceNode] || typeof aChunk === "string") {
+ if (aChunk) {
+ this.children.push(aChunk);
+ }
+ }
+ else {
+ throw new TypeError(
+ "Expected a SourceNode, string, or an array of SourceNodes and strings. Got " + aChunk
+ );
+ }
+ return this;
+ };
+
+ /**
+ * Add a chunk of generated JS to the beginning of this source node.
+ *
+ * @param aChunk A string snippet of generated JS code, another instance of
+ * SourceNode, or an array where each member is one of those things.
+ */
+ SourceNode.prototype.prepend = function SourceNode_prepend(aChunk) {
+ if (Array.isArray(aChunk)) {
+ for (var i = aChunk.length-1; i >= 0; i--) {
+ this.prepend(aChunk[i]);
+ }
+ }
+ else if (aChunk[isSourceNode] || typeof aChunk === "string") {
+ this.children.unshift(aChunk);
+ }
+ else {
+ throw new TypeError(
+ "Expected a SourceNode, string, or an array of SourceNodes and strings. Got " + aChunk
+ );
+ }
+ return this;
+ };
+
+ /**
+ * Walk over the tree of JS snippets in this node and its children. The
+ * walking function is called once for each snippet of JS and is passed that
+ * snippet and the its original associated source's line/column location.
+ *
+ * @param aFn The traversal function.
+ */
+ SourceNode.prototype.walk = function SourceNode_walk(aFn) {
+ var chunk;
+ for (var i = 0, len = this.children.length; i < len; i++) {
+ chunk = this.children[i];
+ if (chunk[isSourceNode]) {
+ chunk.walk(aFn);
+ }
+ else {
+ if (chunk !== '') {
+ aFn(chunk, { source: this.source,
+ line: this.line,
+ column: this.column,
+ name: this.name });
+ }
+ }
+ }
+ };
+
+ /**
+ * Like `String.prototype.join` except for SourceNodes. Inserts `aStr` between
+ * each of `this.children`.
+ *
+ * @param aSep The separator.
+ */
+ SourceNode.prototype.join = function SourceNode_join(aSep) {
+ var newChildren;
+ var i;
+ var len = this.children.length;
+ if (len > 0) {
+ newChildren = [];
+ for (i = 0; i < len-1; i++) {
+ newChildren.push(this.children[i]);
+ newChildren.push(aSep);
+ }
+ newChildren.push(this.children[i]);
+ this.children = newChildren;
+ }
+ return this;
+ };
+
+ /**
+ * Call String.prototype.replace on the very right-most source snippet. Useful
+ * for trimming whitespace from the end of a source node, etc.
+ *
+ * @param aPattern The pattern to replace.
+ * @param aReplacement The thing to replace the pattern with.
+ */
+ SourceNode.prototype.replaceRight = function SourceNode_replaceRight(aPattern, aReplacement) {
+ var lastChild = this.children[this.children.length - 1];
+ if (lastChild[isSourceNode]) {
+ lastChild.replaceRight(aPattern, aReplacement);
+ }
+ else if (typeof lastChild === 'string') {
+ this.children[this.children.length - 1] = lastChild.replace(aPattern, aReplacement);
+ }
+ else {
+ this.children.push(''.replace(aPattern, aReplacement));
+ }
+ return this;
+ };
+
+ /**
+ * Set the source content for a source file. This will be added to the SourceMapGenerator
+ * in the sourcesContent field.
+ *
+ * @param aSourceFile The filename of the source file
+ * @param aSourceContent The content of the source file
+ */
+ SourceNode.prototype.setSourceContent =
+ function SourceNode_setSourceContent(aSourceFile, aSourceContent) {
+ this.sourceContents[util.toSetString(aSourceFile)] = aSourceContent;
+ };
+
+ /**
+ * Walk over the tree of SourceNodes. The walking function is called for each
+ * source file content and is passed the filename and source content.
+ *
+ * @param aFn The traversal function.
+ */
+ SourceNode.prototype.walkSourceContents =
+ function SourceNode_walkSourceContents(aFn) {
+ for (var i = 0, len = this.children.length; i < len; i++) {
+ if (this.children[i][isSourceNode]) {
+ this.children[i].walkSourceContents(aFn);
+ }
+ }
+
+ var sources = Object.keys(this.sourceContents);
+ for (var i = 0, len = sources.length; i < len; i++) {
+ aFn(util.fromSetString(sources[i]), this.sourceContents[sources[i]]);
+ }
+ };
+
+ /**
+ * Return the string representation of this source node. Walks over the tree
+ * and concatenates all the various snippets together to one string.
+ */
+ SourceNode.prototype.toString = function SourceNode_toString() {
+ var str = "";
+ this.walk(function (chunk) {
+ str += chunk;
+ });
+ return str;
+ };
+
+ /**
+ * Returns the string representation of this source node along with a source
+ * map.
+ */
+ SourceNode.prototype.toStringWithSourceMap = function SourceNode_toStringWithSourceMap(aArgs) {
+ var generated = {
+ code: "",
+ line: 1,
+ column: 0
+ };
+ var map = new SourceMapGenerator(aArgs);
+ var sourceMappingActive = false;
+ var lastOriginalSource = null;
+ var lastOriginalLine = null;
+ var lastOriginalColumn = null;
+ var lastOriginalName = null;
+ this.walk(function (chunk, original) {
+ generated.code += chunk;
+ if (original.source !== null
+ && original.line !== null
+ && original.column !== null) {
+ if(lastOriginalSource !== original.source
+ || lastOriginalLine !== original.line
+ || lastOriginalColumn !== original.column
+ || lastOriginalName !== original.name) {
+ map.addMapping({
+ source: original.source,
+ original: {
+ line: original.line,
+ column: original.column
+ },
+ generated: {
+ line: generated.line,
+ column: generated.column
+ },
+ name: original.name
+ });
+ }
+ lastOriginalSource = original.source;
+ lastOriginalLine = original.line;
+ lastOriginalColumn = original.column;
+ lastOriginalName = original.name;
+ sourceMappingActive = true;
+ } else if (sourceMappingActive) {
+ map.addMapping({
+ generated: {
+ line: generated.line,
+ column: generated.column
+ }
+ });
+ lastOriginalSource = null;
+ sourceMappingActive = false;
+ }
+ for (var idx = 0, length = chunk.length; idx < length; idx++) {
+ if (chunk.charCodeAt(idx) === NEWLINE_CODE) {
+ generated.line++;
+ generated.column = 0;
+ // Mappings end at eol
+ if (idx + 1 === length) {
+ lastOriginalSource = null;
+ sourceMappingActive = false;
+ } else if (sourceMappingActive) {
+ map.addMapping({
+ source: original.source,
+ original: {
+ line: original.line,
+ column: original.column
+ },
+ generated: {
+ line: generated.line,
+ column: generated.column
+ },
+ name: original.name
+ });
+ }
+ } else {
+ generated.column++;
+ }
+ }
+ });
+ this.walkSourceContents(function (sourceFile, sourceContent) {
+ map.setSourceContent(sourceFile, sourceContent);
+ });
+
+ return { code: generated.code, map: map };
+ };
+
+ exports.SourceNode = SourceNode;
+
+ }.call(exports, __webpack_require__, exports, module), __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__));
+
+
+/***/ }
+
+/******/ });
+//# sourceMappingURL=pretty-print-worker.js.map \ No newline at end of file
diff --git a/devtools/client/debugger/new/source-map-worker.js b/devtools/client/debugger/new/source-map-worker.js
new file mode 100644
index 000000000..6b1a55521
--- /dev/null
+++ b/devtools/client/debugger/new/source-map-worker.js
@@ -0,0 +1,5831 @@
+var Debugger =
+/******/ (function(modules) { // webpackBootstrap
+/******/ // The module cache
+/******/ var installedModules = {};
+/******/
+/******/ // The require function
+/******/ function __webpack_require__(moduleId) {
+/******/
+/******/ // Check if module is in cache
+/******/ if(installedModules[moduleId])
+/******/ return installedModules[moduleId].exports;
+/******/
+/******/ // Create a new module (and put it into the cache)
+/******/ var module = installedModules[moduleId] = {
+/******/ exports: {},
+/******/ id: moduleId,
+/******/ loaded: false
+/******/ };
+/******/
+/******/ // Execute the module function
+/******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
+/******/
+/******/ // Flag the module as loaded
+/******/ module.loaded = true;
+/******/
+/******/ // Return the exports of the module
+/******/ return module.exports;
+/******/ }
+/******/
+/******/
+/******/ // expose the modules object (__webpack_modules__)
+/******/ __webpack_require__.m = modules;
+/******/
+/******/ // expose the module cache
+/******/ __webpack_require__.c = installedModules;
+/******/
+/******/ // __webpack_public_path__
+/******/ __webpack_require__.p = "/public/build";
+/******/
+/******/ // Load entry module and return exports
+/******/ return __webpack_require__(0);
+/******/ })
+/************************************************************************/
+/******/ ({
+
+/***/ 0:
+/***/ function(module, exports, __webpack_require__) {
+
+ var _resolveAndFetch = (() => {
+ var _ref = _asyncToGenerator(function* (generatedSource) {
+ // Fetch the sourcemap over the network and create it.
+ var sourceMapURL = _resolveSourceMapURL(generatedSource);
+ var fetched = yield networkRequest(sourceMapURL, { loadFromCache: false });
+
+ // Create the source map and fix it up.
+ var map = new SourceMapConsumer(fetched.content);
+ _setSourceMapRoot(map, sourceMapURL, generatedSource);
+ return map;
+ });
+
+ return function _resolveAndFetch(_x) {
+ return _ref.apply(this, arguments);
+ };
+ })();
+
+ var getOriginalURLs = (() => {
+ var _ref2 = _asyncToGenerator(function* (generatedSource) {
+ var map = yield _fetchSourceMap(generatedSource);
+ return map && map.sources;
+ });
+
+ return function getOriginalURLs(_x2) {
+ return _ref2.apply(this, arguments);
+ };
+ })();
+
+ var getGeneratedLocation = (() => {
+ var _ref3 = _asyncToGenerator(function* (location, originalSource) {
+ if (!isOriginalId(location.sourceId)) {
+ return location;
+ }
+
+ var generatedSourceId = originalToGeneratedId(location.sourceId);
+ var map = yield _getSourceMap(generatedSourceId);
+ if (!map) {
+ return location;
+ }
+
+ var _map$generatedPositio = map.generatedPositionFor({
+ source: originalSource.url,
+ line: location.line,
+ column: location.column == null ? 0 : location.column,
+ bias: SourceMapConsumer.LEAST_UPPER_BOUND
+ });
+
+ var line = _map$generatedPositio.line;
+ var column = _map$generatedPositio.column;
+
+
+ return {
+ sourceId: generatedSourceId,
+ line: line,
+ // Treat 0 as no column so that line breakpoints work correctly.
+ column: column === 0 ? undefined : column
+ };
+ });
+
+ return function getGeneratedLocation(_x3, _x4) {
+ return _ref3.apply(this, arguments);
+ };
+ })();
+
+ var getOriginalLocation = (() => {
+ var _ref4 = _asyncToGenerator(function* (location) {
+ if (!isGeneratedId(location.sourceId)) {
+ return location;
+ }
+
+ var map = yield _getSourceMap(location.sourceId);
+ if (!map) {
+ return location;
+ }
+
+ var _map$originalPosition = map.originalPositionFor({
+ line: location.line,
+ column: location.column == null ? Infinity : location.column
+ });
+
+ var url = _map$originalPosition.source;
+ var line = _map$originalPosition.line;
+ var column = _map$originalPosition.column;
+
+
+ if (url == null) {
+ // No url means the location didn't map.
+ return location;
+ }
+
+ return {
+ sourceId: generatedToOriginalId(location.sourceId, url),
+ line,
+ column
+ };
+ });
+
+ return function getOriginalLocation(_x5) {
+ return _ref4.apply(this, arguments);
+ };
+ })();
+
+ var getOriginalSourceText = (() => {
+ var _ref5 = _asyncToGenerator(function* (originalSource) {
+ assert(isOriginalId(originalSource.id), "Source is not an original source");
+
+ var generatedSourceId = originalToGeneratedId(originalSource.id);
+ var map = yield _getSourceMap(generatedSourceId);
+ if (!map) {
+ return null;
+ }
+
+ var text = map.sourceContentFor(originalSource.url);
+ if (!text) {
+ text = (yield networkRequest(originalSource.url, { loadFromCache: false })).content;
+ }
+
+ return {
+ text,
+ contentType: isJavaScript(originalSource.url || "") ? "text/javascript" : "text/plain"
+ };
+ });
+
+ return function getOriginalSourceText(_x6) {
+ return _ref5.apply(this, arguments);
+ };
+ })();
+
+ function _asyncToGenerator(fn) { return function () { var gen = fn.apply(this, arguments); return new Promise(function (resolve, reject) { function step(key, arg) { try { var info = gen[key](arg); var value = info.value; } catch (error) { reject(error); return; } if (info.done) { resolve(value); } else { return Promise.resolve(value).then(function (value) { step("next", value); }, function (err) { step("throw", err); }); } } return step("next"); }); }; }
+
+ /**
+ * Source Map Worker
+ * @module utils/source-map-worker
+ */
+
+ var networkRequest = __webpack_require__(207);
+
+ var _require = __webpack_require__(293);
+
+ var parse = _require.parse;
+
+ var path = __webpack_require__(278);
+
+ var _require2 = __webpack_require__(472);
+
+ var SourceMapConsumer = _require2.SourceMapConsumer;
+ var SourceMapGenerator = _require2.SourceMapGenerator;
+
+ var _require3 = __webpack_require__(277);
+
+ var isJavaScript = _require3.isJavaScript;
+
+ var assert = __webpack_require__(247);
+
+ var _require4 = __webpack_require__(265);
+
+ var originalToGeneratedId = _require4.originalToGeneratedId;
+ var generatedToOriginalId = _require4.generatedToOriginalId;
+ var isGeneratedId = _require4.isGeneratedId;
+ var isOriginalId = _require4.isOriginalId;
+
+
+ var sourceMapRequests = new Map();
+ var sourceMapsEnabled = false;
+
+ function clearSourceMaps() {
+ sourceMapRequests.clear();
+ }
+
+ function enableSourceMaps() {
+ sourceMapsEnabled = true;
+ }
+
+ function _resolveSourceMapURL(source) {
+ var _source$url = source.url;
+ var url = _source$url === undefined ? "" : _source$url;
+ var _source$sourceMapURL = source.sourceMapURL;
+ var sourceMapURL = _source$sourceMapURL === undefined ? "" : _source$sourceMapURL;
+
+ if (path.isURL(sourceMapURL) || url == "") {
+ // If it's already a full URL or the source doesn't have a URL,
+ // don't resolve anything.
+ return sourceMapURL;
+ } else if (path.isAbsolute(sourceMapURL)) {
+ // If it's an absolute path, it should be resolved relative to the
+ // host of the source.
+ var _parse = parse(url);
+
+ var _parse$protocol = _parse.protocol;
+ var protocol = _parse$protocol === undefined ? "" : _parse$protocol;
+ var _parse$host = _parse.host;
+ var host = _parse$host === undefined ? "" : _parse$host;
+
+ return `${ protocol }//${ host }${ sourceMapURL }`;
+ }
+ // Otherwise, it's a relative path and should be resolved relative
+ // to the source.
+ return path.dirname(url) + "/" + sourceMapURL;
+ }
+
+ /**
+ * Sets the source map's sourceRoot to be relative to the source map url.
+ * @memberof utils/source-map-worker
+ * @static
+ */
+ function _setSourceMapRoot(sourceMap, absSourceMapURL, source) {
+ // No need to do this fiddling if we won't be fetching any sources over the
+ // wire.
+ if (sourceMap.hasContentsOfAllSources()) {
+ return;
+ }
+
+ var base = path.dirname(absSourceMapURL.indexOf("data:") === 0 && source.url ? source.url : absSourceMapURL);
+
+ if (sourceMap.sourceRoot) {
+ sourceMap.sourceRoot = path.join(base, sourceMap.sourceRoot);
+ } else {
+ sourceMap.sourceRoot = base;
+ }
+
+ return sourceMap;
+ }
+
+ function _getSourceMap(generatedSourceId) {
+ return sourceMapRequests.get(generatedSourceId);
+ }
+
+ function _fetchSourceMap(generatedSource) {
+ var existingRequest = sourceMapRequests.get(generatedSource.id);
+ if (existingRequest) {
+ // If it has already been requested, return the request. Make sure
+ // to do this even if sourcemapping is turned off, because
+ // pretty-printing uses sourcemaps.
+ //
+ // An important behavior here is that if it's in the middle of
+ // requesting it, all subsequent calls will block on the initial
+ // request.
+ return existingRequest;
+ } else if (!generatedSource.sourceMapURL || !sourceMapsEnabled) {
+ return Promise.resolve(null);
+ }
+
+ // Fire off the request, set it in the cache, and return it.
+ // Suppress any errors and just return null (ignores bogus
+ // sourcemaps).
+ var req = _resolveAndFetch(generatedSource).catch(() => null);
+ sourceMapRequests.set(generatedSource.id, req);
+ return req;
+ }
+
+ function applySourceMap(generatedId, url, code, mappings) {
+ var generator = new SourceMapGenerator({ file: url });
+ mappings.forEach(mapping => generator.addMapping(mapping));
+ generator.setSourceContent(url, code);
+
+ var map = SourceMapConsumer(generator.toJSON());
+ sourceMapRequests.set(generatedId, Promise.resolve(map));
+ }
+
+ var publicInterface = {
+ getOriginalURLs,
+ getGeneratedLocation,
+ getOriginalLocation,
+ getOriginalSourceText,
+ enableSourceMaps,
+ applySourceMap,
+ clearSourceMaps
+ };
+
+ self.onmessage = function (msg) {
+ var _msg$data = msg.data;
+ var id = _msg$data.id;
+ var method = _msg$data.method;
+ var args = _msg$data.args;
+
+ var response = publicInterface[method].apply(undefined, args);
+ if (response instanceof Promise) {
+ response.then(val => self.postMessage({ id, response: val }), err => self.postMessage({ id, error: err }));
+ } else {
+ self.postMessage({ id, response });
+ }
+ };
+
+/***/ },
+
+/***/ 77:
+/***/ function(module, exports) {
+
+ module.exports = function(module) {
+ if(!module.webpackPolyfill) {
+ module.deprecate = function() {};
+ module.paths = [];
+ // module.parent = undefined by default
+ module.children = [];
+ module.webpackPolyfill = 1;
+ }
+ return module;
+ }
+
+
+/***/ },
+
+/***/ 207:
+/***/ function(module, exports) {
+
+ function networkRequest(url, opts) {
+ return new Promise((resolve, reject) => {
+ var req = new XMLHttpRequest();
+
+ req.addEventListener("readystatechange", () => {
+ if (req.readyState === XMLHttpRequest.DONE) {
+ if (req.status === 200) {
+ resolve({ content: req.responseText });
+ } else {
+ resolve(req.statusText);
+ }
+ }
+ });
+
+ // Not working yet.
+ // if (!opts.loadFromCache) {
+ // req.channel.loadFlags = (
+ // Components.interfaces.nsIRequest.LOAD_BYPASS_CACHE |
+ // Components.interfaces.nsIRequest.INHIBIT_CACHING |
+ // Components.interfaces.nsIRequest.LOAD_ANONYMOUS
+ // );
+ // }
+
+ req.open("GET", url);
+ req.send();
+ });
+ }
+
+ module.exports = networkRequest;
+
+/***/ },
+
+/***/ 244:
+/***/ function(module, exports, __webpack_require__) {
+
+ var _slicedToArray = function () { function sliceIterator(arr, i) { var _arr = []; var _n = true; var _d = false; var _e = undefined; try { for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i["return"]) _i["return"](); } finally { if (_d) throw _e; } } return _arr; } return function (arr, i) { if (Array.isArray(arr)) { return arr; } else if (Symbol.iterator in Object(arr)) { return sliceIterator(arr, i); } else { throw new TypeError("Invalid attempt to destructure non-iterable instance"); } }; }();
+
+ function _toConsumableArray(arr) { if (Array.isArray(arr)) { for (var i = 0, arr2 = Array(arr.length); i < arr.length; i++) { arr2[i] = arr[i]; } return arr2; } else { return Array.from(arr); } }
+
+ /* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+ /* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+ /* 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/. */
+
+ /**
+ * Utils for utils, by utils
+ * @module utils/utils
+ */
+
+ var co = __webpack_require__(245);
+
+ /**
+ * @memberof utils/utils
+ * @static
+ */
+ function asPaused(client, func) {
+ if (client.state != "paused") {
+ return co(function* () {
+ yield client.interrupt();
+ var result = void 0;
+
+ try {
+ result = yield func();
+ } catch (e) {
+ // Try to put the debugger back in a working state by resuming
+ // it
+ yield client.resume();
+ throw e;
+ }
+
+ yield client.resume();
+ return result;
+ });
+ }
+ return func();
+ }
+
+ /**
+ * @memberof utils/utils
+ * @static
+ */
+ function handleError(err) {
+ console.log("ERROR: ", err);
+ }
+
+ /**
+ * @memberof utils/utils
+ * @static
+ */
+ function promisify(context, method) {
+ for (var _len = arguments.length, args = Array(_len > 2 ? _len - 2 : 0), _key = 2; _key < _len; _key++) {
+ args[_key - 2] = arguments[_key];
+ }
+
+ return new Promise((resolve, reject) => {
+ args.push(response => {
+ if (response.error) {
+ reject(response);
+ } else {
+ resolve(response);
+ }
+ });
+ method.apply(context, args);
+ });
+ }
+
+ /**
+ * @memberof utils/utils
+ * @static
+ */
+ function truncateStr(str, size) {
+ if (str.length > size) {
+ return str.slice(0, size) + "...";
+ }
+ return str;
+ }
+
+ /**
+ * @memberof utils/utils
+ * @static
+ */
+ function endTruncateStr(str, size) {
+ if (str.length > size) {
+ return "..." + str.slice(str.length - size);
+ }
+ return str;
+ }
+
+ var msgId = 1;
+ /**
+ * @memberof utils/utils
+ * @static
+ */
+ function workerTask(worker, method) {
+ return function () {
+ for (var _len2 = arguments.length, args = Array(_len2), _key2 = 0; _key2 < _len2; _key2++) {
+ args[_key2] = arguments[_key2];
+ }
+
+ return new Promise((resolve, reject) => {
+ var id = msgId++;
+ worker.postMessage({ id, method, args });
+
+ var listener = (_ref) => {
+ var result = _ref.data;
+
+ if (result.id !== id) {
+ return;
+ }
+
+ worker.removeEventListener("message", listener);
+ if (result.error) {
+ reject(result.error);
+ } else {
+ resolve(result.response);
+ }
+ };
+
+ worker.addEventListener("message", listener);
+ });
+ };
+ }
+
+ /**
+ * Interleaves two arrays element by element, returning the combined array, like
+ * a zip. In the case of arrays with different sizes, undefined values will be
+ * interleaved at the end along with the extra values of the larger array.
+ *
+ * @param Array a
+ * @param Array b
+ * @returns Array
+ * The combined array, in the form [a1, b1, a2, b2, ...]
+ * @memberof utils/utils
+ * @static
+ */
+ function zip(a, b) {
+ if (!b) {
+ return a;
+ }
+ if (!a) {
+ return b;
+ }
+ var pairs = [];
+ for (var i = 0, aLength = a.length, bLength = b.length; i < aLength || i < bLength; i++) {
+ pairs.push([a[i], b[i]]);
+ }
+ return pairs;
+ }
+
+ /**
+ * Converts an object into an array with 2-element arrays as key/value
+ * pairs of the object. `{ foo: 1, bar: 2}` would become
+ * `[[foo, 1], [bar 2]]` (order not guaranteed);
+ *
+ * @returns array
+ * @memberof utils/utils
+ * @static
+ */
+ function entries(obj) {
+ return Object.keys(obj).map(k => [k, obj[k]]);
+ }
+
+ /**
+ * @memberof utils/utils
+ * @static
+ */
+ function mapObject(obj, iteratee) {
+ return toObject(entries(obj).map((_ref2) => {
+ var _ref3 = _slicedToArray(_ref2, 2);
+
+ var key = _ref3[0];
+ var value = _ref3[1];
+
+ return [key, iteratee(key, value)];
+ }));
+ }
+
+ /**
+ * Takes an array of 2-element arrays as key/values pairs and
+ * constructs an object using them.
+ * @memberof utils/utils
+ * @static
+ */
+ function toObject(arr) {
+ var obj = {};
+ for (var pair of arr) {
+ obj[pair[0]] = pair[1];
+ }
+ return obj;
+ }
+
+ /**
+ * Composes the given functions into a single function, which will
+ * apply the results of each function right-to-left, starting with
+ * applying the given arguments to the right-most function.
+ * `compose(foo, bar, baz)` === `args => foo(bar(baz(args)`
+ *
+ * @param ...function funcs
+ * @returns function
+ * @memberof utils/utils
+ * @static
+ */
+ function compose() {
+ for (var _len3 = arguments.length, funcs = Array(_len3), _key3 = 0; _key3 < _len3; _key3++) {
+ funcs[_key3] = arguments[_key3];
+ }
+
+ return function () {
+ for (var _len4 = arguments.length, args = Array(_len4), _key4 = 0; _key4 < _len4; _key4++) {
+ args[_key4] = arguments[_key4];
+ }
+
+ var initialValue = funcs[funcs.length - 1].apply(null, args);
+ var leftFuncs = funcs.slice(0, -1);
+ return leftFuncs.reduceRight((composed, f) => f(composed), initialValue);
+ };
+ }
+
+ /**
+ * @memberof utils/utils
+ * @static
+ */
+ function updateObj(obj, fields) {
+ return Object.assign({}, obj, fields);
+ }
+
+ /**
+ * @memberof utils/utils
+ * @static
+ */
+ function throttle(func, ms) {
+ var timeout = void 0,
+ _this = void 0;
+ return function () {
+ for (var _len5 = arguments.length, args = Array(_len5), _key5 = 0; _key5 < _len5; _key5++) {
+ args[_key5] = arguments[_key5];
+ }
+
+ _this = this;
+ if (!timeout) {
+ timeout = setTimeout(() => {
+ func.apply.apply(func, [_this].concat(_toConsumableArray(args)));
+ timeout = null;
+ }, ms);
+ }
+ };
+ }
+
+ module.exports = {
+ asPaused,
+ handleError,
+ promisify,
+ truncateStr,
+ endTruncateStr,
+ workerTask,
+ zip,
+ entries,
+ toObject,
+ mapObject,
+ compose,
+ updateObj,
+ throttle
+ };
+
+/***/ },
+
+/***/ 245:
+/***/ function(module, exports) {
+
+
+ /**
+ * slice() reference.
+ */
+
+ var slice = Array.prototype.slice;
+
+ /**
+ * Expose `co`.
+ */
+
+ module.exports = co['default'] = co.co = co;
+
+ /**
+ * Wrap the given generator `fn` into a
+ * function that returns a promise.
+ * This is a separate function so that
+ * every `co()` call doesn't create a new,
+ * unnecessary closure.
+ *
+ * @param {GeneratorFunction} fn
+ * @return {Function}
+ * @api public
+ */
+
+ co.wrap = function (fn) {
+ createPromise.__generatorFunction__ = fn;
+ return createPromise;
+ function createPromise() {
+ return co.call(this, fn.apply(this, arguments));
+ }
+ };
+
+ /**
+ * Execute the generator function or a generator
+ * and return a promise.
+ *
+ * @param {Function} fn
+ * @return {Promise}
+ * @api public
+ */
+
+ function co(gen) {
+ var ctx = this;
+ var args = slice.call(arguments, 1)
+
+ // we wrap everything in a promise to avoid promise chaining,
+ // which leads to memory leak errors.
+ // see https://github.com/tj/co/issues/180
+ return new Promise(function(resolve, reject) {
+ if (typeof gen === 'function') gen = gen.apply(ctx, args);
+ if (!gen || typeof gen.next !== 'function') return resolve(gen);
+
+ onFulfilled();
+
+ /**
+ * @param {Mixed} res
+ * @return {Promise}
+ * @api private
+ */
+
+ function onFulfilled(res) {
+ var ret;
+ try {
+ ret = gen.next(res);
+ } catch (e) {
+ return reject(e);
+ }
+ next(ret);
+ }
+
+ /**
+ * @param {Error} err
+ * @return {Promise}
+ * @api private
+ */
+
+ function onRejected(err) {
+ var ret;
+ try {
+ ret = gen.throw(err);
+ } catch (e) {
+ return reject(e);
+ }
+ next(ret);
+ }
+
+ /**
+ * Get the next value in the generator,
+ * return a promise.
+ *
+ * @param {Object} ret
+ * @return {Promise}
+ * @api private
+ */
+
+ function next(ret) {
+ if (ret.done) return resolve(ret.value);
+ var value = toPromise.call(ctx, ret.value);
+ if (value && isPromise(value)) return value.then(onFulfilled, onRejected);
+ return onRejected(new TypeError('You may only yield a function, promise, generator, array, or object, '
+ + 'but the following object was passed: "' + String(ret.value) + '"'));
+ }
+ });
+ }
+
+ /**
+ * Convert a `yield`ed value into a promise.
+ *
+ * @param {Mixed} obj
+ * @return {Promise}
+ * @api private
+ */
+
+ function toPromise(obj) {
+ if (!obj) return obj;
+ if (isPromise(obj)) return obj;
+ if (isGeneratorFunction(obj) || isGenerator(obj)) return co.call(this, obj);
+ if ('function' == typeof obj) return thunkToPromise.call(this, obj);
+ if (Array.isArray(obj)) return arrayToPromise.call(this, obj);
+ if (isObject(obj)) return objectToPromise.call(this, obj);
+ return obj;
+ }
+
+ /**
+ * Convert a thunk to a promise.
+ *
+ * @param {Function}
+ * @return {Promise}
+ * @api private
+ */
+
+ function thunkToPromise(fn) {
+ var ctx = this;
+ return new Promise(function (resolve, reject) {
+ fn.call(ctx, function (err, res) {
+ if (err) return reject(err);
+ if (arguments.length > 2) res = slice.call(arguments, 1);
+ resolve(res);
+ });
+ });
+ }
+
+ /**
+ * Convert an array of "yieldables" to a promise.
+ * Uses `Promise.all()` internally.
+ *
+ * @param {Array} obj
+ * @return {Promise}
+ * @api private
+ */
+
+ function arrayToPromise(obj) {
+ return Promise.all(obj.map(toPromise, this));
+ }
+
+ /**
+ * Convert an object of "yieldables" to a promise.
+ * Uses `Promise.all()` internally.
+ *
+ * @param {Object} obj
+ * @return {Promise}
+ * @api private
+ */
+
+ function objectToPromise(obj){
+ var results = new obj.constructor();
+ var keys = Object.keys(obj);
+ var promises = [];
+ for (var i = 0; i < keys.length; i++) {
+ var key = keys[i];
+ var promise = toPromise.call(this, obj[key]);
+ if (promise && isPromise(promise)) defer(promise, key);
+ else results[key] = obj[key];
+ }
+ return Promise.all(promises).then(function () {
+ return results;
+ });
+
+ function defer(promise, key) {
+ // predefine the key in the result
+ results[key] = undefined;
+ promises.push(promise.then(function (res) {
+ results[key] = res;
+ }));
+ }
+ }
+
+ /**
+ * Check if `obj` is a promise.
+ *
+ * @param {Object} obj
+ * @return {Boolean}
+ * @api private
+ */
+
+ function isPromise(obj) {
+ return 'function' == typeof obj.then;
+ }
+
+ /**
+ * Check if `obj` is a generator.
+ *
+ * @param {Mixed} obj
+ * @return {Boolean}
+ * @api private
+ */
+
+ function isGenerator(obj) {
+ return 'function' == typeof obj.next && 'function' == typeof obj.throw;
+ }
+
+ /**
+ * Check if `obj` is a generator function.
+ *
+ * @param {Mixed} obj
+ * @return {Boolean}
+ * @api private
+ */
+ function isGeneratorFunction(obj) {
+ var constructor = obj.constructor;
+ if (!constructor) return false;
+ if ('GeneratorFunction' === constructor.name || 'GeneratorFunction' === constructor.displayName) return true;
+ return isGenerator(constructor.prototype);
+ }
+
+ /**
+ * Check for plain object.
+ *
+ * @param {Mixed} val
+ * @return {Boolean}
+ * @api private
+ */
+
+ function isObject(val) {
+ return Object == val.constructor;
+ }
+
+
+/***/ },
+
+/***/ 247:
+/***/ function(module, exports) {
+
+ function assert(condition, message) {
+ if (!condition) {
+ throw new Error("Assertion failure: " + message);
+ }
+ }
+
+ module.exports = assert;
+
+/***/ },
+
+/***/ 265:
+/***/ function(module, exports, __webpack_require__) {
+
+ var md5 = __webpack_require__(266);
+
+ function originalToGeneratedId(originalId) {
+ var match = originalId.match(/(.*)\/originalSource/);
+ return match ? match[1] : "";
+ }
+
+ function generatedToOriginalId(generatedId, url) {
+ return generatedId + "/originalSource-" + md5(url);
+ }
+
+ function isOriginalId(id) {
+ return !!id.match(/\/originalSource/);
+ }
+
+ function isGeneratedId(id) {
+ return !isOriginalId(id);
+ }
+
+ module.exports = {
+ originalToGeneratedId, generatedToOriginalId, isOriginalId, isGeneratedId
+ };
+
+/***/ },
+
+/***/ 266:
+/***/ function(module, exports, __webpack_require__) {
+
+ (function(){
+ var crypt = __webpack_require__(267),
+ utf8 = __webpack_require__(268).utf8,
+ isBuffer = __webpack_require__(269),
+ bin = __webpack_require__(268).bin,
+
+ // The core
+ md5 = function (message, options) {
+ // Convert to byte array
+ if (message.constructor == String)
+ if (options && options.encoding === 'binary')
+ message = bin.stringToBytes(message);
+ else
+ message = utf8.stringToBytes(message);
+ else if (isBuffer(message))
+ message = Array.prototype.slice.call(message, 0);
+ else if (!Array.isArray(message))
+ message = message.toString();
+ // else, assume byte array already
+
+ var m = crypt.bytesToWords(message),
+ l = message.length * 8,
+ a = 1732584193,
+ b = -271733879,
+ c = -1732584194,
+ d = 271733878;
+
+ // Swap endian
+ for (var i = 0; i < m.length; i++) {
+ m[i] = ((m[i] << 8) | (m[i] >>> 24)) & 0x00FF00FF |
+ ((m[i] << 24) | (m[i] >>> 8)) & 0xFF00FF00;
+ }
+
+ // Padding
+ m[l >>> 5] |= 0x80 << (l % 32);
+ m[(((l + 64) >>> 9) << 4) + 14] = l;
+
+ // Method shortcuts
+ var FF = md5._ff,
+ GG = md5._gg,
+ HH = md5._hh,
+ II = md5._ii;
+
+ for (var i = 0; i < m.length; i += 16) {
+
+ var aa = a,
+ bb = b,
+ cc = c,
+ dd = d;
+
+ a = FF(a, b, c, d, m[i+ 0], 7, -680876936);
+ d = FF(d, a, b, c, m[i+ 1], 12, -389564586);
+ c = FF(c, d, a, b, m[i+ 2], 17, 606105819);
+ b = FF(b, c, d, a, m[i+ 3], 22, -1044525330);
+ a = FF(a, b, c, d, m[i+ 4], 7, -176418897);
+ d = FF(d, a, b, c, m[i+ 5], 12, 1200080426);
+ c = FF(c, d, a, b, m[i+ 6], 17, -1473231341);
+ b = FF(b, c, d, a, m[i+ 7], 22, -45705983);
+ a = FF(a, b, c, d, m[i+ 8], 7, 1770035416);
+ d = FF(d, a, b, c, m[i+ 9], 12, -1958414417);
+ c = FF(c, d, a, b, m[i+10], 17, -42063);
+ b = FF(b, c, d, a, m[i+11], 22, -1990404162);
+ a = FF(a, b, c, d, m[i+12], 7, 1804603682);
+ d = FF(d, a, b, c, m[i+13], 12, -40341101);
+ c = FF(c, d, a, b, m[i+14], 17, -1502002290);
+ b = FF(b, c, d, a, m[i+15], 22, 1236535329);
+
+ a = GG(a, b, c, d, m[i+ 1], 5, -165796510);
+ d = GG(d, a, b, c, m[i+ 6], 9, -1069501632);
+ c = GG(c, d, a, b, m[i+11], 14, 643717713);
+ b = GG(b, c, d, a, m[i+ 0], 20, -373897302);
+ a = GG(a, b, c, d, m[i+ 5], 5, -701558691);
+ d = GG(d, a, b, c, m[i+10], 9, 38016083);
+ c = GG(c, d, a, b, m[i+15], 14, -660478335);
+ b = GG(b, c, d, a, m[i+ 4], 20, -405537848);
+ a = GG(a, b, c, d, m[i+ 9], 5, 568446438);
+ d = GG(d, a, b, c, m[i+14], 9, -1019803690);
+ c = GG(c, d, a, b, m[i+ 3], 14, -187363961);
+ b = GG(b, c, d, a, m[i+ 8], 20, 1163531501);
+ a = GG(a, b, c, d, m[i+13], 5, -1444681467);
+ d = GG(d, a, b, c, m[i+ 2], 9, -51403784);
+ c = GG(c, d, a, b, m[i+ 7], 14, 1735328473);
+ b = GG(b, c, d, a, m[i+12], 20, -1926607734);
+
+ a = HH(a, b, c, d, m[i+ 5], 4, -378558);
+ d = HH(d, a, b, c, m[i+ 8], 11, -2022574463);
+ c = HH(c, d, a, b, m[i+11], 16, 1839030562);
+ b = HH(b, c, d, a, m[i+14], 23, -35309556);
+ a = HH(a, b, c, d, m[i+ 1], 4, -1530992060);
+ d = HH(d, a, b, c, m[i+ 4], 11, 1272893353);
+ c = HH(c, d, a, b, m[i+ 7], 16, -155497632);
+ b = HH(b, c, d, a, m[i+10], 23, -1094730640);
+ a = HH(a, b, c, d, m[i+13], 4, 681279174);
+ d = HH(d, a, b, c, m[i+ 0], 11, -358537222);
+ c = HH(c, d, a, b, m[i+ 3], 16, -722521979);
+ b = HH(b, c, d, a, m[i+ 6], 23, 76029189);
+ a = HH(a, b, c, d, m[i+ 9], 4, -640364487);
+ d = HH(d, a, b, c, m[i+12], 11, -421815835);
+ c = HH(c, d, a, b, m[i+15], 16, 530742520);
+ b = HH(b, c, d, a, m[i+ 2], 23, -995338651);
+
+ a = II(a, b, c, d, m[i+ 0], 6, -198630844);
+ d = II(d, a, b, c, m[i+ 7], 10, 1126891415);
+ c = II(c, d, a, b, m[i+14], 15, -1416354905);
+ b = II(b, c, d, a, m[i+ 5], 21, -57434055);
+ a = II(a, b, c, d, m[i+12], 6, 1700485571);
+ d = II(d, a, b, c, m[i+ 3], 10, -1894986606);
+ c = II(c, d, a, b, m[i+10], 15, -1051523);
+ b = II(b, c, d, a, m[i+ 1], 21, -2054922799);
+ a = II(a, b, c, d, m[i+ 8], 6, 1873313359);
+ d = II(d, a, b, c, m[i+15], 10, -30611744);
+ c = II(c, d, a, b, m[i+ 6], 15, -1560198380);
+ b = II(b, c, d, a, m[i+13], 21, 1309151649);
+ a = II(a, b, c, d, m[i+ 4], 6, -145523070);
+ d = II(d, a, b, c, m[i+11], 10, -1120210379);
+ c = II(c, d, a, b, m[i+ 2], 15, 718787259);
+ b = II(b, c, d, a, m[i+ 9], 21, -343485551);
+
+ a = (a + aa) >>> 0;
+ b = (b + bb) >>> 0;
+ c = (c + cc) >>> 0;
+ d = (d + dd) >>> 0;
+ }
+
+ return crypt.endian([a, b, c, d]);
+ };
+
+ // Auxiliary functions
+ md5._ff = function (a, b, c, d, x, s, t) {
+ var n = a + (b & c | ~b & d) + (x >>> 0) + t;
+ return ((n << s) | (n >>> (32 - s))) + b;
+ };
+ md5._gg = function (a, b, c, d, x, s, t) {
+ var n = a + (b & d | c & ~d) + (x >>> 0) + t;
+ return ((n << s) | (n >>> (32 - s))) + b;
+ };
+ md5._hh = function (a, b, c, d, x, s, t) {
+ var n = a + (b ^ c ^ d) + (x >>> 0) + t;
+ return ((n << s) | (n >>> (32 - s))) + b;
+ };
+ md5._ii = function (a, b, c, d, x, s, t) {
+ var n = a + (c ^ (b | ~d)) + (x >>> 0) + t;
+ return ((n << s) | (n >>> (32 - s))) + b;
+ };
+
+ // Package private blocksize
+ md5._blocksize = 16;
+ md5._digestsize = 16;
+
+ module.exports = function (message, options) {
+ if (message === undefined || message === null)
+ throw new Error('Illegal argument ' + message);
+
+ var digestbytes = crypt.wordsToBytes(md5(message, options));
+ return options && options.asBytes ? digestbytes :
+ options && options.asString ? bin.bytesToString(digestbytes) :
+ crypt.bytesToHex(digestbytes);
+ };
+
+ })();
+
+
+/***/ },
+
+/***/ 267:
+/***/ function(module, exports) {
+
+ (function() {
+ var base64map
+ = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/',
+
+ crypt = {
+ // Bit-wise rotation left
+ rotl: function(n, b) {
+ return (n << b) | (n >>> (32 - b));
+ },
+
+ // Bit-wise rotation right
+ rotr: function(n, b) {
+ return (n << (32 - b)) | (n >>> b);
+ },
+
+ // Swap big-endian to little-endian and vice versa
+ endian: function(n) {
+ // If number given, swap endian
+ if (n.constructor == Number) {
+ return crypt.rotl(n, 8) & 0x00FF00FF | crypt.rotl(n, 24) & 0xFF00FF00;
+ }
+
+ // Else, assume array and swap all items
+ for (var i = 0; i < n.length; i++)
+ n[i] = crypt.endian(n[i]);
+ return n;
+ },
+
+ // Generate an array of any length of random bytes
+ randomBytes: function(n) {
+ for (var bytes = []; n > 0; n--)
+ bytes.push(Math.floor(Math.random() * 256));
+ return bytes;
+ },
+
+ // Convert a byte array to big-endian 32-bit words
+ bytesToWords: function(bytes) {
+ for (var words = [], i = 0, b = 0; i < bytes.length; i++, b += 8)
+ words[b >>> 5] |= bytes[i] << (24 - b % 32);
+ return words;
+ },
+
+ // Convert big-endian 32-bit words to a byte array
+ wordsToBytes: function(words) {
+ for (var bytes = [], b = 0; b < words.length * 32; b += 8)
+ bytes.push((words[b >>> 5] >>> (24 - b % 32)) & 0xFF);
+ return bytes;
+ },
+
+ // Convert a byte array to a hex string
+ bytesToHex: function(bytes) {
+ for (var hex = [], i = 0; i < bytes.length; i++) {
+ hex.push((bytes[i] >>> 4).toString(16));
+ hex.push((bytes[i] & 0xF).toString(16));
+ }
+ return hex.join('');
+ },
+
+ // Convert a hex string to a byte array
+ hexToBytes: function(hex) {
+ for (var bytes = [], c = 0; c < hex.length; c += 2)
+ bytes.push(parseInt(hex.substr(c, 2), 16));
+ return bytes;
+ },
+
+ // Convert a byte array to a base-64 string
+ bytesToBase64: function(bytes) {
+ for (var base64 = [], i = 0; i < bytes.length; i += 3) {
+ var triplet = (bytes[i] << 16) | (bytes[i + 1] << 8) | bytes[i + 2];
+ for (var j = 0; j < 4; j++)
+ if (i * 8 + j * 6 <= bytes.length * 8)
+ base64.push(base64map.charAt((triplet >>> 6 * (3 - j)) & 0x3F));
+ else
+ base64.push('=');
+ }
+ return base64.join('');
+ },
+
+ // Convert a base-64 string to a byte array
+ base64ToBytes: function(base64) {
+ // Remove non-base-64 characters
+ base64 = base64.replace(/[^A-Z0-9+\/]/ig, '');
+
+ for (var bytes = [], i = 0, imod4 = 0; i < base64.length;
+ imod4 = ++i % 4) {
+ if (imod4 == 0) continue;
+ bytes.push(((base64map.indexOf(base64.charAt(i - 1))
+ & (Math.pow(2, -2 * imod4 + 8) - 1)) << (imod4 * 2))
+ | (base64map.indexOf(base64.charAt(i)) >>> (6 - imod4 * 2)));
+ }
+ return bytes;
+ }
+ };
+
+ module.exports = crypt;
+ })();
+
+
+/***/ },
+
+/***/ 268:
+/***/ function(module, exports) {
+
+ var charenc = {
+ // UTF-8 encoding
+ utf8: {
+ // Convert a string to a byte array
+ stringToBytes: function(str) {
+ return charenc.bin.stringToBytes(unescape(encodeURIComponent(str)));
+ },
+
+ // Convert a byte array to a string
+ bytesToString: function(bytes) {
+ return decodeURIComponent(escape(charenc.bin.bytesToString(bytes)));
+ }
+ },
+
+ // Binary encoding
+ bin: {
+ // Convert a string to a byte array
+ stringToBytes: function(str) {
+ for (var bytes = [], i = 0; i < str.length; i++)
+ bytes.push(str.charCodeAt(i) & 0xFF);
+ return bytes;
+ },
+
+ // Convert a byte array to a string
+ bytesToString: function(bytes) {
+ for (var str = [], i = 0; i < bytes.length; i++)
+ str.push(String.fromCharCode(bytes[i]));
+ return str.join('');
+ }
+ }
+ };
+
+ module.exports = charenc;
+
+
+/***/ },
+
+/***/ 269:
+/***/ function(module, exports) {
+
+ /*!
+ * Determine if an object is a Buffer
+ *
+ * @author Feross Aboukhadijeh <feross@feross.org> <http://feross.org>
+ * @license MIT
+ */
+
+ // The _isBuffer check is for Safari 5-7 support, because it's missing
+ // Object.prototype.constructor. Remove this eventually
+ module.exports = function (obj) {
+ return obj != null && (isBuffer(obj) || isSlowBuffer(obj) || !!obj._isBuffer)
+ }
+
+ function isBuffer (obj) {
+ return !!obj.constructor && typeof obj.constructor.isBuffer === 'function' && obj.constructor.isBuffer(obj)
+ }
+
+ // For Node v0.10 support. Remove this eventually.
+ function isSlowBuffer (obj) {
+ return typeof obj.readFloatLE === 'function' && typeof obj.slice === 'function' && isBuffer(obj.slice(0, 0))
+ }
+
+
+/***/ },
+
+/***/ 277:
+/***/ function(module, exports, __webpack_require__) {
+
+
+
+ /**
+ * Utils for working with Source URLs
+ * @module utils/source
+ */
+
+ var _require = __webpack_require__(244);
+
+ var endTruncateStr = _require.endTruncateStr;
+
+ var _require2 = __webpack_require__(278);
+
+ var basename = _require2.basename;
+
+
+ /**
+ * Trims the query part or reference identifier of a url string, if necessary.
+ *
+ * @memberof utils/source
+ * @static
+ */
+ function trimUrlQuery(url) {
+ var length = url.length;
+ var q1 = url.indexOf("?");
+ var q2 = url.indexOf("&");
+ var q3 = url.indexOf("#");
+ var q = Math.min(q1 != -1 ? q1 : length, q2 != -1 ? q2 : length, q3 != -1 ? q3 : length);
+
+ return url.slice(0, q);
+ }
+
+ /**
+ * Returns true if the specified url and/or content type are specific to
+ * javascript files.
+ *
+ * @return boolean
+ * True if the source is likely javascript.
+ *
+ * @memberof utils/source
+ * @static
+ */
+ function isJavaScript(url) {
+ var contentType = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : "";
+
+ return url && /\.(jsm|js)?$/.test(trimUrlQuery(url)) || contentType.includes("javascript");
+ }
+
+ /**
+ * @memberof utils/source
+ * @static
+ */
+ function isPretty(source) {
+ return source.url ? /formatted$/.test(source.url) : false;
+ }
+
+ /**
+ * Show a source url's filename.
+ * If the source does not have a url, use the source id.
+ *
+ * @memberof utils/source
+ * @static
+ */
+ function getFilename(source) {
+ var url = source.url;
+ var id = source.id;
+
+ if (!url) {
+ var sourceId = id.split("/")[1];
+ return `SOURCE${ sourceId }`;
+ }
+
+ var name = basename(source.url || "") || "(index)";
+ return endTruncateStr(name, 50);
+ }
+
+ module.exports = {
+ isJavaScript,
+ isPretty,
+ getFilename
+ };
+
+/***/ },
+
+/***/ 278:
+/***/ function(module, exports) {
+
+ function basename(path) {
+ return path.split("/").pop();
+ }
+
+ function dirname(path) {
+ var idx = path.lastIndexOf("/");
+ return path.slice(0, idx);
+ }
+
+ function isURL(str) {
+ return str.indexOf("://") !== -1;
+ }
+
+ function isAbsolute(str) {
+ return str[0] === "/";
+ }
+
+ function join(base, dir) {
+ return base + "/" + dir;
+ }
+
+ module.exports = {
+ basename, dirname, isURL, isAbsolute, join
+ };
+
+/***/ },
+
+/***/ 293:
+/***/ function(module, exports, __webpack_require__) {
+
+ // Copyright Joyent, Inc. and other Node contributors.
+ //
+ // Permission is hereby granted, free of charge, to any person obtaining a
+ // copy of this software and associated documentation files (the
+ // "Software"), to deal in the Software without restriction, including
+ // without limitation the rights to use, copy, modify, merge, publish,
+ // distribute, sublicense, and/or sell copies of the Software, and to permit
+ // persons to whom the Software is furnished to do so, subject to the
+ // following conditions:
+ //
+ // The above copyright notice and this permission notice shall be included
+ // in all copies or substantial portions of the Software.
+ //
+ // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+ // OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
+ // NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
+ // DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
+ // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
+ // USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+ var punycode = __webpack_require__(294);
+
+ exports.parse = urlParse;
+ exports.resolve = urlResolve;
+ exports.resolveObject = urlResolveObject;
+ exports.format = urlFormat;
+
+ exports.Url = Url;
+
+ function Url() {
+ this.protocol = null;
+ this.slashes = null;
+ this.auth = null;
+ this.host = null;
+ this.port = null;
+ this.hostname = null;
+ this.hash = null;
+ this.search = null;
+ this.query = null;
+ this.pathname = null;
+ this.path = null;
+ this.href = null;
+ }
+
+ // Reference: RFC 3986, RFC 1808, RFC 2396
+
+ // define these here so at least they only have to be
+ // compiled once on the first module load.
+ var protocolPattern = /^([a-z0-9.+-]+:)/i,
+ portPattern = /:[0-9]*$/,
+
+ // RFC 2396: characters reserved for delimiting URLs.
+ // We actually just auto-escape these.
+ delims = ['<', '>', '"', '`', ' ', '\r', '\n', '\t'],
+
+ // RFC 2396: characters not allowed for various reasons.
+ unwise = ['{', '}', '|', '\\', '^', '`'].concat(delims),
+
+ // Allowed by RFCs, but cause of XSS attacks. Always escape these.
+ autoEscape = ['\''].concat(unwise),
+ // Characters that are never ever allowed in a hostname.
+ // Note that any invalid chars are also handled, but these
+ // are the ones that are *expected* to be seen, so we fast-path
+ // them.
+ nonHostChars = ['%', '/', '?', ';', '#'].concat(autoEscape),
+ hostEndingChars = ['/', '?', '#'],
+ hostnameMaxLen = 255,
+ hostnamePartPattern = /^[a-z0-9A-Z_-]{0,63}$/,
+ hostnamePartStart = /^([a-z0-9A-Z_-]{0,63})(.*)$/,
+ // protocols that can allow "unsafe" and "unwise" chars.
+ unsafeProtocol = {
+ 'javascript': true,
+ 'javascript:': true
+ },
+ // protocols that never have a hostname.
+ hostlessProtocol = {
+ 'javascript': true,
+ 'javascript:': true
+ },
+ // protocols that always contain a // bit.
+ slashedProtocol = {
+ 'http': true,
+ 'https': true,
+ 'ftp': true,
+ 'gopher': true,
+ 'file': true,
+ 'http:': true,
+ 'https:': true,
+ 'ftp:': true,
+ 'gopher:': true,
+ 'file:': true
+ },
+ querystring = __webpack_require__(295);
+
+ function urlParse(url, parseQueryString, slashesDenoteHost) {
+ if (url && isObject(url) && url instanceof Url) return url;
+
+ var u = new Url;
+ u.parse(url, parseQueryString, slashesDenoteHost);
+ return u;
+ }
+
+ Url.prototype.parse = function(url, parseQueryString, slashesDenoteHost) {
+ if (!isString(url)) {
+ throw new TypeError("Parameter 'url' must be a string, not " + typeof url);
+ }
+
+ var rest = url;
+
+ // trim before proceeding.
+ // This is to support parse stuff like " http://foo.com \n"
+ rest = rest.trim();
+
+ var proto = protocolPattern.exec(rest);
+ if (proto) {
+ proto = proto[0];
+ var lowerProto = proto.toLowerCase();
+ this.protocol = lowerProto;
+ rest = rest.substr(proto.length);
+ }
+
+ // figure out if it's got a host
+ // user@server is *always* interpreted as a hostname, and url
+ // resolution will treat //foo/bar as host=foo,path=bar because that's
+ // how the browser resolves relative URLs.
+ if (slashesDenoteHost || proto || rest.match(/^\/\/[^@\/]+@[^@\/]+/)) {
+ var slashes = rest.substr(0, 2) === '//';
+ if (slashes && !(proto && hostlessProtocol[proto])) {
+ rest = rest.substr(2);
+ this.slashes = true;
+ }
+ }
+
+ if (!hostlessProtocol[proto] &&
+ (slashes || (proto && !slashedProtocol[proto]))) {
+
+ // there's a hostname.
+ // the first instance of /, ?, ;, or # ends the host.
+ //
+ // If there is an @ in the hostname, then non-host chars *are* allowed
+ // to the left of the last @ sign, unless some host-ending character
+ // comes *before* the @-sign.
+ // URLs are obnoxious.
+ //
+ // ex:
+ // http://a@b@c/ => user:a@b host:c
+ // http://a@b?@c => user:a host:c path:/?@c
+
+ // v0.12 TODO(isaacs): This is not quite how Chrome does things.
+ // Review our test case against browsers more comprehensively.
+
+ // find the first instance of any hostEndingChars
+ var hostEnd = -1;
+ for (var i = 0; i < hostEndingChars.length; i++) {
+ var hec = rest.indexOf(hostEndingChars[i]);
+ if (hec !== -1 && (hostEnd === -1 || hec < hostEnd))
+ hostEnd = hec;
+ }
+
+ // at this point, either we have an explicit point where the
+ // auth portion cannot go past, or the last @ char is the decider.
+ var auth, atSign;
+ if (hostEnd === -1) {
+ // atSign can be anywhere.
+ atSign = rest.lastIndexOf('@');
+ } else {
+ // atSign must be in auth portion.
+ // http://a@b/c@d => host:b auth:a path:/c@d
+ atSign = rest.lastIndexOf('@', hostEnd);
+ }
+
+ // Now we have a portion which is definitely the auth.
+ // Pull that off.
+ if (atSign !== -1) {
+ auth = rest.slice(0, atSign);
+ rest = rest.slice(atSign + 1);
+ this.auth = decodeURIComponent(auth);
+ }
+
+ // the host is the remaining to the left of the first non-host char
+ hostEnd = -1;
+ for (var i = 0; i < nonHostChars.length; i++) {
+ var hec = rest.indexOf(nonHostChars[i]);
+ if (hec !== -1 && (hostEnd === -1 || hec < hostEnd))
+ hostEnd = hec;
+ }
+ // if we still have not hit it, then the entire thing is a host.
+ if (hostEnd === -1)
+ hostEnd = rest.length;
+
+ this.host = rest.slice(0, hostEnd);
+ rest = rest.slice(hostEnd);
+
+ // pull out port.
+ this.parseHost();
+
+ // we've indicated that there is a hostname,
+ // so even if it's empty, it has to be present.
+ this.hostname = this.hostname || '';
+
+ // if hostname begins with [ and ends with ]
+ // assume that it's an IPv6 address.
+ var ipv6Hostname = this.hostname[0] === '[' &&
+ this.hostname[this.hostname.length - 1] === ']';
+
+ // validate a little.
+ if (!ipv6Hostname) {
+ var hostparts = this.hostname.split(/\./);
+ for (var i = 0, l = hostparts.length; i < l; i++) {
+ var part = hostparts[i];
+ if (!part) continue;
+ if (!part.match(hostnamePartPattern)) {
+ var newpart = '';
+ for (var j = 0, k = part.length; j < k; j++) {
+ if (part.charCodeAt(j) > 127) {
+ // we replace non-ASCII char with a temporary placeholder
+ // we need this to make sure size of hostname is not
+ // broken by replacing non-ASCII by nothing
+ newpart += 'x';
+ } else {
+ newpart += part[j];
+ }
+ }
+ // we test again with ASCII char only
+ if (!newpart.match(hostnamePartPattern)) {
+ var validParts = hostparts.slice(0, i);
+ var notHost = hostparts.slice(i + 1);
+ var bit = part.match(hostnamePartStart);
+ if (bit) {
+ validParts.push(bit[1]);
+ notHost.unshift(bit[2]);
+ }
+ if (notHost.length) {
+ rest = '/' + notHost.join('.') + rest;
+ }
+ this.hostname = validParts.join('.');
+ break;
+ }
+ }
+ }
+ }
+
+ if (this.hostname.length > hostnameMaxLen) {
+ this.hostname = '';
+ } else {
+ // hostnames are always lower case.
+ this.hostname = this.hostname.toLowerCase();
+ }
+
+ if (!ipv6Hostname) {
+ // IDNA Support: Returns a puny coded representation of "domain".
+ // It only converts the part of the domain name that
+ // has non ASCII characters. I.e. it dosent matter if
+ // you call it with a domain that already is in ASCII.
+ var domainArray = this.hostname.split('.');
+ var newOut = [];
+ for (var i = 0; i < domainArray.length; ++i) {
+ var s = domainArray[i];
+ newOut.push(s.match(/[^A-Za-z0-9_-]/) ?
+ 'xn--' + punycode.encode(s) : s);
+ }
+ this.hostname = newOut.join('.');
+ }
+
+ var p = this.port ? ':' + this.port : '';
+ var h = this.hostname || '';
+ this.host = h + p;
+ this.href += this.host;
+
+ // strip [ and ] from the hostname
+ // the host field still retains them, though
+ if (ipv6Hostname) {
+ this.hostname = this.hostname.substr(1, this.hostname.length - 2);
+ if (rest[0] !== '/') {
+ rest = '/' + rest;
+ }
+ }
+ }
+
+ // now rest is set to the post-host stuff.
+ // chop off any delim chars.
+ if (!unsafeProtocol[lowerProto]) {
+
+ // First, make 100% sure that any "autoEscape" chars get
+ // escaped, even if encodeURIComponent doesn't think they
+ // need to be.
+ for (var i = 0, l = autoEscape.length; i < l; i++) {
+ var ae = autoEscape[i];
+ var esc = encodeURIComponent(ae);
+ if (esc === ae) {
+ esc = escape(ae);
+ }
+ rest = rest.split(ae).join(esc);
+ }
+ }
+
+
+ // chop off from the tail first.
+ var hash = rest.indexOf('#');
+ if (hash !== -1) {
+ // got a fragment string.
+ this.hash = rest.substr(hash);
+ rest = rest.slice(0, hash);
+ }
+ var qm = rest.indexOf('?');
+ if (qm !== -1) {
+ this.search = rest.substr(qm);
+ this.query = rest.substr(qm + 1);
+ if (parseQueryString) {
+ this.query = querystring.parse(this.query);
+ }
+ rest = rest.slice(0, qm);
+ } else if (parseQueryString) {
+ // no query string, but parseQueryString still requested
+ this.search = '';
+ this.query = {};
+ }
+ if (rest) this.pathname = rest;
+ if (slashedProtocol[lowerProto] &&
+ this.hostname && !this.pathname) {
+ this.pathname = '/';
+ }
+
+ //to support http.request
+ if (this.pathname || this.search) {
+ var p = this.pathname || '';
+ var s = this.search || '';
+ this.path = p + s;
+ }
+
+ // finally, reconstruct the href based on what has been validated.
+ this.href = this.format();
+ return this;
+ };
+
+ // format a parsed object into a url string
+ function urlFormat(obj) {
+ // ensure it's an object, and not a string url.
+ // If it's an obj, this is a no-op.
+ // this way, you can call url_format() on strings
+ // to clean up potentially wonky urls.
+ if (isString(obj)) obj = urlParse(obj);
+ if (!(obj instanceof Url)) return Url.prototype.format.call(obj);
+ return obj.format();
+ }
+
+ Url.prototype.format = function() {
+ var auth = this.auth || '';
+ if (auth) {
+ auth = encodeURIComponent(auth);
+ auth = auth.replace(/%3A/i, ':');
+ auth += '@';
+ }
+
+ var protocol = this.protocol || '',
+ pathname = this.pathname || '',
+ hash = this.hash || '',
+ host = false,
+ query = '';
+
+ if (this.host) {
+ host = auth + this.host;
+ } else if (this.hostname) {
+ host = auth + (this.hostname.indexOf(':') === -1 ?
+ this.hostname :
+ '[' + this.hostname + ']');
+ if (this.port) {
+ host += ':' + this.port;
+ }
+ }
+
+ if (this.query &&
+ isObject(this.query) &&
+ Object.keys(this.query).length) {
+ query = querystring.stringify(this.query);
+ }
+
+ var search = this.search || (query && ('?' + query)) || '';
+
+ if (protocol && protocol.substr(-1) !== ':') protocol += ':';
+
+ // only the slashedProtocols get the //. Not mailto:, xmpp:, etc.
+ // unless they had them to begin with.
+ if (this.slashes ||
+ (!protocol || slashedProtocol[protocol]) && host !== false) {
+ host = '//' + (host || '');
+ if (pathname && pathname.charAt(0) !== '/') pathname = '/' + pathname;
+ } else if (!host) {
+ host = '';
+ }
+
+ if (hash && hash.charAt(0) !== '#') hash = '#' + hash;
+ if (search && search.charAt(0) !== '?') search = '?' + search;
+
+ pathname = pathname.replace(/[?#]/g, function(match) {
+ return encodeURIComponent(match);
+ });
+ search = search.replace('#', '%23');
+
+ return protocol + host + pathname + search + hash;
+ };
+
+ function urlResolve(source, relative) {
+ return urlParse(source, false, true).resolve(relative);
+ }
+
+ Url.prototype.resolve = function(relative) {
+ return this.resolveObject(urlParse(relative, false, true)).format();
+ };
+
+ function urlResolveObject(source, relative) {
+ if (!source) return relative;
+ return urlParse(source, false, true).resolveObject(relative);
+ }
+
+ Url.prototype.resolveObject = function(relative) {
+ if (isString(relative)) {
+ var rel = new Url();
+ rel.parse(relative, false, true);
+ relative = rel;
+ }
+
+ var result = new Url();
+ Object.keys(this).forEach(function(k) {
+ result[k] = this[k];
+ }, this);
+
+ // hash is always overridden, no matter what.
+ // even href="" will remove it.
+ result.hash = relative.hash;
+
+ // if the relative url is empty, then there's nothing left to do here.
+ if (relative.href === '') {
+ result.href = result.format();
+ return result;
+ }
+
+ // hrefs like //foo/bar always cut to the protocol.
+ if (relative.slashes && !relative.protocol) {
+ // take everything except the protocol from relative
+ Object.keys(relative).forEach(function(k) {
+ if (k !== 'protocol')
+ result[k] = relative[k];
+ });
+
+ //urlParse appends trailing / to urls like http://www.example.com
+ if (slashedProtocol[result.protocol] &&
+ result.hostname && !result.pathname) {
+ result.path = result.pathname = '/';
+ }
+
+ result.href = result.format();
+ return result;
+ }
+
+ if (relative.protocol && relative.protocol !== result.protocol) {
+ // if it's a known url protocol, then changing
+ // the protocol does weird things
+ // first, if it's not file:, then we MUST have a host,
+ // and if there was a path
+ // to begin with, then we MUST have a path.
+ // if it is file:, then the host is dropped,
+ // because that's known to be hostless.
+ // anything else is assumed to be absolute.
+ if (!slashedProtocol[relative.protocol]) {
+ Object.keys(relative).forEach(function(k) {
+ result[k] = relative[k];
+ });
+ result.href = result.format();
+ return result;
+ }
+
+ result.protocol = relative.protocol;
+ if (!relative.host && !hostlessProtocol[relative.protocol]) {
+ var relPath = (relative.pathname || '').split('/');
+ while (relPath.length && !(relative.host = relPath.shift()));
+ if (!relative.host) relative.host = '';
+ if (!relative.hostname) relative.hostname = '';
+ if (relPath[0] !== '') relPath.unshift('');
+ if (relPath.length < 2) relPath.unshift('');
+ result.pathname = relPath.join('/');
+ } else {
+ result.pathname = relative.pathname;
+ }
+ result.search = relative.search;
+ result.query = relative.query;
+ result.host = relative.host || '';
+ result.auth = relative.auth;
+ result.hostname = relative.hostname || relative.host;
+ result.port = relative.port;
+ // to support http.request
+ if (result.pathname || result.search) {
+ var p = result.pathname || '';
+ var s = result.search || '';
+ result.path = p + s;
+ }
+ result.slashes = result.slashes || relative.slashes;
+ result.href = result.format();
+ return result;
+ }
+
+ var isSourceAbs = (result.pathname && result.pathname.charAt(0) === '/'),
+ isRelAbs = (
+ relative.host ||
+ relative.pathname && relative.pathname.charAt(0) === '/'
+ ),
+ mustEndAbs = (isRelAbs || isSourceAbs ||
+ (result.host && relative.pathname)),
+ removeAllDots = mustEndAbs,
+ srcPath = result.pathname && result.pathname.split('/') || [],
+ relPath = relative.pathname && relative.pathname.split('/') || [],
+ psychotic = result.protocol && !slashedProtocol[result.protocol];
+
+ // if the url is a non-slashed url, then relative
+ // links like ../.. should be able
+ // to crawl up to the hostname, as well. This is strange.
+ // result.protocol has already been set by now.
+ // Later on, put the first path part into the host field.
+ if (psychotic) {
+ result.hostname = '';
+ result.port = null;
+ if (result.host) {
+ if (srcPath[0] === '') srcPath[0] = result.host;
+ else srcPath.unshift(result.host);
+ }
+ result.host = '';
+ if (relative.protocol) {
+ relative.hostname = null;
+ relative.port = null;
+ if (relative.host) {
+ if (relPath[0] === '') relPath[0] = relative.host;
+ else relPath.unshift(relative.host);
+ }
+ relative.host = null;
+ }
+ mustEndAbs = mustEndAbs && (relPath[0] === '' || srcPath[0] === '');
+ }
+
+ if (isRelAbs) {
+ // it's absolute.
+ result.host = (relative.host || relative.host === '') ?
+ relative.host : result.host;
+ result.hostname = (relative.hostname || relative.hostname === '') ?
+ relative.hostname : result.hostname;
+ result.search = relative.search;
+ result.query = relative.query;
+ srcPath = relPath;
+ // fall through to the dot-handling below.
+ } else if (relPath.length) {
+ // it's relative
+ // throw away the existing file, and take the new path instead.
+ if (!srcPath) srcPath = [];
+ srcPath.pop();
+ srcPath = srcPath.concat(relPath);
+ result.search = relative.search;
+ result.query = relative.query;
+ } else if (!isNullOrUndefined(relative.search)) {
+ // just pull out the search.
+ // like href='?foo'.
+ // Put this after the other two cases because it simplifies the booleans
+ if (psychotic) {
+ result.hostname = result.host = srcPath.shift();
+ //occationaly the auth can get stuck only in host
+ //this especialy happens in cases like
+ //url.resolveObject('mailto:local1@domain1', 'local2@domain2')
+ var authInHost = result.host && result.host.indexOf('@') > 0 ?
+ result.host.split('@') : false;
+ if (authInHost) {
+ result.auth = authInHost.shift();
+ result.host = result.hostname = authInHost.shift();
+ }
+ }
+ result.search = relative.search;
+ result.query = relative.query;
+ //to support http.request
+ if (!isNull(result.pathname) || !isNull(result.search)) {
+ result.path = (result.pathname ? result.pathname : '') +
+ (result.search ? result.search : '');
+ }
+ result.href = result.format();
+ return result;
+ }
+
+ if (!srcPath.length) {
+ // no path at all. easy.
+ // we've already handled the other stuff above.
+ result.pathname = null;
+ //to support http.request
+ if (result.search) {
+ result.path = '/' + result.search;
+ } else {
+ result.path = null;
+ }
+ result.href = result.format();
+ return result;
+ }
+
+ // if a url ENDs in . or .., then it must get a trailing slash.
+ // however, if it ends in anything else non-slashy,
+ // then it must NOT get a trailing slash.
+ var last = srcPath.slice(-1)[0];
+ var hasTrailingSlash = (
+ (result.host || relative.host) && (last === '.' || last === '..') ||
+ last === '');
+
+ // strip single dots, resolve double dots to parent dir
+ // if the path tries to go above the root, `up` ends up > 0
+ var up = 0;
+ for (var i = srcPath.length; i >= 0; i--) {
+ last = srcPath[i];
+ if (last == '.') {
+ srcPath.splice(i, 1);
+ } else if (last === '..') {
+ srcPath.splice(i, 1);
+ up++;
+ } else if (up) {
+ srcPath.splice(i, 1);
+ up--;
+ }
+ }
+
+ // if the path is allowed to go above the root, restore leading ..s
+ if (!mustEndAbs && !removeAllDots) {
+ for (; up--; up) {
+ srcPath.unshift('..');
+ }
+ }
+
+ if (mustEndAbs && srcPath[0] !== '' &&
+ (!srcPath[0] || srcPath[0].charAt(0) !== '/')) {
+ srcPath.unshift('');
+ }
+
+ if (hasTrailingSlash && (srcPath.join('/').substr(-1) !== '/')) {
+ srcPath.push('');
+ }
+
+ var isAbsolute = srcPath[0] === '' ||
+ (srcPath[0] && srcPath[0].charAt(0) === '/');
+
+ // put the host back
+ if (psychotic) {
+ result.hostname = result.host = isAbsolute ? '' :
+ srcPath.length ? srcPath.shift() : '';
+ //occationaly the auth can get stuck only in host
+ //this especialy happens in cases like
+ //url.resolveObject('mailto:local1@domain1', 'local2@domain2')
+ var authInHost = result.host && result.host.indexOf('@') > 0 ?
+ result.host.split('@') : false;
+ if (authInHost) {
+ result.auth = authInHost.shift();
+ result.host = result.hostname = authInHost.shift();
+ }
+ }
+
+ mustEndAbs = mustEndAbs || (result.host && srcPath.length);
+
+ if (mustEndAbs && !isAbsolute) {
+ srcPath.unshift('');
+ }
+
+ if (!srcPath.length) {
+ result.pathname = null;
+ result.path = null;
+ } else {
+ result.pathname = srcPath.join('/');
+ }
+
+ //to support request.http
+ if (!isNull(result.pathname) || !isNull(result.search)) {
+ result.path = (result.pathname ? result.pathname : '') +
+ (result.search ? result.search : '');
+ }
+ result.auth = relative.auth || result.auth;
+ result.slashes = result.slashes || relative.slashes;
+ result.href = result.format();
+ return result;
+ };
+
+ Url.prototype.parseHost = function() {
+ var host = this.host;
+ var port = portPattern.exec(host);
+ if (port) {
+ port = port[0];
+ if (port !== ':') {
+ this.port = port.substr(1);
+ }
+ host = host.substr(0, host.length - port.length);
+ }
+ if (host) this.hostname = host;
+ };
+
+ function isString(arg) {
+ return typeof arg === "string";
+ }
+
+ function isObject(arg) {
+ return typeof arg === 'object' && arg !== null;
+ }
+
+ function isNull(arg) {
+ return arg === null;
+ }
+ function isNullOrUndefined(arg) {
+ return arg == null;
+ }
+
+
+/***/ },
+
+/***/ 294:
+/***/ function(module, exports, __webpack_require__) {
+
+ var __WEBPACK_AMD_DEFINE_RESULT__;/* WEBPACK VAR INJECTION */(function(module, global) {/*! https://mths.be/punycode v1.3.2 by @mathias */
+ ;(function(root) {
+
+ /** Detect free variables */
+ var freeExports = typeof exports == 'object' && exports &&
+ !exports.nodeType && exports;
+ var freeModule = typeof module == 'object' && module &&
+ !module.nodeType && module;
+ var freeGlobal = typeof global == 'object' && global;
+ if (
+ freeGlobal.global === freeGlobal ||
+ freeGlobal.window === freeGlobal ||
+ freeGlobal.self === freeGlobal
+ ) {
+ root = freeGlobal;
+ }
+
+ /**
+ * The `punycode` object.
+ * @name punycode
+ * @type Object
+ */
+ var punycode,
+
+ /** Highest positive signed 32-bit float value */
+ maxInt = 2147483647, // aka. 0x7FFFFFFF or 2^31-1
+
+ /** Bootstring parameters */
+ base = 36,
+ tMin = 1,
+ tMax = 26,
+ skew = 38,
+ damp = 700,
+ initialBias = 72,
+ initialN = 128, // 0x80
+ delimiter = '-', // '\x2D'
+
+ /** Regular expressions */
+ regexPunycode = /^xn--/,
+ regexNonASCII = /[^\x20-\x7E]/, // unprintable ASCII chars + non-ASCII chars
+ regexSeparators = /[\x2E\u3002\uFF0E\uFF61]/g, // RFC 3490 separators
+
+ /** Error messages */
+ errors = {
+ 'overflow': 'Overflow: input needs wider integers to process',
+ 'not-basic': 'Illegal input >= 0x80 (not a basic code point)',
+ 'invalid-input': 'Invalid input'
+ },
+
+ /** Convenience shortcuts */
+ baseMinusTMin = base - tMin,
+ floor = Math.floor,
+ stringFromCharCode = String.fromCharCode,
+
+ /** Temporary variable */
+ key;
+
+ /*--------------------------------------------------------------------------*/
+
+ /**
+ * A generic error utility function.
+ * @private
+ * @param {String} type The error type.
+ * @returns {Error} Throws a `RangeError` with the applicable error message.
+ */
+ function error(type) {
+ throw RangeError(errors[type]);
+ }
+
+ /**
+ * A generic `Array#map` utility function.
+ * @private
+ * @param {Array} array The array to iterate over.
+ * @param {Function} callback The function that gets called for every array
+ * item.
+ * @returns {Array} A new array of values returned by the callback function.
+ */
+ function map(array, fn) {
+ var length = array.length;
+ var result = [];
+ while (length--) {
+ result[length] = fn(array[length]);
+ }
+ return result;
+ }
+
+ /**
+ * A simple `Array#map`-like wrapper to work with domain name strings or email
+ * addresses.
+ * @private
+ * @param {String} domain The domain name or email address.
+ * @param {Function} callback The function that gets called for every
+ * character.
+ * @returns {Array} A new string of characters returned by the callback
+ * function.
+ */
+ function mapDomain(string, fn) {
+ var parts = string.split('@');
+ var result = '';
+ if (parts.length > 1) {
+ // In email addresses, only the domain name should be punycoded. Leave
+ // the local part (i.e. everything up to `@`) intact.
+ result = parts[0] + '@';
+ string = parts[1];
+ }
+ // Avoid `split(regex)` for IE8 compatibility. See #17.
+ string = string.replace(regexSeparators, '\x2E');
+ var labels = string.split('.');
+ var encoded = map(labels, fn).join('.');
+ return result + encoded;
+ }
+
+ /**
+ * Creates an array containing the numeric code points of each Unicode
+ * character in the string. While JavaScript uses UCS-2 internally,
+ * this function will convert a pair of surrogate halves (each of which
+ * UCS-2 exposes as separate characters) into a single code point,
+ * matching UTF-16.
+ * @see `punycode.ucs2.encode`
+ * @see <https://mathiasbynens.be/notes/javascript-encoding>
+ * @memberOf punycode.ucs2
+ * @name decode
+ * @param {String} string The Unicode input string (UCS-2).
+ * @returns {Array} The new array of code points.
+ */
+ function ucs2decode(string) {
+ var output = [],
+ counter = 0,
+ length = string.length,
+ value,
+ extra;
+ while (counter < length) {
+ value = string.charCodeAt(counter++);
+ if (value >= 0xD800 && value <= 0xDBFF && counter < length) {
+ // high surrogate, and there is a next character
+ extra = string.charCodeAt(counter++);
+ if ((extra & 0xFC00) == 0xDC00) { // low surrogate
+ output.push(((value & 0x3FF) << 10) + (extra & 0x3FF) + 0x10000);
+ } else {
+ // unmatched surrogate; only append this code unit, in case the next
+ // code unit is the high surrogate of a surrogate pair
+ output.push(value);
+ counter--;
+ }
+ } else {
+ output.push(value);
+ }
+ }
+ return output;
+ }
+
+ /**
+ * Creates a string based on an array of numeric code points.
+ * @see `punycode.ucs2.decode`
+ * @memberOf punycode.ucs2
+ * @name encode
+ * @param {Array} codePoints The array of numeric code points.
+ * @returns {String} The new Unicode string (UCS-2).
+ */
+ function ucs2encode(array) {
+ return map(array, function(value) {
+ var output = '';
+ if (value > 0xFFFF) {
+ value -= 0x10000;
+ output += stringFromCharCode(value >>> 10 & 0x3FF | 0xD800);
+ value = 0xDC00 | value & 0x3FF;
+ }
+ output += stringFromCharCode(value);
+ return output;
+ }).join('');
+ }
+
+ /**
+ * Converts a basic code point into a digit/integer.
+ * @see `digitToBasic()`
+ * @private
+ * @param {Number} codePoint The basic numeric code point value.
+ * @returns {Number} The numeric value of a basic code point (for use in
+ * representing integers) in the range `0` to `base - 1`, or `base` if
+ * the code point does not represent a value.
+ */
+ function basicToDigit(codePoint) {
+ if (codePoint - 48 < 10) {
+ return codePoint - 22;
+ }
+ if (codePoint - 65 < 26) {
+ return codePoint - 65;
+ }
+ if (codePoint - 97 < 26) {
+ return codePoint - 97;
+ }
+ return base;
+ }
+
+ /**
+ * Converts a digit/integer into a basic code point.
+ * @see `basicToDigit()`
+ * @private
+ * @param {Number} digit The numeric value of a basic code point.
+ * @returns {Number} The basic code point whose value (when used for
+ * representing integers) is `digit`, which needs to be in the range
+ * `0` to `base - 1`. If `flag` is non-zero, the uppercase form is
+ * used; else, the lowercase form is used. The behavior is undefined
+ * if `flag` is non-zero and `digit` has no uppercase form.
+ */
+ function digitToBasic(digit, flag) {
+ // 0..25 map to ASCII a..z or A..Z
+ // 26..35 map to ASCII 0..9
+ return digit + 22 + 75 * (digit < 26) - ((flag != 0) << 5);
+ }
+
+ /**
+ * Bias adaptation function as per section 3.4 of RFC 3492.
+ * http://tools.ietf.org/html/rfc3492#section-3.4
+ * @private
+ */
+ function adapt(delta, numPoints, firstTime) {
+ var k = 0;
+ delta = firstTime ? floor(delta / damp) : delta >> 1;
+ delta += floor(delta / numPoints);
+ for (/* no initialization */; delta > baseMinusTMin * tMax >> 1; k += base) {
+ delta = floor(delta / baseMinusTMin);
+ }
+ return floor(k + (baseMinusTMin + 1) * delta / (delta + skew));
+ }
+
+ /**
+ * Converts a Punycode string of ASCII-only symbols to a string of Unicode
+ * symbols.
+ * @memberOf punycode
+ * @param {String} input The Punycode string of ASCII-only symbols.
+ * @returns {String} The resulting string of Unicode symbols.
+ */
+ function decode(input) {
+ // Don't use UCS-2
+ var output = [],
+ inputLength = input.length,
+ out,
+ i = 0,
+ n = initialN,
+ bias = initialBias,
+ basic,
+ j,
+ index,
+ oldi,
+ w,
+ k,
+ digit,
+ t,
+ /** Cached calculation results */
+ baseMinusT;
+
+ // Handle the basic code points: let `basic` be the number of input code
+ // points before the last delimiter, or `0` if there is none, then copy
+ // the first basic code points to the output.
+
+ basic = input.lastIndexOf(delimiter);
+ if (basic < 0) {
+ basic = 0;
+ }
+
+ for (j = 0; j < basic; ++j) {
+ // if it's not a basic code point
+ if (input.charCodeAt(j) >= 0x80) {
+ error('not-basic');
+ }
+ output.push(input.charCodeAt(j));
+ }
+
+ // Main decoding loop: start just after the last delimiter if any basic code
+ // points were copied; start at the beginning otherwise.
+
+ for (index = basic > 0 ? basic + 1 : 0; index < inputLength; /* no final expression */) {
+
+ // `index` is the index of the next character to be consumed.
+ // Decode a generalized variable-length integer into `delta`,
+ // which gets added to `i`. The overflow checking is easier
+ // if we increase `i` as we go, then subtract off its starting
+ // value at the end to obtain `delta`.
+ for (oldi = i, w = 1, k = base; /* no condition */; k += base) {
+
+ if (index >= inputLength) {
+ error('invalid-input');
+ }
+
+ digit = basicToDigit(input.charCodeAt(index++));
+
+ if (digit >= base || digit > floor((maxInt - i) / w)) {
+ error('overflow');
+ }
+
+ i += digit * w;
+ t = k <= bias ? tMin : (k >= bias + tMax ? tMax : k - bias);
+
+ if (digit < t) {
+ break;
+ }
+
+ baseMinusT = base - t;
+ if (w > floor(maxInt / baseMinusT)) {
+ error('overflow');
+ }
+
+ w *= baseMinusT;
+
+ }
+
+ out = output.length + 1;
+ bias = adapt(i - oldi, out, oldi == 0);
+
+ // `i` was supposed to wrap around from `out` to `0`,
+ // incrementing `n` each time, so we'll fix that now:
+ if (floor(i / out) > maxInt - n) {
+ error('overflow');
+ }
+
+ n += floor(i / out);
+ i %= out;
+
+ // Insert `n` at position `i` of the output
+ output.splice(i++, 0, n);
+
+ }
+
+ return ucs2encode(output);
+ }
+
+ /**
+ * Converts a string of Unicode symbols (e.g. a domain name label) to a
+ * Punycode string of ASCII-only symbols.
+ * @memberOf punycode
+ * @param {String} input The string of Unicode symbols.
+ * @returns {String} The resulting Punycode string of ASCII-only symbols.
+ */
+ function encode(input) {
+ var n,
+ delta,
+ handledCPCount,
+ basicLength,
+ bias,
+ j,
+ m,
+ q,
+ k,
+ t,
+ currentValue,
+ output = [],
+ /** `inputLength` will hold the number of code points in `input`. */
+ inputLength,
+ /** Cached calculation results */
+ handledCPCountPlusOne,
+ baseMinusT,
+ qMinusT;
+
+ // Convert the input in UCS-2 to Unicode
+ input = ucs2decode(input);
+
+ // Cache the length
+ inputLength = input.length;
+
+ // Initialize the state
+ n = initialN;
+ delta = 0;
+ bias = initialBias;
+
+ // Handle the basic code points
+ for (j = 0; j < inputLength; ++j) {
+ currentValue = input[j];
+ if (currentValue < 0x80) {
+ output.push(stringFromCharCode(currentValue));
+ }
+ }
+
+ handledCPCount = basicLength = output.length;
+
+ // `handledCPCount` is the number of code points that have been handled;
+ // `basicLength` is the number of basic code points.
+
+ // Finish the basic string - if it is not empty - with a delimiter
+ if (basicLength) {
+ output.push(delimiter);
+ }
+
+ // Main encoding loop:
+ while (handledCPCount < inputLength) {
+
+ // All non-basic code points < n have been handled already. Find the next
+ // larger one:
+ for (m = maxInt, j = 0; j < inputLength; ++j) {
+ currentValue = input[j];
+ if (currentValue >= n && currentValue < m) {
+ m = currentValue;
+ }
+ }
+
+ // Increase `delta` enough to advance the decoder's <n,i> state to <m,0>,
+ // but guard against overflow
+ handledCPCountPlusOne = handledCPCount + 1;
+ if (m - n > floor((maxInt - delta) / handledCPCountPlusOne)) {
+ error('overflow');
+ }
+
+ delta += (m - n) * handledCPCountPlusOne;
+ n = m;
+
+ for (j = 0; j < inputLength; ++j) {
+ currentValue = input[j];
+
+ if (currentValue < n && ++delta > maxInt) {
+ error('overflow');
+ }
+
+ if (currentValue == n) {
+ // Represent delta as a generalized variable-length integer
+ for (q = delta, k = base; /* no condition */; k += base) {
+ t = k <= bias ? tMin : (k >= bias + tMax ? tMax : k - bias);
+ if (q < t) {
+ break;
+ }
+ qMinusT = q - t;
+ baseMinusT = base - t;
+ output.push(
+ stringFromCharCode(digitToBasic(t + qMinusT % baseMinusT, 0))
+ );
+ q = floor(qMinusT / baseMinusT);
+ }
+
+ output.push(stringFromCharCode(digitToBasic(q, 0)));
+ bias = adapt(delta, handledCPCountPlusOne, handledCPCount == basicLength);
+ delta = 0;
+ ++handledCPCount;
+ }
+ }
+
+ ++delta;
+ ++n;
+
+ }
+ return output.join('');
+ }
+
+ /**
+ * Converts a Punycode string representing a domain name or an email address
+ * to Unicode. Only the Punycoded parts of the input will be converted, i.e.
+ * it doesn't matter if you call it on a string that has already been
+ * converted to Unicode.
+ * @memberOf punycode
+ * @param {String} input The Punycoded domain name or email address to
+ * convert to Unicode.
+ * @returns {String} The Unicode representation of the given Punycode
+ * string.
+ */
+ function toUnicode(input) {
+ return mapDomain(input, function(string) {
+ return regexPunycode.test(string)
+ ? decode(string.slice(4).toLowerCase())
+ : string;
+ });
+ }
+
+ /**
+ * Converts a Unicode string representing a domain name or an email address to
+ * Punycode. Only the non-ASCII parts of the domain name will be converted,
+ * i.e. it doesn't matter if you call it with a domain that's already in
+ * ASCII.
+ * @memberOf punycode
+ * @param {String} input The domain name or email address to convert, as a
+ * Unicode string.
+ * @returns {String} The Punycode representation of the given domain name or
+ * email address.
+ */
+ function toASCII(input) {
+ return mapDomain(input, function(string) {
+ return regexNonASCII.test(string)
+ ? 'xn--' + encode(string)
+ : string;
+ });
+ }
+
+ /*--------------------------------------------------------------------------*/
+
+ /** Define the public API */
+ punycode = {
+ /**
+ * A string representing the current Punycode.js version number.
+ * @memberOf punycode
+ * @type String
+ */
+ 'version': '1.3.2',
+ /**
+ * An object of methods to convert from JavaScript's internal character
+ * representation (UCS-2) to Unicode code points, and back.
+ * @see <https://mathiasbynens.be/notes/javascript-encoding>
+ * @memberOf punycode
+ * @type Object
+ */
+ 'ucs2': {
+ 'decode': ucs2decode,
+ 'encode': ucs2encode
+ },
+ 'decode': decode,
+ 'encode': encode,
+ 'toASCII': toASCII,
+ 'toUnicode': toUnicode
+ };
+
+ /** Expose `punycode` */
+ // Some AMD build optimizers, like r.js, check for specific condition patterns
+ // like the following:
+ if (
+ true
+ ) {
+ !(__WEBPACK_AMD_DEFINE_RESULT__ = function() {
+ return punycode;
+ }.call(exports, __webpack_require__, exports, module), __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__));
+ } else if (freeExports && freeModule) {
+ if (module.exports == freeExports) { // in Node.js or RingoJS v0.8.0+
+ freeModule.exports = punycode;
+ } else { // in Narwhal or RingoJS v0.7.0-
+ for (key in punycode) {
+ punycode.hasOwnProperty(key) && (freeExports[key] = punycode[key]);
+ }
+ }
+ } else { // in Rhino or a web browser
+ root.punycode = punycode;
+ }
+
+ }(this));
+
+ /* WEBPACK VAR INJECTION */}.call(exports, __webpack_require__(77)(module), (function() { return this; }())))
+
+/***/ },
+
+/***/ 295:
+/***/ function(module, exports, __webpack_require__) {
+
+ 'use strict';
+
+ exports.decode = exports.parse = __webpack_require__(296);
+ exports.encode = exports.stringify = __webpack_require__(297);
+
+
+/***/ },
+
+/***/ 296:
+/***/ function(module, exports) {
+
+ // Copyright Joyent, Inc. and other Node contributors.
+ //
+ // Permission is hereby granted, free of charge, to any person obtaining a
+ // copy of this software and associated documentation files (the
+ // "Software"), to deal in the Software without restriction, including
+ // without limitation the rights to use, copy, modify, merge, publish,
+ // distribute, sublicense, and/or sell copies of the Software, and to permit
+ // persons to whom the Software is furnished to do so, subject to the
+ // following conditions:
+ //
+ // The above copyright notice and this permission notice shall be included
+ // in all copies or substantial portions of the Software.
+ //
+ // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+ // OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
+ // NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
+ // DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
+ // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
+ // USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+ 'use strict';
+
+ // If obj.hasOwnProperty has been overridden, then calling
+ // obj.hasOwnProperty(prop) will break.
+ // See: https://github.com/joyent/node/issues/1707
+ function hasOwnProperty(obj, prop) {
+ return Object.prototype.hasOwnProperty.call(obj, prop);
+ }
+
+ module.exports = function(qs, sep, eq, options) {
+ sep = sep || '&';
+ eq = eq || '=';
+ var obj = {};
+
+ if (typeof qs !== 'string' || qs.length === 0) {
+ return obj;
+ }
+
+ var regexp = /\+/g;
+ qs = qs.split(sep);
+
+ var maxKeys = 1000;
+ if (options && typeof options.maxKeys === 'number') {
+ maxKeys = options.maxKeys;
+ }
+
+ var len = qs.length;
+ // maxKeys <= 0 means that we should not limit keys count
+ if (maxKeys > 0 && len > maxKeys) {
+ len = maxKeys;
+ }
+
+ for (var i = 0; i < len; ++i) {
+ var x = qs[i].replace(regexp, '%20'),
+ idx = x.indexOf(eq),
+ kstr, vstr, k, v;
+
+ if (idx >= 0) {
+ kstr = x.substr(0, idx);
+ vstr = x.substr(idx + 1);
+ } else {
+ kstr = x;
+ vstr = '';
+ }
+
+ k = decodeURIComponent(kstr);
+ v = decodeURIComponent(vstr);
+
+ if (!hasOwnProperty(obj, k)) {
+ obj[k] = v;
+ } else if (Array.isArray(obj[k])) {
+ obj[k].push(v);
+ } else {
+ obj[k] = [obj[k], v];
+ }
+ }
+
+ return obj;
+ };
+
+
+/***/ },
+
+/***/ 297:
+/***/ function(module, exports) {
+
+ // Copyright Joyent, Inc. and other Node contributors.
+ //
+ // Permission is hereby granted, free of charge, to any person obtaining a
+ // copy of this software and associated documentation files (the
+ // "Software"), to deal in the Software without restriction, including
+ // without limitation the rights to use, copy, modify, merge, publish,
+ // distribute, sublicense, and/or sell copies of the Software, and to permit
+ // persons to whom the Software is furnished to do so, subject to the
+ // following conditions:
+ //
+ // The above copyright notice and this permission notice shall be included
+ // in all copies or substantial portions of the Software.
+ //
+ // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+ // OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
+ // NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
+ // DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
+ // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
+ // USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+ 'use strict';
+
+ var stringifyPrimitive = function(v) {
+ switch (typeof v) {
+ case 'string':
+ return v;
+
+ case 'boolean':
+ return v ? 'true' : 'false';
+
+ case 'number':
+ return isFinite(v) ? v : '';
+
+ default:
+ return '';
+ }
+ };
+
+ module.exports = function(obj, sep, eq, name) {
+ sep = sep || '&';
+ eq = eq || '=';
+ if (obj === null) {
+ obj = undefined;
+ }
+
+ if (typeof obj === 'object') {
+ return Object.keys(obj).map(function(k) {
+ var ks = encodeURIComponent(stringifyPrimitive(k)) + eq;
+ if (Array.isArray(obj[k])) {
+ return obj[k].map(function(v) {
+ return ks + encodeURIComponent(stringifyPrimitive(v));
+ }).join(sep);
+ } else {
+ return ks + encodeURIComponent(stringifyPrimitive(obj[k]));
+ }
+ }).join(sep);
+
+ }
+
+ if (!name) return '';
+ return encodeURIComponent(stringifyPrimitive(name)) + eq +
+ encodeURIComponent(stringifyPrimitive(obj));
+ };
+
+
+/***/ },
+
+/***/ 472:
+/***/ function(module, exports, __webpack_require__) {
+
+ /*
+ * Copyright 2009-2011 Mozilla Foundation and contributors
+ * Licensed under the New BSD license. See LICENSE.txt or:
+ * http://opensource.org/licenses/BSD-3-Clause
+ */
+ exports.SourceMapGenerator = __webpack_require__(473).SourceMapGenerator;
+ exports.SourceMapConsumer = __webpack_require__(479).SourceMapConsumer;
+ exports.SourceNode = __webpack_require__(482).SourceNode;
+
+
+/***/ },
+
+/***/ 473:
+/***/ function(module, exports, __webpack_require__) {
+
+ /* -*- Mode: js; js-indent-level: 2; -*- */
+ /*
+ * Copyright 2011 Mozilla Foundation and contributors
+ * Licensed under the New BSD license. See LICENSE or:
+ * http://opensource.org/licenses/BSD-3-Clause
+ */
+
+ var base64VLQ = __webpack_require__(474);
+ var util = __webpack_require__(476);
+ var ArraySet = __webpack_require__(477).ArraySet;
+ var MappingList = __webpack_require__(478).MappingList;
+
+ /**
+ * An instance of the SourceMapGenerator represents a source map which is
+ * being built incrementally. You may pass an object with the following
+ * properties:
+ *
+ * - file: The filename of the generated source.
+ * - sourceRoot: A root for all relative URLs in this source map.
+ */
+ function SourceMapGenerator(aArgs) {
+ if (!aArgs) {
+ aArgs = {};
+ }
+ this._file = util.getArg(aArgs, 'file', null);
+ this._sourceRoot = util.getArg(aArgs, 'sourceRoot', null);
+ this._skipValidation = util.getArg(aArgs, 'skipValidation', false);
+ this._sources = new ArraySet();
+ this._names = new ArraySet();
+ this._mappings = new MappingList();
+ this._sourcesContents = null;
+ }
+
+ SourceMapGenerator.prototype._version = 3;
+
+ /**
+ * Creates a new SourceMapGenerator based on a SourceMapConsumer
+ *
+ * @param aSourceMapConsumer The SourceMap.
+ */
+ SourceMapGenerator.fromSourceMap =
+ function SourceMapGenerator_fromSourceMap(aSourceMapConsumer) {
+ var sourceRoot = aSourceMapConsumer.sourceRoot;
+ var generator = new SourceMapGenerator({
+ file: aSourceMapConsumer.file,
+ sourceRoot: sourceRoot
+ });
+ aSourceMapConsumer.eachMapping(function (mapping) {
+ var newMapping = {
+ generated: {
+ line: mapping.generatedLine,
+ column: mapping.generatedColumn
+ }
+ };
+
+ if (mapping.source != null) {
+ newMapping.source = mapping.source;
+ if (sourceRoot != null) {
+ newMapping.source = util.relative(sourceRoot, newMapping.source);
+ }
+
+ newMapping.original = {
+ line: mapping.originalLine,
+ column: mapping.originalColumn
+ };
+
+ if (mapping.name != null) {
+ newMapping.name = mapping.name;
+ }
+ }
+
+ generator.addMapping(newMapping);
+ });
+ aSourceMapConsumer.sources.forEach(function (sourceFile) {
+ var content = aSourceMapConsumer.sourceContentFor(sourceFile);
+ if (content != null) {
+ generator.setSourceContent(sourceFile, content);
+ }
+ });
+ return generator;
+ };
+
+ /**
+ * Add a single mapping from original source line and column to the generated
+ * source's line and column for this source map being created. The mapping
+ * object should have the following properties:
+ *
+ * - generated: An object with the generated line and column positions.
+ * - original: An object with the original line and column positions.
+ * - source: The original source file (relative to the sourceRoot).
+ * - name: An optional original token name for this mapping.
+ */
+ SourceMapGenerator.prototype.addMapping =
+ function SourceMapGenerator_addMapping(aArgs) {
+ var generated = util.getArg(aArgs, 'generated');
+ var original = util.getArg(aArgs, 'original', null);
+ var source = util.getArg(aArgs, 'source', null);
+ var name = util.getArg(aArgs, 'name', null);
+
+ if (!this._skipValidation) {
+ this._validateMapping(generated, original, source, name);
+ }
+
+ if (source != null) {
+ source = String(source);
+ if (!this._sources.has(source)) {
+ this._sources.add(source);
+ }
+ }
+
+ if (name != null) {
+ name = String(name);
+ if (!this._names.has(name)) {
+ this._names.add(name);
+ }
+ }
+
+ this._mappings.add({
+ generatedLine: generated.line,
+ generatedColumn: generated.column,
+ originalLine: original != null && original.line,
+ originalColumn: original != null && original.column,
+ source: source,
+ name: name
+ });
+ };
+
+ /**
+ * Set the source content for a source file.
+ */
+ SourceMapGenerator.prototype.setSourceContent =
+ function SourceMapGenerator_setSourceContent(aSourceFile, aSourceContent) {
+ var source = aSourceFile;
+ if (this._sourceRoot != null) {
+ source = util.relative(this._sourceRoot, source);
+ }
+
+ if (aSourceContent != null) {
+ // Add the source content to the _sourcesContents map.
+ // Create a new _sourcesContents map if the property is null.
+ if (!this._sourcesContents) {
+ this._sourcesContents = Object.create(null);
+ }
+ this._sourcesContents[util.toSetString(source)] = aSourceContent;
+ } else if (this._sourcesContents) {
+ // Remove the source file from the _sourcesContents map.
+ // If the _sourcesContents map is empty, set the property to null.
+ delete this._sourcesContents[util.toSetString(source)];
+ if (Object.keys(this._sourcesContents).length === 0) {
+ this._sourcesContents = null;
+ }
+ }
+ };
+
+ /**
+ * Applies the mappings of a sub-source-map for a specific source file to the
+ * source map being generated. Each mapping to the supplied source file is
+ * rewritten using the supplied source map. Note: The resolution for the
+ * resulting mappings is the minimium of this map and the supplied map.
+ *
+ * @param aSourceMapConsumer The source map to be applied.
+ * @param aSourceFile Optional. The filename of the source file.
+ * If omitted, SourceMapConsumer's file property will be used.
+ * @param aSourceMapPath Optional. The dirname of the path to the source map
+ * to be applied. If relative, it is relative to the SourceMapConsumer.
+ * This parameter is needed when the two source maps aren't in the same
+ * directory, and the source map to be applied contains relative source
+ * paths. If so, those relative source paths need to be rewritten
+ * relative to the SourceMapGenerator.
+ */
+ SourceMapGenerator.prototype.applySourceMap =
+ function SourceMapGenerator_applySourceMap(aSourceMapConsumer, aSourceFile, aSourceMapPath) {
+ var sourceFile = aSourceFile;
+ // If aSourceFile is omitted, we will use the file property of the SourceMap
+ if (aSourceFile == null) {
+ if (aSourceMapConsumer.file == null) {
+ throw new Error(
+ 'SourceMapGenerator.prototype.applySourceMap requires either an explicit source file, ' +
+ 'or the source map\'s "file" property. Both were omitted.'
+ );
+ }
+ sourceFile = aSourceMapConsumer.file;
+ }
+ var sourceRoot = this._sourceRoot;
+ // Make "sourceFile" relative if an absolute Url is passed.
+ if (sourceRoot != null) {
+ sourceFile = util.relative(sourceRoot, sourceFile);
+ }
+ // Applying the SourceMap can add and remove items from the sources and
+ // the names array.
+ var newSources = new ArraySet();
+ var newNames = new ArraySet();
+
+ // Find mappings for the "sourceFile"
+ this._mappings.unsortedForEach(function (mapping) {
+ if (mapping.source === sourceFile && mapping.originalLine != null) {
+ // Check if it can be mapped by the source map, then update the mapping.
+ var original = aSourceMapConsumer.originalPositionFor({
+ line: mapping.originalLine,
+ column: mapping.originalColumn
+ });
+ if (original.source != null) {
+ // Copy mapping
+ mapping.source = original.source;
+ if (aSourceMapPath != null) {
+ mapping.source = util.join(aSourceMapPath, mapping.source)
+ }
+ if (sourceRoot != null) {
+ mapping.source = util.relative(sourceRoot, mapping.source);
+ }
+ mapping.originalLine = original.line;
+ mapping.originalColumn = original.column;
+ if (original.name != null) {
+ mapping.name = original.name;
+ }
+ }
+ }
+
+ var source = mapping.source;
+ if (source != null && !newSources.has(source)) {
+ newSources.add(source);
+ }
+
+ var name = mapping.name;
+ if (name != null && !newNames.has(name)) {
+ newNames.add(name);
+ }
+
+ }, this);
+ this._sources = newSources;
+ this._names = newNames;
+
+ // Copy sourcesContents of applied map.
+ aSourceMapConsumer.sources.forEach(function (sourceFile) {
+ var content = aSourceMapConsumer.sourceContentFor(sourceFile);
+ if (content != null) {
+ if (aSourceMapPath != null) {
+ sourceFile = util.join(aSourceMapPath, sourceFile);
+ }
+ if (sourceRoot != null) {
+ sourceFile = util.relative(sourceRoot, sourceFile);
+ }
+ this.setSourceContent(sourceFile, content);
+ }
+ }, this);
+ };
+
+ /**
+ * A mapping can have one of the three levels of data:
+ *
+ * 1. Just the generated position.
+ * 2. The Generated position, original position, and original source.
+ * 3. Generated and original position, original source, as well as a name
+ * token.
+ *
+ * To maintain consistency, we validate that any new mapping being added falls
+ * in to one of these categories.
+ */
+ SourceMapGenerator.prototype._validateMapping =
+ function SourceMapGenerator_validateMapping(aGenerated, aOriginal, aSource,
+ aName) {
+ if (aGenerated && 'line' in aGenerated && 'column' in aGenerated
+ && aGenerated.line > 0 && aGenerated.column >= 0
+ && !aOriginal && !aSource && !aName) {
+ // Case 1.
+ return;
+ }
+ else if (aGenerated && 'line' in aGenerated && 'column' in aGenerated
+ && aOriginal && 'line' in aOriginal && 'column' in aOriginal
+ && aGenerated.line > 0 && aGenerated.column >= 0
+ && aOriginal.line > 0 && aOriginal.column >= 0
+ && aSource) {
+ // Cases 2 and 3.
+ return;
+ }
+ else {
+ throw new Error('Invalid mapping: ' + JSON.stringify({
+ generated: aGenerated,
+ source: aSource,
+ original: aOriginal,
+ name: aName
+ }));
+ }
+ };
+
+ /**
+ * Serialize the accumulated mappings in to the stream of base 64 VLQs
+ * specified by the source map format.
+ */
+ SourceMapGenerator.prototype._serializeMappings =
+ function SourceMapGenerator_serializeMappings() {
+ var previousGeneratedColumn = 0;
+ var previousGeneratedLine = 1;
+ var previousOriginalColumn = 0;
+ var previousOriginalLine = 0;
+ var previousName = 0;
+ var previousSource = 0;
+ var result = '';
+ var next;
+ var mapping;
+ var nameIdx;
+ var sourceIdx;
+
+ var mappings = this._mappings.toArray();
+ for (var i = 0, len = mappings.length; i < len; i++) {
+ mapping = mappings[i];
+ next = ''
+
+ if (mapping.generatedLine !== previousGeneratedLine) {
+ previousGeneratedColumn = 0;
+ while (mapping.generatedLine !== previousGeneratedLine) {
+ next += ';';
+ previousGeneratedLine++;
+ }
+ }
+ else {
+ if (i > 0) {
+ if (!util.compareByGeneratedPositionsInflated(mapping, mappings[i - 1])) {
+ continue;
+ }
+ next += ',';
+ }
+ }
+
+ next += base64VLQ.encode(mapping.generatedColumn
+ - previousGeneratedColumn);
+ previousGeneratedColumn = mapping.generatedColumn;
+
+ if (mapping.source != null) {
+ sourceIdx = this._sources.indexOf(mapping.source);
+ next += base64VLQ.encode(sourceIdx - previousSource);
+ previousSource = sourceIdx;
+
+ // lines are stored 0-based in SourceMap spec version 3
+ next += base64VLQ.encode(mapping.originalLine - 1
+ - previousOriginalLine);
+ previousOriginalLine = mapping.originalLine - 1;
+
+ next += base64VLQ.encode(mapping.originalColumn
+ - previousOriginalColumn);
+ previousOriginalColumn = mapping.originalColumn;
+
+ if (mapping.name != null) {
+ nameIdx = this._names.indexOf(mapping.name);
+ next += base64VLQ.encode(nameIdx - previousName);
+ previousName = nameIdx;
+ }
+ }
+
+ result += next;
+ }
+
+ return result;
+ };
+
+ SourceMapGenerator.prototype._generateSourcesContent =
+ function SourceMapGenerator_generateSourcesContent(aSources, aSourceRoot) {
+ return aSources.map(function (source) {
+ if (!this._sourcesContents) {
+ return null;
+ }
+ if (aSourceRoot != null) {
+ source = util.relative(aSourceRoot, source);
+ }
+ var key = util.toSetString(source);
+ return Object.prototype.hasOwnProperty.call(this._sourcesContents, key)
+ ? this._sourcesContents[key]
+ : null;
+ }, this);
+ };
+
+ /**
+ * Externalize the source map.
+ */
+ SourceMapGenerator.prototype.toJSON =
+ function SourceMapGenerator_toJSON() {
+ var map = {
+ version: this._version,
+ sources: this._sources.toArray(),
+ names: this._names.toArray(),
+ mappings: this._serializeMappings()
+ };
+ if (this._file != null) {
+ map.file = this._file;
+ }
+ if (this._sourceRoot != null) {
+ map.sourceRoot = this._sourceRoot;
+ }
+ if (this._sourcesContents) {
+ map.sourcesContent = this._generateSourcesContent(map.sources, map.sourceRoot);
+ }
+
+ return map;
+ };
+
+ /**
+ * Render the source map being generated to a string.
+ */
+ SourceMapGenerator.prototype.toString =
+ function SourceMapGenerator_toString() {
+ return JSON.stringify(this.toJSON());
+ };
+
+ exports.SourceMapGenerator = SourceMapGenerator;
+
+
+/***/ },
+
+/***/ 474:
+/***/ function(module, exports, __webpack_require__) {
+
+ /* -*- Mode: js; js-indent-level: 2; -*- */
+ /*
+ * Copyright 2011 Mozilla Foundation and contributors
+ * Licensed under the New BSD license. See LICENSE or:
+ * http://opensource.org/licenses/BSD-3-Clause
+ *
+ * Based on the Base 64 VLQ implementation in Closure Compiler:
+ * https://code.google.com/p/closure-compiler/source/browse/trunk/src/com/google/debugging/sourcemap/Base64VLQ.java
+ *
+ * Copyright 2011 The Closure Compiler Authors. All rights reserved.
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ * * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+ var base64 = __webpack_require__(475);
+
+ // A single base 64 digit can contain 6 bits of data. For the base 64 variable
+ // length quantities we use in the source map spec, the first bit is the sign,
+ // the next four bits are the actual value, and the 6th bit is the
+ // continuation bit. The continuation bit tells us whether there are more
+ // digits in this value following this digit.
+ //
+ // Continuation
+ // | Sign
+ // | |
+ // V V
+ // 101011
+
+ var VLQ_BASE_SHIFT = 5;
+
+ // binary: 100000
+ var VLQ_BASE = 1 << VLQ_BASE_SHIFT;
+
+ // binary: 011111
+ var VLQ_BASE_MASK = VLQ_BASE - 1;
+
+ // binary: 100000
+ var VLQ_CONTINUATION_BIT = VLQ_BASE;
+
+ /**
+ * Converts from a two-complement value to a value where the sign bit is
+ * placed in the least significant bit. For example, as decimals:
+ * 1 becomes 2 (10 binary), -1 becomes 3 (11 binary)
+ * 2 becomes 4 (100 binary), -2 becomes 5 (101 binary)
+ */
+ function toVLQSigned(aValue) {
+ return aValue < 0
+ ? ((-aValue) << 1) + 1
+ : (aValue << 1) + 0;
+ }
+
+ /**
+ * Converts to a two-complement value from a value where the sign bit is
+ * placed in the least significant bit. For example, as decimals:
+ * 2 (10 binary) becomes 1, 3 (11 binary) becomes -1
+ * 4 (100 binary) becomes 2, 5 (101 binary) becomes -2
+ */
+ function fromVLQSigned(aValue) {
+ var isNegative = (aValue & 1) === 1;
+ var shifted = aValue >> 1;
+ return isNegative
+ ? -shifted
+ : shifted;
+ }
+
+ /**
+ * Returns the base 64 VLQ encoded value.
+ */
+ exports.encode = function base64VLQ_encode(aValue) {
+ var encoded = "";
+ var digit;
+
+ var vlq = toVLQSigned(aValue);
+
+ do {
+ digit = vlq & VLQ_BASE_MASK;
+ vlq >>>= VLQ_BASE_SHIFT;
+ if (vlq > 0) {
+ // There are still more digits in this value, so we must make sure the
+ // continuation bit is marked.
+ digit |= VLQ_CONTINUATION_BIT;
+ }
+ encoded += base64.encode(digit);
+ } while (vlq > 0);
+
+ return encoded;
+ };
+
+ /**
+ * Decodes the next base 64 VLQ value from the given string and returns the
+ * value and the rest of the string via the out parameter.
+ */
+ exports.decode = function base64VLQ_decode(aStr, aIndex, aOutParam) {
+ var strLen = aStr.length;
+ var result = 0;
+ var shift = 0;
+ var continuation, digit;
+
+ do {
+ if (aIndex >= strLen) {
+ throw new Error("Expected more digits in base 64 VLQ value.");
+ }
+
+ digit = base64.decode(aStr.charCodeAt(aIndex++));
+ if (digit === -1) {
+ throw new Error("Invalid base64 digit: " + aStr.charAt(aIndex - 1));
+ }
+
+ continuation = !!(digit & VLQ_CONTINUATION_BIT);
+ digit &= VLQ_BASE_MASK;
+ result = result + (digit << shift);
+ shift += VLQ_BASE_SHIFT;
+ } while (continuation);
+
+ aOutParam.value = fromVLQSigned(result);
+ aOutParam.rest = aIndex;
+ };
+
+
+/***/ },
+
+/***/ 475:
+/***/ function(module, exports) {
+
+ /* -*- Mode: js; js-indent-level: 2; -*- */
+ /*
+ * Copyright 2011 Mozilla Foundation and contributors
+ * Licensed under the New BSD license. See LICENSE or:
+ * http://opensource.org/licenses/BSD-3-Clause
+ */
+
+ var intToCharMap = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'.split('');
+
+ /**
+ * Encode an integer in the range of 0 to 63 to a single base 64 digit.
+ */
+ exports.encode = function (number) {
+ if (0 <= number && number < intToCharMap.length) {
+ return intToCharMap[number];
+ }
+ throw new TypeError("Must be between 0 and 63: " + number);
+ };
+
+ /**
+ * Decode a single base 64 character code digit to an integer. Returns -1 on
+ * failure.
+ */
+ exports.decode = function (charCode) {
+ var bigA = 65; // 'A'
+ var bigZ = 90; // 'Z'
+
+ var littleA = 97; // 'a'
+ var littleZ = 122; // 'z'
+
+ var zero = 48; // '0'
+ var nine = 57; // '9'
+
+ var plus = 43; // '+'
+ var slash = 47; // '/'
+
+ var littleOffset = 26;
+ var numberOffset = 52;
+
+ // 0 - 25: ABCDEFGHIJKLMNOPQRSTUVWXYZ
+ if (bigA <= charCode && charCode <= bigZ) {
+ return (charCode - bigA);
+ }
+
+ // 26 - 51: abcdefghijklmnopqrstuvwxyz
+ if (littleA <= charCode && charCode <= littleZ) {
+ return (charCode - littleA + littleOffset);
+ }
+
+ // 52 - 61: 0123456789
+ if (zero <= charCode && charCode <= nine) {
+ return (charCode - zero + numberOffset);
+ }
+
+ // 62: +
+ if (charCode == plus) {
+ return 62;
+ }
+
+ // 63: /
+ if (charCode == slash) {
+ return 63;
+ }
+
+ // Invalid base64 digit.
+ return -1;
+ };
+
+
+/***/ },
+
+/***/ 476:
+/***/ function(module, exports) {
+
+ /* -*- Mode: js; js-indent-level: 2; -*- */
+ /*
+ * Copyright 2011 Mozilla Foundation and contributors
+ * Licensed under the New BSD license. See LICENSE or:
+ * http://opensource.org/licenses/BSD-3-Clause
+ */
+
+ /**
+ * This is a helper function for getting values from parameter/options
+ * objects.
+ *
+ * @param args The object we are extracting values from
+ * @param name The name of the property we are getting.
+ * @param defaultValue An optional value to return if the property is missing
+ * from the object. If this is not specified and the property is missing, an
+ * error will be thrown.
+ */
+ function getArg(aArgs, aName, aDefaultValue) {
+ if (aName in aArgs) {
+ return aArgs[aName];
+ } else if (arguments.length === 3) {
+ return aDefaultValue;
+ } else {
+ throw new Error('"' + aName + '" is a required argument.');
+ }
+ }
+ exports.getArg = getArg;
+
+ var urlRegexp = /^(?:([\w+\-.]+):)?\/\/(?:(\w+:\w+)@)?([\w.]*)(?::(\d+))?(\S*)$/;
+ var dataUrlRegexp = /^data:.+\,.+$/;
+
+ function urlParse(aUrl) {
+ var match = aUrl.match(urlRegexp);
+ if (!match) {
+ return null;
+ }
+ return {
+ scheme: match[1],
+ auth: match[2],
+ host: match[3],
+ port: match[4],
+ path: match[5]
+ };
+ }
+ exports.urlParse = urlParse;
+
+ function urlGenerate(aParsedUrl) {
+ var url = '';
+ if (aParsedUrl.scheme) {
+ url += aParsedUrl.scheme + ':';
+ }
+ url += '//';
+ if (aParsedUrl.auth) {
+ url += aParsedUrl.auth + '@';
+ }
+ if (aParsedUrl.host) {
+ url += aParsedUrl.host;
+ }
+ if (aParsedUrl.port) {
+ url += ":" + aParsedUrl.port
+ }
+ if (aParsedUrl.path) {
+ url += aParsedUrl.path;
+ }
+ return url;
+ }
+ exports.urlGenerate = urlGenerate;
+
+ /**
+ * Normalizes a path, or the path portion of a URL:
+ *
+ * - Replaces consecutive slashes with one slash.
+ * - Removes unnecessary '.' parts.
+ * - Removes unnecessary '<dir>/..' parts.
+ *
+ * Based on code in the Node.js 'path' core module.
+ *
+ * @param aPath The path or url to normalize.
+ */
+ function normalize(aPath) {
+ var path = aPath;
+ var url = urlParse(aPath);
+ if (url) {
+ if (!url.path) {
+ return aPath;
+ }
+ path = url.path;
+ }
+ var isAbsolute = exports.isAbsolute(path);
+
+ var parts = path.split(/\/+/);
+ for (var part, up = 0, i = parts.length - 1; i >= 0; i--) {
+ part = parts[i];
+ if (part === '.') {
+ parts.splice(i, 1);
+ } else if (part === '..') {
+ up++;
+ } else if (up > 0) {
+ if (part === '') {
+ // The first part is blank if the path is absolute. Trying to go
+ // above the root is a no-op. Therefore we can remove all '..' parts
+ // directly after the root.
+ parts.splice(i + 1, up);
+ up = 0;
+ } else {
+ parts.splice(i, 2);
+ up--;
+ }
+ }
+ }
+ path = parts.join('/');
+
+ if (path === '') {
+ path = isAbsolute ? '/' : '.';
+ }
+
+ if (url) {
+ url.path = path;
+ return urlGenerate(url);
+ }
+ return path;
+ }
+ exports.normalize = normalize;
+
+ /**
+ * Joins two paths/URLs.
+ *
+ * @param aRoot The root path or URL.
+ * @param aPath The path or URL to be joined with the root.
+ *
+ * - If aPath is a URL or a data URI, aPath is returned, unless aPath is a
+ * scheme-relative URL: Then the scheme of aRoot, if any, is prepended
+ * first.
+ * - Otherwise aPath is a path. If aRoot is a URL, then its path portion
+ * is updated with the result and aRoot is returned. Otherwise the result
+ * is returned.
+ * - If aPath is absolute, the result is aPath.
+ * - Otherwise the two paths are joined with a slash.
+ * - Joining for example 'http://' and 'www.example.com' is also supported.
+ */
+ function join(aRoot, aPath) {
+ if (aRoot === "") {
+ aRoot = ".";
+ }
+ if (aPath === "") {
+ aPath = ".";
+ }
+ var aPathUrl = urlParse(aPath);
+ var aRootUrl = urlParse(aRoot);
+ if (aRootUrl) {
+ aRoot = aRootUrl.path || '/';
+ }
+
+ // `join(foo, '//www.example.org')`
+ if (aPathUrl && !aPathUrl.scheme) {
+ if (aRootUrl) {
+ aPathUrl.scheme = aRootUrl.scheme;
+ }
+ return urlGenerate(aPathUrl);
+ }
+
+ if (aPathUrl || aPath.match(dataUrlRegexp)) {
+ return aPath;
+ }
+
+ // `join('http://', 'www.example.com')`
+ if (aRootUrl && !aRootUrl.host && !aRootUrl.path) {
+ aRootUrl.host = aPath;
+ return urlGenerate(aRootUrl);
+ }
+
+ var joined = aPath.charAt(0) === '/'
+ ? aPath
+ : normalize(aRoot.replace(/\/+$/, '') + '/' + aPath);
+
+ if (aRootUrl) {
+ aRootUrl.path = joined;
+ return urlGenerate(aRootUrl);
+ }
+ return joined;
+ }
+ exports.join = join;
+
+ exports.isAbsolute = function (aPath) {
+ return aPath.charAt(0) === '/' || !!aPath.match(urlRegexp);
+ };
+
+ /**
+ * Make a path relative to a URL or another path.
+ *
+ * @param aRoot The root path or URL.
+ * @param aPath The path or URL to be made relative to aRoot.
+ */
+ function relative(aRoot, aPath) {
+ if (aRoot === "") {
+ aRoot = ".";
+ }
+
+ aRoot = aRoot.replace(/\/$/, '');
+
+ // It is possible for the path to be above the root. In this case, simply
+ // checking whether the root is a prefix of the path won't work. Instead, we
+ // need to remove components from the root one by one, until either we find
+ // a prefix that fits, or we run out of components to remove.
+ var level = 0;
+ while (aPath.indexOf(aRoot + '/') !== 0) {
+ var index = aRoot.lastIndexOf("/");
+ if (index < 0) {
+ return aPath;
+ }
+
+ // If the only part of the root that is left is the scheme (i.e. http://,
+ // file:///, etc.), one or more slashes (/), or simply nothing at all, we
+ // have exhausted all components, so the path is not relative to the root.
+ aRoot = aRoot.slice(0, index);
+ if (aRoot.match(/^([^\/]+:\/)?\/*$/)) {
+ return aPath;
+ }
+
+ ++level;
+ }
+
+ // Make sure we add a "../" for each component we removed from the root.
+ return Array(level + 1).join("../") + aPath.substr(aRoot.length + 1);
+ }
+ exports.relative = relative;
+
+ var supportsNullProto = (function () {
+ var obj = Object.create(null);
+ return !('__proto__' in obj);
+ }());
+
+ function identity (s) {
+ return s;
+ }
+
+ /**
+ * Because behavior goes wacky when you set `__proto__` on objects, we
+ * have to prefix all the strings in our set with an arbitrary character.
+ *
+ * See https://github.com/mozilla/source-map/pull/31 and
+ * https://github.com/mozilla/source-map/issues/30
+ *
+ * @param String aStr
+ */
+ function toSetString(aStr) {
+ if (isProtoString(aStr)) {
+ return '$' + aStr;
+ }
+
+ return aStr;
+ }
+ exports.toSetString = supportsNullProto ? identity : toSetString;
+
+ function fromSetString(aStr) {
+ if (isProtoString(aStr)) {
+ return aStr.slice(1);
+ }
+
+ return aStr;
+ }
+ exports.fromSetString = supportsNullProto ? identity : fromSetString;
+
+ function isProtoString(s) {
+ if (!s) {
+ return false;
+ }
+
+ var length = s.length;
+
+ if (length < 9 /* "__proto__".length */) {
+ return false;
+ }
+
+ if (s.charCodeAt(length - 1) !== 95 /* '_' */ ||
+ s.charCodeAt(length - 2) !== 95 /* '_' */ ||
+ s.charCodeAt(length - 3) !== 111 /* 'o' */ ||
+ s.charCodeAt(length - 4) !== 116 /* 't' */ ||
+ s.charCodeAt(length - 5) !== 111 /* 'o' */ ||
+ s.charCodeAt(length - 6) !== 114 /* 'r' */ ||
+ s.charCodeAt(length - 7) !== 112 /* 'p' */ ||
+ s.charCodeAt(length - 8) !== 95 /* '_' */ ||
+ s.charCodeAt(length - 9) !== 95 /* '_' */) {
+ return false;
+ }
+
+ for (var i = length - 10; i >= 0; i--) {
+ if (s.charCodeAt(i) !== 36 /* '$' */) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * Comparator between two mappings where the original positions are compared.
+ *
+ * Optionally pass in `true` as `onlyCompareGenerated` to consider two
+ * mappings with the same original source/line/column, but different generated
+ * line and column the same. Useful when searching for a mapping with a
+ * stubbed out mapping.
+ */
+ function compareByOriginalPositions(mappingA, mappingB, onlyCompareOriginal) {
+ var cmp = mappingA.source - mappingB.source;
+ if (cmp !== 0) {
+ return cmp;
+ }
+
+ cmp = mappingA.originalLine - mappingB.originalLine;
+ if (cmp !== 0) {
+ return cmp;
+ }
+
+ cmp = mappingA.originalColumn - mappingB.originalColumn;
+ if (cmp !== 0 || onlyCompareOriginal) {
+ return cmp;
+ }
+
+ cmp = mappingA.generatedColumn - mappingB.generatedColumn;
+ if (cmp !== 0) {
+ return cmp;
+ }
+
+ cmp = mappingA.generatedLine - mappingB.generatedLine;
+ if (cmp !== 0) {
+ return cmp;
+ }
+
+ return mappingA.name - mappingB.name;
+ }
+ exports.compareByOriginalPositions = compareByOriginalPositions;
+
+ /**
+ * Comparator between two mappings with deflated source and name indices where
+ * the generated positions are compared.
+ *
+ * Optionally pass in `true` as `onlyCompareGenerated` to consider two
+ * mappings with the same generated line and column, but different
+ * source/name/original line and column the same. Useful when searching for a
+ * mapping with a stubbed out mapping.
+ */
+ function compareByGeneratedPositionsDeflated(mappingA, mappingB, onlyCompareGenerated) {
+ var cmp = mappingA.generatedLine - mappingB.generatedLine;
+ if (cmp !== 0) {
+ return cmp;
+ }
+
+ cmp = mappingA.generatedColumn - mappingB.generatedColumn;
+ if (cmp !== 0 || onlyCompareGenerated) {
+ return cmp;
+ }
+
+ cmp = mappingA.source - mappingB.source;
+ if (cmp !== 0) {
+ return cmp;
+ }
+
+ cmp = mappingA.originalLine - mappingB.originalLine;
+ if (cmp !== 0) {
+ return cmp;
+ }
+
+ cmp = mappingA.originalColumn - mappingB.originalColumn;
+ if (cmp !== 0) {
+ return cmp;
+ }
+
+ return mappingA.name - mappingB.name;
+ }
+ exports.compareByGeneratedPositionsDeflated = compareByGeneratedPositionsDeflated;
+
+ function strcmp(aStr1, aStr2) {
+ if (aStr1 === aStr2) {
+ return 0;
+ }
+
+ if (aStr1 > aStr2) {
+ return 1;
+ }
+
+ return -1;
+ }
+
+ /**
+ * Comparator between two mappings with inflated source and name strings where
+ * the generated positions are compared.
+ */
+ function compareByGeneratedPositionsInflated(mappingA, mappingB) {
+ var cmp = mappingA.generatedLine - mappingB.generatedLine;
+ if (cmp !== 0) {
+ return cmp;
+ }
+
+ cmp = mappingA.generatedColumn - mappingB.generatedColumn;
+ if (cmp !== 0) {
+ return cmp;
+ }
+
+ cmp = strcmp(mappingA.source, mappingB.source);
+ if (cmp !== 0) {
+ return cmp;
+ }
+
+ cmp = mappingA.originalLine - mappingB.originalLine;
+ if (cmp !== 0) {
+ return cmp;
+ }
+
+ cmp = mappingA.originalColumn - mappingB.originalColumn;
+ if (cmp !== 0) {
+ return cmp;
+ }
+
+ return strcmp(mappingA.name, mappingB.name);
+ }
+ exports.compareByGeneratedPositionsInflated = compareByGeneratedPositionsInflated;
+
+
+/***/ },
+
+/***/ 477:
+/***/ function(module, exports, __webpack_require__) {
+
+ /* -*- Mode: js; js-indent-level: 2; -*- */
+ /*
+ * Copyright 2011 Mozilla Foundation and contributors
+ * Licensed under the New BSD license. See LICENSE or:
+ * http://opensource.org/licenses/BSD-3-Clause
+ */
+
+ var util = __webpack_require__(476);
+ var has = Object.prototype.hasOwnProperty;
+
+ /**
+ * A data structure which is a combination of an array and a set. Adding a new
+ * member is O(1), testing for membership is O(1), and finding the index of an
+ * element is O(1). Removing elements from the set is not supported. Only
+ * strings are supported for membership.
+ */
+ function ArraySet() {
+ this._array = [];
+ this._set = Object.create(null);
+ }
+
+ /**
+ * Static method for creating ArraySet instances from an existing array.
+ */
+ ArraySet.fromArray = function ArraySet_fromArray(aArray, aAllowDuplicates) {
+ var set = new ArraySet();
+ for (var i = 0, len = aArray.length; i < len; i++) {
+ set.add(aArray[i], aAllowDuplicates);
+ }
+ return set;
+ };
+
+ /**
+ * Return how many unique items are in this ArraySet. If duplicates have been
+ * added, than those do not count towards the size.
+ *
+ * @returns Number
+ */
+ ArraySet.prototype.size = function ArraySet_size() {
+ return Object.getOwnPropertyNames(this._set).length;
+ };
+
+ /**
+ * Add the given string to this set.
+ *
+ * @param String aStr
+ */
+ ArraySet.prototype.add = function ArraySet_add(aStr, aAllowDuplicates) {
+ var sStr = util.toSetString(aStr);
+ var isDuplicate = has.call(this._set, sStr);
+ var idx = this._array.length;
+ if (!isDuplicate || aAllowDuplicates) {
+ this._array.push(aStr);
+ }
+ if (!isDuplicate) {
+ this._set[sStr] = idx;
+ }
+ };
+
+ /**
+ * Is the given string a member of this set?
+ *
+ * @param String aStr
+ */
+ ArraySet.prototype.has = function ArraySet_has(aStr) {
+ var sStr = util.toSetString(aStr);
+ return has.call(this._set, sStr);
+ };
+
+ /**
+ * What is the index of the given string in the array?
+ *
+ * @param String aStr
+ */
+ ArraySet.prototype.indexOf = function ArraySet_indexOf(aStr) {
+ var sStr = util.toSetString(aStr);
+ if (has.call(this._set, sStr)) {
+ return this._set[sStr];
+ }
+ throw new Error('"' + aStr + '" is not in the set.');
+ };
+
+ /**
+ * What is the element at the given index?
+ *
+ * @param Number aIdx
+ */
+ ArraySet.prototype.at = function ArraySet_at(aIdx) {
+ if (aIdx >= 0 && aIdx < this._array.length) {
+ return this._array[aIdx];
+ }
+ throw new Error('No element indexed by ' + aIdx);
+ };
+
+ /**
+ * Returns the array representation of this set (which has the proper indices
+ * indicated by indexOf). Note that this is a copy of the internal array used
+ * for storing the members so that no one can mess with internal state.
+ */
+ ArraySet.prototype.toArray = function ArraySet_toArray() {
+ return this._array.slice();
+ };
+
+ exports.ArraySet = ArraySet;
+
+
+/***/ },
+
+/***/ 478:
+/***/ function(module, exports, __webpack_require__) {
+
+ /* -*- Mode: js; js-indent-level: 2; -*- */
+ /*
+ * Copyright 2014 Mozilla Foundation and contributors
+ * Licensed under the New BSD license. See LICENSE or:
+ * http://opensource.org/licenses/BSD-3-Clause
+ */
+
+ var util = __webpack_require__(476);
+
+ /**
+ * Determine whether mappingB is after mappingA with respect to generated
+ * position.
+ */
+ function generatedPositionAfter(mappingA, mappingB) {
+ // Optimized for most common case
+ var lineA = mappingA.generatedLine;
+ var lineB = mappingB.generatedLine;
+ var columnA = mappingA.generatedColumn;
+ var columnB = mappingB.generatedColumn;
+ return lineB > lineA || lineB == lineA && columnB >= columnA ||
+ util.compareByGeneratedPositionsInflated(mappingA, mappingB) <= 0;
+ }
+
+ /**
+ * A data structure to provide a sorted view of accumulated mappings in a
+ * performance conscious manner. It trades a neglibable overhead in general
+ * case for a large speedup in case of mappings being added in order.
+ */
+ function MappingList() {
+ this._array = [];
+ this._sorted = true;
+ // Serves as infimum
+ this._last = {generatedLine: -1, generatedColumn: 0};
+ }
+
+ /**
+ * Iterate through internal items. This method takes the same arguments that
+ * `Array.prototype.forEach` takes.
+ *
+ * NOTE: The order of the mappings is NOT guaranteed.
+ */
+ MappingList.prototype.unsortedForEach =
+ function MappingList_forEach(aCallback, aThisArg) {
+ this._array.forEach(aCallback, aThisArg);
+ };
+
+ /**
+ * Add the given source mapping.
+ *
+ * @param Object aMapping
+ */
+ MappingList.prototype.add = function MappingList_add(aMapping) {
+ if (generatedPositionAfter(this._last, aMapping)) {
+ this._last = aMapping;
+ this._array.push(aMapping);
+ } else {
+ this._sorted = false;
+ this._array.push(aMapping);
+ }
+ };
+
+ /**
+ * Returns the flat, sorted array of mappings. The mappings are sorted by
+ * generated position.
+ *
+ * WARNING: This method returns internal data without copying, for
+ * performance. The return value must NOT be mutated, and should be treated as
+ * an immutable borrow. If you want to take ownership, you must make your own
+ * copy.
+ */
+ MappingList.prototype.toArray = function MappingList_toArray() {
+ if (!this._sorted) {
+ this._array.sort(util.compareByGeneratedPositionsInflated);
+ this._sorted = true;
+ }
+ return this._array;
+ };
+
+ exports.MappingList = MappingList;
+
+
+/***/ },
+
+/***/ 479:
+/***/ function(module, exports, __webpack_require__) {
+
+ /* -*- Mode: js; js-indent-level: 2; -*- */
+ /*
+ * Copyright 2011 Mozilla Foundation and contributors
+ * Licensed under the New BSD license. See LICENSE or:
+ * http://opensource.org/licenses/BSD-3-Clause
+ */
+
+ var util = __webpack_require__(476);
+ var binarySearch = __webpack_require__(480);
+ var ArraySet = __webpack_require__(477).ArraySet;
+ var base64VLQ = __webpack_require__(474);
+ var quickSort = __webpack_require__(481).quickSort;
+
+ function SourceMapConsumer(aSourceMap) {
+ var sourceMap = aSourceMap;
+ if (typeof aSourceMap === 'string') {
+ sourceMap = JSON.parse(aSourceMap.replace(/^\)\]\}'/, ''));
+ }
+
+ return sourceMap.sections != null
+ ? new IndexedSourceMapConsumer(sourceMap)
+ : new BasicSourceMapConsumer(sourceMap);
+ }
+
+ SourceMapConsumer.fromSourceMap = function(aSourceMap) {
+ return BasicSourceMapConsumer.fromSourceMap(aSourceMap);
+ }
+
+ /**
+ * The version of the source mapping spec that we are consuming.
+ */
+ SourceMapConsumer.prototype._version = 3;
+
+ // `__generatedMappings` and `__originalMappings` are arrays that hold the
+ // parsed mapping coordinates from the source map's "mappings" attribute. They
+ // are lazily instantiated, accessed via the `_generatedMappings` and
+ // `_originalMappings` getters respectively, and we only parse the mappings
+ // and create these arrays once queried for a source location. We jump through
+ // these hoops because there can be many thousands of mappings, and parsing
+ // them is expensive, so we only want to do it if we must.
+ //
+ // Each object in the arrays is of the form:
+ //
+ // {
+ // generatedLine: The line number in the generated code,
+ // generatedColumn: The column number in the generated code,
+ // source: The path to the original source file that generated this
+ // chunk of code,
+ // originalLine: The line number in the original source that
+ // corresponds to this chunk of generated code,
+ // originalColumn: The column number in the original source that
+ // corresponds to this chunk of generated code,
+ // name: The name of the original symbol which generated this chunk of
+ // code.
+ // }
+ //
+ // All properties except for `generatedLine` and `generatedColumn` can be
+ // `null`.
+ //
+ // `_generatedMappings` is ordered by the generated positions.
+ //
+ // `_originalMappings` is ordered by the original positions.
+
+ SourceMapConsumer.prototype.__generatedMappings = null;
+ Object.defineProperty(SourceMapConsumer.prototype, '_generatedMappings', {
+ get: function () {
+ if (!this.__generatedMappings) {
+ this._parseMappings(this._mappings, this.sourceRoot);
+ }
+
+ return this.__generatedMappings;
+ }
+ });
+
+ SourceMapConsumer.prototype.__originalMappings = null;
+ Object.defineProperty(SourceMapConsumer.prototype, '_originalMappings', {
+ get: function () {
+ if (!this.__originalMappings) {
+ this._parseMappings(this._mappings, this.sourceRoot);
+ }
+
+ return this.__originalMappings;
+ }
+ });
+
+ SourceMapConsumer.prototype._charIsMappingSeparator =
+ function SourceMapConsumer_charIsMappingSeparator(aStr, index) {
+ var c = aStr.charAt(index);
+ return c === ";" || c === ",";
+ };
+
+ /**
+ * Parse the mappings in a string in to a data structure which we can easily
+ * query (the ordered arrays in the `this.__generatedMappings` and
+ * `this.__originalMappings` properties).
+ */
+ SourceMapConsumer.prototype._parseMappings =
+ function SourceMapConsumer_parseMappings(aStr, aSourceRoot) {
+ throw new Error("Subclasses must implement _parseMappings");
+ };
+
+ SourceMapConsumer.GENERATED_ORDER = 1;
+ SourceMapConsumer.ORIGINAL_ORDER = 2;
+
+ SourceMapConsumer.GREATEST_LOWER_BOUND = 1;
+ SourceMapConsumer.LEAST_UPPER_BOUND = 2;
+
+ /**
+ * Iterate over each mapping between an original source/line/column and a
+ * generated line/column in this source map.
+ *
+ * @param Function aCallback
+ * The function that is called with each mapping.
+ * @param Object aContext
+ * Optional. If specified, this object will be the value of `this` every
+ * time that `aCallback` is called.
+ * @param aOrder
+ * Either `SourceMapConsumer.GENERATED_ORDER` or
+ * `SourceMapConsumer.ORIGINAL_ORDER`. Specifies whether you want to
+ * iterate over the mappings sorted by the generated file's line/column
+ * order or the original's source/line/column order, respectively. Defaults to
+ * `SourceMapConsumer.GENERATED_ORDER`.
+ */
+ SourceMapConsumer.prototype.eachMapping =
+ function SourceMapConsumer_eachMapping(aCallback, aContext, aOrder) {
+ var context = aContext || null;
+ var order = aOrder || SourceMapConsumer.GENERATED_ORDER;
+
+ var mappings;
+ switch (order) {
+ case SourceMapConsumer.GENERATED_ORDER:
+ mappings = this._generatedMappings;
+ break;
+ case SourceMapConsumer.ORIGINAL_ORDER:
+ mappings = this._originalMappings;
+ break;
+ default:
+ throw new Error("Unknown order of iteration.");
+ }
+
+ var sourceRoot = this.sourceRoot;
+ mappings.map(function (mapping) {
+ var source = mapping.source === null ? null : this._sources.at(mapping.source);
+ if (source != null && sourceRoot != null) {
+ source = util.join(sourceRoot, source);
+ }
+ return {
+ source: source,
+ generatedLine: mapping.generatedLine,
+ generatedColumn: mapping.generatedColumn,
+ originalLine: mapping.originalLine,
+ originalColumn: mapping.originalColumn,
+ name: mapping.name === null ? null : this._names.at(mapping.name)
+ };
+ }, this).forEach(aCallback, context);
+ };
+
+ /**
+ * Returns all generated line and column information for the original source,
+ * line, and column provided. If no column is provided, returns all mappings
+ * corresponding to a either the line we are searching for or the next
+ * closest line that has any mappings. Otherwise, returns all mappings
+ * corresponding to the given line and either the column we are searching for
+ * or the next closest column that has any offsets.
+ *
+ * The only argument is an object with the following properties:
+ *
+ * - source: The filename of the original source.
+ * - line: The line number in the original source.
+ * - column: Optional. the column number in the original source.
+ *
+ * and an array of objects is returned, each with the following properties:
+ *
+ * - line: The line number in the generated source, or null.
+ * - column: The column number in the generated source, or null.
+ */
+ SourceMapConsumer.prototype.allGeneratedPositionsFor =
+ function SourceMapConsumer_allGeneratedPositionsFor(aArgs) {
+ var line = util.getArg(aArgs, 'line');
+
+ // When there is no exact match, BasicSourceMapConsumer.prototype._findMapping
+ // returns the index of the closest mapping less than the needle. By
+ // setting needle.originalColumn to 0, we thus find the last mapping for
+ // the given line, provided such a mapping exists.
+ var needle = {
+ source: util.getArg(aArgs, 'source'),
+ originalLine: line,
+ originalColumn: util.getArg(aArgs, 'column', 0)
+ };
+
+ if (this.sourceRoot != null) {
+ needle.source = util.relative(this.sourceRoot, needle.source);
+ }
+ if (!this._sources.has(needle.source)) {
+ return [];
+ }
+ needle.source = this._sources.indexOf(needle.source);
+
+ var mappings = [];
+
+ var index = this._findMapping(needle,
+ this._originalMappings,
+ "originalLine",
+ "originalColumn",
+ util.compareByOriginalPositions,
+ binarySearch.LEAST_UPPER_BOUND);
+ if (index >= 0) {
+ var mapping = this._originalMappings[index];
+
+ if (aArgs.column === undefined) {
+ var originalLine = mapping.originalLine;
+
+ // Iterate until either we run out of mappings, or we run into
+ // a mapping for a different line than the one we found. Since
+ // mappings are sorted, this is guaranteed to find all mappings for
+ // the line we found.
+ while (mapping && mapping.originalLine === originalLine) {
+ mappings.push({
+ line: util.getArg(mapping, 'generatedLine', null),
+ column: util.getArg(mapping, 'generatedColumn', null),
+ lastColumn: util.getArg(mapping, 'lastGeneratedColumn', null)
+ });
+
+ mapping = this._originalMappings[++index];
+ }
+ } else {
+ var originalColumn = mapping.originalColumn;
+
+ // Iterate until either we run out of mappings, or we run into
+ // a mapping for a different line than the one we were searching for.
+ // Since mappings are sorted, this is guaranteed to find all mappings for
+ // the line we are searching for.
+ while (mapping &&
+ mapping.originalLine === line &&
+ mapping.originalColumn == originalColumn) {
+ mappings.push({
+ line: util.getArg(mapping, 'generatedLine', null),
+ column: util.getArg(mapping, 'generatedColumn', null),
+ lastColumn: util.getArg(mapping, 'lastGeneratedColumn', null)
+ });
+
+ mapping = this._originalMappings[++index];
+ }
+ }
+ }
+
+ return mappings;
+ };
+
+ exports.SourceMapConsumer = SourceMapConsumer;
+
+ /**
+ * A BasicSourceMapConsumer instance represents a parsed source map which we can
+ * query for information about the original file positions by giving it a file
+ * position in the generated source.
+ *
+ * The only parameter is the raw source map (either as a JSON string, or
+ * already parsed to an object). According to the spec, source maps have the
+ * following attributes:
+ *
+ * - version: Which version of the source map spec this map is following.
+ * - sources: An array of URLs to the original source files.
+ * - names: An array of identifiers which can be referrenced by individual mappings.
+ * - sourceRoot: Optional. The URL root from which all sources are relative.
+ * - sourcesContent: Optional. An array of contents of the original source files.
+ * - mappings: A string of base64 VLQs which contain the actual mappings.
+ * - file: Optional. The generated file this source map is associated with.
+ *
+ * Here is an example source map, taken from the source map spec[0]:
+ *
+ * {
+ * version : 3,
+ * file: "out.js",
+ * sourceRoot : "",
+ * sources: ["foo.js", "bar.js"],
+ * names: ["src", "maps", "are", "fun"],
+ * mappings: "AA,AB;;ABCDE;"
+ * }
+ *
+ * [0]: https://docs.google.com/document/d/1U1RGAehQwRypUTovF1KRlpiOFze0b-_2gc6fAH0KY0k/edit?pli=1#
+ */
+ function BasicSourceMapConsumer(aSourceMap) {
+ var sourceMap = aSourceMap;
+ if (typeof aSourceMap === 'string') {
+ sourceMap = JSON.parse(aSourceMap.replace(/^\)\]\}'/, ''));
+ }
+
+ var version = util.getArg(sourceMap, 'version');
+ var sources = util.getArg(sourceMap, 'sources');
+ // Sass 3.3 leaves out the 'names' array, so we deviate from the spec (which
+ // requires the array) to play nice here.
+ var names = util.getArg(sourceMap, 'names', []);
+ var sourceRoot = util.getArg(sourceMap, 'sourceRoot', null);
+ var sourcesContent = util.getArg(sourceMap, 'sourcesContent', null);
+ var mappings = util.getArg(sourceMap, 'mappings');
+ var file = util.getArg(sourceMap, 'file', null);
+
+ // Once again, Sass deviates from the spec and supplies the version as a
+ // string rather than a number, so we use loose equality checking here.
+ if (version != this._version) {
+ throw new Error('Unsupported version: ' + version);
+ }
+
+ sources = sources
+ .map(String)
+ // Some source maps produce relative source paths like "./foo.js" instead of
+ // "foo.js". Normalize these first so that future comparisons will succeed.
+ // See bugzil.la/1090768.
+ .map(util.normalize)
+ // Always ensure that absolute sources are internally stored relative to
+ // the source root, if the source root is absolute. Not doing this would
+ // be particularly problematic when the source root is a prefix of the
+ // source (valid, but why??). See github issue #199 and bugzil.la/1188982.
+ .map(function (source) {
+ return sourceRoot && util.isAbsolute(sourceRoot) && util.isAbsolute(source)
+ ? util.relative(sourceRoot, source)
+ : source;
+ });
+
+ // Pass `true` below to allow duplicate names and sources. While source maps
+ // are intended to be compressed and deduplicated, the TypeScript compiler
+ // sometimes generates source maps with duplicates in them. See Github issue
+ // #72 and bugzil.la/889492.
+ this._names = ArraySet.fromArray(names.map(String), true);
+ this._sources = ArraySet.fromArray(sources, true);
+
+ this.sourceRoot = sourceRoot;
+ this.sourcesContent = sourcesContent;
+ this._mappings = mappings;
+ this.file = file;
+ }
+
+ BasicSourceMapConsumer.prototype = Object.create(SourceMapConsumer.prototype);
+ BasicSourceMapConsumer.prototype.consumer = SourceMapConsumer;
+
+ /**
+ * Create a BasicSourceMapConsumer from a SourceMapGenerator.
+ *
+ * @param SourceMapGenerator aSourceMap
+ * The source map that will be consumed.
+ * @returns BasicSourceMapConsumer
+ */
+ BasicSourceMapConsumer.fromSourceMap =
+ function SourceMapConsumer_fromSourceMap(aSourceMap) {
+ var smc = Object.create(BasicSourceMapConsumer.prototype);
+
+ var names = smc._names = ArraySet.fromArray(aSourceMap._names.toArray(), true);
+ var sources = smc._sources = ArraySet.fromArray(aSourceMap._sources.toArray(), true);
+ smc.sourceRoot = aSourceMap._sourceRoot;
+ smc.sourcesContent = aSourceMap._generateSourcesContent(smc._sources.toArray(),
+ smc.sourceRoot);
+ smc.file = aSourceMap._file;
+
+ // Because we are modifying the entries (by converting string sources and
+ // names to indices into the sources and names ArraySets), we have to make
+ // a copy of the entry or else bad things happen. Shared mutable state
+ // strikes again! See github issue #191.
+
+ var generatedMappings = aSourceMap._mappings.toArray().slice();
+ var destGeneratedMappings = smc.__generatedMappings = [];
+ var destOriginalMappings = smc.__originalMappings = [];
+
+ for (var i = 0, length = generatedMappings.length; i < length; i++) {
+ var srcMapping = generatedMappings[i];
+ var destMapping = new Mapping;
+ destMapping.generatedLine = srcMapping.generatedLine;
+ destMapping.generatedColumn = srcMapping.generatedColumn;
+
+ if (srcMapping.source) {
+ destMapping.source = sources.indexOf(srcMapping.source);
+ destMapping.originalLine = srcMapping.originalLine;
+ destMapping.originalColumn = srcMapping.originalColumn;
+
+ if (srcMapping.name) {
+ destMapping.name = names.indexOf(srcMapping.name);
+ }
+
+ destOriginalMappings.push(destMapping);
+ }
+
+ destGeneratedMappings.push(destMapping);
+ }
+
+ quickSort(smc.__originalMappings, util.compareByOriginalPositions);
+
+ return smc;
+ };
+
+ /**
+ * The version of the source mapping spec that we are consuming.
+ */
+ BasicSourceMapConsumer.prototype._version = 3;
+
+ /**
+ * The list of original sources.
+ */
+ Object.defineProperty(BasicSourceMapConsumer.prototype, 'sources', {
+ get: function () {
+ return this._sources.toArray().map(function (s) {
+ return this.sourceRoot != null ? util.join(this.sourceRoot, s) : s;
+ }, this);
+ }
+ });
+
+ /**
+ * Provide the JIT with a nice shape / hidden class.
+ */
+ function Mapping() {
+ this.generatedLine = 0;
+ this.generatedColumn = 0;
+ this.source = null;
+ this.originalLine = null;
+ this.originalColumn = null;
+ this.name = null;
+ }
+
+ /**
+ * Parse the mappings in a string in to a data structure which we can easily
+ * query (the ordered arrays in the `this.__generatedMappings` and
+ * `this.__originalMappings` properties).
+ */
+ BasicSourceMapConsumer.prototype._parseMappings =
+ function SourceMapConsumer_parseMappings(aStr, aSourceRoot) {
+ var generatedLine = 1;
+ var previousGeneratedColumn = 0;
+ var previousOriginalLine = 0;
+ var previousOriginalColumn = 0;
+ var previousSource = 0;
+ var previousName = 0;
+ var length = aStr.length;
+ var index = 0;
+ var cachedSegments = {};
+ var temp = {};
+ var originalMappings = [];
+ var generatedMappings = [];
+ var mapping, str, segment, end, value;
+
+ while (index < length) {
+ if (aStr.charAt(index) === ';') {
+ generatedLine++;
+ index++;
+ previousGeneratedColumn = 0;
+ }
+ else if (aStr.charAt(index) === ',') {
+ index++;
+ }
+ else {
+ mapping = new Mapping();
+ mapping.generatedLine = generatedLine;
+
+ // Because each offset is encoded relative to the previous one,
+ // many segments often have the same encoding. We can exploit this
+ // fact by caching the parsed variable length fields of each segment,
+ // allowing us to avoid a second parse if we encounter the same
+ // segment again.
+ for (end = index; end < length; end++) {
+ if (this._charIsMappingSeparator(aStr, end)) {
+ break;
+ }
+ }
+ str = aStr.slice(index, end);
+
+ segment = cachedSegments[str];
+ if (segment) {
+ index += str.length;
+ } else {
+ segment = [];
+ while (index < end) {
+ base64VLQ.decode(aStr, index, temp);
+ value = temp.value;
+ index = temp.rest;
+ segment.push(value);
+ }
+
+ if (segment.length === 2) {
+ throw new Error('Found a source, but no line and column');
+ }
+
+ if (segment.length === 3) {
+ throw new Error('Found a source and line, but no column');
+ }
+
+ cachedSegments[str] = segment;
+ }
+
+ // Generated column.
+ mapping.generatedColumn = previousGeneratedColumn + segment[0];
+ previousGeneratedColumn = mapping.generatedColumn;
+
+ if (segment.length > 1) {
+ // Original source.
+ mapping.source = previousSource + segment[1];
+ previousSource += segment[1];
+
+ // Original line.
+ mapping.originalLine = previousOriginalLine + segment[2];
+ previousOriginalLine = mapping.originalLine;
+ // Lines are stored 0-based
+ mapping.originalLine += 1;
+
+ // Original column.
+ mapping.originalColumn = previousOriginalColumn + segment[3];
+ previousOriginalColumn = mapping.originalColumn;
+
+ if (segment.length > 4) {
+ // Original name.
+ mapping.name = previousName + segment[4];
+ previousName += segment[4];
+ }
+ }
+
+ generatedMappings.push(mapping);
+ if (typeof mapping.originalLine === 'number') {
+ originalMappings.push(mapping);
+ }
+ }
+ }
+
+ quickSort(generatedMappings, util.compareByGeneratedPositionsDeflated);
+ this.__generatedMappings = generatedMappings;
+
+ quickSort(originalMappings, util.compareByOriginalPositions);
+ this.__originalMappings = originalMappings;
+ };
+
+ /**
+ * Find the mapping that best matches the hypothetical "needle" mapping that
+ * we are searching for in the given "haystack" of mappings.
+ */
+ BasicSourceMapConsumer.prototype._findMapping =
+ function SourceMapConsumer_findMapping(aNeedle, aMappings, aLineName,
+ aColumnName, aComparator, aBias) {
+ // To return the position we are searching for, we must first find the
+ // mapping for the given position and then return the opposite position it
+ // points to. Because the mappings are sorted, we can use binary search to
+ // find the best mapping.
+
+ if (aNeedle[aLineName] <= 0) {
+ throw new TypeError('Line must be greater than or equal to 1, got '
+ + aNeedle[aLineName]);
+ }
+ if (aNeedle[aColumnName] < 0) {
+ throw new TypeError('Column must be greater than or equal to 0, got '
+ + aNeedle[aColumnName]);
+ }
+
+ return binarySearch.search(aNeedle, aMappings, aComparator, aBias);
+ };
+
+ /**
+ * Compute the last column for each generated mapping. The last column is
+ * inclusive.
+ */
+ BasicSourceMapConsumer.prototype.computeColumnSpans =
+ function SourceMapConsumer_computeColumnSpans() {
+ for (var index = 0; index < this._generatedMappings.length; ++index) {
+ var mapping = this._generatedMappings[index];
+
+ // Mappings do not contain a field for the last generated columnt. We
+ // can come up with an optimistic estimate, however, by assuming that
+ // mappings are contiguous (i.e. given two consecutive mappings, the
+ // first mapping ends where the second one starts).
+ if (index + 1 < this._generatedMappings.length) {
+ var nextMapping = this._generatedMappings[index + 1];
+
+ if (mapping.generatedLine === nextMapping.generatedLine) {
+ mapping.lastGeneratedColumn = nextMapping.generatedColumn - 1;
+ continue;
+ }
+ }
+
+ // The last mapping for each line spans the entire line.
+ mapping.lastGeneratedColumn = Infinity;
+ }
+ };
+
+ /**
+ * Returns the original source, line, and column information for the generated
+ * source's line and column positions provided. The only argument is an object
+ * with the following properties:
+ *
+ * - line: The line number in the generated source.
+ * - column: The column number in the generated source.
+ * - bias: Either 'SourceMapConsumer.GREATEST_LOWER_BOUND' or
+ * 'SourceMapConsumer.LEAST_UPPER_BOUND'. Specifies whether to return the
+ * closest element that is smaller than or greater than the one we are
+ * searching for, respectively, if the exact element cannot be found.
+ * Defaults to 'SourceMapConsumer.GREATEST_LOWER_BOUND'.
+ *
+ * and an object is returned with the following properties:
+ *
+ * - source: The original source file, or null.
+ * - line: The line number in the original source, or null.
+ * - column: The column number in the original source, or null.
+ * - name: The original identifier, or null.
+ */
+ BasicSourceMapConsumer.prototype.originalPositionFor =
+ function SourceMapConsumer_originalPositionFor(aArgs) {
+ var needle = {
+ generatedLine: util.getArg(aArgs, 'line'),
+ generatedColumn: util.getArg(aArgs, 'column')
+ };
+
+ var index = this._findMapping(
+ needle,
+ this._generatedMappings,
+ "generatedLine",
+ "generatedColumn",
+ util.compareByGeneratedPositionsDeflated,
+ util.getArg(aArgs, 'bias', SourceMapConsumer.GREATEST_LOWER_BOUND)
+ );
+
+ if (index >= 0) {
+ var mapping = this._generatedMappings[index];
+
+ if (mapping.generatedLine === needle.generatedLine) {
+ var source = util.getArg(mapping, 'source', null);
+ if (source !== null) {
+ source = this._sources.at(source);
+ if (this.sourceRoot != null) {
+ source = util.join(this.sourceRoot, source);
+ }
+ }
+ var name = util.getArg(mapping, 'name', null);
+ if (name !== null) {
+ name = this._names.at(name);
+ }
+ return {
+ source: source,
+ line: util.getArg(mapping, 'originalLine', null),
+ column: util.getArg(mapping, 'originalColumn', null),
+ name: name
+ };
+ }
+ }
+
+ return {
+ source: null,
+ line: null,
+ column: null,
+ name: null
+ };
+ };
+
+ /**
+ * Return true if we have the source content for every source in the source
+ * map, false otherwise.
+ */
+ BasicSourceMapConsumer.prototype.hasContentsOfAllSources =
+ function BasicSourceMapConsumer_hasContentsOfAllSources() {
+ if (!this.sourcesContent) {
+ return false;
+ }
+ return this.sourcesContent.length >= this._sources.size() &&
+ !this.sourcesContent.some(function (sc) { return sc == null; });
+ };
+
+ /**
+ * Returns the original source content. The only argument is the url of the
+ * original source file. Returns null if no original source content is
+ * available.
+ */
+ BasicSourceMapConsumer.prototype.sourceContentFor =
+ function SourceMapConsumer_sourceContentFor(aSource, nullOnMissing) {
+ if (!this.sourcesContent) {
+ return null;
+ }
+
+ if (this.sourceRoot != null) {
+ aSource = util.relative(this.sourceRoot, aSource);
+ }
+
+ if (this._sources.has(aSource)) {
+ return this.sourcesContent[this._sources.indexOf(aSource)];
+ }
+
+ var url;
+ if (this.sourceRoot != null
+ && (url = util.urlParse(this.sourceRoot))) {
+ // XXX: file:// URIs and absolute paths lead to unexpected behavior for
+ // many users. We can help them out when they expect file:// URIs to
+ // behave like it would if they were running a local HTTP server. See
+ // https://bugzilla.mozilla.org/show_bug.cgi?id=885597.
+ var fileUriAbsPath = aSource.replace(/^file:\/\//, "");
+ if (url.scheme == "file"
+ && this._sources.has(fileUriAbsPath)) {
+ return this.sourcesContent[this._sources.indexOf(fileUriAbsPath)]
+ }
+
+ if ((!url.path || url.path == "/")
+ && this._sources.has("/" + aSource)) {
+ return this.sourcesContent[this._sources.indexOf("/" + aSource)];
+ }
+ }
+
+ // This function is used recursively from
+ // IndexedSourceMapConsumer.prototype.sourceContentFor. In that case, we
+ // don't want to throw if we can't find the source - we just want to
+ // return null, so we provide a flag to exit gracefully.
+ if (nullOnMissing) {
+ return null;
+ }
+ else {
+ throw new Error('"' + aSource + '" is not in the SourceMap.');
+ }
+ };
+
+ /**
+ * Returns the generated line and column information for the original source,
+ * line, and column positions provided. The only argument is an object with
+ * the following properties:
+ *
+ * - source: The filename of the original source.
+ * - line: The line number in the original source.
+ * - column: The column number in the original source.
+ * - bias: Either 'SourceMapConsumer.GREATEST_LOWER_BOUND' or
+ * 'SourceMapConsumer.LEAST_UPPER_BOUND'. Specifies whether to return the
+ * closest element that is smaller than or greater than the one we are
+ * searching for, respectively, if the exact element cannot be found.
+ * Defaults to 'SourceMapConsumer.GREATEST_LOWER_BOUND'.
+ *
+ * and an object is returned with the following properties:
+ *
+ * - line: The line number in the generated source, or null.
+ * - column: The column number in the generated source, or null.
+ */
+ BasicSourceMapConsumer.prototype.generatedPositionFor =
+ function SourceMapConsumer_generatedPositionFor(aArgs) {
+ var source = util.getArg(aArgs, 'source');
+ if (this.sourceRoot != null) {
+ source = util.relative(this.sourceRoot, source);
+ }
+ if (!this._sources.has(source)) {
+ return {
+ line: null,
+ column: null,
+ lastColumn: null
+ };
+ }
+ source = this._sources.indexOf(source);
+
+ var needle = {
+ source: source,
+ originalLine: util.getArg(aArgs, 'line'),
+ originalColumn: util.getArg(aArgs, 'column')
+ };
+
+ var index = this._findMapping(
+ needle,
+ this._originalMappings,
+ "originalLine",
+ "originalColumn",
+ util.compareByOriginalPositions,
+ util.getArg(aArgs, 'bias', SourceMapConsumer.GREATEST_LOWER_BOUND)
+ );
+
+ if (index >= 0) {
+ var mapping = this._originalMappings[index];
+
+ if (mapping.source === needle.source) {
+ return {
+ line: util.getArg(mapping, 'generatedLine', null),
+ column: util.getArg(mapping, 'generatedColumn', null),
+ lastColumn: util.getArg(mapping, 'lastGeneratedColumn', null)
+ };
+ }
+ }
+
+ return {
+ line: null,
+ column: null,
+ lastColumn: null
+ };
+ };
+
+ exports.BasicSourceMapConsumer = BasicSourceMapConsumer;
+
+ /**
+ * An IndexedSourceMapConsumer instance represents a parsed source map which
+ * we can query for information. It differs from BasicSourceMapConsumer in
+ * that it takes "indexed" source maps (i.e. ones with a "sections" field) as
+ * input.
+ *
+ * The only parameter is a raw source map (either as a JSON string, or already
+ * parsed to an object). According to the spec for indexed source maps, they
+ * have the following attributes:
+ *
+ * - version: Which version of the source map spec this map is following.
+ * - file: Optional. The generated file this source map is associated with.
+ * - sections: A list of section definitions.
+ *
+ * Each value under the "sections" field has two fields:
+ * - offset: The offset into the original specified at which this section
+ * begins to apply, defined as an object with a "line" and "column"
+ * field.
+ * - map: A source map definition. This source map could also be indexed,
+ * but doesn't have to be.
+ *
+ * Instead of the "map" field, it's also possible to have a "url" field
+ * specifying a URL to retrieve a source map from, but that's currently
+ * unsupported.
+ *
+ * Here's an example source map, taken from the source map spec[0], but
+ * modified to omit a section which uses the "url" field.
+ *
+ * {
+ * version : 3,
+ * file: "app.js",
+ * sections: [{
+ * offset: {line:100, column:10},
+ * map: {
+ * version : 3,
+ * file: "section.js",
+ * sources: ["foo.js", "bar.js"],
+ * names: ["src", "maps", "are", "fun"],
+ * mappings: "AAAA,E;;ABCDE;"
+ * }
+ * }],
+ * }
+ *
+ * [0]: https://docs.google.com/document/d/1U1RGAehQwRypUTovF1KRlpiOFze0b-_2gc6fAH0KY0k/edit#heading=h.535es3xeprgt
+ */
+ function IndexedSourceMapConsumer(aSourceMap) {
+ var sourceMap = aSourceMap;
+ if (typeof aSourceMap === 'string') {
+ sourceMap = JSON.parse(aSourceMap.replace(/^\)\]\}'/, ''));
+ }
+
+ var version = util.getArg(sourceMap, 'version');
+ var sections = util.getArg(sourceMap, 'sections');
+
+ if (version != this._version) {
+ throw new Error('Unsupported version: ' + version);
+ }
+
+ this._sources = new ArraySet();
+ this._names = new ArraySet();
+
+ var lastOffset = {
+ line: -1,
+ column: 0
+ };
+ this._sections = sections.map(function (s) {
+ if (s.url) {
+ // The url field will require support for asynchronicity.
+ // See https://github.com/mozilla/source-map/issues/16
+ throw new Error('Support for url field in sections not implemented.');
+ }
+ var offset = util.getArg(s, 'offset');
+ var offsetLine = util.getArg(offset, 'line');
+ var offsetColumn = util.getArg(offset, 'column');
+
+ if (offsetLine < lastOffset.line ||
+ (offsetLine === lastOffset.line && offsetColumn < lastOffset.column)) {
+ throw new Error('Section offsets must be ordered and non-overlapping.');
+ }
+ lastOffset = offset;
+
+ return {
+ generatedOffset: {
+ // The offset fields are 0-based, but we use 1-based indices when
+ // encoding/decoding from VLQ.
+ generatedLine: offsetLine + 1,
+ generatedColumn: offsetColumn + 1
+ },
+ consumer: new SourceMapConsumer(util.getArg(s, 'map'))
+ }
+ });
+ }
+
+ IndexedSourceMapConsumer.prototype = Object.create(SourceMapConsumer.prototype);
+ IndexedSourceMapConsumer.prototype.constructor = SourceMapConsumer;
+
+ /**
+ * The version of the source mapping spec that we are consuming.
+ */
+ IndexedSourceMapConsumer.prototype._version = 3;
+
+ /**
+ * The list of original sources.
+ */
+ Object.defineProperty(IndexedSourceMapConsumer.prototype, 'sources', {
+ get: function () {
+ var sources = [];
+ for (var i = 0; i < this._sections.length; i++) {
+ for (var j = 0; j < this._sections[i].consumer.sources.length; j++) {
+ sources.push(this._sections[i].consumer.sources[j]);
+ }
+ }
+ return sources;
+ }
+ });
+
+ /**
+ * Returns the original source, line, and column information for the generated
+ * source's line and column positions provided. The only argument is an object
+ * with the following properties:
+ *
+ * - line: The line number in the generated source.
+ * - column: The column number in the generated source.
+ *
+ * and an object is returned with the following properties:
+ *
+ * - source: The original source file, or null.
+ * - line: The line number in the original source, or null.
+ * - column: The column number in the original source, or null.
+ * - name: The original identifier, or null.
+ */
+ IndexedSourceMapConsumer.prototype.originalPositionFor =
+ function IndexedSourceMapConsumer_originalPositionFor(aArgs) {
+ var needle = {
+ generatedLine: util.getArg(aArgs, 'line'),
+ generatedColumn: util.getArg(aArgs, 'column')
+ };
+
+ // Find the section containing the generated position we're trying to map
+ // to an original position.
+ var sectionIndex = binarySearch.search(needle, this._sections,
+ function(needle, section) {
+ var cmp = needle.generatedLine - section.generatedOffset.generatedLine;
+ if (cmp) {
+ return cmp;
+ }
+
+ return (needle.generatedColumn -
+ section.generatedOffset.generatedColumn);
+ });
+ var section = this._sections[sectionIndex];
+
+ if (!section) {
+ return {
+ source: null,
+ line: null,
+ column: null,
+ name: null
+ };
+ }
+
+ return section.consumer.originalPositionFor({
+ line: needle.generatedLine -
+ (section.generatedOffset.generatedLine - 1),
+ column: needle.generatedColumn -
+ (section.generatedOffset.generatedLine === needle.generatedLine
+ ? section.generatedOffset.generatedColumn - 1
+ : 0),
+ bias: aArgs.bias
+ });
+ };
+
+ /**
+ * Return true if we have the source content for every source in the source
+ * map, false otherwise.
+ */
+ IndexedSourceMapConsumer.prototype.hasContentsOfAllSources =
+ function IndexedSourceMapConsumer_hasContentsOfAllSources() {
+ return this._sections.every(function (s) {
+ return s.consumer.hasContentsOfAllSources();
+ });
+ };
+
+ /**
+ * Returns the original source content. The only argument is the url of the
+ * original source file. Returns null if no original source content is
+ * available.
+ */
+ IndexedSourceMapConsumer.prototype.sourceContentFor =
+ function IndexedSourceMapConsumer_sourceContentFor(aSource, nullOnMissing) {
+ for (var i = 0; i < this._sections.length; i++) {
+ var section = this._sections[i];
+
+ var content = section.consumer.sourceContentFor(aSource, true);
+ if (content) {
+ return content;
+ }
+ }
+ if (nullOnMissing) {
+ return null;
+ }
+ else {
+ throw new Error('"' + aSource + '" is not in the SourceMap.');
+ }
+ };
+
+ /**
+ * Returns the generated line and column information for the original source,
+ * line, and column positions provided. The only argument is an object with
+ * the following properties:
+ *
+ * - source: The filename of the original source.
+ * - line: The line number in the original source.
+ * - column: The column number in the original source.
+ *
+ * and an object is returned with the following properties:
+ *
+ * - line: The line number in the generated source, or null.
+ * - column: The column number in the generated source, or null.
+ */
+ IndexedSourceMapConsumer.prototype.generatedPositionFor =
+ function IndexedSourceMapConsumer_generatedPositionFor(aArgs) {
+ for (var i = 0; i < this._sections.length; i++) {
+ var section = this._sections[i];
+
+ // Only consider this section if the requested source is in the list of
+ // sources of the consumer.
+ if (section.consumer.sources.indexOf(util.getArg(aArgs, 'source')) === -1) {
+ continue;
+ }
+ var generatedPosition = section.consumer.generatedPositionFor(aArgs);
+ if (generatedPosition) {
+ var ret = {
+ line: generatedPosition.line +
+ (section.generatedOffset.generatedLine - 1),
+ column: generatedPosition.column +
+ (section.generatedOffset.generatedLine === generatedPosition.line
+ ? section.generatedOffset.generatedColumn - 1
+ : 0)
+ };
+ return ret;
+ }
+ }
+
+ return {
+ line: null,
+ column: null
+ };
+ };
+
+ /**
+ * Parse the mappings in a string in to a data structure which we can easily
+ * query (the ordered arrays in the `this.__generatedMappings` and
+ * `this.__originalMappings` properties).
+ */
+ IndexedSourceMapConsumer.prototype._parseMappings =
+ function IndexedSourceMapConsumer_parseMappings(aStr, aSourceRoot) {
+ this.__generatedMappings = [];
+ this.__originalMappings = [];
+ for (var i = 0; i < this._sections.length; i++) {
+ var section = this._sections[i];
+ var sectionMappings = section.consumer._generatedMappings;
+ for (var j = 0; j < sectionMappings.length; j++) {
+ var mapping = sectionMappings[j];
+
+ var source = section.consumer._sources.at(mapping.source);
+ if (section.consumer.sourceRoot !== null) {
+ source = util.join(section.consumer.sourceRoot, source);
+ }
+ this._sources.add(source);
+ source = this._sources.indexOf(source);
+
+ var name = section.consumer._names.at(mapping.name);
+ this._names.add(name);
+ name = this._names.indexOf(name);
+
+ // The mappings coming from the consumer for the section have
+ // generated positions relative to the start of the section, so we
+ // need to offset them to be relative to the start of the concatenated
+ // generated file.
+ var adjustedMapping = {
+ source: source,
+ generatedLine: mapping.generatedLine +
+ (section.generatedOffset.generatedLine - 1),
+ generatedColumn: mapping.generatedColumn +
+ (section.generatedOffset.generatedLine === mapping.generatedLine
+ ? section.generatedOffset.generatedColumn - 1
+ : 0),
+ originalLine: mapping.originalLine,
+ originalColumn: mapping.originalColumn,
+ name: name
+ };
+
+ this.__generatedMappings.push(adjustedMapping);
+ if (typeof adjustedMapping.originalLine === 'number') {
+ this.__originalMappings.push(adjustedMapping);
+ }
+ }
+ }
+
+ quickSort(this.__generatedMappings, util.compareByGeneratedPositionsDeflated);
+ quickSort(this.__originalMappings, util.compareByOriginalPositions);
+ };
+
+ exports.IndexedSourceMapConsumer = IndexedSourceMapConsumer;
+
+
+/***/ },
+
+/***/ 480:
+/***/ function(module, exports) {
+
+ /* -*- Mode: js; js-indent-level: 2; -*- */
+ /*
+ * Copyright 2011 Mozilla Foundation and contributors
+ * Licensed under the New BSD license. See LICENSE or:
+ * http://opensource.org/licenses/BSD-3-Clause
+ */
+
+ exports.GREATEST_LOWER_BOUND = 1;
+ exports.LEAST_UPPER_BOUND = 2;
+
+ /**
+ * Recursive implementation of binary search.
+ *
+ * @param aLow Indices here and lower do not contain the needle.
+ * @param aHigh Indices here and higher do not contain the needle.
+ * @param aNeedle The element being searched for.
+ * @param aHaystack The non-empty array being searched.
+ * @param aCompare Function which takes two elements and returns -1, 0, or 1.
+ * @param aBias Either 'binarySearch.GREATEST_LOWER_BOUND' or
+ * 'binarySearch.LEAST_UPPER_BOUND'. Specifies whether to return the
+ * closest element that is smaller than or greater than the one we are
+ * searching for, respectively, if the exact element cannot be found.
+ */
+ function recursiveSearch(aLow, aHigh, aNeedle, aHaystack, aCompare, aBias) {
+ // This function terminates when one of the following is true:
+ //
+ // 1. We find the exact element we are looking for.
+ //
+ // 2. We did not find the exact element, but we can return the index of
+ // the next-closest element.
+ //
+ // 3. We did not find the exact element, and there is no next-closest
+ // element than the one we are searching for, so we return -1.
+ var mid = Math.floor((aHigh - aLow) / 2) + aLow;
+ var cmp = aCompare(aNeedle, aHaystack[mid], true);
+ if (cmp === 0) {
+ // Found the element we are looking for.
+ return mid;
+ }
+ else if (cmp > 0) {
+ // Our needle is greater than aHaystack[mid].
+ if (aHigh - mid > 1) {
+ // The element is in the upper half.
+ return recursiveSearch(mid, aHigh, aNeedle, aHaystack, aCompare, aBias);
+ }
+
+ // The exact needle element was not found in this haystack. Determine if
+ // we are in termination case (3) or (2) and return the appropriate thing.
+ if (aBias == exports.LEAST_UPPER_BOUND) {
+ return aHigh < aHaystack.length ? aHigh : -1;
+ } else {
+ return mid;
+ }
+ }
+ else {
+ // Our needle is less than aHaystack[mid].
+ if (mid - aLow > 1) {
+ // The element is in the lower half.
+ return recursiveSearch(aLow, mid, aNeedle, aHaystack, aCompare, aBias);
+ }
+
+ // we are in termination case (3) or (2) and return the appropriate thing.
+ if (aBias == exports.LEAST_UPPER_BOUND) {
+ return mid;
+ } else {
+ return aLow < 0 ? -1 : aLow;
+ }
+ }
+ }
+
+ /**
+ * This is an implementation of binary search which will always try and return
+ * the index of the closest element if there is no exact hit. This is because
+ * mappings between original and generated line/col pairs are single points,
+ * and there is an implicit region between each of them, so a miss just means
+ * that you aren't on the very start of a region.
+ *
+ * @param aNeedle The element you are looking for.
+ * @param aHaystack The array that is being searched.
+ * @param aCompare A function which takes the needle and an element in the
+ * array and returns -1, 0, or 1 depending on whether the needle is less
+ * than, equal to, or greater than the element, respectively.
+ * @param aBias Either 'binarySearch.GREATEST_LOWER_BOUND' or
+ * 'binarySearch.LEAST_UPPER_BOUND'. Specifies whether to return the
+ * closest element that is smaller than or greater than the one we are
+ * searching for, respectively, if the exact element cannot be found.
+ * Defaults to 'binarySearch.GREATEST_LOWER_BOUND'.
+ */
+ exports.search = function search(aNeedle, aHaystack, aCompare, aBias) {
+ if (aHaystack.length === 0) {
+ return -1;
+ }
+
+ var index = recursiveSearch(-1, aHaystack.length, aNeedle, aHaystack,
+ aCompare, aBias || exports.GREATEST_LOWER_BOUND);
+ if (index < 0) {
+ return -1;
+ }
+
+ // We have found either the exact element, or the next-closest element than
+ // the one we are searching for. However, there may be more than one such
+ // element. Make sure we always return the smallest of these.
+ while (index - 1 >= 0) {
+ if (aCompare(aHaystack[index], aHaystack[index - 1], true) !== 0) {
+ break;
+ }
+ --index;
+ }
+
+ return index;
+ };
+
+
+/***/ },
+
+/***/ 481:
+/***/ function(module, exports) {
+
+ /* -*- Mode: js; js-indent-level: 2; -*- */
+ /*
+ * Copyright 2011 Mozilla Foundation and contributors
+ * Licensed under the New BSD license. See LICENSE or:
+ * http://opensource.org/licenses/BSD-3-Clause
+ */
+
+ // It turns out that some (most?) JavaScript engines don't self-host
+ // `Array.prototype.sort`. This makes sense because C++ will likely remain
+ // faster than JS when doing raw CPU-intensive sorting. However, when using a
+ // custom comparator function, calling back and forth between the VM's C++ and
+ // JIT'd JS is rather slow *and* loses JIT type information, resulting in
+ // worse generated code for the comparator function than would be optimal. In
+ // fact, when sorting with a comparator, these costs outweigh the benefits of
+ // sorting in C++. By using our own JS-implemented Quick Sort (below), we get
+ // a ~3500ms mean speed-up in `bench/bench.html`.
+
+ /**
+ * Swap the elements indexed by `x` and `y` in the array `ary`.
+ *
+ * @param {Array} ary
+ * The array.
+ * @param {Number} x
+ * The index of the first item.
+ * @param {Number} y
+ * The index of the second item.
+ */
+ function swap(ary, x, y) {
+ var temp = ary[x];
+ ary[x] = ary[y];
+ ary[y] = temp;
+ }
+
+ /**
+ * Returns a random integer within the range `low .. high` inclusive.
+ *
+ * @param {Number} low
+ * The lower bound on the range.
+ * @param {Number} high
+ * The upper bound on the range.
+ */
+ function randomIntInRange(low, high) {
+ return Math.round(low + (Math.random() * (high - low)));
+ }
+
+ /**
+ * The Quick Sort algorithm.
+ *
+ * @param {Array} ary
+ * An array to sort.
+ * @param {function} comparator
+ * Function to use to compare two items.
+ * @param {Number} p
+ * Start index of the array
+ * @param {Number} r
+ * End index of the array
+ */
+ function doQuickSort(ary, comparator, p, r) {
+ // If our lower bound is less than our upper bound, we (1) partition the
+ // array into two pieces and (2) recurse on each half. If it is not, this is
+ // the empty array and our base case.
+
+ if (p < r) {
+ // (1) Partitioning.
+ //
+ // The partitioning chooses a pivot between `p` and `r` and moves all
+ // elements that are less than or equal to the pivot to the before it, and
+ // all the elements that are greater than it after it. The effect is that
+ // once partition is done, the pivot is in the exact place it will be when
+ // the array is put in sorted order, and it will not need to be moved
+ // again. This runs in O(n) time.
+
+ // Always choose a random pivot so that an input array which is reverse
+ // sorted does not cause O(n^2) running time.
+ var pivotIndex = randomIntInRange(p, r);
+ var i = p - 1;
+
+ swap(ary, pivotIndex, r);
+ var pivot = ary[r];
+
+ // Immediately after `j` is incremented in this loop, the following hold
+ // true:
+ //
+ // * Every element in `ary[p .. i]` is less than or equal to the pivot.
+ //
+ // * Every element in `ary[i+1 .. j-1]` is greater than the pivot.
+ for (var j = p; j < r; j++) {
+ if (comparator(ary[j], pivot) <= 0) {
+ i += 1;
+ swap(ary, i, j);
+ }
+ }
+
+ swap(ary, i + 1, j);
+ var q = i + 1;
+
+ // (2) Recurse on each half.
+
+ doQuickSort(ary, comparator, p, q - 1);
+ doQuickSort(ary, comparator, q + 1, r);
+ }
+ }
+
+ /**
+ * Sort the given array in-place with the given comparator function.
+ *
+ * @param {Array} ary
+ * An array to sort.
+ * @param {function} comparator
+ * Function to use to compare two items.
+ */
+ exports.quickSort = function (ary, comparator) {
+ doQuickSort(ary, comparator, 0, ary.length - 1);
+ };
+
+
+/***/ },
+
+/***/ 482:
+/***/ function(module, exports, __webpack_require__) {
+
+ /* -*- Mode: js; js-indent-level: 2; -*- */
+ /*
+ * Copyright 2011 Mozilla Foundation and contributors
+ * Licensed under the New BSD license. See LICENSE or:
+ * http://opensource.org/licenses/BSD-3-Clause
+ */
+
+ var SourceMapGenerator = __webpack_require__(473).SourceMapGenerator;
+ var util = __webpack_require__(476);
+
+ // Matches a Windows-style `\r\n` newline or a `\n` newline used by all other
+ // operating systems these days (capturing the result).
+ var REGEX_NEWLINE = /(\r?\n)/;
+
+ // Newline character code for charCodeAt() comparisons
+ var NEWLINE_CODE = 10;
+
+ // Private symbol for identifying `SourceNode`s when multiple versions of
+ // the source-map library are loaded. This MUST NOT CHANGE across
+ // versions!
+ var isSourceNode = "$$$isSourceNode$$$";
+
+ /**
+ * SourceNodes provide a way to abstract over interpolating/concatenating
+ * snippets of generated JavaScript source code while maintaining the line and
+ * column information associated with the original source code.
+ *
+ * @param aLine The original line number.
+ * @param aColumn The original column number.
+ * @param aSource The original source's filename.
+ * @param aChunks Optional. An array of strings which are snippets of
+ * generated JS, or other SourceNodes.
+ * @param aName The original identifier.
+ */
+ function SourceNode(aLine, aColumn, aSource, aChunks, aName) {
+ this.children = [];
+ this.sourceContents = {};
+ this.line = aLine == null ? null : aLine;
+ this.column = aColumn == null ? null : aColumn;
+ this.source = aSource == null ? null : aSource;
+ this.name = aName == null ? null : aName;
+ this[isSourceNode] = true;
+ if (aChunks != null) this.add(aChunks);
+ }
+
+ /**
+ * Creates a SourceNode from generated code and a SourceMapConsumer.
+ *
+ * @param aGeneratedCode The generated code
+ * @param aSourceMapConsumer The SourceMap for the generated code
+ * @param aRelativePath Optional. The path that relative sources in the
+ * SourceMapConsumer should be relative to.
+ */
+ SourceNode.fromStringWithSourceMap =
+ function SourceNode_fromStringWithSourceMap(aGeneratedCode, aSourceMapConsumer, aRelativePath) {
+ // The SourceNode we want to fill with the generated code
+ // and the SourceMap
+ var node = new SourceNode();
+
+ // All even indices of this array are one line of the generated code,
+ // while all odd indices are the newlines between two adjacent lines
+ // (since `REGEX_NEWLINE` captures its match).
+ // Processed fragments are removed from this array, by calling `shiftNextLine`.
+ var remainingLines = aGeneratedCode.split(REGEX_NEWLINE);
+ var shiftNextLine = function() {
+ var lineContents = remainingLines.shift();
+ // The last line of a file might not have a newline.
+ var newLine = remainingLines.shift() || "";
+ return lineContents + newLine;
+ };
+
+ // We need to remember the position of "remainingLines"
+ var lastGeneratedLine = 1, lastGeneratedColumn = 0;
+
+ // The generate SourceNodes we need a code range.
+ // To extract it current and last mapping is used.
+ // Here we store the last mapping.
+ var lastMapping = null;
+
+ aSourceMapConsumer.eachMapping(function (mapping) {
+ if (lastMapping !== null) {
+ // We add the code from "lastMapping" to "mapping":
+ // First check if there is a new line in between.
+ if (lastGeneratedLine < mapping.generatedLine) {
+ // Associate first line with "lastMapping"
+ addMappingWithCode(lastMapping, shiftNextLine());
+ lastGeneratedLine++;
+ lastGeneratedColumn = 0;
+ // The remaining code is added without mapping
+ } else {
+ // There is no new line in between.
+ // Associate the code between "lastGeneratedColumn" and
+ // "mapping.generatedColumn" with "lastMapping"
+ var nextLine = remainingLines[0];
+ var code = nextLine.substr(0, mapping.generatedColumn -
+ lastGeneratedColumn);
+ remainingLines[0] = nextLine.substr(mapping.generatedColumn -
+ lastGeneratedColumn);
+ lastGeneratedColumn = mapping.generatedColumn;
+ addMappingWithCode(lastMapping, code);
+ // No more remaining code, continue
+ lastMapping = mapping;
+ return;
+ }
+ }
+ // We add the generated code until the first mapping
+ // to the SourceNode without any mapping.
+ // Each line is added as separate string.
+ while (lastGeneratedLine < mapping.generatedLine) {
+ node.add(shiftNextLine());
+ lastGeneratedLine++;
+ }
+ if (lastGeneratedColumn < mapping.generatedColumn) {
+ var nextLine = remainingLines[0];
+ node.add(nextLine.substr(0, mapping.generatedColumn));
+ remainingLines[0] = nextLine.substr(mapping.generatedColumn);
+ lastGeneratedColumn = mapping.generatedColumn;
+ }
+ lastMapping = mapping;
+ }, this);
+ // We have processed all mappings.
+ if (remainingLines.length > 0) {
+ if (lastMapping) {
+ // Associate the remaining code in the current line with "lastMapping"
+ addMappingWithCode(lastMapping, shiftNextLine());
+ }
+ // and add the remaining lines without any mapping
+ node.add(remainingLines.join(""));
+ }
+
+ // Copy sourcesContent into SourceNode
+ aSourceMapConsumer.sources.forEach(function (sourceFile) {
+ var content = aSourceMapConsumer.sourceContentFor(sourceFile);
+ if (content != null) {
+ if (aRelativePath != null) {
+ sourceFile = util.join(aRelativePath, sourceFile);
+ }
+ node.setSourceContent(sourceFile, content);
+ }
+ });
+
+ return node;
+
+ function addMappingWithCode(mapping, code) {
+ if (mapping === null || mapping.source === undefined) {
+ node.add(code);
+ } else {
+ var source = aRelativePath
+ ? util.join(aRelativePath, mapping.source)
+ : mapping.source;
+ node.add(new SourceNode(mapping.originalLine,
+ mapping.originalColumn,
+ source,
+ code,
+ mapping.name));
+ }
+ }
+ };
+
+ /**
+ * Add a chunk of generated JS to this source node.
+ *
+ * @param aChunk A string snippet of generated JS code, another instance of
+ * SourceNode, or an array where each member is one of those things.
+ */
+ SourceNode.prototype.add = function SourceNode_add(aChunk) {
+ if (Array.isArray(aChunk)) {
+ aChunk.forEach(function (chunk) {
+ this.add(chunk);
+ }, this);
+ }
+ else if (aChunk[isSourceNode] || typeof aChunk === "string") {
+ if (aChunk) {
+ this.children.push(aChunk);
+ }
+ }
+ else {
+ throw new TypeError(
+ "Expected a SourceNode, string, or an array of SourceNodes and strings. Got " + aChunk
+ );
+ }
+ return this;
+ };
+
+ /**
+ * Add a chunk of generated JS to the beginning of this source node.
+ *
+ * @param aChunk A string snippet of generated JS code, another instance of
+ * SourceNode, or an array where each member is one of those things.
+ */
+ SourceNode.prototype.prepend = function SourceNode_prepend(aChunk) {
+ if (Array.isArray(aChunk)) {
+ for (var i = aChunk.length-1; i >= 0; i--) {
+ this.prepend(aChunk[i]);
+ }
+ }
+ else if (aChunk[isSourceNode] || typeof aChunk === "string") {
+ this.children.unshift(aChunk);
+ }
+ else {
+ throw new TypeError(
+ "Expected a SourceNode, string, or an array of SourceNodes and strings. Got " + aChunk
+ );
+ }
+ return this;
+ };
+
+ /**
+ * Walk over the tree of JS snippets in this node and its children. The
+ * walking function is called once for each snippet of JS and is passed that
+ * snippet and the its original associated source's line/column location.
+ *
+ * @param aFn The traversal function.
+ */
+ SourceNode.prototype.walk = function SourceNode_walk(aFn) {
+ var chunk;
+ for (var i = 0, len = this.children.length; i < len; i++) {
+ chunk = this.children[i];
+ if (chunk[isSourceNode]) {
+ chunk.walk(aFn);
+ }
+ else {
+ if (chunk !== '') {
+ aFn(chunk, { source: this.source,
+ line: this.line,
+ column: this.column,
+ name: this.name });
+ }
+ }
+ }
+ };
+
+ /**
+ * Like `String.prototype.join` except for SourceNodes. Inserts `aStr` between
+ * each of `this.children`.
+ *
+ * @param aSep The separator.
+ */
+ SourceNode.prototype.join = function SourceNode_join(aSep) {
+ var newChildren;
+ var i;
+ var len = this.children.length;
+ if (len > 0) {
+ newChildren = [];
+ for (i = 0; i < len-1; i++) {
+ newChildren.push(this.children[i]);
+ newChildren.push(aSep);
+ }
+ newChildren.push(this.children[i]);
+ this.children = newChildren;
+ }
+ return this;
+ };
+
+ /**
+ * Call String.prototype.replace on the very right-most source snippet. Useful
+ * for trimming whitespace from the end of a source node, etc.
+ *
+ * @param aPattern The pattern to replace.
+ * @param aReplacement The thing to replace the pattern with.
+ */
+ SourceNode.prototype.replaceRight = function SourceNode_replaceRight(aPattern, aReplacement) {
+ var lastChild = this.children[this.children.length - 1];
+ if (lastChild[isSourceNode]) {
+ lastChild.replaceRight(aPattern, aReplacement);
+ }
+ else if (typeof lastChild === 'string') {
+ this.children[this.children.length - 1] = lastChild.replace(aPattern, aReplacement);
+ }
+ else {
+ this.children.push(''.replace(aPattern, aReplacement));
+ }
+ return this;
+ };
+
+ /**
+ * Set the source content for a source file. This will be added to the SourceMapGenerator
+ * in the sourcesContent field.
+ *
+ * @param aSourceFile The filename of the source file
+ * @param aSourceContent The content of the source file
+ */
+ SourceNode.prototype.setSourceContent =
+ function SourceNode_setSourceContent(aSourceFile, aSourceContent) {
+ this.sourceContents[util.toSetString(aSourceFile)] = aSourceContent;
+ };
+
+ /**
+ * Walk over the tree of SourceNodes. The walking function is called for each
+ * source file content and is passed the filename and source content.
+ *
+ * @param aFn The traversal function.
+ */
+ SourceNode.prototype.walkSourceContents =
+ function SourceNode_walkSourceContents(aFn) {
+ for (var i = 0, len = this.children.length; i < len; i++) {
+ if (this.children[i][isSourceNode]) {
+ this.children[i].walkSourceContents(aFn);
+ }
+ }
+
+ var sources = Object.keys(this.sourceContents);
+ for (var i = 0, len = sources.length; i < len; i++) {
+ aFn(util.fromSetString(sources[i]), this.sourceContents[sources[i]]);
+ }
+ };
+
+ /**
+ * Return the string representation of this source node. Walks over the tree
+ * and concatenates all the various snippets together to one string.
+ */
+ SourceNode.prototype.toString = function SourceNode_toString() {
+ var str = "";
+ this.walk(function (chunk) {
+ str += chunk;
+ });
+ return str;
+ };
+
+ /**
+ * Returns the string representation of this source node along with a source
+ * map.
+ */
+ SourceNode.prototype.toStringWithSourceMap = function SourceNode_toStringWithSourceMap(aArgs) {
+ var generated = {
+ code: "",
+ line: 1,
+ column: 0
+ };
+ var map = new SourceMapGenerator(aArgs);
+ var sourceMappingActive = false;
+ var lastOriginalSource = null;
+ var lastOriginalLine = null;
+ var lastOriginalColumn = null;
+ var lastOriginalName = null;
+ this.walk(function (chunk, original) {
+ generated.code += chunk;
+ if (original.source !== null
+ && original.line !== null
+ && original.column !== null) {
+ if(lastOriginalSource !== original.source
+ || lastOriginalLine !== original.line
+ || lastOriginalColumn !== original.column
+ || lastOriginalName !== original.name) {
+ map.addMapping({
+ source: original.source,
+ original: {
+ line: original.line,
+ column: original.column
+ },
+ generated: {
+ line: generated.line,
+ column: generated.column
+ },
+ name: original.name
+ });
+ }
+ lastOriginalSource = original.source;
+ lastOriginalLine = original.line;
+ lastOriginalColumn = original.column;
+ lastOriginalName = original.name;
+ sourceMappingActive = true;
+ } else if (sourceMappingActive) {
+ map.addMapping({
+ generated: {
+ line: generated.line,
+ column: generated.column
+ }
+ });
+ lastOriginalSource = null;
+ sourceMappingActive = false;
+ }
+ for (var idx = 0, length = chunk.length; idx < length; idx++) {
+ if (chunk.charCodeAt(idx) === NEWLINE_CODE) {
+ generated.line++;
+ generated.column = 0;
+ // Mappings end at eol
+ if (idx + 1 === length) {
+ lastOriginalSource = null;
+ sourceMappingActive = false;
+ } else if (sourceMappingActive) {
+ map.addMapping({
+ source: original.source,
+ original: {
+ line: original.line,
+ column: original.column
+ },
+ generated: {
+ line: generated.line,
+ column: generated.column
+ },
+ name: original.name
+ });
+ }
+ } else {
+ generated.column++;
+ }
+ }
+ });
+ this.walkSourceContents(function (sourceFile, sourceContent) {
+ map.setSourceContent(sourceFile, sourceContent);
+ });
+
+ return { code: generated.code, map: map };
+ };
+
+ exports.SourceNode = SourceNode;
+
+
+/***/ }
+
+/******/ });
+//# sourceMappingURL=source-map-worker.js.map
diff --git a/devtools/client/debugger/new/styles.css b/devtools/client/debugger/new/styles.css
new file mode 100644
index 000000000..479bee363
--- /dev/null
+++ b/devtools/client/debugger/new/styles.css
@@ -0,0 +1,1724 @@
+:root.theme-light,
+:root .theme-light {
+ --theme-search-overlays-semitransparent: rgba(221, 225, 228, 0.66);
+}
+
+* {
+ box-sizing: border-box;
+}
+
+html,
+body {
+ height: 100%;
+ margin: 0;
+ padding: 0;
+ width: 100%;
+}
+
+#mount {
+ display: flex;
+ height: 100%;
+}
+
+
+::-webkit-scrollbar {
+ width: 8px;
+ height: 8px;
+ background: transparent;
+}
+
+::-webkit-scrollbar-track {
+ border-radius: 8px;
+ background: transparent;
+}
+
+::-webkit-scrollbar-thumb {
+ border-radius: 8px;
+ background: rgba(113,113,113,0.5);
+}
+
+:root.theme-dark .CodeMirror-scrollbar-filler {
+ background: transparent;
+}
+.landing-page {
+ flex: 1;
+ display: flex;
+ width: 100%;
+ height: 100%;
+ flex-direction: row;
+}
+
+.landing-page .sidebar {
+ display: flex;
+ background-color: var(--theme-tab-toolbar-background);
+ width: 200px;
+ height: 100%;
+ flex-direction: column;
+}
+
+.landing-page .sidebar h1 {
+ color: var(--theme-body-color);
+ font-size: 24px;
+ margin: 0;
+ line-height: 30px;
+ font-weight: normal;
+ padding: 40px 20px;
+}
+
+.landing-page .sidebar ul {
+ list-style: none;
+ padding: 0;
+ line-height: 30px;
+ font-size: 18px;
+}
+
+.landing-page .sidebar li {
+ padding: 5px 20px;
+}
+
+.landing-page .sidebar li.selected {
+ background: var(--theme-search-overlays-semitransparent);
+ transition: all 0.25s ease;
+}
+
+.landing-page .sidebar li:hover {
+ background: var(--theme-selection-background);
+ cursor: pointer;
+}
+
+.landing-page .sidebar li a {
+ color: var(--theme-body-color);
+}
+
+.landing-page .sidebar li:hover a {
+ color: var(--theme-selection-color);
+}
+
+.landing-page .panel {
+ display: flex;
+ flex: 1;
+ height: 100%;
+ overflow: auto;
+ flex-direction: column;
+}
+
+.landing-page .panel .title {
+ margin: 20px 40px;
+ width: calc(100% - 80px);
+ font-size: 16px;
+ border-bottom: 1px solid var(--theme-splitter-color);
+ height: 54px;
+}
+
+.landing-page .panel h2 {
+ color: var(--theme-body-color);
+ font-weight: normal;
+}
+
+.landing-page .panel .center {
+ flex: 1;
+ display: flex;
+ flex-direction: column;
+}
+
+.landing-page .panel .center .center-message {
+ margin: 40px;
+ font-size: 16px;
+ line-height: 25px;
+ padding: 10px;
+}
+
+.landing-page .center a {
+ color: var(--theme-highlight-bluegrey);
+ text-decoration: none;
+}
+
+.landing-page .tab-group {
+ margin: 40px;
+}
+
+.landing-page .tab-list {
+ list-style: none;
+ padding: 0px;
+ margin: 0px;
+}
+
+.landing-page .tab {
+ border-bottom: 1px solid var(--theme-splitter-color);
+ padding: 10px;
+ font-family: sans-serif;
+}
+
+.landing-page .tab:hover {
+ background-color: var(--theme-toolbar-background);
+ cursor: pointer;
+}
+
+.landing-page .tab-title {
+ line-height: 25px;
+ font-size: 16px;
+ color: var(--theme-highlight-bluegrey);
+}
+
+.landing-page .tab-url {
+ color: var(--theme-comment);
+}
+
+.landing-page .panel .center .footer-note {
+ flex: 1;
+ padding: 50px;
+ font-size: 14px;
+ color: var(--theme-comment);
+ bottom: 0;
+ position: absolute;
+}
+/* vim:set ts=2 sw=2 sts=2 et: */
+
+/* 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/. */
+
+:root.theme-light,
+:root .theme-light {
+ --theme-search-overlays-semitransparent: rgba(221, 225, 228, 0.66);
+ --theme-faded-tab-color: #7e7e7e;
+}
+
+:root.theme-dark,
+:root .theme-dark {
+ --theme-faded-tab-color: #6e7d8c;
+}
+
+* {
+ box-sizing: border-box;
+}
+
+html,
+body {
+ height: 100%;
+ margin: 0;
+ padding: 0;
+ width: 100%;
+}
+
+#mount {
+ display: flex;
+ height: 100%;
+}
+
+.debugger {
+ display: flex;
+ flex: 1;
+ height: 100%;
+}
+
+.center-pane {
+ display: flex;
+ position: relative;
+ flex: 1;
+ background-color: var(--theme-tab-toolbar-background);
+ overflow: hidden;
+}
+
+.editor-container {
+ display: flex;
+ flex: 1;
+}
+
+.subsettings:hover {
+ cursor: pointer;
+}
+
+.search-container {
+ position: absolute;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ display: flex;
+ z-index: 200;
+ background-color: var(--theme-search-overlays-semitransparent);
+}
+
+.search-container .autocomplete {
+ flex: 1;
+}
+
+.search-container .close-button {
+ width: 16px;
+ margin-top: 25px;
+ margin-right: 20px;
+}
+
+.welcomebox {
+ width: calc(100% - 1px);
+
+ /* Offsetting it by 30px for the sources-header area */
+ height: calc(100% - 30px);
+ position: absolute;
+ top: 30px;
+ left: 0;
+ padding: 50px 0;
+ text-align: center;
+ font-size: 1.25em;
+ color: var(--theme-comment-alt);
+ background-color: var(--theme-tab-toolbar-background);
+ font-weight: lighter;
+ z-index: 100;
+}
+menupopup {
+ position: fixed;
+ z-index: 10000;
+ background: white;
+ border: 1px solid #cccccc;
+ padding: 5px 0;
+ background: #f2f2f2;
+ border-radius: 5px;
+ color: #585858;
+ box-shadow: 0 0 4px 0 rgba(190, 190, 190, 0.8);
+ min-width: 130px;
+}
+
+menuitem {
+ display: block;
+ padding: 0 20px;
+ line-height: 20px;
+ font-weight: 500;
+ font-size: 13px;
+}
+
+menuitem:hover {
+ background: #3780fb;
+ color: white;
+ cursor: pointer;
+}
+
+menuseparator {
+ border-bottom: 1px solid #cacdd3;
+ width: 100%;
+ height: 5px;
+ display: block;
+ margin-bottom: 5px;
+}
+
+#contextmenu-mask.show {
+ position: fixed;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ z-index: 999;
+}
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* 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/. */
+
+.split-box {
+ display: flex;
+ flex: 1;
+ min-width: 0;
+ height: 100%;
+ width: 100%;
+}
+
+.split-box.vert {
+ flex-direction: row;
+}
+
+.split-box.horz {
+ flex-direction: column;
+}
+
+.split-box > .uncontrolled {
+ display: flex;
+ flex: 1;
+ min-width: 0;
+ overflow: auto;
+}
+
+.split-box > .controlled {
+ display: flex;
+ overflow: auto;
+}
+
+.split-box > .splitter {
+ background-image: none;
+ border: 0;
+ border-style: solid;
+ border-color: transparent;
+ background-color: var(--theme-splitter-color);
+ background-clip: content-box;
+ position: relative;
+
+ box-sizing: border-box;
+
+ /* Positive z-index positions the splitter on top of its siblings and makes
+ it clickable on both sides. */
+ z-index: 1;
+}
+
+.split-box.vert > .splitter {
+ min-width: calc(var(--devtools-splitter-inline-start-width) +
+ var(--devtools-splitter-inline-end-width) + 1px);
+
+ border-left-width: var(--devtools-splitter-inline-start-width);
+ border-right-width: var(--devtools-splitter-inline-end-width);
+
+ margin-left: calc(-1 * var(--devtools-splitter-inline-start-width) - 1px);
+ margin-right: calc(-1 * var(--devtools-splitter-inline-end-width));
+
+ cursor: ew-resize;
+}
+
+.split-box.horz > .splitter {
+ min-height: calc(var(--devtools-splitter-top-width) +
+ var(--devtools-splitter-bottom-width) + 1px);
+
+ border-top-width: var(--devtools-splitter-top-width);
+ border-bottom-width: var(--devtools-splitter-bottom-width);
+
+ margin-top: calc(-1 * var(--devtools-splitter-top-width) - 1px);
+ margin-bottom: calc(-1 * var(--devtools-splitter-bottom-width));
+
+ cursor: ns-resize;
+}
+
+.split-box.disabled {
+ pointer-events: none;
+}
+
+/**
+ * Make sure splitter panels are not processing any mouse
+ * events. This is good for performance during splitter
+ * bar dragging.
+ */
+.split-box.dragging > .controlled,
+.split-box.dragging > .uncontrolled {
+ pointer-events: none;
+}
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* 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/. */
+
+.theme-dark,
+.theme-light {
+ --number-color: var(--theme-highlight-green);
+ --string-color: var(--theme-highlight-orange);
+ --null-color: var(--theme-comment);
+ --object-color: var(--theme-body-color);
+ --caption-color: var(--theme-highlight-blue);
+ --location-color: var(--theme-content-color1);
+ --source-link-color: var(--theme-highlight-blue);
+ --node-color: var(--theme-highlight-bluegrey);
+ --reference-color: var(--theme-highlight-purple);
+}
+
+.theme-firebug {
+ --number-color: #000088;
+ --string-color: #FF0000;
+ --null-color: #787878;
+ --object-color: DarkGreen;
+ --caption-color: #444444;
+ --location-color: #555555;
+ --source-link-color: blue;
+ --node-color: rgb(0, 0, 136);
+ --reference-color: rgb(102, 102, 255);
+}
+
+/******************************************************************************/
+
+.objectLink:hover {
+ cursor: pointer;
+ text-decoration: underline;
+}
+
+.inline {
+ display: inline;
+ white-space: normal;
+}
+
+.objectBox-object {
+ font-weight: bold;
+ color: var(--object-color);
+ white-space: pre-wrap;
+}
+
+.objectBox-string,
+.objectBox-text,
+.objectLink-textNode,
+.objectBox-table {
+ white-space: pre-wrap;
+}
+
+.objectBox-number,
+.objectLink-styleRule,
+.objectLink-element,
+.objectLink-textNode,
+.objectBox-array > .length {
+ color: var(--number-color);
+}
+
+.objectBox-string {
+ color: var(--string-color);
+}
+
+.objectLink-function,
+.objectBox-stackTrace,
+.objectLink-profile {
+ color: var(--object-color);
+}
+
+.objectLink-Location {
+ font-style: italic;
+ color: var(--location-color);
+}
+
+.objectBox-null,
+.objectBox-undefined,
+.objectBox-hint,
+.logRowHint {
+ font-style: italic;
+ color: var(--null-color);
+}
+
+.objectLink-sourceLink {
+ position: absolute;
+ right: 4px;
+ top: 2px;
+ padding-left: 8px;
+ font-weight: bold;
+ color: var(--source-link-color);
+}
+
+/******************************************************************************/
+
+.objectLink-event,
+.objectLink-eventLog,
+.objectLink-regexp,
+.objectLink-object,
+.objectLink-Date {
+ font-weight: bold;
+ color: var(--object-color);
+ white-space: pre-wrap;
+}
+
+/******************************************************************************/
+
+.objectLink-object .nodeName,
+.objectLink-NamedNodeMap .nodeName,
+.objectLink-NamedNodeMap .objectEqual,
+.objectLink-NamedNodeMap .arrayLeftBracket,
+.objectLink-NamedNodeMap .arrayRightBracket,
+.objectLink-Attr .attrEqual,
+.objectLink-Attr .attrTitle {
+ color: var(--node-color);
+}
+
+.objectLink-object .nodeName {
+ font-weight: normal;
+}
+
+/******************************************************************************/
+
+.objectLeftBrace,
+.objectRightBrace,
+.arrayLeftBracket,
+.arrayRightBracket {
+ cursor: pointer;
+ font-weight: bold;
+}
+
+.objectLeftBrace,
+.arrayLeftBracket {
+ margin-right: 4px;
+}
+
+.objectRightBrace,
+.arrayRightBracket {
+ margin-left: 4px;
+}
+
+/******************************************************************************/
+/* Cycle reference*/
+
+.objectLink-Reference {
+ font-weight: bold;
+ color: var(--reference-color);
+}
+
+.objectBox-array > .objectTitle {
+ font-weight: bold;
+ color: var(--object-color);
+}
+
+.caption {
+ font-weight: bold;
+ color: var(--caption-color);
+}
+
+/******************************************************************************/
+/* Themes */
+
+.theme-dark .objectBox-null,
+.theme-dark .objectBox-undefined,
+.theme-light .objectBox-null,
+.theme-light .objectBox-undefined {
+ font-style: normal;
+}
+
+.theme-dark .objectBox-object,
+.theme-light .objectBox-object {
+ font-weight: normal;
+ white-space: pre-wrap;
+}
+
+.theme-dark .caption,
+.theme-light .caption {
+ font-weight: normal;
+}
+/* vim:set ts=2 sw=2 sts=2 et: */
+
+/* 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/. */
+
+.search-container {
+ position: absolute;
+ top: 30px;
+ left: 0;
+ width: calc(100% - 1px);
+ height: calc(100% - 31px);
+ display: flex;
+ z-index: 200;
+ background-color: var(--theme-body-background);
+}
+
+.search-container .autocomplete {
+ flex: 1;
+}
+
+.searchinput-container {
+ display: flex;
+}
+
+.searchinput-container .close-btn-big {
+ border-bottom: 1px solid var(--theme-splitter-color);
+}
+
+.autocomplete {
+ width: 100%;
+}
+
+.autocomplete .results * {
+ -moz-user-select: none;
+ user-select: none;
+}
+
+.autocomplete .results-summary {
+ margin: 10px;
+}
+
+.autocomplete ul {
+ list-style: none;
+ width: 100%;
+ max-height: calc(100% - 32px);
+ margin: 0px;
+ padding: 0px;
+ overflow: auto;
+}
+
+.autocomplete li {
+ border: 2px solid var(--theme-splitter-color);
+ background-color: var(--theme-tab-toolbar-background);
+ padding: 10px;
+ margin: 10px;
+}
+
+.autocomplete li:hover {
+ background: var(--theme-tab-toolbar-background);
+ cursor: pointer;
+}
+
+.autocomplete li.selected {
+ border: 2px solid var(--theme-selection-background);
+}
+
+.autocomplete li .title {
+ line-height: 1.5em;
+ word-break: break-all;
+}
+
+.autocomplete li .subtitle {
+ line-height: 1.5em;
+ color: grey;
+ word-break: break-all;
+}
+
+.autocomplete input {
+ width: 100%;
+ border: none;
+ background-color: var(--theme-body-background);
+ color: var(--theme-comment);
+ border-bottom: 1px solid var(--theme-splitter-color);
+ outline: none;
+ line-height: 30px;
+ font-size: 14px;
+ height: 40px;
+ padding-left: 30px;
+}
+
+.autocomplete input::placeholder {
+ color: var(--theme-body-color-inactive);
+}
+
+.autocomplete .magnifying-glass svg {
+ width: 16px;
+ position: absolute;
+ top: 12px;
+ left: 10px;
+}
+
+.autocomplete.focused .magnifying-glass path,
+.autocomplete.focused .magnifying-glass ellipse {
+ stroke: var(--theme-highlight-blue);
+}
+
+.autocomplete .magnifying-glass path,
+.autocomplete .magnifying-glass ellipse {
+ stroke: var(--theme-splitter-color);
+}
+
+.autocomplete .no-result-msg {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ width: 100%;
+ height: 100%;
+ color: var(--theme-graphs-full-red);
+ font-size: 24px;
+}
+
+.autocomplete .no-result-msg .sad-face {
+ width: 24px;
+ margin-right: 4px;
+ line-height: 0;
+}
+
+.autocomplete .no-result-msg .sad-face svg {
+ fill: var(--theme-graphs-full-red);
+}
+.close-btn path {
+ fill: var(--theme-body-color);
+}
+
+.close-btn .close {
+ cursor: pointer;
+ width: 12px;
+ height: 12px;
+ padding: 2px;
+ text-align: center;
+ margin-top: 2px;
+ line-height: 5px;
+ transition: all 0.25s easeinout;
+}
+
+.close-btn .close svg {
+ width: 6px;
+}
+
+.close-btn .close:hover {
+ background: var(--theme-selection-background);
+ border-radius: 2px;
+}
+
+.close-btn .close:hover path {
+ fill: white;
+}
+
+.close-btn-big {
+ padding: 13px;
+ width: 40px;
+ height: 40px;
+}
+
+.close-btn-big path {
+ fill: var(--theme-body-color);
+}
+
+.close-btn-big .close {
+ cursor: pointer;
+ display: inline-block;
+ padding: 2px;
+ text-align: center;
+ transition: all 0.25s easeinout;
+ line-height: 100%;
+ width: 16px;
+ height: 16px;
+}
+
+.close-btn-big .close svg {
+ width: 9px;
+ height: 9px;
+}
+
+.close-btn-big .close:hover {
+ background: var(--theme-selection-background);
+ border-radius: 2px;
+}
+
+.close-btn-big .close:hover path {
+ fill: white;
+}
+.tree {
+ -webkit-user-select: none;
+ -moz-user-select: none;
+ -ms-user-select: none;
+ -o-user-select: none;
+ user-select: none;
+
+ flex: 1;
+ white-space: nowrap;
+ overflow: auto;
+}
+
+.tree button {
+ display: block;
+}
+
+.tree .node {
+ padding: 2px 5px;
+ position: relative;
+}
+
+.tree .node.focused {
+ color: white;
+ background-color: var(--theme-selection-background);
+}
+
+html:not([dir="rtl"]) .tree .node > div {
+ margin-left: 10px;
+}
+
+html[dir="rtl"] .tree .node > div {
+ margin-right: 10px;
+}
+
+.tree .node.focused svg {
+ fill: white;
+}
+
+.tree-node button {
+ position: fixed;
+}
+.sources-panel {
+ flex: 1;
+ display: flex;
+ flex-direction: column;
+ overflow: hidden;
+}
+
+.sources-panel * {
+ -moz-user-select: none;
+ user-select: none;
+}
+
+.sources-header {
+ height: 30px;
+ border-bottom: 1px solid var(--theme-splitter-color);
+ padding-top: 0px;
+ padding-bottom: 0px;
+ line-height: 30px;
+ font-size: 1.2em;
+ display: flex;
+ align-items: baseline;
+ justify-content: space-between;
+ -moz-user-select: none;
+ user-select: none;
+}
+
+html:not([dir="rtl"]) .sources-header {
+ padding-left: 10px;
+}
+
+html[dir="rtl"] .sources-header {
+ padding-right: 10px;
+}
+
+.sources-header-info {
+ font-size: 12px;
+ color: var(--theme-comment-alt);
+ font-weight: lighter;
+ white-space: nowrap;
+}
+
+html:not([dir="rtl"]) .sources-header-info {
+ padding-right: 10px;
+ float: right;
+}
+
+html[dir="rtl"] .sources-header-info {
+ padding-left: 10px;
+ float: left;
+}
+
+.sources-list {
+ flex: 1;
+ display: flex;
+ overflow: hidden;
+}
+
+.arrow,
+.folder,
+.domain,
+.file,
+.worker {
+ fill: var(--theme-splitter-color);
+}
+
+.domain,
+.file,
+.worker {
+ position: relative;
+ top: 1px;
+}
+
+.worker,
+.folder {
+ position: relative;
+ top: 2px;
+}
+
+.domain svg,
+.folder svg,
+.worker svg {
+ width: 15px;
+}
+
+.file svg {
+ width: 13px;
+}
+
+html:not([dir="rtl"]) .file svg,
+html:not([dir="rtl"]) .domain svg,
+html:not([dir="rtl"]) .folder svg,
+html:not([dir="rtl"]) .worker svg {
+ margin-right: 5px;
+}
+
+html[dir="rtl"] .file svg,
+html[dir="rtl"] .domain svg,
+html[dir="rtl"] .folder svg,
+html[dir="rtl"] .worker svg {
+ margin-left: 5px;
+}
+
+.tree {
+ -webkit-user-select: none;
+ -moz-user-select: none;
+ -ms-user-select: none;
+ -o-user-select: none;
+ user-select: none;
+
+ flex: 1;
+ white-space: nowrap;
+ overflow: auto;
+}
+
+.tree button {
+ display: block;
+}
+
+.tree .node {
+ padding: 2px 5px;
+ position: relative;
+ cursor: pointer;
+}
+
+.tree .node:hover {
+ background: var(--theme-tab-toolbar-background);
+}
+
+.tree .node.focused {
+ color: white;
+ background-color: var(--theme-selection-background);
+}
+
+.tree .node > div {
+ margin-left: 10px;
+}
+
+.tree .node.focused svg {
+ fill: white;
+}
+
+.sources-list .tree-node button {
+ position: fixed;
+}
+
+.source-footer {
+ background: var(--theme-body-background);
+ border-top: 1px solid var(--theme-splitter-color);
+ position: absolute;
+ bottom: 0;
+ left: 0;
+ right: 1px;
+ opacity: 1;
+ z-index: 100;
+ -moz-user-select: none;
+ user-select: none;
+}
+
+html:not([dir="rtl"]) .source-footer .command-bar {
+ float: right;
+}
+
+html[dir="rtl"] .source-footer .command-bar {
+ float: left;
+}
+
+.source-footer .command-bar * {
+ -moz-user-select: none;
+ user-select: none;
+}
+
+.command-bar > span {
+ cursor: pointer;
+ width: 1em;
+ height: 1.1em;
+ display: inline-block;
+ text-align: center;
+ transition: opacity 200ms;
+}
+
+html:not([dir="rtl"]) .command-bar > span {
+ margin-right: 0.7em;
+}
+
+html[dir="rtl"] .command-bar > span {
+ margin-left: 0.7em;
+}
+
+.source-footer .prettyPrint.pretty {
+ stroke: var(--theme-highlight-blue);
+}
+
+.source-footer input:focus {
+ border-color: var(--theme-highlight-blue);
+ outline: none;
+}
+
+.source-footer input {
+ line-height: 16px;
+ margin: 7px;
+ border-radius: 2px;
+ border: 1px solid var(--theme-splitter-color);
+ padding-left: 4px;
+ font-size: 10px;
+}
+.search-bar {
+ width: calc(100% - 1px);
+ height: 40px;
+ background: white;
+ border-bottom: 1px solid var(--theme-splitter-color);
+ display: flex;
+}
+
+.search-bar i {
+ display: block;
+ padding: 13px 0 0 13px;
+ width: 40px;
+}
+
+.search-bar i svg {
+ width: 16px;
+}
+
+.search-bar input {
+ border: none;
+ line-height: 30px;
+ font-size: 14px;
+ background-color: var(--theme-body-background);
+ color: var(--theme-comment);
+ width: calc(100% - 38px);
+ flex: 1;
+}
+
+.search-bar .magnifying-glass {
+ background-color: var(--theme-body-background);
+ width: 40px;
+}
+
+.search-bar .magnifying-glass path,
+.search-bar .magnifying-glass ellipse {
+ stroke: var(--theme-splitter-color);
+}
+
+.search-bar input::placeholder {
+ color: var(--theme-body-color-inactive);
+}
+
+.search-bar input:focus {
+ outline-width: 0;
+}
+
+.search-bar input.empty {
+ color: var(--theme-highlight-orange);
+}
+
+.search-bar .summary {
+ line-height: 40px;
+ padding-right: 10px;
+ color: var(--theme-body-color-inactive);
+}
+/* vim:set ts=2 sw=2 sts=2 et: */
+
+/* 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/. */
+
+/**
+ * There's a known codemirror flex issue with chrome that this addresses.
+ * BUG https://github.com/devtools-html/debugger.html/issues/63
+ */
+.editor-wrapper {
+ position: absolute;
+ height: calc(100% - 31px);
+ width: 100%;
+ top: 30px;
+ left: 0px;
+}
+
+html[dir="rtl"] .editor-mount {
+ direction: ltr;
+}
+
+.editor-wrapper .breakpoints {
+ position: absolute;
+ top: 0;
+ left: 0;
+}
+
+.editor.new-breakpoint svg {
+ fill: var(--theme-selection-background);
+ width: 60px;
+ height: 14px;
+ position: absolute;
+ top: 0px;
+ right: -4px;
+}
+
+.new-breakpoint.has-condition svg {
+ fill: var(--theme-graphs-yellow);
+}
+
+.editor.new-breakpoint.breakpoint-disabled svg {
+ opacity: 0.3;
+}
+
+.CodeMirror {
+ width: 100%;
+ height: 100%;
+}
+
+.editor-wrapper .editor-mount {
+ width: 100%;
+ height: calc(100% - 32px);
+ background-color: var(--theme-body-background);
+}
+
+.search-bar ~ .editor-mount {
+ height: calc(100% - 72px);
+}
+
+.CodeMirror-linenumber {
+ font-size: 11px;
+ line-height: 14px;
+}
+
+/* set the linenumber white when there is a breakpoint */
+.new-breakpoint .CodeMirror-gutter-wrapper .CodeMirror-linenumber {
+ color: white;
+}
+
+/* move the breakpoint below the linenumber */
+.new-breakpoint .CodeMirror-gutter-elt:last-child {
+ z-index: 0;
+}
+
+.editor-wrapper .CodeMirror-line {
+ font-size: 11px;
+ line-height: 14px;
+}
+
+.debug-line .CodeMirror-line {
+ background-color: var(--breakpoint-active-color) !important;
+}
+
+/* Don't display the highlight color since the debug line
+ is already highlighted */
+.debug-line .CodeMirror-activeline-background {
+ display: none;
+}
+
+.highlight-line .CodeMirror-line {
+ animation: fade-highlight-out 1.5s normal forwards;
+}
+
+@keyframes fade-highlight-out {
+ 0% { background-color: var(--theme-highlight-gray); }
+ 100% { background-color: transparent; }
+}
+
+.welcomebox {
+ width: calc(100% - 1px);
+
+ /* Offsetting it by 30px for the sources-header area */
+ height: calc(100% - 30px);
+ position: absolute;
+ top: 30px;
+ left: 0;
+ padding: 50px 0;
+ text-align: center;
+ font-size: 1.25em;
+ color: var(--theme-comment-alt);
+ background-color: var(--theme-tab-toolbar-background);
+ font-weight: lighter;
+ z-index: 100;
+ -moz-user-select: none;
+ user-select: none;
+}
+
+.conditional-breakpoint-panel {
+ cursor: initial;
+ margin: 1em 0;
+ position: relative;
+ background: var(--theme-toolbar-background);
+ border-top: 1px solid var(--theme-splitter-color);
+ border-bottom: 1px solid var(--theme-splitter-color);
+}
+
+.conditional-breakpoint-panel input {
+ margin: 5px 10px;
+ width: calc(100% - 2em);
+ border: none;
+ background: var(--theme-toolbar-background);
+ font-size: 14px;
+ color: var(--theme-comment);
+ line-height: 30px;
+}
+
+.conditional-breakpoint-panel input:focus {
+ outline-width: 0;
+}
+.breakpoints-list * {
+ -moz-user-select: none;
+ user-select: none;
+}
+
+.breakpoints-list .breakpoint {
+ font-size: 12px;
+ color: var(--theme-content-color1);
+ padding: 0.5em 1px;
+ line-height: 1em;
+ position: relative;
+ border-left: 4px solid transparent;
+ transition: all 0.25s ease;
+}
+
+.breakpoints-list .breakpoint:last-of-type {
+ padding-bottom: 0.45em;
+}
+
+.breakpoints-list .breakpoint.paused {
+ background-color: var(--theme-toolbar-background-alt);
+ border-color: var(--breakpoint-active-color);
+}
+
+.breakpoints-list .breakpoint.disabled .breakpoint-label {
+ color: var(--theme-content-color3);
+ transition: color 0.5s linear;
+}
+
+.breakpoints-list .breakpoint:hover {
+ cursor: pointer;
+ background-color: var(--theme-search-overlays-semitransparent);
+}
+
+.breakpoints-list .breakpoint.paused:hover {
+ border-color: var(--breakpoint-active-color-hover);
+}
+
+.breakpoints-list .breakpoint-checkbox {
+ margin-left: 0;
+}
+
+.breakpoints-list .breakpoint-label {
+ display: inline-block;
+ padding-left: 2px;
+ padding-bottom: 4px;
+}
+
+.breakpoints-list .pause-indicator {
+ flex: 0 1 content;
+ order: 3;
+}
+
+.breakpoint-snippet {
+ color: var(--theme-comment);
+ padding-left: 18px;
+}
+
+.breakpoint .close-btn {
+ position: absolute;
+ right: 6px;
+ top: 12px;
+}
+
+.breakpoint .close {
+ display: none;
+}
+
+.breakpoint:hover .close {
+ display: block;
+}
+.input-expression {
+ width: 100%;
+ padding: 5px;
+ margin: 0px;
+ border: none;
+ cursor: hand;
+}
+
+.expression-container {
+ border: 1px;
+ padding: 5px 2px 5px 5px;
+ margin: 1px;
+ width: 100%;
+ color: var(--theme-body-color) !important;
+ background-color: var(--theme-tab-toolbar-background);
+}
+
+.expression-container:hover {
+ background-color: var(--theme-selection-background);
+ color: var(--theme-body-background) !important;
+}
+
+.expression-output-container .close-btn {
+ width: 6px;
+ height: 6px;
+ float: right;
+ margin-right: 6px;
+ display: block;
+ cursor: pointer;
+}
+
+.expression-input {
+ cursor: pointer;
+ max-width: 50%;
+}
+
+.expression-value {
+ overflow-x: scroll;
+ color: var(--theme-content-color2);
+ max-width: 50% !important;
+}
+
+.expression-error {
+ color: var(--theme-highlight-red);
+}
+.arrow svg {
+ fill: var(--theme-splitter-color);
+ margin-top: 3px;
+ transition: transform 0.25s ease;
+ width: 10px;
+}
+
+html:not([dir="rtl"]) .arrow svg {
+ margin-right: 5px;
+ transform: rotate(-90deg);
+}
+
+html[dir="rtl"] .arrow svg {
+ margin-left: 5px;
+ transform: rotate(90deg);
+}
+
+/* TODO (Amit): html is just for specificity. keep it like this? */
+html .arrow.expanded svg {
+ transform: rotate(0deg);
+}
+
+.arrow.hidden {
+ visibility: hidden;
+}
+
+.object-label {
+ color: var(--theme-highlight-blue);
+}
+
+.objectBox-object,
+.objectBox-string,
+.objectBox-text,
+.objectBox-table,
+.objectLink-textNode,
+.objectLink-event,
+.objectLink-eventLog,
+.objectLink-regexp,
+.objectLink-object,
+.objectLink-Date,
+.theme-dark .objectBox-object,
+.theme-light .objectBox-object {
+ white-space: nowrap;
+}
+
+.scopes-list .tree-node {
+ overflow: hidden;
+}
+.frames ul {
+ list-style: none;
+ margin: 0;
+ padding: 0;
+}
+
+.frames ul li {
+ cursor: pointer;
+ padding: 7px 10px 7px 21px;
+ clear: both;
+ overflow: hidden;
+}
+
+/* Style the focused call frame like so:
+.frames ul li:focus {
+ border: 3px solid red;
+}
+*/
+
+.frames ul li * {
+ -moz-user-select: none;
+ user-select: none;
+}
+
+.frames ul li:nth-of-type(2n) {
+ background-color: var(--theme-tab-toolbar-background);
+}
+
+.frames .location {
+ float: right;
+ color: var(--theme-comment);
+ font-weight: lighter;
+}
+
+.frames .title {
+ float: left;
+ text-overflow: ellipsis;
+ overflow: hidden;
+ margin-right: 1em;
+}
+
+.frames ul li.selected,
+.frames ul li.selected .location {
+ background-color: var(--theme-selection-background);
+ color: white;
+}
+
+.show-more {
+ cursor: pointer;
+ text-align: center;
+ padding: 8px 0px;
+ border-top: 1px solid var(--theme-splitter-color);
+ background-color: var(--theme-tab-toolbar-background);
+}
+
+.show-more:hover {
+ background-color: var(--theme-search-overlays-semitransparent);
+}
+.accordion {
+ background-color: var(--theme-body-background);
+ width: 100%;
+}
+
+.accordion ._header {
+ background-color: var(--theme-toolbar-background);
+ border-bottom: 1px solid var(--theme-splitter-color);
+ cursor: pointer;
+ font-size: 12px;
+ padding: 5px;
+ transition: all 0.25s ease;
+ width: 100%;
+
+ -webkit-user-select: none;
+ -moz-user-select: none;
+ -ms-user-select: none;
+ -o-user-select: none;
+ user-select: none;
+}
+
+.accordion ._header:hover {
+ background-color: var(--theme-search-overlays-semitransparent);
+}
+
+.accordion ._header:hover svg {
+ fill: var(--theme-comment-alt);
+}
+
+.accordion ._content {
+ border-bottom: 1px solid var(--theme-splitter-color);
+ font-size: 12px;
+}
+.right-sidebar {
+ display: flex;
+ flex-direction: column;
+ flex: 1;
+ white-space: nowrap;
+}
+
+.right-siderbar * {
+ -moz-user-select: none;
+ user-select: none;
+}
+
+.right-sidebar .accordion {
+ overflow-y: auto;
+ overflow-x: hidden;
+}
+
+.right-sidebar .command-bar {
+ border-bottom: 1px solid var(--theme-splitter-color);
+}
+
+.command-bar {
+ height: 30px;
+}
+
+html:not([dir="rtl"]) .command-bar {
+ padding: 8px 5px 10px 1px;
+}
+
+html[dir="rtl"] .command-bar {
+ padding: 8px 1px 10px 5px;
+}
+
+.command-bar > span {
+ cursor: pointer;
+ width: 16px;
+ height: 17px;
+ display: inline-block;
+ text-align: center;
+ transition: all 0.25s ease;
+}
+
+:root.theme-dark .command-bar > span {
+ fill: var(--theme-body-color);
+}
+
+:root.theme-dark .command-bar > span:hover {
+ fill: var(--theme-selection-color);
+}
+
+html:not([dir="rtl"]) .command-bar > span {
+ margin-right: 0.7em;
+}
+
+html[dir="rtl"] .command-bar > span {
+ margin-left: 0.7em;
+}
+
+.command-bar > span.disabled {
+ opacity: 0.3;
+ cursor: default;
+}
+
+html:not([dir="rtl"]) .command-bar .stepOut {
+ margin-right: 2em;
+}
+
+html[dir="rtl"] .command-bar .stepOut {
+ margin-left: 2em;
+}
+
+.command-bar .subSettings {
+ float: right;
+}
+
+.pane {
+ color: var(--theme-body-color);
+}
+
+.pane .pane-info {
+ font-style: italic;
+ text-align: center;
+ padding: 0.5em;
+ -moz-user-select: none;
+ user-select: none;
+}
+
+.toggleBreakpoints.breakpoints-disabled path {
+ stroke: var(--theme-highlight-blue);
+}
+
+span.pause-exceptions.uncaught {
+ stroke: var(--theme-highlight-purple);
+}
+
+span.pause-exceptions.all {
+ stroke: var(--theme-highlight-blue);
+}
+.source-header {
+ border-bottom: 1px solid var(--theme-splitter-color);
+ height: 30px;
+ flex: 1;
+}
+
+.source-header * {
+ -moz-user-select: none;
+ user-select: none;
+}
+
+.source-tabs {
+ min-width: 50px;
+ max-width: calc(100% - 60px);
+ overflow: hidden;
+ float: left;
+}
+
+.source-header .new-tab-btn {
+ width: 16px;
+ display: inline-block;
+ position: relative;
+ top: 4px;
+ margin: 4px;
+ line-height: 0;
+}
+
+.source-header .new-tab-btn path {
+ fill: var(--theme-splitter-color);
+}
+
+.source-header .new-tab-btn:hover path {
+ fill: var(--theme-comment);
+}
+
+.source-tab {
+ background-color: var(--theme-toolbar-background-alt);
+ color: var(--theme-faded-tab-color);
+ border: 1px solid var(--theme-splitter-color);
+ border-top-left-radius: 2px;
+ border-top-right-radius: 2px;
+ height: 23px;
+ line-height: 20px;
+ display: inline-block;
+ border-bottom: none;
+ position: relative;
+ transition: all 0.25s ease;
+ min-width: 40px;
+ overflow: hidden;
+}
+
+html:not([dir="rtl"]) .source-tab {
+ padding: 2px 20px 2px 12px;
+ margin: 6px 0 0 8px;
+}
+
+html[dir="rtl"] .source-tab {
+ padding: 2px 12px 2px 20px;
+ margin: 6px 8px 0 0;
+}
+
+.source-tab:hover {
+ background: var(--theme-toolbar-background);
+ cursor: pointer;
+}
+
+.source-tab.active {
+ color: var(--theme-body-color);
+ background-color: var(--theme-body-background);
+}
+
+.source-tab path {
+ fill: var(--theme-faded-tab-color);
+}
+
+.source-tab.active path {
+ fill: var(--theme-body-color);
+}
+
+.source-tab .close-btn {
+ position: absolute;
+ top: 3px;
+}
+
+.source-tab .filename {
+ text-overflow: ellipsis;
+ overflow: hidden;
+}
+
+html:not([dir="rtl"]) .source-tab .close-btn {
+ right: 4px;
+}
+
+html[dir="rtl"] .source-tab .close-btn {
+ left: 4px;
+}
+
+.source-tab .close {
+ display: none;
+}
+
+.source-tab:hover .close {
+ display: block;
+}
+.dropdown {
+ background: var(--theme-body-background);
+ border: 1px solid var(--theme-splitter-color);
+ box-shadow: 0 4px 4px 0 var(--theme-search-overlays-semitransparent);
+ max-height: 300px;
+ position: absolute;
+ right: 8px;
+ top: 35px;
+ width: 150px;
+ z-index: 1000;
+}
+
+.dropdown-button {
+ position: absolute;
+ right: 12px;
+ top: 5px;
+ font-size: 16px;
+ color: var(--theme-body-color);
+ cursor: pointer;
+}
+
+.dropdown li {
+ transition: all 0.25s ease;
+ padding: 2px 10px 10px 5px;
+ overflow: hidden;
+ height: 30px;
+ text-overflow: ellipsis;
+}
+
+.dropdown li:hover {
+ background-color: var(--theme-search-overlays-semitransparent);
+ cursor: pointer;
+}
+
+.dropdown ul {
+ list-style: none;
+ line-height: 2em;
+ font-size: 1em;
+ margin: 0;
+ padding: 0;
+}
+
+.dropdown-mask {
+ position: fixed;
+ width: 100%;
+ height: 100%;
+ background: transparent;
+ z-index: 999;
+ left: 0;
+ top: 0;
+}
+
+/*# sourceMappingURL=styles.css.map*/ \ No newline at end of file
diff --git a/devtools/client/debugger/new/test/mochitest/.eslintrc b/devtools/client/debugger/new/test/mochitest/.eslintrc
new file mode 100644
index 000000000..017b921f8
--- /dev/null
+++ b/devtools/client/debugger/new/test/mochitest/.eslintrc
@@ -0,0 +1,80 @@
+{
+ "globals": {
+ "add_task": false,
+ "Assert": false,
+ "BrowserTestUtils": false,
+ "content": false,
+ "ContentTask": false,
+ "ContentTaskUtils": false,
+ "EventUtils": false,
+ "executeSoon": false,
+ "expectUncaughtException": false,
+ "export_assertions": false,
+ "extractJarToTmp": false,
+ "finish": false,
+ "getJar": false,
+ "getRootDirectory": false,
+ "getTestFilePath": false,
+ "gBrowser": false,
+ "gTestPath": false,
+ "info": false,
+ "is": false,
+ "isnot": false,
+ "ok": false,
+ "registerCleanupFunction": false,
+ "requestLongerTimeout": false,
+ "SimpleTest": false,
+ "SpecialPowers": false,
+ "TestUtils": false,
+ "thisTestLeaksUncaughtRejectionsAndShouldBeFixed": false,
+ "todo": false,
+ "todo_is": false,
+ "todo_isnot": false,
+ "waitForClipboard": false,
+ "waitForExplicitFinish": false,
+ "waitForFocus": false,
+
+ // Globals introduced in debugger-specific head.js
+ "promise": false,
+ "BrowserToolboxProcess": false,
+ "OS": false,
+ "waitForNextDispatch": false,
+ "waitForDispatch": false,
+ "waitForThreadEvents": false,
+ "waitForState": false,
+ "waitForElement": false,
+ "waitForPaused": false,
+ "waitForSources": false,
+ "isPaused": false,
+ "assertPausedLocation": false,
+ "assertHighlightLocation": false,
+ "createDebuggerContext": false,
+ "initDebugger": false,
+ "invokeInTab": false,
+ "findSource": false,
+ "findElement": false,
+ "findElementWithSelector": false,
+ "findAllElements": false,
+ "openNewTabAndToolbox": false,
+ "selectSource": false,
+ "stepOver": false,
+ "stepIn": false,
+ "stepOut": false,
+ "resume": false,
+ "reload": false,
+ "navigate": false,
+ "removeBreakpoint": false,
+ "addBreakpoint": false,
+ "toggleCallStack": false,
+ "toggleScopes": false,
+ "isVisibleWithin": false,
+ "clickElement": false,
+ "rightClickElement": false,
+ "selectMenuItem": false,
+ "togglePauseOnExceptions": false,
+ "type": false,
+ "pressKey": false,
+ "EXAMPLE_URL": false,
+ "waitUntil": false
+ }
+}
diff --git a/devtools/client/debugger/new/test/mochitest/browser.ini b/devtools/client/debugger/new/test/mochitest/browser.ini
new file mode 100644
index 000000000..d0e40a4a7
--- /dev/null
+++ b/devtools/client/debugger/new/test/mochitest/browser.ini
@@ -0,0 +1,60 @@
+[DEFAULT]
+tags = devtools
+subsuite = devtools
+skip-if = (os == 'linux' && debug && bits == 32)
+support-files =
+ head.js
+ !/devtools/client/commandline/test/helpers.js
+ !/devtools/client/framework/test/shared-head.js
+ examples/bundle.js
+ examples/bundle.js.map
+ examples/doc-scripts.html
+ examples/doc-script-switching.html
+ examples/doc-exceptions.html
+ examples/doc-iframes.html
+ examples/doc-frames.html
+ examples/doc-debugger-statements.html
+ examples/doc-minified.html
+ examples/doc-sourcemaps.html
+ examples/doc-sourcemap-bogus.html
+ examples/doc-sources.html
+ examples/bogus-map.js
+ examples/entry.js
+ examples/exceptions.js
+ examples/long.js
+ examples/math.min.js
+ examples/nested/nested-source.js
+ examples/opts.js
+ examples/output.js
+ examples/simple1.js
+ examples/simple2.js
+ examples/frames.js
+ examples/script-switching-02.js
+ examples/script-switching-01.js
+ examples/times2.js
+
+[browser_dbg-breaking.js]
+[browser_dbg-breaking-from-console.js]
+[browser_dbg-breakpoints.js]
+[browser_dbg-breakpoints-cond.js]
+[browser_dbg-call-stack.js]
+[browser_dbg-scopes.js]
+[browser_dbg-chrome-create.js]
+[browser_dbg-chrome-debugging.js]
+[browser_dbg-console.js]
+[browser_dbg-debugger-buttons.js]
+[browser_dbg-editor-gutter.js]
+[browser_dbg-editor-mode.js]
+[browser_dbg-editor-select.js]
+[browser_dbg-editor-highlight.js]
+[browser_dbg-iframes.js]
+[browser_dbg_keyboard-shortcuts.js]
+[browser_dbg-pause-exceptions.js]
+[browser_dbg-navigation.js]
+[browser_dbg-pretty-print.js]
+[browser_dbg-pretty-print-paused.js]
+[browser_dbg-searching.js]
+skip-if = true
+[browser_dbg-sourcemaps.js]
+[browser_dbg-sourcemaps-bogus.js]
+[browser_dbg-sources.js] \ No newline at end of file
diff --git a/devtools/client/debugger/new/test/mochitest/browser_dbg-breaking-from-console.js b/devtools/client/debugger/new/test/mochitest/browser_dbg-breaking-from-console.js
new file mode 100644
index 000000000..8005b518d
--- /dev/null
+++ b/devtools/client/debugger/new/test/mochitest/browser_dbg-breaking-from-console.js
@@ -0,0 +1,31 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Tests that `debugger` statements are hit before the debugger even
+// initializes and it properly highlights the right location in the
+// debugger.
+
+add_task(function* () {
+ const url = EXAMPLE_URL + "doc-script-switching.html";
+ const toolbox = yield openNewTabAndToolbox(url, "webconsole");
+
+ // Type "debugger" into console
+ let jsterm = toolbox.getPanel("webconsole").hud.jsterm;
+ jsterm.execute("debugger");
+
+ // Wait for the debugger to be selected and make sure it's paused
+ yield new Promise((resolve) => {
+ toolbox.on("jsdebugger-selected", resolve);
+ });
+ is(toolbox.threadClient.state, "paused");
+
+ // Create a dbg context
+ const dbg = createDebuggerContext(toolbox);
+ const { selectors: { getSelectedSource }, getState } = dbg;
+
+ // Make sure the thread is paused in the right source and location
+ yield waitForDispatch(dbg, "LOAD_SOURCE_TEXT");
+ is(dbg.win.cm.getValue(), "debugger");
+ const source = getSelectedSource(getState()).toJS();
+ assertPausedLocation(dbg, source, 1);
+});
diff --git a/devtools/client/debugger/new/test/mochitest/browser_dbg-breaking.js b/devtools/client/debugger/new/test/mochitest/browser_dbg-breaking.js
new file mode 100644
index 000000000..8994897c4
--- /dev/null
+++ b/devtools/client/debugger/new/test/mochitest/browser_dbg-breaking.js
@@ -0,0 +1,32 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Tests the breakpoints are hit in various situations.
+
+add_task(function* () {
+ const dbg = yield initDebugger("doc-scripts.html");
+ const { selectors: { getSelectedSource }, getState } = dbg;
+
+ // Make sure we can set a top-level breakpoint and it will be hit on
+ // reload.
+ yield addBreakpoint(dbg, "scripts.html", 18);
+ reload(dbg);
+ yield waitForPaused(dbg);
+ assertPausedLocation(dbg, "scripts.html", 18);
+ yield resume(dbg);
+
+ const paused = waitForPaused(dbg);
+
+ // Create an eval script that pauses itself.
+ invokeInTab("doEval");
+
+ yield paused;
+ yield resume(dbg);
+ const source = getSelectedSource(getState()).toJS();
+ ok(!source.url, "It is an eval source");
+
+ yield addBreakpoint(dbg, source, 5);
+ invokeInTab("evaledFunc");
+ yield waitForPaused(dbg);
+ assertPausedLocation(dbg, source, 5);
+});
diff --git a/devtools/client/debugger/new/test/mochitest/browser_dbg-breakpoints-cond.js b/devtools/client/debugger/new/test/mochitest/browser_dbg-breakpoints-cond.js
new file mode 100644
index 000000000..b6f7fb021
--- /dev/null
+++ b/devtools/client/debugger/new/test/mochitest/browser_dbg-breakpoints-cond.js
@@ -0,0 +1,50 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+function findBreakpoint(dbg, url, line) {
+ const { selectors: { getBreakpoint }, getState } = dbg;
+ const source = findSource(dbg, url);
+ return getBreakpoint(getState(), { sourceId: source.id, line });
+}
+
+function setConditionalBreakpoint(dbg, index, condition) {
+ return Task.spawn(function* () {
+ rightClickElement(dbg, "gutter", index);
+ selectMenuItem(dbg, 2);
+ yield waitForElement(dbg, ".conditional-breakpoint-panel input");
+ findElementWithSelector(dbg, ".conditional-breakpoint-panel input").focus();
+ type(dbg, condition);
+ pressKey(dbg, "Enter");
+ });
+}
+
+add_task(function* () {
+ const dbg = yield initDebugger("doc-scripts.html");
+ yield selectSource(dbg, "simple2");
+
+ // Adding a conditional Breakpoint
+ yield setConditionalBreakpoint(dbg, 5, "1");
+ yield waitForDispatch(dbg, "ADD_BREAKPOINT");
+ let bp = findBreakpoint(dbg, "simple2", 5);
+ is(bp.condition, "1", "breakpoint is created with the condition");
+
+ // Editing a conditional Breakpoint
+ yield setConditionalBreakpoint(dbg, 5, "2");
+ yield waitForDispatch(dbg, "SET_BREAKPOINT_CONDITION");
+ bp = findBreakpoint(dbg, "simple2", 5);
+ is(bp.condition, "21", "breakpoint is created with the condition");
+
+ // Removing a conditional breakpoint
+ clickElement(dbg, "gutter", 5);
+ yield waitForDispatch(dbg, "REMOVE_BREAKPOINT");
+ bp = findBreakpoint(dbg, "simple2", 5);
+ is(bp, null, "breakpoint was removed");
+
+ // Adding a condition to a breakpoint
+ clickElement(dbg, "gutter", 5);
+ yield waitForDispatch(dbg, "ADD_BREAKPOINT");
+ yield setConditionalBreakpoint(dbg, 5, "1");
+ bp = findBreakpoint(dbg, "simple2", 5);
+ is(bp.condition, "1", "breakpoint is created with the condition");
+});
+
diff --git a/devtools/client/debugger/new/test/mochitest/browser_dbg-breakpoints.js b/devtools/client/debugger/new/test/mochitest/browser_dbg-breakpoints.js
new file mode 100644
index 000000000..10bf44957
--- /dev/null
+++ b/devtools/client/debugger/new/test/mochitest/browser_dbg-breakpoints.js
@@ -0,0 +1,101 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+function toggleBreakpoint(dbg, index) {
+ const bp = findElement(dbg, "breakpointItem", index);
+ const input = bp.querySelector("input");
+ input.click();
+}
+
+function removeBreakpoint(dbg, index) {
+ return Task.spawn(function* () {
+ const bp = findElement(dbg, "breakpointItem", index);
+ bp.querySelector(".close-btn").click();
+ yield waitForDispatch(dbg, "REMOVE_BREAKPOINT");
+ });
+}
+
+function disableBreakpoint(dbg, index) {
+ return Task.spawn(function* () {
+ toggleBreakpoint(dbg, index);
+ yield waitForDispatch(dbg, "REMOVE_BREAKPOINT");
+ });
+}
+
+function enableBreakpoint(dbg, index) {
+ return Task.spawn(function* () {
+ toggleBreakpoint(dbg, index);
+ yield waitForDispatch(dbg, "ADD_BREAKPOINT");
+ });
+}
+
+function toggleBreakpoints(dbg) {
+ return Task.spawn(function* () {
+ const btn = findElement(dbg, "toggleBreakpoints");
+ btn.click();
+ yield waitForDispatch(dbg, "TOGGLE_BREAKPOINTS");
+ });
+}
+
+function findBreakpoint(dbg, url, line) {
+ const { selectors: { getBreakpoint }, getState } = dbg;
+ const source = findSource(dbg, url);
+ return getBreakpoint(getState(), { sourceId: source.id, line });
+}
+
+function findBreakpoints(dbg) {
+ const { selectors: { getBreakpoints }, getState } = dbg;
+ return getBreakpoints(getState());
+}
+
+add_task(function* () {
+ const dbg = yield initDebugger("doc-scripts.html");
+
+ // Create two breakpoints
+ yield selectSource(dbg, "simple2");
+ yield addBreakpoint(dbg, "simple2", 3);
+ yield addBreakpoint(dbg, "simple2", 5);
+
+ // Disable the first one
+ yield disableBreakpoint(dbg, 1);
+ let bp1 = findBreakpoint(dbg, "simple2", 3);
+ let bp2 = findBreakpoint(dbg, "simple2", 5);
+ is(bp1.disabled, true, "first breakpoint is disabled");
+ is(bp2.disabled, false, "second breakpoint is enabled");
+
+ // Disable and Re-Enable the second one
+ yield disableBreakpoint(dbg, 2);
+ yield enableBreakpoint(dbg, 2);
+ bp2 = findBreakpoint(dbg, "simple2", 5);
+ is(bp2.disabled, false, "second breakpoint is enabled");
+});
+
+// toggle all
+add_task(function* () {
+ const dbg = yield initDebugger("doc-scripts.html");
+
+ // Create two breakpoints
+ yield selectSource(dbg, "simple2");
+ yield addBreakpoint(dbg, "simple2", 3);
+ yield addBreakpoint(dbg, "simple2", 5);
+
+ // Disable all of the breakpoints
+ yield toggleBreakpoints(dbg);
+ let bp1 = findBreakpoint(dbg, "simple2", 3);
+ let bp2 = findBreakpoint(dbg, "simple2", 5);
+ is(bp1.disabled, true, "first breakpoint is disabled");
+ is(bp2.disabled, true, "second breakpoint is disabled");
+
+ // Enable all of the breakpoints
+ yield toggleBreakpoints(dbg);
+ bp1 = findBreakpoint(dbg, "simple2", 3);
+ bp2 = findBreakpoint(dbg, "simple2", 5);
+ is(bp1.disabled, false, "first breakpoint is enabled");
+ is(bp2.disabled, false, "second breakpoint is enabled");
+
+ // Remove the breakpoints
+ yield removeBreakpoint(dbg, 1);
+ yield removeBreakpoint(dbg, 1);
+ const bps = findBreakpoints(dbg);
+ is(bps.size, 0, "breakpoints are removed");
+});
diff --git a/devtools/client/debugger/new/test/mochitest/browser_dbg-call-stack.js b/devtools/client/debugger/new/test/mochitest/browser_dbg-call-stack.js
new file mode 100644
index 000000000..54a401eeb
--- /dev/null
+++ b/devtools/client/debugger/new/test/mochitest/browser_dbg-call-stack.js
@@ -0,0 +1,62 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// checks to see if the frame is selected and the title is correct
+function isFrameSelected(dbg, index, title) {
+ const $frame = findElement(dbg, "frame", index);
+ const frame = dbg.selectors.getSelectedFrame(dbg.getState());
+
+ const elSelected = $frame.classList.contains("selected");
+ const titleSelected = frame.displayName == title;
+
+ return elSelected && titleSelected;
+}
+
+function toggleButton(dbg) {
+ const callStackBody = findElement(dbg, "callStackBody");
+ return callStackBody.querySelector(".show-more");
+}
+
+add_task(function* () {
+ const dbg = yield initDebugger("doc-script-switching.html");
+
+ toggleCallStack(dbg);
+
+ const notPaused = findElement(dbg, "callStackBody").innerText;
+ is(notPaused, "Not Paused", "Not paused message is shown");
+
+ invokeInTab("firstCall");
+ yield waitForPaused(dbg);
+
+ ok(isFrameSelected(dbg, 1, "secondCall"), "the first frame is selected");
+
+ clickElement(dbg, "frame", 2);
+ ok(isFrameSelected(dbg, 2, "firstCall"), "the second frame is selected");
+
+ let button = toggleButton(dbg);
+ ok(!button, "toggle button shouldn't be there");
+});
+
+add_task(function* () {
+ const dbg = yield initDebugger("doc-frames.html");
+
+ toggleCallStack(dbg);
+
+ invokeInTab("startRecursion");
+ yield waitForPaused(dbg);
+
+ ok(isFrameSelected(dbg, 1, "recurseA"), "the first frame is selected");
+
+ // check to make sure that the toggle button isn't there
+ let button = toggleButton(dbg);
+ let frames = findAllElements(dbg, "frames");
+ is(button.innerText, "Expand Rows", "toggle button should be expand");
+ is(frames.length, 7, "There should be at most seven frames");
+
+ button.click();
+
+ button = toggleButton(dbg);
+ frames = findAllElements(dbg, "frames");
+ is(button.innerText, "Collapse Rows", "toggle button should be collapse");
+ is(frames.length, 22, "All of the frames should be shown");
+});
diff --git a/devtools/client/debugger/new/test/mochitest/browser_dbg-chrome-create.js b/devtools/client/debugger/new/test/mochitest/browser_dbg-chrome-create.js
new file mode 100644
index 000000000..a2d88c064
--- /dev/null
+++ b/devtools/client/debugger/new/test/mochitest/browser_dbg-chrome-create.js
@@ -0,0 +1,72 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Tests that a chrome debugger can be created in a new process.
+ */
+
+const { BrowserToolboxProcess } = Cu.import("resource://devtools/client/framework/ToolboxProcess.jsm", {});
+let gProcess = undefined;
+
+function initChromeDebugger() {
+ info("Initializing a chrome debugger process.");
+ return new Promise(resolve => {
+ BrowserToolboxProcess.init(onClose, (event, _process) => {
+ info("Browser toolbox process started successfully.");
+ resolve(_process);
+ });
+ });
+}
+
+function onClose() {
+ ok(!gProcess._dbgProcess.isRunning,
+ "The remote debugger process isn't closed as it should be!");
+ is(gProcess._dbgProcess.exitValue, (Services.appinfo.OS == "WINNT" ? 0 : 256),
+ "The remote debugger process didn't die cleanly.");
+
+ info("process exit value: " + gProcess._dbgProcess.exitValue);
+
+ info("profile path: " + gProcess._dbgProfilePath);
+
+ finish();
+}
+
+registerCleanupFunction(function() {
+ Services.prefs.clearUserPref("devtools.debugger.remote-enabled");
+ gProcess = null;
+});
+
+add_task(function* () {
+ // Windows XP and 8.1 test slaves are terribly slow at this test.
+ requestLongerTimeout(5);
+ Services.prefs.setBoolPref("devtools.debugger.remote-enabled", true);
+
+ gProcess = yield initChromeDebugger();
+
+ ok(gProcess._dbgProcess,
+ "The remote debugger process wasn't created properly!");
+ ok(gProcess._dbgProcess.isRunning,
+ "The remote debugger process isn't running!");
+ is(typeof gProcess._dbgProcess.pid, "number",
+ "The remote debugger process doesn't have a pid (?!)");
+
+ info("process location: " + gProcess._dbgProcess.location);
+ info("process pid: " + gProcess._dbgProcess.pid);
+ info("process name: " + gProcess._dbgProcess.processName);
+ info("process sig: " + gProcess._dbgProcess.processSignature);
+
+ ok(gProcess._dbgProfilePath,
+ "The remote debugger profile wasn't created properly!");
+
+ is(
+ gProcess._dbgProfilePath,
+ OS.Path.join(OS.Constants.Path.profileDir, "chrome_debugger_profile"),
+ "The remote debugger profile isn't where we expect it!"
+ );
+
+ info("profile path: " + gProcess._dbgProfilePath);
+
+ gProcess.close();
+});
diff --git a/devtools/client/debugger/new/test/mochitest/browser_dbg-chrome-debugging.js b/devtools/client/debugger/new/test/mochitest/browser_dbg-chrome-debugging.js
new file mode 100644
index 000000000..3933c919b
--- /dev/null
+++ b/devtools/client/debugger/new/test/mochitest/browser_dbg-chrome-debugging.js
@@ -0,0 +1,88 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Tests that chrome debugging works.
+ */
+
+var gClient, gThreadClient;
+var gNewGlobal = promise.defer();
+var gNewChromeSource = promise.defer();
+
+var { DevToolsLoader } = Cu.import("resource://devtools/shared/Loader.jsm", {});
+var customLoader = new DevToolsLoader();
+customLoader.invisibleToDebugger = true;
+var { DebuggerServer } = customLoader.require("devtools/server/main");
+var { DebuggerClient } = require("devtools/shared/client/main");
+
+function initDebuggerClient() {
+ if (!DebuggerServer.initialized) {
+ DebuggerServer.init();
+ DebuggerServer.addBrowserActors();
+ }
+ DebuggerServer.allowChromeProcess = true;
+
+ let transport = DebuggerServer.connectPipe();
+ return new DebuggerClient(transport);
+}
+
+function attachThread(client, actor) {
+ return new Promise(resolve => {
+ client.attachTab(actor, (response, tabClient) => {
+ tabClient.attachThread(null, (r, threadClient) => {
+ resolve(threadClient);
+ });
+ });
+ });
+}
+
+function onNewGlobal() {
+ ok(true, "Received a new chrome global.");
+ gClient.removeListener("newGlobal", onNewGlobal);
+ gNewGlobal.resolve();
+}
+
+function onNewSource(event, packet) {
+ if (packet.source.url.startsWith("chrome:")) {
+ ok(true, "Received a new chrome source: " + packet.source.url);
+ gThreadClient.removeListener("newSource", onNewSource);
+ gNewChromeSource.resolve();
+ }
+}
+
+function resumeAndCloseConnection() {
+ return new Promise(resolve => {
+ gThreadClient.resume(() => resolve(gClient.close()));
+ });
+}
+
+registerCleanupFunction(function() {
+ gClient = null;
+ gThreadClient = null;
+ gNewGlobal = null;
+ gNewChromeSource = null;
+
+ customLoader = null;
+ DebuggerServer = null;
+});
+
+add_task(function* () {
+ gClient = initDebuggerClient();
+
+ const [type] = yield gClient.connect();
+ is(type, "browser", "Root actor should identify itself as a browser.");
+
+ const response = yield gClient.getProcess();
+ let actor = response.form.actor;
+ gThreadClient = yield attachThread(gClient, actor);
+ gBrowser.selectedTab = gBrowser.addTab("about:mozilla");
+
+ // listen for a new source and global
+ gThreadClient.addListener("newSource", onNewSource);
+ gClient.addListener("newGlobal", onNewGlobal);
+ yield promise.all([ gNewGlobal.promise, gNewChromeSource.promise ]);
+
+ yield resumeAndCloseConnection();
+});
diff --git a/devtools/client/debugger/new/test/mochitest/browser_dbg-console.js b/devtools/client/debugger/new/test/mochitest/browser_dbg-console.js
new file mode 100644
index 000000000..c57103663
--- /dev/null
+++ b/devtools/client/debugger/new/test/mochitest/browser_dbg-console.js
@@ -0,0 +1,34 @@
+// Return a promise with a reference to jsterm, opening the split
+// console if necessary. This cleans up the split console pref so
+// it won't pollute other tests.
+function getSplitConsole(dbg) {
+ const { toolbox, win } = dbg;
+
+ registerCleanupFunction(() => {
+ Services.prefs.clearUserPref("devtools.toolbox.splitconsoleEnabled");
+ });
+
+ if (!win) {
+ win = toolbox.win;
+ }
+
+ if (!toolbox.splitConsole) {
+ EventUtils.synthesizeKey("VK_ESCAPE", {}, win);
+ }
+
+ return new Promise(resolve => {
+ toolbox.getPanelWhenReady("webconsole").then(() => {
+ ok(toolbox.splitConsole, "Split console is shown.");
+ let jsterm = toolbox.getPanel("webconsole").hud.jsterm;
+ resolve(jsterm);
+ });
+ });
+}
+
+add_task(function* () {
+ Services.prefs.setBoolPref("devtools.toolbox.splitconsoleEnabled", true);
+ const dbg = yield initDebugger("doc-script-switching.html");
+
+ yield getSplitConsole(dbg);
+ ok(dbg.toolbox.splitConsole, "Split console is shown.");
+});
diff --git a/devtools/client/debugger/new/test/mochitest/browser_dbg-debugger-buttons.js b/devtools/client/debugger/new/test/mochitest/browser_dbg-debugger-buttons.js
new file mode 100644
index 000000000..0094650bc
--- /dev/null
+++ b/devtools/client/debugger/new/test/mochitest/browser_dbg-debugger-buttons.js
@@ -0,0 +1,54 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+function clickStepOver(dbg) {
+ clickElement(dbg, "stepOver");
+ return waitForPaused(dbg);
+}
+
+function clickStepIn(dbg) {
+ clickElement(dbg, "stepIn");
+ return waitForPaused(dbg);
+}
+
+function clickStepOut(dbg) {
+ clickElement(dbg, "stepOut");
+ return waitForPaused(dbg);
+}
+
+/**
+ * Test debugger buttons
+ * 1. resume
+ * 2. stepOver
+ * 3. stepIn
+ * 4. stepOver to the end of a function
+ * 5. stepUp at the end of a function
+ */
+add_task(function* () {
+ const dbg = yield initDebugger("doc-debugger-statements.html");
+
+ yield reload(dbg);
+ yield waitForPaused(dbg);
+ assertPausedLocation(dbg, "debugger-statements.html", 8);
+
+ // resume
+ clickElement(dbg, "resume");
+ yield waitForPaused(dbg);
+ assertPausedLocation(dbg, "debugger-statements.html", 12);
+
+ // step over
+ yield clickStepOver(dbg);
+ assertPausedLocation(dbg, "debugger-statements.html", 13);
+
+ // step into
+ yield clickStepIn(dbg);
+ assertPausedLocation(dbg, "debugger-statements.html", 18);
+
+ // step over
+ yield clickStepOver(dbg);
+ assertPausedLocation(dbg, "debugger-statements.html", 20);
+
+ // step out
+ yield clickStepOut(dbg);
+ assertPausedLocation(dbg, "debugger-statements.html", 20);
+});
diff --git a/devtools/client/debugger/new/test/mochitest/browser_dbg-editor-gutter.js b/devtools/client/debugger/new/test/mochitest/browser_dbg-editor-gutter.js
new file mode 100644
index 000000000..12a771c31
--- /dev/null
+++ b/devtools/client/debugger/new/test/mochitest/browser_dbg-editor-gutter.js
@@ -0,0 +1,64 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Tests the breakpoint gutter and making sure breakpoint icons exist
+// correctly
+
+// Utilities for interacting with the editor
+function clickGutter(dbg, line) {
+ clickElement(dbg, "gutter", line);
+}
+
+function getLineEl(dbg, line) {
+ const lines = dbg.win.document.querySelectorAll(".CodeMirror-code > div");
+ return lines[line - 1];
+}
+
+function assertEditorBreakpoint(dbg, line, shouldExist) {
+ const exists = !!getLineEl(dbg, line).querySelector(".new-breakpoint");
+ ok(exists === shouldExist,
+ "Breakpoint " + (shouldExist ? "exists" : "does not exist") +
+ " on line " + line);
+}
+
+add_task(function* () {
+ const dbg = yield initDebugger("doc-scripts.html");
+ const { selectors: { getBreakpoints, getBreakpoint }, getState } = dbg;
+ const source = findSource(dbg, "simple1.js");
+
+ yield selectSource(dbg, source.url);
+
+ // Make sure that clicking the gutter creates a breakpoint icon.
+ clickGutter(dbg, 4);
+ yield waitForDispatch(dbg, "ADD_BREAKPOINT");
+ is(getBreakpoints(getState()).size, 1, "One breakpoint exists");
+ assertEditorBreakpoint(dbg, 4, true);
+
+ // Make sure clicking at the same place removes the icon.
+ clickGutter(dbg, 4);
+ yield waitForDispatch(dbg, "REMOVE_BREAKPOINT");
+ is(getBreakpoints(getState()).size, 0, "No breakpoints exist");
+ assertEditorBreakpoint(dbg, 4, false);
+
+ // Test that a breakpoint icon slides down to the correct line.
+ clickGutter(dbg, 2);
+ yield waitForDispatch(dbg, "ADD_BREAKPOINT");
+ is(getBreakpoints(getState()).size, 1, "One breakpoint exists");
+ ok(getBreakpoint(getState(), { sourceId: source.id, line: 4 }),
+ "Breakpoint has correct line");
+ assertEditorBreakpoint(dbg, 2, false);
+ assertEditorBreakpoint(dbg, 4, true);
+
+ // Do the same sliding and make sure it works if there's already a
+ // breakpoint.
+ clickGutter(dbg, 2);
+ yield waitForDispatch(dbg, "ADD_BREAKPOINT");
+ is(getBreakpoints(getState()).size, 1, "One breakpoint exists");
+ assertEditorBreakpoint(dbg, 2, false);
+ assertEditorBreakpoint(dbg, 4, true);
+
+ clickGutter(dbg, 4);
+ yield waitForDispatch(dbg, "REMOVE_BREAKPOINT");
+ is(getBreakpoints(getState()).size, 0, "No breakpoints exist");
+ assertEditorBreakpoint(dbg, 4, false);
+});
diff --git a/devtools/client/debugger/new/test/mochitest/browser_dbg-editor-highlight.js b/devtools/client/debugger/new/test/mochitest/browser_dbg-editor-highlight.js
new file mode 100644
index 000000000..d7892e629
--- /dev/null
+++ b/devtools/client/debugger/new/test/mochitest/browser_dbg-editor-highlight.js
@@ -0,0 +1,46 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Tests that the editor will always highight the right line, no
+// matter if the source text doesn't exist yet or even if the source
+// doesn't exist.
+
+add_task(function* () {
+ const dbg = yield initDebugger("doc-scripts.html");
+ const { selectors: { getSourceText }, getState } = dbg;
+ const sourceUrl = EXAMPLE_URL + "long.js";
+
+ // The source itself doesn't even exist yet, and using
+ // `selectSourceURL` will set a pending request to load this source
+ // and highlight a specific line.
+ dbg.actions.selectSourceURL(sourceUrl, { line: 66 });
+
+ // Wait for the source text to load and make sure we're in the right
+ // place.
+ yield waitForDispatch(dbg, "LOAD_SOURCE_TEXT");
+ assertHighlightLocation(dbg, "long.js", 66);
+
+ // Jump to line 16 and make sure the editor scrolled.
+ yield selectSource(dbg, "long.js", 16);
+ assertHighlightLocation(dbg, "long.js", 16);
+
+ // Make sure only one line is ever highlighted and the flash
+ // animation is cancelled on old lines.
+ yield selectSource(dbg, "long.js", 17);
+ yield selectSource(dbg, "long.js", 18);
+ assertHighlightLocation(dbg, "long.js", 18);
+ is(findAllElements(dbg, "highlightLine").length, 1,
+ "Only 1 line is highlighted");
+
+ // Test jumping to a line in a source that exists but hasn't been
+ // loaded yet.
+ selectSource(dbg, "simple1.js", 6);
+
+ // Make sure the source is in the loading state, wait for it to be
+ // fully loaded, and check the highlighted line.
+ const simple1 = findSource(dbg, "simple1.js");
+ ok(getSourceText(getState(), simple1.id).get("loading"));
+ yield waitForDispatch(dbg, "LOAD_SOURCE_TEXT");
+ ok(getSourceText(getState(), simple1.id).get("text"));
+ assertHighlightLocation(dbg, "simple1.js", 6);
+});
diff --git a/devtools/client/debugger/new/test/mochitest/browser_dbg-editor-mode.js b/devtools/client/debugger/new/test/mochitest/browser_dbg-editor-mode.js
new file mode 100644
index 000000000..2a23aa09f
--- /dev/null
+++ b/devtools/client/debugger/new/test/mochitest/browser_dbg-editor-mode.js
@@ -0,0 +1,14 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Tests that the editor sets the correct mode for different file
+// types
+add_task(function* () {
+ const dbg = yield initDebugger("doc-scripts.html");
+
+ yield selectSource(dbg, "simple1.js");
+ is(dbg.win.cm.getOption("mode").name, "javascript", "Mode is correct");
+
+ yield selectSource(dbg, "doc-scripts.html");
+ is(dbg.win.cm.getOption("mode").name, "htmlmixed", "Mode is correct");
+});
diff --git a/devtools/client/debugger/new/test/mochitest/browser_dbg-editor-select.js b/devtools/client/debugger/new/test/mochitest/browser_dbg-editor-select.js
new file mode 100644
index 000000000..8b954f899
--- /dev/null
+++ b/devtools/client/debugger/new/test/mochitest/browser_dbg-editor-select.js
@@ -0,0 +1,54 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Tests that the editor highlights the correct location when the
+// debugger pauses
+
+// checks to see if the first breakpoint is visible
+function isElementVisible(dbg, elementName) {
+ const bpLine = findElement(dbg, elementName);
+ const cm = findElement(dbg, "codeMirror");
+ return bpLine && isVisibleWithin(cm, bpLine);
+}
+
+add_task(function* () {
+ // This test runs too slowly on linux debug. I'd like to figure out
+ // which is the slowest part of this and make it run faster, but to
+ // fix a frequent failure allow a longer timeout.
+ requestLongerTimeout(2);
+
+ const dbg = yield initDebugger("doc-scripts.html");
+ const { selectors: { getSelectedSource }, getState } = dbg;
+ const simple1 = findSource(dbg, "simple1.js");
+ const simple2 = findSource(dbg, "simple2.js");
+
+ // Set the initial breakpoint.
+ yield addBreakpoint(dbg, simple1, 4);
+ ok(!getSelectedSource(getState()), "No selected source");
+
+ // Call the function that we set a breakpoint in.
+ invokeInTab("main");
+ yield waitForPaused(dbg);
+ assertPausedLocation(dbg, simple1, 4);
+
+ // Step through to another file and make sure it's paused in the
+ // right place.
+ yield stepIn(dbg);
+ assertPausedLocation(dbg, simple2, 2);
+
+ // Step back out to the initial file.
+ yield stepOut(dbg);
+ yield stepOut(dbg);
+ assertPausedLocation(dbg, simple1, 5);
+ yield resume(dbg);
+
+ // Make sure that we can set a breakpoint on a line out of the
+ // viewport, and that pausing there scrolls the editor to it.
+ let longSrc = findSource(dbg, "long.js");
+ yield addBreakpoint(dbg, longSrc, 66);
+
+ invokeInTab("testModel");
+ yield waitForPaused(dbg);
+ assertPausedLocation(dbg, longSrc, 66);
+ ok(isElementVisible(dbg, "breakpoint"), "Breakpoint is visible");
+});
diff --git a/devtools/client/debugger/new/test/mochitest/browser_dbg-iframes.js b/devtools/client/debugger/new/test/mochitest/browser_dbg-iframes.js
new file mode 100644
index 000000000..9039da1be
--- /dev/null
+++ b/devtools/client/debugger/new/test/mochitest/browser_dbg-iframes.js
@@ -0,0 +1,26 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Test debugging a page with iframes
+ * 1. pause in the main thread
+ * 2. pause in the iframe
+ */
+add_task(function* () {
+ const dbg = yield initDebugger("doc-iframes.html");
+
+ // test pausing in the main thread
+ yield reload(dbg);
+ yield waitForPaused(dbg);
+ assertPausedLocation(dbg, "iframes.html", 8);
+
+ // test pausing in the iframe
+ yield resume(dbg);
+ yield waitForPaused(dbg);
+ assertPausedLocation(dbg, "debugger-statements.html", 8);
+
+ // test pausing in the iframe
+ yield resume(dbg);
+ yield waitForPaused(dbg);
+ assertPausedLocation(dbg, "debugger-statements.html", 12);
+});
diff --git a/devtools/client/debugger/new/test/mochitest/browser_dbg-navigation.js b/devtools/client/debugger/new/test/mochitest/browser_dbg-navigation.js
new file mode 100644
index 000000000..381b6b7fd
--- /dev/null
+++ b/devtools/client/debugger/new/test/mochitest/browser_dbg-navigation.js
@@ -0,0 +1,47 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+function countSources(dbg) {
+ const sources = dbg.selectors.getSources(dbg.getState());
+ return sources.size;
+}
+
+/**
+ * Test navigating
+ * navigating while paused will reset the pause state and sources
+ */
+add_task(function* () {
+ const dbg = yield initDebugger("doc-script-switching.html");
+ const { selectors: { getSelectedSource, getPause }, getState } = dbg;
+
+ invokeInTab("firstCall");
+ yield waitForPaused(dbg);
+
+ yield navigate(dbg, "doc-scripts.html", "simple1.js");
+ yield addBreakpoint(dbg, "simple1.js", 4);
+ invokeInTab("main");
+ yield waitForPaused(dbg);
+ assertPausedLocation(dbg, "simple1.js", 4);
+ is(countSources(dbg), 4, "4 sources are loaded.");
+
+ yield navigate(dbg, "about:blank");
+ yield waitForDispatch(dbg, "NAVIGATE");
+ is(countSources(dbg), 0, "0 sources are loaded.");
+ ok(!getPause(getState()), "No pause state exists");
+
+ yield navigate(dbg,
+ "doc-scripts.html",
+ "simple1.js",
+ "simple2.js",
+ "long.js",
+ "scripts.html"
+ );
+
+ is(countSources(dbg), 4, "4 sources are loaded.");
+
+ // Test that the current select source persists across reloads
+ yield selectSource(dbg, "long.js");
+ yield reload(dbg, "long.js");
+ ok(getSelectedSource(getState()).get("url").includes("long.js"),
+ "Selected source is long.js");
+});
diff --git a/devtools/client/debugger/new/test/mochitest/browser_dbg-pause-exceptions.js b/devtools/client/debugger/new/test/mochitest/browser_dbg-pause-exceptions.js
new file mode 100644
index 000000000..133316b54
--- /dev/null
+++ b/devtools/client/debugger/new/test/mochitest/browser_dbg-pause-exceptions.js
@@ -0,0 +1,46 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+function uncaughtException() {
+ return invokeInTab("uncaughtException").catch(() => {});
+}
+
+function caughtException() {
+ return invokeInTab("caughtException");
+}
+
+/*
+ Tests Pausing on exception
+ 1. skip an uncaught exception
+ 2. pause on an uncaught exception
+ 3. pause on a caught error
+ 4. skip a caught error
+*/
+add_task(function* () {
+ const dbg = yield initDebugger("doc-exceptions.html");
+
+ // test skipping an uncaught exception
+ yield togglePauseOnExceptions(dbg, false, false);
+ yield uncaughtException();
+ ok(!isPaused(dbg));
+
+ // Test pausing on an uncaught exception
+ yield togglePauseOnExceptions(dbg, true, false);
+ uncaughtException();
+ yield waitForPaused(dbg);
+ assertPausedLocation(dbg, "exceptions.js", 2);
+ yield resume(dbg);
+
+ // Test pausing on a caught Error
+ caughtException();
+ yield waitForPaused(dbg);
+ assertPausedLocation(dbg, "exceptions.js", 15);
+ yield resume(dbg);
+
+ // Test skipping a caught error
+ yield togglePauseOnExceptions(dbg, true, true);
+ caughtException();
+ yield waitForPaused(dbg);
+ assertPausedLocation(dbg, "exceptions.js", 17);
+ yield resume(dbg);
+});
diff --git a/devtools/client/debugger/new/test/mochitest/browser_dbg-pretty-print-paused.js b/devtools/client/debugger/new/test/mochitest/browser_dbg-pretty-print-paused.js
new file mode 100644
index 000000000..73919e65e
--- /dev/null
+++ b/devtools/client/debugger/new/test/mochitest/browser_dbg-pretty-print-paused.js
@@ -0,0 +1,22 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Tests pretty-printing a source that is currently paused.
+
+add_task(function* () {
+ const dbg = yield initDebugger("doc-minified.html");
+
+ yield selectSource(dbg, "math.min.js");
+ yield addBreakpoint(dbg, "math.min.js", 2);
+
+ invokeInTab("arithmetic");
+ yield waitForPaused(dbg);
+ assertPausedLocation(dbg, "math.min.js", 2);
+
+ clickElement(dbg, "prettyPrintButton");
+ yield waitForDispatch(dbg, "TOGGLE_PRETTY_PRINT");
+
+ assertPausedLocation(dbg, "math.min.js:formatted", 18);
+
+ yield resume(dbg);
+});
diff --git a/devtools/client/debugger/new/test/mochitest/browser_dbg-pretty-print.js b/devtools/client/debugger/new/test/mochitest/browser_dbg-pretty-print.js
new file mode 100644
index 000000000..260bfef38
--- /dev/null
+++ b/devtools/client/debugger/new/test/mochitest/browser_dbg-pretty-print.js
@@ -0,0 +1,31 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Tests basic pretty-printing functionality.
+
+add_task(function* () {
+ const dbg = yield initDebugger("doc-minified.html");
+
+ yield selectSource(dbg, "math.min.js");
+ clickElement(dbg, "prettyPrintButton");
+ yield waitForDispatch(dbg, "TOGGLE_PRETTY_PRINT");
+
+ const ppSrc = findSource(dbg, "math.min.js:formatted");
+ ok(ppSrc, "Pretty-printed source exists");
+
+ yield addBreakpoint(dbg, ppSrc, 18);
+
+ invokeInTab("arithmetic");
+ yield waitForPaused(dbg);
+ assertPausedLocation(dbg, ppSrc, 18);
+ yield stepOver(dbg);
+ assertPausedLocation(dbg, ppSrc, 27);
+ yield resume(dbg);
+
+ // The pretty-print button should go away in the pretty-printed
+ // source.
+ ok(!findElement(dbg, "sourceFooter"), "Footer is hidden");
+
+ yield selectSource(dbg, "math.min.js");
+ ok(findElement(dbg, "sourceFooter"), "Footer is hidden");
+});
diff --git a/devtools/client/debugger/new/test/mochitest/browser_dbg-scopes.js b/devtools/client/debugger/new/test/mochitest/browser_dbg-scopes.js
new file mode 100644
index 000000000..adb99be84
--- /dev/null
+++ b/devtools/client/debugger/new/test/mochitest/browser_dbg-scopes.js
@@ -0,0 +1,27 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+function toggleNode(dbg, index) {
+ clickElement(dbg, "scopeNode", index);
+}
+
+function getLabel(dbg, index) {
+ return findElement(dbg, "scopeNode", index).innerText;
+}
+
+add_task(function* () {
+ const dbg = yield initDebugger("doc-script-switching.html");
+
+ toggleScopes(dbg);
+
+ invokeInTab("firstCall");
+ yield waitForPaused(dbg);
+
+ toggleNode(dbg, 1);
+ toggleNode(dbg, 2);
+
+ yield waitForDispatch(dbg, "LOAD_OBJECT_PROPERTIES");
+
+ is(getLabel(dbg, 1), "secondCall");
+ is(getLabel(dbg, 2), "<this>");
+});
diff --git a/devtools/client/debugger/new/test/mochitest/browser_dbg-searching.js b/devtools/client/debugger/new/test/mochitest/browser_dbg-searching.js
new file mode 100644
index 000000000..dd25e2b54
--- /dev/null
+++ b/devtools/client/debugger/new/test/mochitest/browser_dbg-searching.js
@@ -0,0 +1,28 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Testing source search
+add_task(function* () {
+ const dbg = yield initDebugger("doc-script-switching.html");
+
+ pressKey(dbg, "sourceSearch");
+ yield waitForElement(dbg, "input");
+ findElementWithSelector(dbg, "input").focus();
+ type(dbg, "sw");
+ pressKey(dbg, "Enter");
+
+ yield waitForDispatch(dbg, "LOAD_SOURCE_TEXT");
+ let source = dbg.selectors.getSelectedSource(dbg.getState());
+ ok(source.get("url").match(/switching-01/), "first source is selected");
+
+ // 2. arrow keys and check to see if source is selected
+ pressKey(dbg, "sourceSearch");
+ findElementWithSelector(dbg, "input").focus();
+ type(dbg, "sw");
+ pressKey(dbg, "Down");
+ pressKey(dbg, "Enter");
+
+ yield waitForDispatch(dbg, "LOAD_SOURCE_TEXT");
+ source = dbg.selectors.getSelectedSource(dbg.getState());
+ ok(source.get("url").match(/switching-02/), "second source is selected");
+});
diff --git a/devtools/client/debugger/new/test/mochitest/browser_dbg-sourcemaps-bogus.js b/devtools/client/debugger/new/test/mochitest/browser_dbg-sourcemaps-bogus.js
new file mode 100644
index 000000000..e8c6070fc
--- /dev/null
+++ b/devtools/client/debugger/new/test/mochitest/browser_dbg-sourcemaps-bogus.js
@@ -0,0 +1,23 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Test that an error while loading a sourcemap does not break
+// debugging.
+
+add_task(function* () {
+ const dbg = yield initDebugger("doc-sourcemap-bogus.html");
+ const { selectors: { getSources }, getState } = dbg;
+
+ yield selectSource(dbg, "bogus-map.js");
+
+ // We should still be able to set breakpoints and pause in the
+ // generated source.
+ yield addBreakpoint(dbg, "bogus-map.js", 4);
+ invokeInTab("runCode");
+ yield waitForPaused(dbg);
+ assertPausedLocation(dbg, "bogus-map.js", 4);
+
+ // Make sure that only the single generated source exists. The
+ // sourcemap failed to download.
+ is(getSources(getState()).size, 1, "Only 1 source exists");
+});
diff --git a/devtools/client/debugger/new/test/mochitest/browser_dbg-sourcemaps.js b/devtools/client/debugger/new/test/mochitest/browser_dbg-sourcemaps.js
new file mode 100644
index 000000000..30fd7b70c
--- /dev/null
+++ b/devtools/client/debugger/new/test/mochitest/browser_dbg-sourcemaps.js
@@ -0,0 +1,44 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Tests loading sourcemapped sources, setting breakpoints, and
+// stepping in them.
+
+add_task(function* () {
+ const dbg = yield initDebugger("doc-sourcemaps.html");
+ const { selectors: { getBreakpoint, getBreakpoints }, getState } = dbg;
+
+ yield waitForSources(dbg, "entry.js", "output.js", "times2.js", "opts.js");
+ ok(true, "Original sources exist");
+ const entrySrc = findSource(dbg, "entry.js");
+
+ yield selectSource(dbg, entrySrc);
+ ok(dbg.win.cm.getValue().includes("window.keepMeAlive"),
+ "Original source text loaded correctly");
+
+ // Test that breakpoint sliding is not attempted. The breakpoint
+ // should not move anywhere.
+ yield addBreakpoint(dbg, entrySrc, 13);
+ is(getBreakpoints(getState()).size, 1, "One breakpoint exists");
+ ok(getBreakpoint(getState(), { sourceId: entrySrc.id, line: 13 }),
+ "Breakpoint has correct line");
+
+ // Test breaking on a breakpoint
+ yield addBreakpoint(dbg, "entry.js", 15);
+ is(getBreakpoints(getState()).size, 2, "Two breakpoints exist");
+ ok(getBreakpoint(getState(), { sourceId: entrySrc.id, line: 15 }),
+ "Breakpoint has correct line");
+
+ invokeInTab("keepMeAlive");
+ yield waitForPaused(dbg);
+ assertPausedLocation(dbg, entrySrc, 15);
+
+ yield stepIn(dbg);
+ assertPausedLocation(dbg, "times2.js", 2);
+ yield stepOver(dbg);
+ assertPausedLocation(dbg, "times2.js", 3);
+
+ yield stepOut(dbg);
+ yield stepOut(dbg);
+ assertPausedLocation(dbg, "entry.js", 16);
+});
diff --git a/devtools/client/debugger/new/test/mochitest/browser_dbg-sources.js b/devtools/client/debugger/new/test/mochitest/browser_dbg-sources.js
new file mode 100644
index 000000000..64b7f56ae
--- /dev/null
+++ b/devtools/client/debugger/new/test/mochitest/browser_dbg-sources.js
@@ -0,0 +1,58 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Tests that the source tree works.
+
+function* waitForSourceCount(dbg, i) {
+ // We are forced to wait until the DOM nodes appear because the
+ // source tree batches its rendering.
+ yield waitUntil(() => {
+ return findAllElements(dbg, "sourceNodes").length === i;
+ });
+}
+
+add_task(function* () {
+ const dbg = yield initDebugger("doc-sources.html");
+ const { selectors: { getSelectedSource }, getState } = dbg;
+
+ // Expand nodes and make sure more sources appear.
+ is(findAllElements(dbg, "sourceNodes").length, 2);
+
+ clickElement(dbg, "sourceArrow", 2);
+ is(findAllElements(dbg, "sourceNodes").length, 7);
+
+ clickElement(dbg, "sourceArrow", 3);
+ is(findAllElements(dbg, "sourceNodes").length, 8);
+
+ // Select a source.
+ ok(!findElementWithSelector(dbg, ".sources-list .focused"),
+ "Source is not focused");
+ const selected = waitForDispatch(dbg, "SELECT_SOURCE");
+ clickElement(dbg, "sourceNode", 4);
+ yield selected;
+ ok(findElementWithSelector(dbg, ".sources-list .focused"),
+ "Source is focused");
+ ok(getSelectedSource(getState()).get("url").includes("nested-source.js"),
+ "The right source is selected");
+
+ // Make sure new sources appear in the list.
+ ContentTask.spawn(gBrowser.selectedBrowser, null, function() {
+ const script = content.document.createElement("script");
+ script.src = "math.min.js";
+ content.document.body.appendChild(script);
+ });
+
+ yield waitForSourceCount(dbg, 9);
+ is(findElement(dbg, "sourceNode", 7).querySelector("span").innerText,
+ "math.min.js",
+ "The dynamic script exists");
+
+ // Make sure named eval sources appear in the list.
+ ContentTask.spawn(gBrowser.selectedBrowser, null, function() {
+ content.eval("window.evaledFunc = function() {} //# sourceURL=evaled.js");
+ });
+ yield waitForSourceCount(dbg, 11);
+ is(findElement(dbg, "sourceNode", 2).querySelector("span").innerText,
+ "evaled.js",
+ "The eval script exists");
+});
diff --git a/devtools/client/debugger/new/test/mochitest/browser_dbg_keyboard-shortcuts.js b/devtools/client/debugger/new/test/mochitest/browser_dbg_keyboard-shortcuts.js
new file mode 100644
index 000000000..0d7e572ef
--- /dev/null
+++ b/devtools/client/debugger/new/test/mochitest/browser_dbg_keyboard-shortcuts.js
@@ -0,0 +1,46 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Test keyboard shortcuts.
+ */
+
+function pressResume(dbg) {
+ pressKey(dbg, "resumeKey");
+ return waitForPaused(dbg);
+}
+
+function pressStepOver(dbg) {
+ pressKey(dbg, "stepOverKey");
+ return waitForPaused(dbg);
+}
+
+function pressStepIn(dbg) {
+ pressKey(dbg, "stepInKey");
+ return waitForPaused(dbg);
+}
+
+function pressStepOut(dbg) {
+ pressKey(dbg, "stepOutKey");
+ return waitForPaused(dbg);
+}
+
+add_task(function*() {
+ const dbg = yield initDebugger("doc-debugger-statements.html");
+
+ yield reload(dbg);
+ yield waitForPaused(dbg);
+ assertPausedLocation(dbg, "debugger-statements.html", 8);
+
+ yield pressResume(dbg);
+ assertPausedLocation(dbg, "debugger-statements.html", 12);
+
+ yield pressStepIn(dbg);
+ assertPausedLocation(dbg, "debugger-statements.html", 13);
+
+ yield pressStepOut(dbg);
+ assertPausedLocation(dbg, "debugger-statements.html", 14);
+
+ yield pressStepOver(dbg);
+ assertPausedLocation(dbg, "debugger-statements.html", 9);
+});
diff --git a/devtools/client/debugger/new/test/mochitest/examples/README.md b/devtools/client/debugger/new/test/mochitest/examples/README.md
new file mode 100644
index 000000000..1be42619d
--- /dev/null
+++ b/devtools/client/debugger/new/test/mochitest/examples/README.md
@@ -0,0 +1,7 @@
+### Test Examples
+
+##### Pages
+* **doc_script-switching-01** - includes two scripts that reference each other. The second function has a debugger.
+* **doc-scripts** - includes three sources, a long source and two sources that reference each other.
+* **doc-iframes** - includes an iframe with the debugger statements source.
+* **debugger-statements** - inline script with functions for testing stepping.
diff --git a/devtools/client/debugger/new/test/mochitest/examples/bogus-map.js b/devtools/client/debugger/new/test/mochitest/examples/bogus-map.js
new file mode 100644
index 000000000..20b5bbf7e
--- /dev/null
+++ b/devtools/client/debugger/new/test/mochitest/examples/bogus-map.js
@@ -0,0 +1,8 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+function runCode(){
+ var a=1;
+ a=a*2;
+ return a;
+}
+//# sourceMappingURL=bogus.map
diff --git a/devtools/client/debugger/new/test/mochitest/examples/bundle.js b/devtools/client/debugger/new/test/mochitest/examples/bundle.js
new file mode 100644
index 000000000..a03ace934
--- /dev/null
+++ b/devtools/client/debugger/new/test/mochitest/examples/bundle.js
@@ -0,0 +1,96 @@
+/******/ (function(modules) { // webpackBootstrap
+/******/ // The module cache
+/******/ var installedModules = {};
+/******/
+/******/ // The require function
+/******/ function __webpack_require__(moduleId) {
+/******/
+/******/ // Check if module is in cache
+/******/ if(installedModules[moduleId])
+/******/ return installedModules[moduleId].exports;
+/******/
+/******/ // Create a new module (and put it into the cache)
+/******/ var module = installedModules[moduleId] = {
+/******/ exports: {},
+/******/ id: moduleId,
+/******/ loaded: false
+/******/ };
+/******/
+/******/ // Execute the module function
+/******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
+/******/
+/******/ // Flag the module as loaded
+/******/ module.loaded = true;
+/******/
+/******/ // Return the exports of the module
+/******/ return module.exports;
+/******/ }
+/******/
+/******/
+/******/ // expose the modules object (__webpack_modules__)
+/******/ __webpack_require__.m = modules;
+/******/
+/******/ // expose the module cache
+/******/ __webpack_require__.c = installedModules;
+/******/
+/******/ // __webpack_public_path__
+/******/ __webpack_require__.p = "";
+/******/
+/******/ // Load entry module and return exports
+/******/ return __webpack_require__(0);
+/******/ })
+/************************************************************************/
+/******/ ([
+/* 0 */
+/***/ function(module, exports, __webpack_require__) {
+
+ const times2 = __webpack_require__(1);
+ const { output } = __webpack_require__(2);
+ const opts = __webpack_require__(3);
+
+ output(times2(1));
+ output(times2(2));
+
+ if(opts.extra) {
+ output(times2(3));
+ }
+
+ window.keepMeAlive = function() {
+ // This function exists to make sure this script is never garbage
+ // collected. It is also callable from tests.
+ return times2(4);
+ }
+
+
+/***/ },
+/* 1 */
+/***/ function(module, exports) {
+
+ module.exports = function(x) {
+ return x * 2;
+ }
+
+
+/***/ },
+/* 2 */
+/***/ function(module, exports) {
+
+ function output(str) {
+ console.log(str);
+ }
+
+ module.exports = { output };
+
+
+/***/ },
+/* 3 */
+/***/ function(module, exports) {
+
+ module.exports = {
+ extra: true
+ };
+
+
+/***/ }
+/******/ ]);
+//# sourceMappingURL=bundle.js.map \ No newline at end of file
diff --git a/devtools/client/debugger/new/test/mochitest/examples/bundle.js.map b/devtools/client/debugger/new/test/mochitest/examples/bundle.js.map
new file mode 100644
index 000000000..ed7336ad1
--- /dev/null
+++ b/devtools/client/debugger/new/test/mochitest/examples/bundle.js.map
@@ -0,0 +1 @@
+{"version":3,"sources":["webpack:///webpack/bootstrap 4ef8c7ec7c1df790781e","webpack:///./entry.js","webpack:///./times2.js","webpack:///./output.js","webpack:///./opts.js"],"names":[],"mappings":";AAAA;AACA;;AAEA;AACA;;AAEA;AACA;AACA;;AAEA;AACA;AACA,uBAAe;AACf;AACA;AACA;;AAEA;AACA;;AAEA;AACA;;AAEA;AACA;AACA;;;AAGA;AACA;;AAEA;AACA;;AAEA;AACA;;AAEA;AACA;;;;;;;ACtCA;AACA,QAAO,SAAS;AAChB;;AAEA;AACA;;AAEA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;;;;;;;ACfA;AACA;AACA;;;;;;;ACFA;AACA;AACA;;AAEA,mBAAkB;;;;;;;ACJlB;AACA;AACA","file":"bundle.js","sourcesContent":[" \t// The module cache\n \tvar installedModules = {};\n\n \t// The require function\n \tfunction __webpack_require__(moduleId) {\n\n \t\t// Check if module is in cache\n \t\tif(installedModules[moduleId])\n \t\t\treturn installedModules[moduleId].exports;\n\n \t\t// Create a new module (and put it into the cache)\n \t\tvar module = installedModules[moduleId] = {\n \t\t\texports: {},\n \t\t\tid: moduleId,\n \t\t\tloaded: false\n \t\t};\n\n \t\t// Execute the module function\n \t\tmodules[moduleId].call(module.exports, module, module.exports, __webpack_require__);\n\n \t\t// Flag the module as loaded\n \t\tmodule.loaded = true;\n\n \t\t// Return the exports of the module\n \t\treturn module.exports;\n \t}\n\n\n \t// expose the modules object (__webpack_modules__)\n \t__webpack_require__.m = modules;\n\n \t// expose the module cache\n \t__webpack_require__.c = installedModules;\n\n \t// __webpack_public_path__\n \t__webpack_require__.p = \"\";\n\n \t// Load entry module and return exports\n \treturn __webpack_require__(0);\n\n\n\n// WEBPACK FOOTER //\n// webpack/bootstrap 4ef8c7ec7c1df790781e","const times2 = require(\"./times2\");\nconst { output } = require(\"./output\");\nconst opts = require(\"./opts\");\n\noutput(times2(1));\noutput(times2(2));\n\nif(opts.extra) {\n output(times2(3));\n}\n\nwindow.keepMeAlive = function() {\n // This function exists to make sure this script is never garbage\n // collected. It is also callable from tests.\n return times2(4);\n}\n\n\n\n//////////////////\n// WEBPACK FOOTER\n// ./entry.js\n// module id = 0\n// module chunks = 0","module.exports = function(x) {\n return x * 2;\n}\n\n\n\n//////////////////\n// WEBPACK FOOTER\n// ./times2.js\n// module id = 1\n// module chunks = 0","function output(str) {\n console.log(str);\n}\n\nmodule.exports = { output };\n\n\n\n//////////////////\n// WEBPACK FOOTER\n// ./output.js\n// module id = 2\n// module chunks = 0","module.exports = {\n extra: true\n};\n\n\n\n//////////////////\n// WEBPACK FOOTER\n// ./opts.js\n// module id = 3\n// module chunks = 0"],"sourceRoot":""} \ No newline at end of file
diff --git a/devtools/client/debugger/new/test/mochitest/examples/doc-debugger-statements.html b/devtools/client/debugger/new/test/mochitest/examples/doc-debugger-statements.html
new file mode 100644
index 000000000..967619d31
--- /dev/null
+++ b/devtools/client/debugger/new/test/mochitest/examples/doc-debugger-statements.html
@@ -0,0 +1,27 @@
+<html>
+ <head>
+ <title>Debugger Statements</title>
+ </head>
+
+ <body>
+ <script>
+ debugger;
+ test();
+
+ function test() {
+ debugger;
+ stepIntoMe();
+ }
+
+ function stepIntoMe() {
+ // step in
+ stepOverMe();
+ // step out
+ }
+
+ function stepOverMe() {
+ }
+ </script>
+ </body>
+
+</html>
diff --git a/devtools/client/debugger/new/test/mochitest/examples/doc-exceptions.html b/devtools/client/debugger/new/test/mochitest/examples/doc-exceptions.html
new file mode 100644
index 000000000..5ca65b755
--- /dev/null
+++ b/devtools/client/debugger/new/test/mochitest/examples/doc-exceptions.html
@@ -0,0 +1,7 @@
+<html>
+ <head>
+ <title>Debugger test page</title>
+ <script type="text/javascript" src="exceptions.js"></script>
+ </head>
+ <body></body>
+</html>
diff --git a/devtools/client/debugger/new/test/mochitest/examples/doc-frames.html b/devtools/client/debugger/new/test/mochitest/examples/doc-frames.html
new file mode 100644
index 000000000..408c55b28
--- /dev/null
+++ b/devtools/client/debugger/new/test/mochitest/examples/doc-frames.html
@@ -0,0 +1,17 @@
+<html>
+ <head>
+ <title>Frames</title>
+ </head>
+
+ <body>
+ <script>
+ debugger;
+ // This inline script allows this HTML page to show up as a
+ // source. It also needs to introduce a new global variable so
+ // it's not immediately garbage collected.
+ function inline_script() { var x = 5; }
+ </script>
+ <script type="text/javascript" src="frames.js"></script>
+ </body>
+
+</html>
diff --git a/devtools/client/debugger/new/test/mochitest/examples/doc-iframes.html b/devtools/client/debugger/new/test/mochitest/examples/doc-iframes.html
new file mode 100644
index 000000000..26446eaa1
--- /dev/null
+++ b/devtools/client/debugger/new/test/mochitest/examples/doc-iframes.html
@@ -0,0 +1,17 @@
+<html>
+ <head>
+ <title>Iframe</title>
+ </head>
+
+ <body>
+ <script>
+ debugger;
+ // This inline script allows this HTML page to show up as a
+ // source. It also needs to introduce a new global variable so
+ // it's not immediately garbage collected.
+ function inline_script() { var x = 5; }
+ </script>
+ <iframe src="doc-debugger-statements.html"></iframe>
+ </body>
+
+</html>
diff --git a/devtools/client/debugger/new/test/mochitest/examples/doc-minified.html b/devtools/client/debugger/new/test/mochitest/examples/doc-minified.html
new file mode 100644
index 000000000..4c95a9b4a
--- /dev/null
+++ b/devtools/client/debugger/new/test/mochitest/examples/doc-minified.html
@@ -0,0 +1,14 @@
+<!-- Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ -->
+<!doctype html>
+
+<html>
+ <head>
+ <meta charset="utf-8"/>
+ <title>Debugger test page</title>
+ </head>
+
+ <body>
+ <script src="math.min.js"></script>
+ </body>
+</html>
diff --git a/devtools/client/debugger/new/test/mochitest/examples/doc-script-switching.html b/devtools/client/debugger/new/test/mochitest/examples/doc-script-switching.html
new file mode 100644
index 000000000..3c71497c2
--- /dev/null
+++ b/devtools/client/debugger/new/test/mochitest/examples/doc-script-switching.html
@@ -0,0 +1,18 @@
+<!-- Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ -->
+<!doctype html>
+
+<html>
+ <head>
+ <meta charset="utf-8"/>
+ <title>Debugger test page</title>
+ </head>
+
+ <body>
+ <button onclick="firstCall()">Click me!</button>
+
+ <script type="text/javascript" src="script-switching-01.js"></script>
+ <script type="text/javascript" src="script-switching-02.js"></script>
+ </body>
+
+</html>
diff --git a/devtools/client/debugger/new/test/mochitest/examples/doc-scripts.html b/devtools/client/debugger/new/test/mochitest/examples/doc-scripts.html
new file mode 100644
index 000000000..212b4802f
--- /dev/null
+++ b/devtools/client/debugger/new/test/mochitest/examples/doc-scripts.html
@@ -0,0 +1,21 @@
+<!-- Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ -->
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta charset="utf-8"/>
+ <title>Debugger test page</title>
+ </head>
+
+ <body>
+ <script src="simple1.js"></script>
+ <script src="simple2.js"></script>
+ <script src="long.js"></script>
+ <script>
+ // This inline script allows this HTML page to show up as a
+ // source. It also needs to introduce a new global variable so
+ // it's not immediately garbage collected.
+ function inline_script() { var x = 5; }
+ </script>
+ </body>
+</html>
diff --git a/devtools/client/debugger/new/test/mochitest/examples/doc-sourcemap-bogus.html b/devtools/client/debugger/new/test/mochitest/examples/doc-sourcemap-bogus.html
new file mode 100644
index 000000000..da448a2cd
--- /dev/null
+++ b/devtools/client/debugger/new/test/mochitest/examples/doc-sourcemap-bogus.html
@@ -0,0 +1,13 @@
+<!-- Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ -->
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta charset="utf-8"/>
+ <title>Debugger test page</title>
+ </head>
+
+ <body>
+ <script src="bogus-map.js"></script>
+ </body>
+</html>
diff --git a/devtools/client/debugger/new/test/mochitest/examples/doc-sourcemaps.html b/devtools/client/debugger/new/test/mochitest/examples/doc-sourcemaps.html
new file mode 100644
index 000000000..10f5da047
--- /dev/null
+++ b/devtools/client/debugger/new/test/mochitest/examples/doc-sourcemaps.html
@@ -0,0 +1,13 @@
+<!-- Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ -->
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta charset="utf-8"/>
+ <title>Debugger test page</title>
+ </head>
+
+ <body>
+ <script src="bundle.js"></script>
+ </body>
+</html>
diff --git a/devtools/client/debugger/new/test/mochitest/examples/doc-sources.html b/devtools/client/debugger/new/test/mochitest/examples/doc-sources.html
new file mode 100644
index 000000000..14cc86701
--- /dev/null
+++ b/devtools/client/debugger/new/test/mochitest/examples/doc-sources.html
@@ -0,0 +1,23 @@
+<!-- Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ -->
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta charset="utf-8"/>
+ <title>Debugger test page</title>
+ </head>
+
+ <body>
+ <script src="simple1.js"></script>
+ <script src="simple2.js"></script>
+ <script src="long.js"></script>
+ <script>
+ // This inline script allows this HTML page to show up as a
+ // source. It also needs to introduce a new global variable so
+ // it's not immediately garbage collected.
+ function inline_script() { var x = 5; }
+ </script>
+ <script src="nested/nested-source.js"></script>
+ <script src="nested/deeper/deeper-source.js"></script>
+ </body>
+</html>
diff --git a/devtools/client/debugger/new/test/mochitest/examples/entry.js b/devtools/client/debugger/new/test/mochitest/examples/entry.js
new file mode 100644
index 000000000..d397a966b
--- /dev/null
+++ b/devtools/client/debugger/new/test/mochitest/examples/entry.js
@@ -0,0 +1,16 @@
+const times2 = require("./times2");
+const { output } = require("./output");
+const opts = require("./opts");
+
+output(times2(1));
+output(times2(2));
+
+if(opts.extra) {
+ output(times2(3));
+}
+
+window.keepMeAlive = function() {
+ // This function exists to make sure this script is never garbage
+ // collected. It is also callable from tests.
+ return times2(4);
+}
diff --git a/devtools/client/debugger/new/test/mochitest/examples/exceptions.js b/devtools/client/debugger/new/test/mochitest/examples/exceptions.js
new file mode 100644
index 000000000..9523f00ca
--- /dev/null
+++ b/devtools/client/debugger/new/test/mochitest/examples/exceptions.js
@@ -0,0 +1,19 @@
+function uncaughtException() {
+ throw "unreachable"
+}
+
+function caughtError() {
+ try {
+ throw new Error("error");
+ } catch (e) {
+ debugger;
+ }
+}
+
+function caughtException() {
+ try {
+ throw "reachable";
+ } catch (e) {
+ debugger;
+ }
+}
diff --git a/devtools/client/debugger/new/test/mochitest/examples/frames.js b/devtools/client/debugger/new/test/mochitest/examples/frames.js
new file mode 100644
index 000000000..0f031582e
--- /dev/null
+++ b/devtools/client/debugger/new/test/mochitest/examples/frames.js
@@ -0,0 +1,24 @@
+function recurseA(i) {
+ if (i == 20) {
+ debugger;
+ return;
+ }
+
+ // down into the rabbit hole we go
+ return (i % 2) ? recurseA(++i) : recurseB(++i);
+}
+
+function recurseB(i) {
+ if (i == 20) {
+ debugger;
+ return;
+ }
+
+ // down into the rabbit hole we go
+ return (i % 2) ? recurseA(++i) : recurseB(++i);
+}
+
+
+window.startRecursion = function() {
+ return recurseA(0);
+}
diff --git a/devtools/client/debugger/new/test/mochitest/examples/long.js b/devtools/client/debugger/new/test/mochitest/examples/long.js
new file mode 100644
index 000000000..58d605b36
--- /dev/null
+++ b/devtools/client/debugger/new/test/mochitest/examples/long.js
@@ -0,0 +1,76 @@
+var app = {};
+
+// Generic "model" object. You can use whatever
+// framework you want. For this application it
+// may not even be worth separating this logic
+// out, but we do this to demonstrate one way to
+// separate out parts of your application.
+app.TodoModel = function (key) {
+ this.key = key;
+ this.todos = [];
+ this.onChanges = [];
+};
+
+app.TodoModel.prototype.addTodo = function (title) {
+ this.todos = this.todos.concat([{
+ id: Utils.uuid(),
+ title: title,
+ completed: false
+ }]);
+};
+
+app.TodoModel.prototype.inform = function() {
+ // Something changed, but we do nothing
+ return null;
+};
+
+app.TodoModel.prototype.toggleAll = function (checked) {
+ // Note: it's usually better to use immutable data structures since they're
+ // easier to reason about and React works very well with them. That's why
+ // we use map() and filter() everywhere instead of mutating the array or
+ // todo items themselves.
+ this.todos = this.todos.map(function (todo) {
+ return Object.assign({}, todo, {completed: checked});
+ });
+
+ this.inform();
+};
+
+app.TodoModel.prototype.toggle = function (todoToToggle) {
+ this.todos = this.todos.map(function (todo) {
+ return todo !== todoToToggle ?
+ todo :
+ Object.assign({}, todo, {completed: !todo.completed});
+ });
+
+ this.inform();
+};
+
+app.TodoModel.prototype.destroy = function (todo) {
+ this.todos = this.todos.filter(function (candidate) {
+ return candidate !== todo;
+ });
+
+ this.inform();
+};
+
+app.TodoModel.prototype.save = function (todoToSave, text) {
+ this.todos = this.todos.map(function (todo) {
+ return todo !== todoToSave ? todo : Object.assign({}, todo, {title: text});
+ });
+
+ this.inform();
+};
+
+app.TodoModel.prototype.clearCompleted = function () {
+ this.todos = this.todos.filter(function (todo) {
+ return !todo.completed;
+ });
+
+ this.inform();
+};
+
+function testModel() {
+ const model = new app.TodoModel();
+ model.clearCompleted();
+}
diff --git a/devtools/client/debugger/new/test/mochitest/examples/math.min.js b/devtools/client/debugger/new/test/mochitest/examples/math.min.js
new file mode 100644
index 000000000..5a8593345
--- /dev/null
+++ b/devtools/client/debugger/new/test/mochitest/examples/math.min.js
@@ -0,0 +1,3 @@
+function add(a,b,k){var result=a+b;return k(result)}function sub(a,b,k){var result=a-b;return k(result)}function mul(a,b,k){var result=a*b;return k(result)}function div(a,b,k){var result=a/b;return k(result)}function arithmetic(){
+ add(4,4,function(a){
+ sub(a,2,function(b){mul(b,3,function(c){div(c,2,function(d){console.log(d)})})})})};
diff --git a/devtools/client/debugger/new/test/mochitest/examples/nested/nested-source.js b/devtools/client/debugger/new/test/mochitest/examples/nested/nested-source.js
new file mode 100644
index 000000000..a7b20f015
--- /dev/null
+++ b/devtools/client/debugger/new/test/mochitest/examples/nested/nested-source.js
@@ -0,0 +1,3 @@
+function computeSomething() {
+ return 1;
+}
diff --git a/devtools/client/debugger/new/test/mochitest/examples/opts.js b/devtools/client/debugger/new/test/mochitest/examples/opts.js
new file mode 100644
index 000000000..20988fa4a
--- /dev/null
+++ b/devtools/client/debugger/new/test/mochitest/examples/opts.js
@@ -0,0 +1,3 @@
+module.exports = {
+ extra: true
+};
diff --git a/devtools/client/debugger/new/test/mochitest/examples/output.js b/devtools/client/debugger/new/test/mochitest/examples/output.js
new file mode 100644
index 000000000..14281fdbf
--- /dev/null
+++ b/devtools/client/debugger/new/test/mochitest/examples/output.js
@@ -0,0 +1,5 @@
+function output(str) {
+ console.log(str);
+}
+
+module.exports = { output };
diff --git a/devtools/client/debugger/new/test/mochitest/examples/script-switching-01.js b/devtools/client/debugger/new/test/mochitest/examples/script-switching-01.js
new file mode 100644
index 000000000..4ba2772de
--- /dev/null
+++ b/devtools/client/debugger/new/test/mochitest/examples/script-switching-01.js
@@ -0,0 +1,6 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+function firstCall() {
+ secondCall();
+}
diff --git a/devtools/client/debugger/new/test/mochitest/examples/script-switching-02.js b/devtools/client/debugger/new/test/mochitest/examples/script-switching-02.js
new file mode 100644
index 000000000..feb74315f
--- /dev/null
+++ b/devtools/client/debugger/new/test/mochitest/examples/script-switching-02.js
@@ -0,0 +1,13 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+function secondCall() {
+ // This comment is useful: ☺
+ debugger;
+ function foo() {}
+ if (x) {
+ foo();
+ }
+}
+
+var x = true;
diff --git a/devtools/client/debugger/new/test/mochitest/examples/simple1.js b/devtools/client/debugger/new/test/mochitest/examples/simple1.js
new file mode 100644
index 000000000..87cc50f44
--- /dev/null
+++ b/devtools/client/debugger/new/test/mochitest/examples/simple1.js
@@ -0,0 +1,31 @@
+function main() {
+ // A comment so we can test that breakpoint sliding works across
+ // multiple lines
+ const func = foo(1, 2);
+ const result = func();
+ return result;
+}
+
+function doEval() {
+ eval("(" + function() {
+ debugger;
+
+ window.evaledFunc = function() {
+ var foo = 1;
+ var bar = 2;
+ return foo + bar;
+ };
+ }.toString() + ")()");
+}
+
+function doNamedEval() {
+ eval("(" + function() {
+ debugger;
+
+ window.evaledFunc = function() {
+ var foo = 1;
+ var bar = 2;
+ return foo + bar;
+ };
+ }.toString() + ")();\n //# sourceURL=evaled.js");
+}
diff --git a/devtools/client/debugger/new/test/mochitest/examples/simple2.js b/devtools/client/debugger/new/test/mochitest/examples/simple2.js
new file mode 100644
index 000000000..40c280edf
--- /dev/null
+++ b/devtools/client/debugger/new/test/mochitest/examples/simple2.js
@@ -0,0 +1,6 @@
+function foo(x, y) {
+ function bar() {
+ return x + y;
+ }
+ return bar;
+}
diff --git a/devtools/client/debugger/new/test/mochitest/examples/times2.js b/devtools/client/debugger/new/test/mochitest/examples/times2.js
new file mode 100644
index 000000000..2d51ed87a
--- /dev/null
+++ b/devtools/client/debugger/new/test/mochitest/examples/times2.js
@@ -0,0 +1,3 @@
+module.exports = function(x) {
+ return x * 2;
+}
diff --git a/devtools/client/debugger/new/test/mochitest/examples/webpack.config.js b/devtools/client/debugger/new/test/mochitest/examples/webpack.config.js
new file mode 100644
index 000000000..ff22342ce
--- /dev/null
+++ b/devtools/client/debugger/new/test/mochitest/examples/webpack.config.js
@@ -0,0 +1,8 @@
+
+module.exports = {
+ entry: "./entry.js",
+ output: {
+ filename: "bundle.js"
+ },
+ devtool: "sourcemap"
+}
diff --git a/devtools/client/debugger/new/test/mochitest/head.js b/devtools/client/debugger/new/test/mochitest/head.js
new file mode 100644
index 000000000..b0964d890
--- /dev/null
+++ b/devtools/client/debugger/new/test/mochitest/head.js
@@ -0,0 +1,684 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * The Mochitest API documentation
+ * @module mochitest
+ */
+
+/**
+ * The mochitest API to wait for certain events.
+ * @module mochitest/waits
+ * @parent mochitest
+ */
+
+/**
+ * The mochitest API predefined asserts.
+ * @module mochitest/asserts
+ * @parent mochitest
+ */
+
+/**
+ * The mochitest API for interacting with the debugger.
+ * @module mochitest/actions
+ * @parent mochitest
+ */
+
+/**
+ * Helper methods for the mochitest API.
+ * @module mochitest/helpers
+ * @parent mochitest
+ */
+
+// shared-head.js handles imports, constants, and utility functions
+Services.scriptloader.loadSubScript("chrome://mochitests/content/browser/devtools/client/framework/test/shared-head.js", this);
+var { Toolbox } = require("devtools/client/framework/toolbox");
+const EXAMPLE_URL = "http://example.com/browser/devtools/client/debugger/new/test/mochitest/examples/";
+
+Services.prefs.setBoolPref("devtools.debugger.new-debugger-frontend", true);
+registerCleanupFunction(() => {
+ Services.prefs.clearUserPref("devtools.debugger.new-debugger-frontend");
+ delete window.resumeTest;
+});
+
+// Wait until an action of `type` is dispatched. This is different
+// then `_afterDispatchDone` because it doesn't wait for async actions
+// to be done/errored. Use this if you want to listen for the "start"
+// action of an async operation (somewhat rare).
+function waitForNextDispatch(store, type) {
+ return new Promise(resolve => {
+ store.dispatch({
+ // Normally we would use `services.WAIT_UNTIL`, but use the
+ // internal name here so tests aren't forced to always pass it
+ // in
+ type: "@@service/waitUntil",
+ predicate: action => action.type === type,
+ run: (dispatch, getState, action) => {
+ resolve(action);
+ }
+ });
+ });
+}
+
+// Wait until an action of `type` is dispatched. If it's part of an
+// async operation, wait until the `status` field is "done" or "error"
+function _afterDispatchDone(store, type) {
+ return new Promise(resolve => {
+ store.dispatch({
+ // Normally we would use `services.WAIT_UNTIL`, but use the
+ // internal name here so tests aren't forced to always pass it
+ // in
+ type: "@@service/waitUntil",
+ predicate: action => {
+ if (action.type === type) {
+ return action.status ?
+ (action.status === "done" || action.status === "error") :
+ true;
+ }
+ },
+ run: (dispatch, getState, action) => {
+ resolve(action);
+ }
+ });
+ });
+}
+
+/**
+ * Wait for a specific action type to be dispatch.
+ * If an async action, will wait for it to be done.
+ *
+ * @memberof mochitest/waits
+ * @param {Object} dbg
+ * @param {String} type
+ * @param {Number} eventRepeat
+ * @return {Promise}
+ * @static
+ */
+function waitForDispatch(dbg, type, eventRepeat = 1) {
+ let count = 0;
+
+ return Task.spawn(function* () {
+ info("Waiting for " + type + " to dispatch " + eventRepeat + " time(s)");
+ while (count < eventRepeat) {
+ yield _afterDispatchDone(dbg.store, type);
+ count++;
+ info(type + " dispatched " + count + " time(s)");
+ }
+ });
+}
+
+/**
+ * Waits for specific thread events.
+ *
+ * @memberof mochitest/waits
+ * @param {Object} dbg
+ * @param {String} eventName
+ * @return {Promise}
+ * @static
+ */
+function waitForThreadEvents(dbg, eventName) {
+ info("Waiting for thread event '" + eventName + "' to fire.");
+ const thread = dbg.toolbox.threadClient;
+
+ return new Promise(function(resolve, reject) {
+ thread.addListener(eventName, function onEvent(eventName, ...args) {
+ info("Thread event '" + eventName + "' fired.");
+ thread.removeListener(eventName, onEvent);
+ resolve.apply(resolve, args);
+ });
+ });
+}
+
+/**
+ * Waits for `predicate(state)` to be true. `state` is the redux app state.
+ *
+ * @memberof mochitest/waits
+ * @param {Object} dbg
+ * @param {Function} predicate
+ * @return {Promise}
+ * @static
+ */
+function waitForState(dbg, predicate) {
+ return new Promise(resolve => {
+ const unsubscribe = dbg.store.subscribe(() => {
+ if (predicate(dbg.store.getState())) {
+ unsubscribe();
+ resolve();
+ }
+ });
+ });
+}
+
+/**
+ * Waits for sources to be loaded.
+ *
+ * @memberof mochitest/waits
+ * @param {Object} dbg
+ * @param {Array} sources
+ * @return {Promise}
+ * @static
+ */
+function waitForSources(dbg, ...sources) {
+ if (sources.length === 0) {
+ return Promise.resolve();
+ }
+
+ info("Waiting on sources: " + sources.join(", "));
+ const { selectors: { getSources }, store } = dbg;
+ return Promise.all(sources.map(url => {
+ function sourceExists(state) {
+ return getSources(state).some(s => {
+ return s.get("url").includes(url);
+ });
+ }
+
+ if (!sourceExists(store.getState())) {
+ return waitForState(dbg, sourceExists);
+ }
+ }));
+}
+
+function waitForElement(dbg, selector) {
+ return waitUntil(() => findElementWithSelector(dbg, selector))
+}
+
+/**
+ * Assert that the debugger is paused at the correct location.
+ *
+ * @memberof mochitest/asserts
+ * @param {Object} dbg
+ * @param {String} source
+ * @param {Number} line
+ * @static
+ */
+function assertPausedLocation(dbg, source, line) {
+ const { selectors: { getSelectedSource, getPause }, getState } = dbg;
+ source = findSource(dbg, source);
+
+ // Check the selected source
+ is(getSelectedSource(getState()).get("id"), source.id);
+
+ // Check the pause location
+ const location = getPause(getState()).getIn(["frame", "location"]);
+ is(location.get("sourceId"), source.id);
+ is(location.get("line"), line);
+
+ // Check the debug line
+ ok(dbg.win.cm.lineInfo(line - 1).wrapClass.includes("debug-line"),
+ "Line is highlighted as paused");
+}
+
+/**
+ * Assert that the debugger is highlighting the correct location.
+ *
+ * @memberof mochitest/asserts
+ * @param {Object} dbg
+ * @param {String} source
+ * @param {Number} line
+ * @static
+ */
+function assertHighlightLocation(dbg, source, line) {
+ const { selectors: { getSelectedSource, getPause }, getState } = dbg;
+ source = findSource(dbg, source);
+
+ // Check the selected source
+ is(getSelectedSource(getState()).get("url"), source.url);
+
+ // Check the highlight line
+ const lineEl = findElement(dbg, "highlightLine");
+ ok(lineEl, "Line is highlighted");
+ ok(isVisibleWithin(findElement(dbg, "codeMirror"), lineEl),
+ "Highlighted line is visible");
+ ok(dbg.win.cm.lineInfo(line - 1).wrapClass.includes("highlight-line"),
+ "Line is highlighted");
+}
+
+/**
+ * Returns boolean for whether the debugger is paused.
+ *
+ * @memberof mochitest/asserts
+ * @param {Object} dbg
+ * @static
+ */
+function isPaused(dbg) {
+ const { selectors: { getPause }, getState } = dbg;
+ return !!getPause(getState());
+}
+
+/**
+ * Waits for the debugger to be fully paused.
+ *
+ * @memberof mochitest/waits
+ * @param {Object} dbg
+ * @static
+ */
+function waitForPaused(dbg) {
+ return Task.spawn(function* () {
+ // We want to make sure that we get both a real paused event and
+ // that the state is fully populated. The client may do some more
+ // work (call other client methods) before populating the state.
+ yield waitForThreadEvents(dbg, "paused"),
+ yield waitForState(dbg, state => {
+ const pause = dbg.selectors.getPause(state);
+ // Make sure we have the paused state.
+ if (!pause) {
+ return false;
+ }
+ // Make sure the source text is completely loaded for the
+ // source we are paused in.
+ const sourceId = pause.getIn(["frame", "location", "sourceId"]);
+ const sourceText = dbg.selectors.getSourceText(dbg.getState(), sourceId);
+ return sourceText && !sourceText.get("loading");
+ });
+ });
+}
+
+function createDebuggerContext(toolbox) {
+ const win = toolbox.getPanel("jsdebugger").panelWin;
+ const store = win.Debugger.store;
+
+ return {
+ actions: win.Debugger.actions,
+ selectors: win.Debugger.selectors,
+ getState: store.getState,
+ store: store,
+ client: win.Debugger.client,
+ toolbox: toolbox,
+ win: win
+ };
+}
+
+/**
+ * Intilializes the debugger.
+ *
+ * @memberof mochitest
+ * @param {String} url
+ * @param {Array} sources
+ * @return {Promise} dbg
+ * @static
+ */
+function initDebugger(url, ...sources) {
+ return Task.spawn(function* () {
+ const toolbox = yield openNewTabAndToolbox(EXAMPLE_URL + url, "jsdebugger");
+ return createDebuggerContext(toolbox);
+ });
+}
+
+window.resumeTest = undefined;
+/**
+ * Pause the test and let you interact with the debugger.
+ * The test can be resumed by invoking `resumeTest` in the console.
+ *
+ * @memberof mochitest
+ * @static
+ */
+function pauseTest() {
+ info("Test paused. Invoke resumeTest to continue.");
+ return new Promise(resolve => resumeTest = resolve);
+}
+
+// Actions
+/**
+ * Returns a source that matches the URL.
+ *
+ * @memberof mochitest/actions
+ * @param {Object} dbg
+ * @param {String} url
+ * @return {Object} source
+ * @static
+ */
+function findSource(dbg, url) {
+ if (typeof url !== "string") {
+ // Support passing in a source object itelf all APIs that use this
+ // function support both styles
+ const source = url;
+ return source;
+ }
+
+ const sources = dbg.selectors.getSources(dbg.getState());
+ const source = sources.find(s => s.get("url").includes(url));
+
+ if (!source) {
+ throw new Error("Unable to find source: " + url);
+ }
+
+ return source.toJS();
+}
+
+/**
+ * Selects the source.
+ *
+ * @memberof mochitest/actions
+ * @param {Object} dbg
+ * @param {String} url
+ * @param {Number} line
+ * @return {Promise}
+ * @static
+ */
+function selectSource(dbg, url, line) {
+ info("Selecting source: " + url);
+ const source = findSource(dbg, url);
+ const hasText = !!dbg.selectors.getSourceText(dbg.getState(), source.id);
+ dbg.actions.selectSource(source.id, { line });
+
+ if (!hasText) {
+ return waitForDispatch(dbg, "LOAD_SOURCE_TEXT");
+ }
+}
+
+/**
+ * Steps over.
+ *
+ * @memberof mochitest/actions
+ * @param {Object} dbg
+ * @return {Promise}
+ * @static
+ */
+function stepOver(dbg) {
+ info("Stepping over");
+ dbg.actions.stepOver();
+ return waitForPaused(dbg);
+}
+
+/**
+ * Steps in.
+ *
+ * @memberof mochitest/actions
+ * @param {Object} dbg
+ * @return {Promise}
+ * @static
+ */
+function stepIn(dbg) {
+ info("Stepping in");
+ dbg.actions.stepIn();
+ return waitForPaused(dbg);
+}
+
+/**
+ * Steps out.
+ *
+ * @memberof mochitest/actions
+ * @param {Object} dbg
+ * @return {Promise}
+ * @static
+ */
+function stepOut(dbg) {
+ info("Stepping out");
+ dbg.actions.stepOut();
+ return waitForPaused(dbg);
+}
+
+/**
+ * Resumes.
+ *
+ * @memberof mochitest/actions
+ * @param {Object} dbg
+ * @return {Promise}
+ * @static
+ */
+function resume(dbg) {
+ info("Resuming");
+ dbg.actions.resume();
+ return waitForThreadEvents(dbg, "resumed");
+}
+
+/**
+ * Reloads the debuggee.
+ *
+ * @memberof mochitest/actions
+ * @param {Object} dbg
+ * @param {Array} sources
+ * @return {Promise}
+ * @static
+ */
+function reload(dbg, ...sources) {
+ return dbg.client.reload().then(() => waitForSources(...sources));
+}
+
+/**
+ * Navigates the debuggee to another url.
+ *
+ * @memberof mochitest/actions
+ * @param {Object} dbg
+ * @param {String} url
+ * @param {Array} sources
+ * @return {Promise}
+ * @static
+ */
+function navigate(dbg, url, ...sources) {
+ dbg.client.navigate(url);
+ return waitForSources(dbg, ...sources);
+}
+
+/**
+ * Adds a breakpoint to a source at line/col.
+ *
+ * @memberof mochitest/actions
+ * @param {Object} dbg
+ * @param {String} source
+ * @param {Number} line
+ * @param {Number} col
+ * @return {Promise}
+ * @static
+ */
+function addBreakpoint(dbg, source, line, col) {
+ source = findSource(dbg, source);
+ const sourceId = source.id;
+ dbg.actions.addBreakpoint({ sourceId, line, col });
+ return waitForDispatch(dbg, "ADD_BREAKPOINT");
+}
+
+/**
+ * Removes a breakpoint from a source at line/col.
+ *
+ * @memberof mochitest/actions
+ * @param {Object} dbg
+ * @param {String} source
+ * @param {Number} line
+ * @param {Number} col
+ * @return {Promise}
+ * @static
+ */
+function removeBreakpoint(dbg, sourceId, line, col) {
+ return dbg.actions.removeBreakpoint({ sourceId, line, col });
+}
+
+/**
+ * Toggles the Pause on exceptions feature in the debugger.
+ *
+ * @memberof mochitest/actions
+ * @param {Object} dbg
+ * @param {Boolean} pauseOnExceptions
+ * @param {Boolean} ignoreCaughtExceptions
+ * @return {Promise}
+ * @static
+ */
+function togglePauseOnExceptions(dbg,
+ pauseOnExceptions, ignoreCaughtExceptions) {
+ const command = dbg.actions.pauseOnExceptions(
+ pauseOnExceptions,
+ ignoreCaughtExceptions
+ );
+
+ if (!isPaused(dbg)) {
+ return waitForThreadEvents(dbg, "resumed");
+ }
+
+ return command;
+}
+
+// Helpers
+
+/**
+ * Invokes a global function in the debuggee tab.
+ *
+ * @memberof mochitest/helpers
+ * @param {String} fnc
+ * @return {Promise}
+ * @static
+ */
+function invokeInTab(fnc) {
+ info(`Invoking function ${fnc} in tab`);
+ return ContentTask.spawn(gBrowser.selectedBrowser, fnc, function* (fnc) {
+ content.wrappedJSObject[fnc](); // eslint-disable-line mozilla/no-cpows-in-tests, max-len
+ });
+}
+
+const isLinux = Services.appinfo.OS === "Linux";
+const cmdOrCtrl = isLinux ? { ctrlKey: true } : { metaKey: true };
+const keyMappings = {
+ sourceSearch: { code: "p", modifiers: cmdOrCtrl},
+ fileSearch: { code: "f", modifiers: cmdOrCtrl},
+ "Enter": { code: "VK_RETURN" },
+ "Up": { code: "VK_UP" },
+ "Down": { code: "VK_DOWN" },
+ pauseKey: { code: "VK_F8" },
+ resumeKey: { code: "VK_F8" },
+ stepOverKey: { code: "VK_F10" },
+ stepInKey: { code: "VK_F11", modifiers: { ctrlKey: isLinux }},
+ stepOutKey: { code: "VK_F11", modifiers: { ctrlKey: isLinux, shiftKey: true }}
+};
+
+/**
+ * Simulates a key press in the debugger window.
+ *
+ * @memberof mochitest/helpers
+ * @param {Object} dbg
+ * @param {String} keyName
+ * @return {Promise}
+ * @static
+ */
+function pressKey(dbg, keyName) {
+ let keyEvent = keyMappings[keyName];
+
+ const { code, modifiers } = keyEvent;
+ return EventUtils.synthesizeKey(
+ code,
+ modifiers || {},
+ dbg.win
+ );
+}
+
+function type(dbg, string) {
+ string.split("").forEach(char => {
+ EventUtils.synthesizeKey(char, {}, dbg.win);
+ });
+}
+
+function isVisibleWithin(outerEl, innerEl) {
+ const innerRect = innerEl.getBoundingClientRect();
+ const outerRect = outerEl.getBoundingClientRect();
+ return innerRect.top > outerRect.top &&
+ innerRect.bottom < outerRect.bottom;
+}
+
+const selectors = {
+ callStackHeader: ".call-stack-pane ._header",
+ callStackBody: ".call-stack-pane .pane",
+ scopesHeader: ".scopes-pane ._header",
+ breakpointItem: i => `.breakpoints-list .breakpoint:nth-child(${i})`,
+ scopeNode: i => `.scopes-list .tree-node:nth-child(${i}) .object-label`,
+ frame: i => `.frames ul li:nth-child(${i})`,
+ frames: ".frames ul li",
+ gutter: i => `.CodeMirror-code *:nth-child(${i}) .CodeMirror-linenumber`,
+ menuitem: i => `menupopup menuitem:nth-child(${i})`,
+ pauseOnExceptions: ".pause-exceptions",
+ breakpoint: ".CodeMirror-code > .new-breakpoint",
+ highlightLine: ".CodeMirror-code > .highlight-line",
+ codeMirror: ".CodeMirror",
+ resume: ".resume.active",
+ stepOver: ".stepOver.active",
+ stepOut: ".stepOut.active",
+ stepIn: ".stepIn.active",
+ toggleBreakpoints: ".toggleBreakpoints",
+ prettyPrintButton: ".prettyPrint",
+ sourceFooter: ".source-footer",
+ sourceNode: i => `.sources-list .tree-node:nth-child(${i})`,
+ sourceNodes: ".sources-list .tree-node",
+ sourceArrow: i => `.sources-list .tree-node:nth-child(${i}) .arrow`,
+};
+
+function getSelector(elementName, ...args) {
+ let selector = selectors[elementName];
+ if (!selector) {
+ throw new Error(`The selector ${elementName} is not defined`);
+ }
+
+ if (typeof selector == "function") {
+ selector = selector(...args);
+ }
+
+ return selector;
+}
+
+function findElement(dbg, elementName, ...args) {
+ const selector = getSelector(elementName, ...args);
+ return findElementWithSelector(dbg, selector);
+}
+
+function findElementWithSelector(dbg, selector) {
+ return dbg.win.document.querySelector(selector);
+}
+
+function findAllElements(dbg, elementName, ...args) {
+ const selector = getSelector(elementName, ...args);
+ return dbg.win.document.querySelectorAll(selector);
+}
+
+/**
+ * Simulates a mouse click in the debugger DOM.
+ *
+ * @memberof mochitest/helpers
+ * @param {Object} dbg
+ * @param {String} elementName
+ * @param {Array} args
+ * @return {Promise}
+ * @static
+ */
+function clickElement(dbg, elementName, ...args) {
+ const selector = getSelector(elementName, ...args);
+ return EventUtils.synthesizeMouseAtCenter(
+ findElementWithSelector(dbg, selector),
+ {},
+ dbg.win
+ );
+}
+
+function rightClickElement(dbg, elementName, ...args) {
+ const selector = getSelector(elementName, ...args);
+ const doc = dbg.win.document;
+ return EventUtils.synthesizeMouseAtCenter(
+ doc.querySelector(selector),
+ {type: "contextmenu"},
+ dbg.win
+ );
+}
+
+function selectMenuItem(dbg, index) {
+ // the context menu is in the toolbox window
+ const doc = dbg.toolbox.win.document;
+
+ // there are several context menus, we want the one with the menu-api
+ const popup = doc.querySelector("menupopup[menu-api=\"true\"]");
+
+ const item = popup.querySelector(`menuitem:nth-child(${index})`);
+ return EventUtils.synthesizeMouseAtCenter(item, {}, dbg.toolbox.win );
+}
+
+/**
+ * Toggles the debugger call stack accordian.
+ *
+ * @memberof mochitest/actions
+ * @param {Object} dbg
+ * @return {Promise}
+ * @static
+ */
+function toggleCallStack(dbg) {
+ return findElement(dbg, "callStackHeader").click();
+}
+
+function toggleScopes(dbg) {
+ return findElement(dbg, "scopesHeader").click();
+}
diff --git a/devtools/client/debugger/panel.js b/devtools/client/debugger/panel.js
new file mode 100644
index 000000000..352d7b284
--- /dev/null
+++ b/devtools/client/debugger/panel.js
@@ -0,0 +1,180 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* 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 { Cc, Ci, Cu, Cr } = require("chrome");
+const promise = require("promise");
+const EventEmitter = require("devtools/shared/event-emitter");
+const DevToolsUtils = require("devtools/shared/DevToolsUtils");
+
+function DebuggerPanel(iframeWindow, toolbox) {
+ this.panelWin = iframeWindow;
+ this._toolbox = toolbox;
+ this._destroyer = null;
+
+ this._view = this.panelWin.DebuggerView;
+ this._controller = this.panelWin.DebuggerController;
+ this._view._hostType = this._toolbox.hostType;
+ this._controller._target = this.target;
+ this._controller._toolbox = this._toolbox;
+
+ this.handleHostChanged = this.handleHostChanged.bind(this);
+ EventEmitter.decorate(this);
+}
+
+exports.DebuggerPanel = DebuggerPanel;
+
+DebuggerPanel.prototype = {
+ /**
+ * Open is effectively an asynchronous constructor.
+ *
+ * @return object
+ * A promise that is resolved when the Debugger completes opening.
+ */
+ open: function () {
+ let targetPromise;
+
+ // Local debugging needs to make the target remote.
+ if (!this.target.isRemote) {
+ targetPromise = this.target.makeRemote();
+ // Listen for tab switching events to manage focus when the content window
+ // is paused and events suppressed.
+ this.target.tab.addEventListener("TabSelect", this);
+ } else {
+ targetPromise = promise.resolve(this.target);
+ }
+
+ return targetPromise
+ .then(() => this._controller.startupDebugger())
+ .then(() => this._controller.connect())
+ .then(() => {
+ this._toolbox.on("host-changed", this.handleHostChanged);
+ // Add keys from this document's keyset to the toolbox, so they
+ // can work when the split console is focused.
+ let keysToClone = ["resumeKey", "stepOverKey", "stepInKey", "stepOutKey"];
+ for (let key of keysToClone) {
+ let elm = this.panelWin.document.getElementById(key);
+ let keycode = elm.getAttribute("keycode");
+ let modifiers = elm.getAttribute("modifiers");
+ let command = elm.getAttribute("command");
+ let handler = this._view.Toolbar.getCommandHandler(command);
+
+ let keyShortcut = this.translateToKeyShortcut(keycode, modifiers);
+ this._toolbox.useKeyWithSplitConsole(keyShortcut, handler, "jsdebugger");
+ }
+ this.isReady = true;
+ this.emit("ready");
+ return this;
+ })
+ .then(null, function onError(aReason) {
+ DevToolsUtils.reportException("DebuggerPanel.prototype.open", aReason);
+ });
+ },
+
+ /**
+ * Translate a VK_ keycode, with modifiers, to a key shortcut that can be used with
+ * shared/key-shortcut.
+ *
+ * @param {String} keycode
+ * The VK_* keycode to translate
+ * @param {String} modifiers
+ * The list (blank-space separated) of modifiers applying to this keycode.
+ * @return {String} a key shortcut ready to be used with shared/key-shortcut.js
+ */
+ translateToKeyShortcut: function (keycode, modifiers) {
+ // Remove the VK_ prefix.
+ keycode = keycode.replace("VK_", "");
+
+ // Translate modifiers
+ if (modifiers.includes("shift")) {
+ keycode = "Shift+" + keycode;
+ }
+ if (modifiers.includes("alt")) {
+ keycode = "Alt+" + keycode;
+ }
+ if (modifiers.includes("control")) {
+ keycode = "Ctrl+" + keycode;
+ }
+ if (modifiers.includes("meta")) {
+ keycode = "Cmd+" + keycode;
+ }
+ if (modifiers.includes("accel")) {
+ keycode = "CmdOrCtrl+" + keycode;
+ }
+
+ return keycode;
+ },
+
+ // DevToolPanel API
+
+ get target() {
+ return this._toolbox.target;
+ },
+
+ destroy: function () {
+ // Make sure this panel is not already destroyed.
+ if (this._destroyer) {
+ return this._destroyer;
+ }
+
+ if (!this.target.isRemote) {
+ this.target.tab.removeEventListener("TabSelect", this);
+ }
+
+ return this._destroyer = this._controller.shutdownDebugger().then(() => {
+ this.emit("destroyed");
+ });
+ },
+
+ // DebuggerPanel API
+
+ getFrames() {
+ let framesController = this.panelWin.DebuggerController.StackFrames;
+ let thread = framesController.activeThread;
+ if (thread && thread.paused) {
+ return {
+ frames: thread.cachedFrames,
+ selected: framesController.currentFrameDepth,
+ };
+ }
+
+ return null;
+ },
+
+ addBreakpoint: function (location) {
+ const { actions } = this.panelWin;
+ const { dispatch } = this._controller;
+
+ return dispatch(actions.addBreakpoint(location));
+ },
+
+ removeBreakpoint: function (location) {
+ const { actions } = this.panelWin;
+ const { dispatch } = this._controller;
+
+ return dispatch(actions.removeBreakpoint(location));
+ },
+
+ blackbox: function (source, flag) {
+ const { actions } = this.panelWin;
+ const { dispatch } = this._controller;
+ return dispatch(actions.blackbox(source, flag));
+ },
+
+ handleHostChanged: function () {
+ this._view.handleHostChanged(this._toolbox.hostType);
+ },
+
+ // nsIDOMEventListener API
+
+ handleEvent: function (aEvent) {
+ if (aEvent.target == this.target.tab &&
+ this._controller.activeThread.state == "paused") {
+ // Wait a tick for the content focus event to be delivered.
+ DevToolsUtils.executeSoon(() => this._toolbox.focusTool("jsdebugger"));
+ }
+ }
+};
diff --git a/devtools/client/debugger/test/.eslintrc.js b/devtools/client/debugger/test/.eslintrc.js
new file mode 100644
index 000000000..8d15a76d9
--- /dev/null
+++ b/devtools/client/debugger/test/.eslintrc.js
@@ -0,0 +1,6 @@
+"use strict";
+
+module.exports = {
+ // Extend from the shared list of defined globals for mochitests.
+ "extends": "../../../.eslintrc.mochitests.js"
+};
diff --git a/devtools/client/debugger/test/mochitest/addon-source/browser_dbg_addon3/lib/main.js b/devtools/client/debugger/test/mochitest/addon-source/browser_dbg_addon3/lib/main.js
new file mode 100644
index 000000000..fc00b60a1
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/addon-source/browser_dbg_addon3/lib/main.js
@@ -0,0 +1,13 @@
+var { Cc, Ci } = require("chrome");
+var { once } = require("sdk/system/events");
+
+var observerService = Cc["@mozilla.org/observer-service;1"].getService(Ci.nsIObserverService);
+var observer = {
+ observe: function () {
+ debugger;
+ }
+};
+
+once("sdk:loader:destroy", () => observerService.removeObserver(observer, "debuggerAttached"));
+
+observerService.addObserver(observer, "debuggerAttached", false);
diff --git a/devtools/client/debugger/test/mochitest/addon-source/browser_dbg_addon3/package.json b/devtools/client/debugger/test/mochitest/addon-source/browser_dbg_addon3/package.json
new file mode 100644
index 000000000..4bf1bed50
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/addon-source/browser_dbg_addon3/package.json
@@ -0,0 +1,9 @@
+{
+ "name": "browser_dbg_addon3",
+ "title": "browser_dbg_addon3",
+ "id": "jid1-ami3akps3baaeg",
+ "description": "a basic add-on",
+ "author": "",
+ "license": "MPL 2.0",
+ "version": "0.1"
+}
diff --git a/devtools/client/debugger/test/mochitest/addon-source/browser_dbg_addon4/bootstrap.js b/devtools/client/debugger/test/mochitest/addon-source/browser_dbg_addon4/bootstrap.js
new file mode 100644
index 000000000..e8bb9fcce
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/addon-source/browser_dbg_addon4/bootstrap.js
@@ -0,0 +1,36 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+var { interfaces: Ci, utils: Cu } = Components;
+
+function notify() {
+ // Log objects so makeDebuggeeValue can get the global to use
+ console.log({ msg: "Hello again" });
+}
+
+function startup(aParams, aReason) {
+ const { Services } = Cu.import("resource://gre/modules/Services.jsm", {});
+ let res = Services.io.getProtocolHandler("resource")
+ .QueryInterface(Ci.nsIResProtocolHandler);
+ res.setSubstitution("browser_dbg_addon4", aParams.resourceURI);
+
+ // Load a JS module
+ Cu.import("resource://browser_dbg_addon4/test.jsm"); // eslint-disable-line mozilla/no-single-arg-cu-import
+ // Log objects so makeDebuggeeValue can get the global to use
+ console.log({ msg: "Hello from the test add-on" });
+
+ Services.obs.addObserver(notify, "addon-test-ping", false);
+}
+
+function shutdown(aParams, aReason) {
+ Services.obs.removeObserver(notify, "addon-test-ping");
+
+ // Unload the JS module
+ Cu.unload("resource://browser_dbg_addon4/test.jsm");
+
+ let res = Services.io.getProtocolHandler("resource")
+ .QueryInterface(Ci.nsIResProtocolHandler);
+ res.setSubstitution("browser_dbg_addon4", null);
+}
diff --git a/devtools/client/debugger/test/mochitest/addon-source/browser_dbg_addon4/chrome.manifest b/devtools/client/debugger/test/mochitest/addon-source/browser_dbg_addon4/chrome.manifest
new file mode 100644
index 000000000..ccb88ddf1
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/addon-source/browser_dbg_addon4/chrome.manifest
@@ -0,0 +1 @@
+content browser_dbg_addon4 .
diff --git a/devtools/client/debugger/test/mochitest/addon-source/browser_dbg_addon4/install.rdf b/devtools/client/debugger/test/mochitest/addon-source/browser_dbg_addon4/install.rdf
new file mode 100644
index 000000000..45679ffc9
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/addon-source/browser_dbg_addon4/install.rdf
@@ -0,0 +1,19 @@
+<?xml version="1.0"?>
+
+<RDF xmlns="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:em="http://www.mozilla.org/2004/em-rdf#">
+
+ <Description about="urn:mozilla:install-manifest">
+ <em:id>browser_dbg_addon4@tests.mozilla.org</em:id>
+ <em:version>1.0</em:version>
+ <em:name>Test add-on with JS Modules</em:name>
+ <em:bootstrap>true</em:bootstrap>
+ <em:targetApplication>
+ <Description>
+ <em:id>toolkit@mozilla.org</em:id>
+ <em:minVersion>0</em:minVersion>
+ <em:maxVersion>*</em:maxVersion>
+ </Description>
+ </em:targetApplication>
+ </Description>
+</RDF>
diff --git a/devtools/client/debugger/test/mochitest/addon-source/browser_dbg_addon4/test.jsm b/devtools/client/debugger/test/mochitest/addon-source/browser_dbg_addon4/test.jsm
new file mode 100644
index 000000000..17bebfd8e
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/addon-source/browser_dbg_addon4/test.jsm
@@ -0,0 +1,6 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+const EXPORTED_SYMBOLS = ["Foo"];
+
+const Foo = {};
diff --git a/devtools/client/debugger/test/mochitest/addon-source/browser_dbg_addon4/test.xul b/devtools/client/debugger/test/mochitest/addon-source/browser_dbg_addon4/test.xul
new file mode 100644
index 000000000..733817ad8
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/addon-source/browser_dbg_addon4/test.xul
@@ -0,0 +1,8 @@
+<?xml version="1.0"?>
+
+<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
+
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+ <script type="text/javascript" src="testxul.js"/>
+ <label value="test.xul"/>
+</window>
diff --git a/devtools/client/debugger/test/mochitest/addon-source/browser_dbg_addon4/test2.jsm b/devtools/client/debugger/test/mochitest/addon-source/browser_dbg_addon4/test2.jsm
new file mode 100644
index 000000000..703869f43
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/addon-source/browser_dbg_addon4/test2.jsm
@@ -0,0 +1,6 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+const EXPORTED_SYMBOLS = ["Bar"];
+
+const Bar = {};
diff --git a/devtools/client/debugger/test/mochitest/addon-source/browser_dbg_addon4/test2.xul b/devtools/client/debugger/test/mochitest/addon-source/browser_dbg_addon4/test2.xul
new file mode 100644
index 000000000..372d05587
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/addon-source/browser_dbg_addon4/test2.xul
@@ -0,0 +1,8 @@
+<?xml version="1.0"?>
+
+<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
+
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+ <script type="text/javascript" src="testxul2.js"/>
+ <label value="test2.xul"/>
+</window>
diff --git a/devtools/client/debugger/test/mochitest/addon-source/browser_dbg_addon4/testxul.js b/devtools/client/debugger/test/mochitest/addon-source/browser_dbg_addon4/testxul.js
new file mode 100644
index 000000000..30ad9d2f8
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/addon-source/browser_dbg_addon4/testxul.js
@@ -0,0 +1,4 @@
+// Define something in here or the script may get collected
+window.addEventListener("unload", function () {
+ window.foo = "bar";
+});
diff --git a/devtools/client/debugger/test/mochitest/addon-source/browser_dbg_addon4/testxul2.js b/devtools/client/debugger/test/mochitest/addon-source/browser_dbg_addon4/testxul2.js
new file mode 100644
index 000000000..30ad9d2f8
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/addon-source/browser_dbg_addon4/testxul2.js
@@ -0,0 +1,4 @@
+// Define something in here or the script may get collected
+window.addEventListener("unload", function () {
+ window.foo = "bar";
+});
diff --git a/devtools/client/debugger/test/mochitest/addon-source/browser_dbg_addon5/bootstrap.js b/devtools/client/debugger/test/mochitest/addon-source/browser_dbg_addon5/bootstrap.js
new file mode 100644
index 000000000..8edc53756
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/addon-source/browser_dbg_addon5/bootstrap.js
@@ -0,0 +1,23 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+var { interfaces: Ci, classes: Cc } = Components;
+
+function startup(aParams, aReason) {
+ Components.utils.import("resource://gre/modules/Services.jsm");
+ let res = Services.io.getProtocolHandler("resource")
+ .QueryInterface(Ci.nsIResProtocolHandler);
+ res.setSubstitution("browser_dbg_addon5", aParams.resourceURI);
+
+ // Load a JS module
+ Components.utils.import("resource://browser_dbg_addon5/test.jsm");
+}
+
+function shutdown(aParams, aReason) {
+ // Unload the JS module
+ Components.utils.unload("resource://browser_dbg_addon5/test.jsm");
+
+ let res = Services.io.getProtocolHandler("resource")
+ .QueryInterface(Ci.nsIResProtocolHandler);
+ res.setSubstitution("browser_dbg_addon5", null);
+}
diff --git a/devtools/client/debugger/test/mochitest/addon-source/browser_dbg_addon5/chrome.manifest b/devtools/client/debugger/test/mochitest/addon-source/browser_dbg_addon5/chrome.manifest
new file mode 100644
index 000000000..ceef8d06d
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/addon-source/browser_dbg_addon5/chrome.manifest
@@ -0,0 +1 @@
+content browser_dbg_addon5 .
diff --git a/devtools/client/debugger/test/mochitest/addon-source/browser_dbg_addon5/install.rdf b/devtools/client/debugger/test/mochitest/addon-source/browser_dbg_addon5/install.rdf
new file mode 100644
index 000000000..af2cbbb5d
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/addon-source/browser_dbg_addon5/install.rdf
@@ -0,0 +1,20 @@
+<?xml version="1.0"?>
+
+<RDF xmlns="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:em="http://www.mozilla.org/2004/em-rdf#">
+
+ <Description about="urn:mozilla:install-manifest">
+ <em:id>browser_dbg_addon5@tests.mozilla.org</em:id>
+ <em:version>1.0</em:version>
+ <em:name>Test unpacked add-on with JS Modules</em:name>
+ <em:bootstrap>true</em:bootstrap>
+ <em:unpack>true</em:unpack>
+ <em:targetApplication>
+ <Description>
+ <em:id>toolkit@mozilla.org</em:id>
+ <em:minVersion>0</em:minVersion>
+ <em:maxVersion>*</em:maxVersion>
+ </Description>
+ </em:targetApplication>
+ </Description>
+</RDF>
diff --git a/devtools/client/debugger/test/mochitest/addon-source/browser_dbg_addon5/test.jsm b/devtools/client/debugger/test/mochitest/addon-source/browser_dbg_addon5/test.jsm
new file mode 100644
index 000000000..17bebfd8e
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/addon-source/browser_dbg_addon5/test.jsm
@@ -0,0 +1,6 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+const EXPORTED_SYMBOLS = ["Foo"];
+
+const Foo = {};
diff --git a/devtools/client/debugger/test/mochitest/addon-source/browser_dbg_addon5/test.xul b/devtools/client/debugger/test/mochitest/addon-source/browser_dbg_addon5/test.xul
new file mode 100644
index 000000000..733817ad8
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/addon-source/browser_dbg_addon5/test.xul
@@ -0,0 +1,8 @@
+<?xml version="1.0"?>
+
+<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
+
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+ <script type="text/javascript" src="testxul.js"/>
+ <label value="test.xul"/>
+</window>
diff --git a/devtools/client/debugger/test/mochitest/addon-source/browser_dbg_addon5/test2.jsm b/devtools/client/debugger/test/mochitest/addon-source/browser_dbg_addon5/test2.jsm
new file mode 100644
index 000000000..703869f43
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/addon-source/browser_dbg_addon5/test2.jsm
@@ -0,0 +1,6 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+const EXPORTED_SYMBOLS = ["Bar"];
+
+const Bar = {};
diff --git a/devtools/client/debugger/test/mochitest/addon-source/browser_dbg_addon5/test2.xul b/devtools/client/debugger/test/mochitest/addon-source/browser_dbg_addon5/test2.xul
new file mode 100644
index 000000000..372d05587
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/addon-source/browser_dbg_addon5/test2.xul
@@ -0,0 +1,8 @@
+<?xml version="1.0"?>
+
+<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
+
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+ <script type="text/javascript" src="testxul2.js"/>
+ <label value="test2.xul"/>
+</window>
diff --git a/devtools/client/debugger/test/mochitest/addon-source/browser_dbg_addon5/testxul.js b/devtools/client/debugger/test/mochitest/addon-source/browser_dbg_addon5/testxul.js
new file mode 100644
index 000000000..30ad9d2f8
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/addon-source/browser_dbg_addon5/testxul.js
@@ -0,0 +1,4 @@
+// Define something in here or the script may get collected
+window.addEventListener("unload", function () {
+ window.foo = "bar";
+});
diff --git a/devtools/client/debugger/test/mochitest/addon-source/browser_dbg_addon5/testxul2.js b/devtools/client/debugger/test/mochitest/addon-source/browser_dbg_addon5/testxul2.js
new file mode 100644
index 000000000..30ad9d2f8
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/addon-source/browser_dbg_addon5/testxul2.js
@@ -0,0 +1,4 @@
+// Define something in here or the script may get collected
+window.addEventListener("unload", function () {
+ window.foo = "bar";
+});
diff --git a/devtools/client/debugger/test/mochitest/addon-source/browser_dbg_addon_webext_contentscript/manifest.json b/devtools/client/debugger/test/mochitest/addon-source/browser_dbg_addon_webext_contentscript/manifest.json
new file mode 100644
index 000000000..ebc834bf7
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/addon-source/browser_dbg_addon_webext_contentscript/manifest.json
@@ -0,0 +1,18 @@
+{
+ "manifest_version": 2,
+ "name": "test content script sources",
+ "description": "test content script sources",
+ "version": "0.1.0",
+ "applications": {
+ "gecko": {
+ "id": "test-contentscript-sources@mozilla.com"
+ }
+ },
+ "content_scripts": [
+ {
+ "matches": ["<all_urls>"],
+ "js": ["webext-content-script.js"],
+ "run_at": "document_start"
+ }
+ ]
+}
diff --git a/devtools/client/debugger/test/mochitest/addon-source/browser_dbg_addon_webext_contentscript/webext-content-script.js b/devtools/client/debugger/test/mochitest/addon-source/browser_dbg_addon_webext_contentscript/webext-content-script.js
new file mode 100644
index 000000000..591c78840
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/addon-source/browser_dbg_addon_webext_contentscript/webext-content-script.js
@@ -0,0 +1 @@
+console.log("CONTENT SCRIPT LOADED");
diff --git a/devtools/client/debugger/test/mochitest/addon-webext-contentscript.xpi b/devtools/client/debugger/test/mochitest/addon-webext-contentscript.xpi
new file mode 100644
index 000000000..9e61dec1d
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/addon-webext-contentscript.xpi
Binary files differ
diff --git a/devtools/client/debugger/test/mochitest/addon1.xpi b/devtools/client/debugger/test/mochitest/addon1.xpi
new file mode 100644
index 000000000..689689ebe
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/addon1.xpi
Binary files differ
diff --git a/devtools/client/debugger/test/mochitest/addon2.xpi b/devtools/client/debugger/test/mochitest/addon2.xpi
new file mode 100644
index 000000000..8f6ec6dc1
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/addon2.xpi
Binary files differ
diff --git a/devtools/client/debugger/test/mochitest/addon3.xpi b/devtools/client/debugger/test/mochitest/addon3.xpi
new file mode 100644
index 000000000..b22fc3da7
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/addon3.xpi
Binary files differ
diff --git a/devtools/client/debugger/test/mochitest/addon4.xpi b/devtools/client/debugger/test/mochitest/addon4.xpi
new file mode 100644
index 000000000..1f6f106f3
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/addon4.xpi
Binary files differ
diff --git a/devtools/client/debugger/test/mochitest/addon5.xpi b/devtools/client/debugger/test/mochitest/addon5.xpi
new file mode 100644
index 000000000..56b38761d
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/addon5.xpi
Binary files differ
diff --git a/devtools/client/debugger/test/mochitest/browser.ini b/devtools/client/debugger/test/mochitest/browser.ini
new file mode 100644
index 000000000..832addf89
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/browser.ini
@@ -0,0 +1,317 @@
+# Tests in this directory are split into two manifests (this and browser2.ini)
+# to facilitate better chunking; see bug 1294489.
+
+[DEFAULT]
+tags = devtools
+subsuite = devtools
+skip-if = (os == 'linux' && debug && bits == 32)
+support-files =
+ addon1.xpi
+ addon2.xpi
+ addon3.xpi
+ addon4.xpi
+ addon5.xpi
+ addon-webext-contentscript.xpi
+ addon-source/browser_dbg_addon5/*
+ code_binary_search.coffee
+ code_binary_search.js
+ code_binary_search.map
+ code_blackboxing_blackboxme.js
+ code_blackboxing_one.js
+ code_blackboxing_three.js
+ code_blackboxing_two.js
+ code_blackboxing_unblackbox.min.js
+ code_breakpoints-break-on-last-line-of-script-on-reload.js
+ code_breakpoints-other-tabs.js
+ code_bug-896139.js
+ code_frame-script.js
+ code_function-jump-01.js
+ code_function-search-01.js
+ code_function-search-02.js
+ code_function-search-03.js
+ code_location-changes.js
+ code_listworkers-worker1.js
+ code_listworkers-worker2.js
+ code_math.js
+ code_math.map
+ code_math.min.js
+ code_math_bogus_map.js
+ code_same-line-functions.js
+ code_script-eval.js
+ code_script-switching-01.js
+ code_script-switching-02.js
+ code_test-editor-mode
+ code_ugly.js
+ code_ugly-2.js
+ code_ugly-3.js
+ code_ugly-4.js
+ code_ugly-5.js
+ code_ugly-6.js
+ code_ugly-7.js
+ code_ugly-8
+ code_ugly-8^headers^
+ code_worker-source-map.coffee
+ code_worker-source-map.js
+ code_worker-source-map.js.map
+ code_WorkerActor.attach-worker1.js
+ code_WorkerActor.attach-worker2.js
+ code_WorkerActor.attachThread-worker.js
+ doc_auto-pretty-print-01.html
+ doc_auto-pretty-print-02.html
+ doc_binary_search.html
+ doc_blackboxing.html
+ doc_blackboxing_unblackbox.html
+ doc_breakpoints-break-on-last-line-of-script-on-reload.html
+ doc_breakpoints-other-tabs.html
+ doc_breakpoints-reload.html
+ doc_bug-896139.html
+ doc_closures.html
+ doc_closure-optimized-out.html
+ doc_cmd-break.html
+ doc_cmd-dbg.html
+ doc_breakpoint-move.html
+ doc_conditional-breakpoints.html
+ doc_domnode-variables.html
+ doc_editor-mode.html
+ doc_empty-tab-01.html
+ doc_empty-tab-02.html
+ doc_event-listeners-01.html
+ doc_event-listeners-02.html
+ doc_event-listeners-03.html
+ doc_event-listeners-04.html
+ doc_frame-parameters.html
+ doc_function-display-name.html
+ doc_function-jump.html
+ doc_function-search.html
+ doc_global-method-override.html
+ doc_iframes.html
+ doc_included-script.html
+ doc_inline-debugger-statement.html
+ doc_inline-script.html
+ doc_large-array-buffer.html
+ doc_listworkers-tab.html
+ doc_map-set.html
+ doc_minified.html
+ doc_minified_bogus_map.html
+ doc_native-event-handler.html
+ doc_no-page-sources.html
+ doc_pause-exceptions.html
+ doc_pretty-print.html
+ doc_pretty-print-2.html
+ doc_pretty-print-3.html
+ doc_pretty-print-on-paused.html
+ doc_promise-get-allocation-stack.html
+ doc_promise-get-fulfillment-stack.html
+ doc_promise-get-rejection-stack.html
+ doc_promise.html
+ doc_proxy.html
+ doc_random-javascript.html
+ doc_recursion-stack.html
+ doc_scope-variable.html
+ doc_scope-variable-2.html
+ doc_scope-variable-3.html
+ doc_scope-variable-4.html
+ doc_script-eval.html
+ doc_script-bookmarklet.html
+ doc_script-switching-01.html
+ doc_script-switching-02.html
+ doc_script_webext_contentscript.html
+ doc_split-console-paused-reload.html
+ doc_step-many-statements.html
+ doc_step-out.html
+ doc_terminate-on-tab-close.html
+ doc_watch-expressions.html
+ doc_watch-expression-button.html
+ doc_whitespace-property-names.html
+ doc_with-frame.html
+ doc_worker-source-map.html
+ doc_WorkerActor.attach-tab1.html
+ doc_WorkerActor.attach-tab2.html
+ doc_WorkerActor.attachThread-tab.html
+ head.js
+ sjs_post-page.sjs
+ sjs_random-javascript.sjs
+ testactors.js
+ !/devtools/client/commandline/test/helpers.js
+ !/devtools/client/framework/test/shared-head.js
+
+[browser_dbg_aaa_run_first_leaktest.js]
+skip-if = e10s && debug
+[browser_dbg_addonactor.js]
+tags = addons
+[browser_dbg_addon-sources.js]
+tags = addons
+[browser_dbg_addon-workers-dbg-enabled.js]
+tags = addons
+[browser_dbg_addon-modules.js]
+skip-if = e10s # TODO
+tags = addons
+[browser_dbg_addon-modules-unpacked.js]
+skip-if = e10s # TODO
+tags = addons
+[browser_dbg_addon-panels.js]
+tags = addons
+[browser_dbg_addon-console.js]
+skip-if = e10s && debug || os == 'win' # bug 1005274
+tags = addons
+[browser_dbg_auto-pretty-print-01.js]
+[browser_dbg_auto-pretty-print-02.js]
+[browser_dbg_auto-pretty-print-03.js]
+[browser_dbg_bfcache.js]
+skip-if = e10s || true # bug 1113935
+[browser_dbg_blackboxing-01.js]
+[browser_dbg_blackboxing-02.js]
+[browser_dbg_blackboxing-03.js]
+[browser_dbg_blackboxing-04.js]
+[browser_dbg_blackboxing-05.js]
+[browser_dbg_blackboxing-06.js]
+[browser_dbg_blackboxing-07.js]
+[browser_dbg_breadcrumbs-access.js]
+[browser_dbg_break-in-anon.js]
+[browser_dbg_break-on-next.js]
+[browser_dbg_break-on-next-console.js]
+[browser_dbg_break-on-dom-01.js]
+[browser_dbg_break-on-dom-02.js]
+[browser_dbg_break-on-dom-03.js]
+[browser_dbg_break-on-dom-04.js]
+[browser_dbg_break-on-dom-05.js]
+[browser_dbg_break-on-dom-06.js]
+[browser_dbg_break-on-dom-07.js]
+[browser_dbg_break-on-dom-08.js]
+[browser_dbg_break-on-dom-event-01.js]
+skip-if = e10s || os == "mac" || e10s # Bug 895426
+[browser_dbg_break-on-dom-event-02.js]
+skip-if = e10s # TODO
+[browser_dbg_break-on-dom-event-03.js]
+skip-if = e10s # TODO
+[browser_dbg_break-unselected.js]
+[browser_dbg_breakpoints-actual-location.js]
+[browser_dbg_breakpoints-actual-location2.js]
+[browser_dbg_breakpoints-break-on-last-line-of-script-on-reload.js]
+skip-if = e10s # Bug 1093535
+[browser_dbg_breakpoints-button-01.js]
+[browser_dbg_breakpoints-button-02.js]
+[browser_dbg_breakpoints-condition-thrown-message.js]
+skip-if = e10s && debug
+[browser_dbg_breakpoints-contextmenu-add.js]
+[browser_dbg_breakpoints-contextmenu.js]
+[browser_dbg_breakpoints-disabled-reload.js]
+skip-if = e10s # Bug 1093535
+[browser_dbg_breakpoints-editor.js]
+skip-if = e10s && debug
+[browser_dbg_breakpoints-eval.js]
+skip-if = e10s && debug
+[browser_dbg_breakpoints-highlight.js]
+skip-if = e10s && debug
+[browser_dbg_breakpoints-new-script.js]
+skip-if = e10s && debug
+[browser_dbg_breakpoints-other-tabs.js]
+skip-if = e10s && debug
+[browser_dbg_breakpoints-pane.js]
+skip-if = e10s && debug
+[browser_dbg_breakpoints-reload.js]
+skip-if = e10s && debug
+[browser_dbg_bug-896139.js]
+skip-if = e10s && debug
+[browser_dbg_chrome-create.js]
+skip-if = e10s && debug
+[browser_dbg_chrome-debugging.js]
+skip-if = e10s && debug
+[browser_dbg_clean-exit-window.js]
+skip-if = true # Bug 933950 (leaky test)
+[browser_dbg_clean-exit.js]
+skip-if = true # Bug 1044985 (racy test)
+[browser_dbg_closure-inspection.js]
+skip-if = e10s && debug
+[browser_dbg_cmd-blackbox.js]
+skip-if = e10s && debug
+[browser_dbg_cmd-break.js]
+skip-if = e10s # TODO
+[browser_dbg_cmd-dbg.js]
+skip-if = e10s # TODO
+[browser_dbg_conditional-breakpoints-01.js]
+skip-if = e10s && debug
+[browser_dbg_conditional-breakpoints-02.js]
+skip-if = e10s && debug
+[browser_dbg_conditional-breakpoints-03.js]
+skip-if = e10s && debug
+[browser_dbg_conditional-breakpoints-04.js]
+skip-if = e10s && debug
+[browser_dbg_conditional-breakpoints-05.js]
+skip-if = e10s && debug
+[browser_dbg_console-eval.js]
+skip-if = e10s && debug
+[browser_dbg_console-named-eval.js]
+skip-if = e10s && debug
+[browser_dbg_server-conditional-bp-01.js]
+skip-if = e10s && debug
+[browser_dbg_server-conditional-bp-02.js]
+skip-if = e10s && debug
+[browser_dbg_server-conditional-bp-03.js]
+skip-if = e10s && debug
+[browser_dbg_server-conditional-bp-04.js]
+skip-if = e10s && debug
+[browser_dbg_server-conditional-bp-05.js]
+skip-if = e10s && debug
+[browser_dbg_controller-evaluate-01.js]
+skip-if = e10s && debug
+[browser_dbg_controller-evaluate-02.js]
+skip-if = e10s && debug
+[browser_dbg_debugger-statement.js]
+skip-if = e10s && debug
+[browser_dbg_editor-contextmenu.js]
+skip-if = e10s && debug
+[browser_dbg_editor-mode.js]
+skip-if = e10s && debug
+[browser_dbg_event-listeners-01.js]
+skip-if = e10s && debug
+[browser_dbg_event-listeners-02.js]
+skip-if = e10s && debug
+[browser_dbg_event-listeners-03.js]
+skip-if = e10s && debug
+[browser_dbg_event-listeners-04.js]
+skip-if = debug || e10s # debug bug 1142597, e10s bug 1146603.
+[browser_dbg_file-reload.js]
+skip-if = e10s && debug
+[browser_dbg_function-display-name.js]
+skip-if = e10s && debug
+[browser_dbg_global-method-override.js]
+skip-if = e10s && debug
+[browser_dbg_globalactor.js]
+skip-if = e10s # TODO
+[browser_dbg_hide-toolbar-buttons.js]
+skip-if = e10s
+[browser_dbg_host-layout.js]
+skip-if = e10s && debug
+[browser_dbg_jump-to-function-definition.js]
+skip-if = e10s && debug
+[browser_dbg_iframes.js]
+skip-if = e10s # TODO
+[browser_dbg_instruments-pane-collapse.js]
+skip-if = e10s && debug
+[browser_dbg_instruments-pane-collapse_keyboard.js]
+skip-if = (os == 'mac' && e10s && debug) # Full keyboard navigation on OSX only works if Full Keyboard Access setting is set to All Control
+[browser_dbg_interrupts.js]
+skip-if = e10s && debug
+[browser_dbg_listaddons.js]
+skip-if = e10s && debug
+tags = addons
+[browser_dbg_listtabs-01.js]
+skip-if = e10s # TODO
+[browser_dbg_listtabs-02.js]
+skip-if = true # Never worked for remote frames, needs a mock DebuggerServerConnection
+[browser_dbg_listtabs-03.js]
+skip-if = e10s && debug
+[browser_dbg_listworkers.js]
+[browser_dbg_location-changes-01-simple.js]
+skip-if = e10s && debug
+[browser_dbg_location-changes-02-blank.js]
+skip-if = e10s && debug
+[browser_dbg_location-changes-03-new.js]
+skip-if = e10s # TODO
+[browser_dbg_location-changes-04-breakpoint.js]
+skip-if = e10s # TODO
+[browser_dbg_multiple-windows.js]
+skip-if = e10s # TODO
+[browser_dbg_navigation.js]
+skip-if = e10s && debug
diff --git a/devtools/client/debugger/test/mochitest/browser2.ini b/devtools/client/debugger/test/mochitest/browser2.ini
new file mode 100644
index 000000000..193c510ff
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/browser2.ini
@@ -0,0 +1,460 @@
+# Tests in this directory are split into two manifests (this and browser.ini)
+# to facilitate better chunking; see bug 1294489.
+
+[DEFAULT]
+tags = devtools
+subsuite = devtools
+skip-if = (os == 'linux' && debug && bits == 32)
+support-files =
+ addon1.xpi
+ addon2.xpi
+ addon3.xpi
+ addon4.xpi
+ addon5.xpi
+ addon-webext-contentscript.xpi
+ addon-source/browser_dbg_addon5/*
+ code_binary_search.coffee
+ code_binary_search.js
+ code_binary_search.map
+ code_blackboxing_blackboxme.js
+ code_blackboxing_one.js
+ code_blackboxing_three.js
+ code_blackboxing_two.js
+ code_blackboxing_unblackbox.min.js
+ code_breakpoints-break-on-last-line-of-script-on-reload.js
+ code_breakpoints-other-tabs.js
+ code_bug-896139.js
+ code_frame-script.js
+ code_function-jump-01.js
+ code_function-search-01.js
+ code_function-search-02.js
+ code_function-search-03.js
+ code_location-changes.js
+ code_listworkers-worker1.js
+ code_listworkers-worker2.js
+ code_math.js
+ code_math.map
+ code_math.min.js
+ code_math_bogus_map.js
+ code_same-line-functions.js
+ code_script-eval.js
+ code_script-switching-01.js
+ code_script-switching-02.js
+ code_test-editor-mode
+ code_ugly.js
+ code_ugly-2.js
+ code_ugly-3.js
+ code_ugly-4.js
+ code_ugly-5.js
+ code_ugly-6.js
+ code_ugly-7.js
+ code_ugly-8
+ code_ugly-8^headers^
+ code_worker-source-map.coffee
+ code_worker-source-map.js
+ code_worker-source-map.js.map
+ code_WorkerActor.attach-worker1.js
+ code_WorkerActor.attach-worker2.js
+ code_WorkerActor.attachThread-worker.js
+ doc_auto-pretty-print-01.html
+ doc_auto-pretty-print-02.html
+ doc_binary_search.html
+ doc_blackboxing.html
+ doc_blackboxing_unblackbox.html
+ doc_breakpoints-break-on-last-line-of-script-on-reload.html
+ doc_breakpoints-other-tabs.html
+ doc_breakpoints-reload.html
+ doc_bug-896139.html
+ doc_closures.html
+ doc_closure-optimized-out.html
+ doc_cmd-break.html
+ doc_cmd-dbg.html
+ doc_breakpoint-move.html
+ doc_conditional-breakpoints.html
+ doc_domnode-variables.html
+ doc_editor-mode.html
+ doc_empty-tab-01.html
+ doc_empty-tab-02.html
+ doc_event-listeners-01.html
+ doc_event-listeners-02.html
+ doc_event-listeners-03.html
+ doc_event-listeners-04.html
+ doc_frame-parameters.html
+ doc_function-display-name.html
+ doc_function-jump.html
+ doc_function-search.html
+ doc_global-method-override.html
+ doc_iframes.html
+ doc_included-script.html
+ doc_inline-debugger-statement.html
+ doc_inline-script.html
+ doc_large-array-buffer.html
+ doc_listworkers-tab.html
+ doc_map-set.html
+ doc_minified.html
+ doc_minified_bogus_map.html
+ doc_native-event-handler.html
+ doc_no-page-sources.html
+ doc_pause-exceptions.html
+ doc_pretty-print.html
+ doc_pretty-print-2.html
+ doc_pretty-print-3.html
+ doc_pretty-print-on-paused.html
+ doc_promise-get-allocation-stack.html
+ doc_promise-get-fulfillment-stack.html
+ doc_promise-get-rejection-stack.html
+ doc_promise.html
+ doc_proxy.html
+ doc_random-javascript.html
+ doc_recursion-stack.html
+ doc_scope-variable.html
+ doc_scope-variable-2.html
+ doc_scope-variable-3.html
+ doc_scope-variable-4.html
+ doc_script-eval.html
+ doc_script-bookmarklet.html
+ doc_script-switching-01.html
+ doc_script-switching-02.html
+ doc_script_webext_contentscript.html
+ doc_split-console-paused-reload.html
+ doc_step-many-statements.html
+ doc_step-out.html
+ doc_terminate-on-tab-close.html
+ doc_watch-expressions.html
+ doc_watch-expression-button.html
+ doc_whitespace-property-names.html
+ doc_with-frame.html
+ doc_worker-source-map.html
+ doc_WorkerActor.attach-tab1.html
+ doc_WorkerActor.attach-tab2.html
+ doc_WorkerActor.attachThread-tab.html
+ head.js
+ sjs_post-page.sjs
+ sjs_random-javascript.sjs
+ testactors.js
+ !/devtools/client/commandline/test/helpers.js
+ !/devtools/client/framework/test/shared-head.js
+
+[browser_dbg_no-dangling-breakpoints.js]
+skip-if = e10s && debug
+[browser_dbg_no-page-sources.js]
+skip-if = e10s && debug
+[browser_dbg_on-pause-highlight.js]
+skip-if = e10s && debug
+[browser_dbg_on-pause-raise.js]
+skip-if = e10s && debug || os == "linux" # Bug 888811 & bug 891176
+[browser_dbg_optimized-out-vars.js]
+skip-if = e10s && debug
+[browser_dbg_panel-size.js]
+skip-if = e10s && debug
+[browser_dbg_parser-01.js]
+skip-if = e10s && debug
+[browser_dbg_parser-02.js]
+skip-if = e10s && debug
+[browser_dbg_parser-03.js]
+skip-if = e10s && debug
+[browser_dbg_parser-04.js]
+skip-if = e10s && debug
+[browser_dbg_parser-05.js]
+skip-if = e10s && debug
+[browser_dbg_parser-06.js]
+skip-if = e10s && debug
+[browser_dbg_parser-07.js]
+skip-if = e10s && debug
+[browser_dbg_parser-08.js]
+skip-if = e10s && debug
+[browser_dbg_parser-09.js]
+skip-if = e10s && debug
+[browser_dbg_parser-10.js]
+skip-if = e10s && debug
+[browser_dbg_parser-11.js]
+[browser_dbg_parser-computed-name.js]
+[browser_dbg_parser-function-defaults.js]
+[browser_dbg_parser-spread-expression.js]
+[browser_dbg_parser-template-strings.js]
+skip-if = e10s && debug
+[browser_dbg_pause-exceptions-01.js]
+skip-if = e10s && debug
+[browser_dbg_pause-exceptions-02.js]
+skip-if = e10s && debug
+[browser_dbg_pause-no-step.js]
+skip-if = e10s && debug
+[browser_dbg_pause-resume.js]
+skip-if = e10s && debug
+[browser_dbg_pause-warning.js]
+skip-if = e10s && debug
+[browser_dbg_paused-keybindings.js]
+skip-if = e10s
+[browser_dbg_post-page.js]
+[browser_dbg_pretty-print-01.js]
+skip-if = e10s && debug
+[browser_dbg_pretty-print-02.js]
+skip-if = e10s && debug
+[browser_dbg_pretty-print-03.js]
+skip-if = e10s && debug
+[browser_dbg_pretty-print-04.js]
+skip-if = e10s && debug
+[browser_dbg_pretty-print-05.js]
+skip-if = e10s && debug
+[browser_dbg_pretty-print-06.js]
+skip-if = e10s && debug
+[browser_dbg_pretty-print-07.js]
+skip-if = e10s && debug
+[browser_dbg_pretty-print-08.js]
+skip-if = e10s && debug
+[browser_dbg_pretty-print-09.js]
+skip-if = e10s && debug
+[browser_dbg_pretty-print-10.js]
+skip-if = e10s && debug
+[browser_dbg_pretty-print-11.js]
+skip-if = e10s && debug
+[browser_dbg_pretty-print-12.js]
+skip-if = e10s && debug
+[browser_dbg_pretty-print-13.js]
+skip-if = e10s && debug
+[browser_dbg_pretty-print-on-paused.js]
+skip-if = e10s && debug
+[browser_dbg_progress-listener-bug.js]
+skip-if = e10s && debug
+[browser_dbg_promises-allocation-stack.js]
+skip-if = e10s && debug
+[browser_dbg_promises-chrome-allocation-stack.js]
+skip-if = true # Bug 1177730
+[browser_dbg_promises-fulfillment-stack.js]
+skip-if = e10s && debug
+[browser_dbg_promises-rejection-stack.js]
+skip-if = e10s && debug
+[browser_dbg_reload-preferred-script-02.js]
+skip-if = e10s && debug
+[browser_dbg_reload-preferred-script-03.js]
+skip-if = e10s && debug
+[browser_dbg_reload-same-script.js]
+skip-if = e10s && debug
+[browser_dbg_scripts-switching-01.js]
+skip-if = e10s && debug
+[browser_dbg_scripts-switching-02.js]
+skip-if = e10s && debug
+[browser_dbg_scripts-switching-03.js]
+skip-if = e10s && debug
+[browser_dbg_search-autofill-identifier.js]
+skip-if = e10s && debug
+[browser_dbg_search-basic-01.js]
+skip-if = e10s && debug
+[browser_dbg_search-basic-02.js]
+skip-if = e10s && debug
+[browser_dbg_search-basic-03.js]
+skip-if = e10s && debug
+[browser_dbg_search-basic-04.js]
+skip-if = e10s && debug
+[browser_dbg_search-global-01.js]
+skip-if = e10s && debug
+[browser_dbg_search-global-02.js]
+skip-if = e10s && debug
+[browser_dbg_search-global-03.js]
+skip-if = e10s # Bug 1093535
+[browser_dbg_search-global-04.js]
+skip-if = e10s && debug
+[browser_dbg_search-global-05.js]
+skip-if = e10s && debug
+[browser_dbg_search-global-06.js]
+skip-if = e10s && debug
+[browser_dbg_search-popup-jank.js]
+skip-if = e10s && debug
+[browser_dbg_search-sources-01.js]
+skip-if = e10s && debug
+[browser_dbg_search-sources-02.js]
+skip-if = e10s && debug
+[browser_dbg_search-sources-03.js]
+skip-if = e10s && debug
+[browser_dbg_search-symbols.js]
+skip-if = (e10s && debug) || os == "linux" # Bug 1132375
+[browser_dbg_searchbox-help-popup-01.js]
+skip-if = e10s && debug
+[browser_dbg_searchbox-help-popup-02.js]
+skip-if = e10s && debug
+[browser_dbg_searchbox-parse.js]
+skip-if = (debug) || (os == 'linux' && asan) # asan, bug 1313861, debug: bug 1313861
+[browser_dbg_source-maps-01.js]
+skip-if = e10s && debug
+[browser_dbg_source-maps-02.js]
+skip-if = e10s && debug
+[browser_dbg_source-maps-03.js]
+skip-if = e10s && debug
+[browser_dbg_source-maps-04.js]
+skip-if = e10s # Bug 1093535
+[browser_dbg_sources-cache.js]
+[browser_dbg_sources-contextmenu-01.js]
+subsuite = clipboard
+[browser_dbg_sources-contextmenu-02.js]
+skip-if = e10s && debug
+[browser_dbg_sources-eval-01.js]
+skip-if = true # non-named eval sources turned off for now, bug 1124106
+[browser_dbg_sources-eval-02.js]
+[browser_dbg_sources-iframe-reload.js]
+[browser_dbg_sources-keybindings.js]
+subsuite = clipboard
+skip-if = e10s && debug
+[browser_dbg_sources-labels.js]
+skip-if = e10s && debug
+[browser_dbg_sources-large.js]
+[browser_dbg_sources-sorting.js]
+skip-if = e10s && debug
+[browser_dbg_sources-bookmarklet.js]
+skip-if = e10s && debug
+[browser_dbg_sources-webext-contentscript.js]
+[browser_dbg_split-console-paused-reload.js]
+skip-if = true # Bug 1288348 - previously e10s && debug
+[browser_dbg_stack-01.js]
+skip-if = e10s && debug
+[browser_dbg_stack-02.js]
+skip-if = e10s && debug
+[browser_dbg_stack-03.js]
+skip-if = e10s # TODO
+[browser_dbg_stack-04.js]
+skip-if = e10s && debug
+[browser_dbg_stack-05.js]
+skip-if = e10s && (debug || asan) # timeouts
+[browser_dbg_stack-06.js]
+skip-if = e10s && debug
+[browser_dbg_stack-07.js]
+skip-if = e10s && debug
+[browser_dbg_stack-contextmenu-01.js]
+skip-if = e10s && debug
+[browser_dbg_stack-contextmenu-02.js]
+subsuite = clipboard
+skip-if = e10s && debug
+[browser_dbg_step-out.js]
+skip-if = e10s && debug
+[browser_dbg_tabactor-01.js]
+skip-if = e10s # TODO
+[browser_dbg_tabactor-02.js]
+skip-if = e10s # TODO
+[browser_dbg_terminate-on-tab-close.js]
+skip-if = e10s && debug
+[browser_dbg_variables-view-01.js]
+skip-if = e10s && debug
+[browser_dbg_variables-view-02.js]
+skip-if = e10s && debug
+[browser_dbg_variables-view-03.js]
+skip-if = e10s && debug
+[browser_dbg_variables-view-04.js]
+skip-if = e10s && debug
+[browser_dbg_variables-view-05.js]
+skip-if = e10s && debug
+[browser_dbg_variables-view-06.js]
+skip-if = e10s && debug
+[browser_dbg_variables-view-07.js]
+skip-if = e10s && debug
+[browser_dbg_variables-view-08.js]
+skip-if = e10s && debug
+[browser_dbg_variables-view-accessibility.js]
+subsuite = clipboard
+skip-if = e10s && debug
+[browser_dbg_variables-view-data.js]
+skip-if = e10s && debug
+[browser_dbg_variables-view-edit-cancel.js]
+skip-if = e10s && debug
+[browser_dbg_variables-view-edit-click.js]
+skip-if = e10s || (os == 'mac' || os == 'win') && (debug == false) # Bug 986166
+[browser_dbg_variables-view-edit-getset-01.js]
+skip-if = e10s && debug
+[browser_dbg_variables-view-edit-getset-02.js]
+skip-if = e10s && debug
+[browser_dbg_variables-view-edit-value.js]
+skip-if = e10s && debug
+[browser_dbg_variables-view-edit-watch.js]
+skip-if = e10s && debug
+[browser_dbg_variables-view-filter-01.js]
+skip-if = e10s && debug
+[browser_dbg_variables-view-filter-02.js]
+skip-if = e10s && debug
+[browser_dbg_variables-view-filter-03.js]
+skip-if = e10s && debug
+[browser_dbg_variables-view-filter-04.js]
+skip-if = e10s && debug
+[browser_dbg_variables-view-filter-05.js]
+skip-if = e10s && debug
+[browser_dbg_variables-view-filter-pref.js]
+skip-if = e10s && debug
+[browser_dbg_variables-view-filter-searchbox.js]
+skip-if = e10s && debug
+[browser_dbg_variables-view-frame-parameters-01.js]
+skip-if = e10s && debug
+[browser_dbg_variables-view-frame-parameters-02.js]
+skip-if = e10s && debug
+[browser_dbg_variables-view-frame-parameters-03.js]
+skip-if = e10s && debug
+[browser_dbg_variables-view-frame-with.js]
+skip-if = e10s && debug
+[browser_dbg_variables-view-frozen-sealed-nonext.js]
+skip-if = e10s && debug
+[browser_dbg_variables-view-hide-non-enums.js]
+[browser_dbg_variables-view-large-array-buffer.js]
+[browser_dbg_variables-view-map-set.js]
+skip-if = e10s && debug
+[browser_dbg_variables-view-override-01.js]
+skip-if = e10s && debug
+[browser_dbg_variables-view-override-02.js]
+skip-if = e10s && debug
+[browser_dbg_variables-view-popup-01.js]
+skip-if = e10s && debug
+[browser_dbg_variables-view-popup-02.js]
+skip-if = e10s && debug
+[browser_dbg_variables-view-popup-03.js]
+skip-if = e10s && debug
+[browser_dbg_variables-view-popup-04.js]
+skip-if = e10s && debug
+[browser_dbg_variables-view-popup-05.js]
+skip-if = e10s && debug
+[browser_dbg_variables-view-popup-06.js]
+skip-if = e10s && debug
+[browser_dbg_variables-view-popup-07.js]
+skip-if = e10s && debug
+[browser_dbg_variables-view-popup-08.js]
+skip-if = e10s && debug
+[browser_dbg_variables-view-popup-09.js]
+skip-if = e10s && debug
+[browser_dbg_variables-view-popup-10.js]
+skip-if = e10s && debug
+[browser_dbg_variables-view-popup-11.js]
+skip-if = e10s && debug
+[browser_dbg_variables-view-popup-12.js]
+skip-if = e10s && debug
+[browser_dbg_variables-view-popup-13.js]
+skip-if = e10s && debug
+[browser_dbg_variables-view-popup-14.js]
+skip-if = true # Bug 1029545
+[browser_dbg_variables-view-popup-15.js]
+skip-if = e10s && debug
+[browser_dbg_variables-view-popup-16.js]
+skip-if = e10s && debug
+[browser_dbg_variables-view-popup-17.js]
+skip-if = e10s && debug
+[browser_dbg_variables-view-reexpand-01.js]
+skip-if = e10s && debug
+[browser_dbg_variables-view-reexpand-02.js]
+skip-if = e10s && debug
+[browser_dbg_variables-view-reexpand-03.js]
+skip-if = e10s && debug
+[browser_dbg_variables-view-webidl.js]
+skip-if = e10s && debug
+[browser_dbg_watch-expressions-01.js]
+skip-if = e10s && debug
+[browser_dbg_watch-expressions-02.js]
+skip-if = e10s && debug
+[browser_dbg_worker-console-01.js]
+skip-if = e10s && debug
+[browser_dbg_worker-console-02.js]
+skip-if = e10s && debug
+[browser_dbg_worker-console-03.js]
+skip-if = e10s && debug
+[browser_dbg_worker-source-map.js]
+skip-if = e10s && debug
+[browser_dbg_worker-window.js]
+skip-if = e10s && debug
+[browser_dbg_WorkerActor.attach.js]
+skip-if = e10s && debug
+[browser_dbg_WorkerActor.attachThread.js]
+skip-if = e10s && debug
+[browser_dbg_split-console-keypress.js]
+skip-if = e10s && (debug || os == "linux") # Bug 1214439
diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_WorkerActor.attach.js b/devtools/client/debugger/test/mochitest/browser_dbg_WorkerActor.attach.js
new file mode 100644
index 000000000..68d7f1b26
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_WorkerActor.attach.js
@@ -0,0 +1,62 @@
+var MAX_TOTAL_VIEWERS = "browser.sessionhistory.max_total_viewers";
+
+var TAB1_URL = EXAMPLE_URL + "doc_WorkerActor.attach-tab1.html";
+var TAB2_URL = EXAMPLE_URL + "doc_WorkerActor.attach-tab2.html";
+var WORKER1_URL = "code_WorkerActor.attach-worker1.js";
+var WORKER2_URL = "code_WorkerActor.attach-worker2.js";
+
+function test() {
+ Task.spawn(function* () {
+ let oldMaxTotalViewers = SpecialPowers.getIntPref(MAX_TOTAL_VIEWERS);
+ SpecialPowers.setIntPref(MAX_TOTAL_VIEWERS, 10);
+
+ DebuggerServer.init();
+ DebuggerServer.addBrowserActors();
+
+ let client = new DebuggerClient(DebuggerServer.connectPipe());
+ yield connect(client);
+
+ let tab = yield addTab(TAB1_URL);
+ let { tabs } = yield listTabs(client);
+ let [, tabClient] = yield attachTab(client, findTab(tabs, TAB1_URL));
+ yield listWorkers(tabClient);
+
+ // If a page still has pending network requests, it will not be moved into
+ // the bfcache. Consequently, we cannot use waitForWorkerListChanged here,
+ // because the worker is not guaranteed to have finished loading when it is
+ // registered. Instead, we have to wait for the promise returned by
+ // createWorker in the tab to be resolved.
+ yield createWorkerInTab(tab, WORKER1_URL);
+ let { workers } = yield listWorkers(tabClient);
+ let [, workerClient1] = yield attachWorker(tabClient,
+ findWorker(workers, WORKER1_URL));
+ is(workerClient1.isClosed, false, "worker in tab 1 should not be closed");
+
+ executeSoon(() => {
+ tab.linkedBrowser.loadURI(TAB2_URL);
+ });
+ yield waitForWorkerClose(workerClient1);
+ is(workerClient1.isClosed, true, "worker in tab 1 should be closed");
+
+ yield createWorkerInTab(tab, WORKER2_URL);
+ ({ workers } = yield listWorkers(tabClient));
+ let [, workerClient2] = yield attachWorker(tabClient,
+ findWorker(workers, WORKER2_URL));
+ is(workerClient2.isClosed, false, "worker in tab 2 should not be closed");
+
+ executeSoon(() => {
+ tab.linkedBrowser.contentWindow.history.back();
+ });
+ yield waitForWorkerClose(workerClient2);
+ is(workerClient2.isClosed, true, "worker in tab 2 should be closed");
+
+ ({ workers } = yield listWorkers(tabClient));
+ [, workerClient1] = yield attachWorker(tabClient,
+ findWorker(workers, WORKER1_URL));
+ is(workerClient1.isClosed, false, "worker in tab 1 should not be closed");
+
+ yield close(client);
+ SpecialPowers.setIntPref(MAX_TOTAL_VIEWERS, oldMaxTotalViewers);
+ finish();
+ });
+}
diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_WorkerActor.attachThread.js b/devtools/client/debugger/test/mochitest/browser_dbg_WorkerActor.attachThread.js
new file mode 100644
index 000000000..34e59a418
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_WorkerActor.attachThread.js
@@ -0,0 +1,100 @@
+var TAB_URL = EXAMPLE_URL + "doc_WorkerActor.attachThread-tab.html";
+var WORKER_URL = "code_WorkerActor.attachThread-worker.js";
+
+function test() {
+ Task.spawn(function* () {
+ DebuggerServer.init();
+ DebuggerServer.addBrowserActors();
+
+ let client1 = new DebuggerClient(DebuggerServer.connectPipe());
+ yield connect(client1);
+ let client2 = new DebuggerClient(DebuggerServer.connectPipe());
+ yield connect(client2);
+
+ let tab = yield addTab(TAB_URL);
+ let { tabs: tabs1 } = yield listTabs(client1);
+ let [, tabClient1] = yield attachTab(client1, findTab(tabs1, TAB_URL));
+ let { tabs: tabs2 } = yield listTabs(client2);
+ let [, tabClient2] = yield attachTab(client2, findTab(tabs2, TAB_URL));
+
+ yield listWorkers(tabClient1);
+ yield listWorkers(tabClient2);
+ yield createWorkerInTab(tab, WORKER_URL);
+ let { workers: workers1 } = yield listWorkers(tabClient1);
+ let [, workerClient1] = yield attachWorker(tabClient1,
+ findWorker(workers1, WORKER_URL));
+ let { workers: workers2 } = yield listWorkers(tabClient2);
+ let [, workerClient2] = yield attachWorker(tabClient2,
+ findWorker(workers2, WORKER_URL));
+
+ let location = { line: 5 };
+
+ let [, threadClient1] = yield attachThread(workerClient1);
+ let sources1 = yield getSources(threadClient1);
+ let sourceClient1 = threadClient1.source(findSource(sources1,
+ EXAMPLE_URL + WORKER_URL));
+ let [, breakpointClient1] = yield setBreakpoint(sourceClient1, location);
+ yield resume(threadClient1);
+
+ let [, threadClient2] = yield attachThread(workerClient2);
+ let sources2 = yield getSources(threadClient2);
+ let sourceClient2 = threadClient2.source(findSource(sources2,
+ EXAMPLE_URL + WORKER_URL));
+ let [, breakpointClient2] = yield setBreakpoint(sourceClient2, location);
+ yield resume(threadClient2);
+
+ let packet = yield source(sourceClient1);
+ let text = (yield new Promise(function (resolve) {
+ let request = new XMLHttpRequest();
+ request.open("GET", EXAMPLE_URL + WORKER_URL, true);
+ request.send();
+ request.onload = function () {
+ resolve(request.responseText);
+ };
+ }));
+ is(packet.source, text);
+
+ postMessageToWorkerInTab(tab, WORKER_URL, "ping");
+ yield Promise.all([
+ waitForPause(threadClient1).then((packet) => {
+ is(packet.type, "paused");
+ let why = packet.why;
+ is(why.type, "breakpoint");
+ is(why.actors.length, 1);
+ is(why.actors[0], breakpointClient1.actor);
+ let frame = packet.frame;
+ let where = frame.where;
+ is(where.source.actor, sourceClient1.actor);
+ is(where.line, location.line);
+ let variables = frame.environment.bindings.variables;
+ is(variables.a.value, 1);
+ is(variables.b.value.type, "undefined");
+ is(variables.c.value.type, "undefined");
+ return resume(threadClient1);
+ }),
+ waitForPause(threadClient2).then((packet) => {
+ is(packet.type, "paused");
+ let why = packet.why;
+ is(why.type, "breakpoint");
+ is(why.actors.length, 1);
+ is(why.actors[0], breakpointClient2.actor);
+ let frame = packet.frame;
+ let where = frame.where;
+ is(where.source.actor, sourceClient2.actor);
+ is(where.line, location.line);
+ let variables = frame.environment.bindings.variables;
+ is(variables.a.value, 1);
+ is(variables.b.value.type, "undefined");
+ is(variables.c.value.type, "undefined");
+ return resume(threadClient2);
+ }),
+ ]);
+
+ terminateWorkerInTab(tab, WORKER_URL);
+ yield waitForWorkerClose(workerClient1);
+ yield waitForWorkerClose(workerClient2);
+ yield close(client1);
+ yield close(client2);
+ finish();
+ });
+}
diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_aaa_run_first_leaktest.js b/devtools/client/debugger/test/mochitest/browser_dbg_aaa_run_first_leaktest.js
new file mode 100644
index 000000000..bd612456b
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_aaa_run_first_leaktest.js
@@ -0,0 +1,33 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * This tests if the debugger leaks on initialization and sudden destruction.
+ * You can also use this initialization format as a template for other tests.
+ * If leaks happen here, there's something very, very fishy going on.
+ */
+
+const TAB_URL = EXAMPLE_URL + "doc_script-switching-01.html";
+
+function test() {
+ // Wait longer for this very simple test that comes first, to make sure that
+ // GC from previous tests does not interfere with the debugger suite.
+ requestLongerTimeout(2);
+
+ let options = {
+ source: EXAMPLE_URL + "code_script-switching-01.js",
+ line: 1
+ };
+ initDebugger(TAB_URL, options).then(([aTab,, aPanel]) => {
+ ok(aTab, "Should have a tab available.");
+ ok(aPanel, "Should have a debugger pane available.");
+
+ waitForSourceAndCaretAndScopes(aPanel, "-02.js", 1).then(() => {
+ resumeDebuggerThenCloseAndFinish(aPanel);
+ });
+
+ callInTab(aTab, "firstCall");
+ });
+}
diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_addon-console.js b/devtools/client/debugger/test/mochitest/browser_dbg_addon-console.js
new file mode 100644
index 000000000..cf615f181
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_addon-console.js
@@ -0,0 +1,47 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Test that the we can see console messages from the add-on
+
+const ADDON_ID = "browser_dbg_addon4@tests.mozilla.org";
+const ADDON_PATH = "addon4.xpi";
+
+function getCachedMessages(webConsole) {
+ let deferred = promise.defer();
+ webConsole.getCachedMessages(["ConsoleAPI"], (aResponse) => {
+ if (aResponse.error) {
+ deferred.reject(aResponse.error);
+ return;
+ }
+ deferred.resolve(aResponse.messages);
+ });
+ return deferred.promise;
+}
+
+function test() {
+ Task.spawn(function* () {
+ let addon = yield addTemporaryAddon(ADDON_PATH);
+ let addonDebugger = yield initAddonDebugger(ADDON_ID);
+
+ let webConsole = addonDebugger.webConsole;
+ let messages = yield getCachedMessages(webConsole);
+ is(messages.length, 1, "Should be one cached message");
+ is(messages[0].arguments[0].type, "object", "Should have logged an object");
+ is(messages[0].arguments[0].preview.ownProperties.msg.value, "Hello from the test add-on", "Should have got the right message");
+
+ let consolePromise = addonDebugger.once("console");
+
+ console.log("Bad message");
+ Services.obs.notifyObservers(null, "addon-test-ping", "");
+
+ let messageGrip = yield consolePromise;
+ is(messageGrip.arguments[0].type, "object", "Should have logged an object");
+ is(messageGrip.arguments[0].preview.ownProperties.msg.value, "Hello again", "Should have got the right message");
+
+ yield addonDebugger.destroy();
+ yield removeAddon(addon);
+ finish();
+ });
+}
diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_addon-modules-unpacked.js b/devtools/client/debugger/test/mochitest/browser_dbg_addon-modules-unpacked.js
new file mode 100644
index 000000000..5784eecb4
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_addon-modules-unpacked.js
@@ -0,0 +1,67 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Make sure the add-on actor can see loaded JS Modules from an add-on
+
+const ADDON_ID = "browser_dbg_addon5@tests.mozilla.org";
+const ADDON_PATH = "addon-source/browser_dbg_addon5/";
+const ADDON_URL = getTemporaryAddonURLFromPath(ADDON_PATH);
+
+function test() {
+ Task.spawn(function* () {
+ let addon = yield addTemporaryAddon(ADDON_PATH);
+ let tab1 = yield addTab("chrome://browser_dbg_addon5/content/test.xul");
+
+ let addonDebugger = yield initAddonDebugger(ADDON_ID);
+
+ is(addonDebugger.title,
+ `Developer Tools - Test unpacked add-on with JS Modules - ${ADDON_URL}`,
+ "Saw the right toolbox title.");
+
+ // Check the inital list of sources is correct
+ let groups = yield addonDebugger.getSourceGroups();
+ is(groups[0].name, "browser_dbg_addon5@tests.mozilla.org", "Add-on code should be the first group");
+ is(groups[1].name, "chrome://global", "XUL code should be the second group");
+ is(groups.length, 2, "Should be only two groups.");
+
+ let sources = groups[0].sources;
+ is(sources.length, 3, "Should be three sources");
+ ok(sources[0].url.endsWith("/browser_dbg_addon5/bootstrap.js"), "correct url for bootstrap code");
+ is(sources[0].label, "bootstrap.js", "correct label for bootstrap code");
+ is(sources[1].url, "resource://browser_dbg_addon5/test.jsm", "correct url for addon code");
+ is(sources[1].label, "test.jsm", "correct label for addon code");
+ is(sources[2].url, "chrome://browser_dbg_addon5/content/testxul.js", "correct url for addon tab code");
+ is(sources[2].label, "testxul.js", "correct label for addon tab code");
+
+ // Load a new module and tab and check they appear in the list of sources
+ Cu.import("resource://browser_dbg_addon5/test2.jsm", {});
+ let tab2 = yield addTab("chrome://browser_dbg_addon5/content/test2.xul");
+
+ groups = yield addonDebugger.getSourceGroups();
+ is(groups[0].name, "browser_dbg_addon5@tests.mozilla.org", "Add-on code should be the first group");
+ is(groups[1].name, "chrome://global", "XUL code should be the second group");
+ is(groups.length, 2, "Should be only two groups.");
+
+ sources = groups[0].sources;
+ is(sources.length, 5, "Should be five sources");
+ ok(sources[0].url.endsWith("/browser_dbg_addon5/bootstrap.js"), "correct url for bootstrap code");
+ is(sources[0].label, "bootstrap.js", "correct label for bootstrap code");
+ is(sources[1].url, "resource://browser_dbg_addon5/test.jsm", "correct url for addon code");
+ is(sources[1].label, "test.jsm", "correct label for addon code");
+ is(sources[2].url, "chrome://browser_dbg_addon5/content/testxul.js", "correct url for addon tab code");
+ is(sources[2].label, "testxul.js", "correct label for addon tab code");
+ is(sources[3].url, "resource://browser_dbg_addon5/test2.jsm", "correct url for addon code");
+ is(sources[3].label, "test2.jsm", "correct label for addon code");
+ is(sources[4].url, "chrome://browser_dbg_addon5/content/testxul2.js", "correct url for addon tab code");
+ is(sources[4].label, "testxul2.js", "correct label for addon tab code");
+
+ Cu.unload("resource://browser_dbg_addon5/test2.jsm");
+ yield addonDebugger.destroy();
+ yield removeTab(tab1);
+ yield removeTab(tab2);
+ yield removeAddon(addon);
+ finish();
+ });
+}
diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_addon-modules.js b/devtools/client/debugger/test/mochitest/browser_dbg_addon-modules.js
new file mode 100644
index 000000000..1ff7c600d
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_addon-modules.js
@@ -0,0 +1,66 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Make sure the add-on actor can see loaded JS Modules from an add-on
+
+const ADDON_ID = "browser_dbg_addon4@tests.mozilla.org";
+const ADDON_PATH = "addon4.xpi";
+const ADDON_URL = getTemporaryAddonURLFromPath(ADDON_PATH);
+
+function test() {
+ Task.spawn(function* () {
+ let addon = yield addTemporaryAddon(ADDON_PATH);
+ let tab1 = yield addTab("chrome://browser_dbg_addon4/content/test.xul");
+
+ let addonDebugger = yield initAddonDebugger(ADDON_ID);
+
+ is(addonDebugger.title, `Developer Tools - Test add-on with JS Modules - ${ADDON_URL}`,
+ "Saw the right toolbox title.");
+
+ // Check the inital list of sources is correct
+ let groups = yield addonDebugger.getSourceGroups();
+ is(groups[0].name, "browser_dbg_addon4@tests.mozilla.org", "Add-on code should be the first group");
+ is(groups[1].name, "chrome://global", "XUL code should be the second group");
+ is(groups.length, 2, "Should be only two groups.");
+
+ let sources = groups[0].sources;
+ is(sources.length, 3, "Should be three sources");
+ ok(sources[0].url.endsWith("/addon4.xpi!/bootstrap.js"), "correct url for bootstrap code");
+ is(sources[0].label, "bootstrap.js", "correct label for bootstrap code");
+ is(sources[1].url, "resource://browser_dbg_addon4/test.jsm", "correct url for addon code");
+ is(sources[1].label, "test.jsm", "correct label for addon code");
+ is(sources[2].url, "chrome://browser_dbg_addon4/content/testxul.js", "correct url for addon tab code");
+ is(sources[2].label, "testxul.js", "correct label for addon tab code");
+
+ // Load a new module and tab and check they appear in the list of sources
+ Cu.import("resource://browser_dbg_addon4/test2.jsm", {});
+ let tab2 = yield addTab("chrome://browser_dbg_addon4/content/test2.xul");
+
+ groups = yield addonDebugger.getSourceGroups();
+ is(groups[0].name, "browser_dbg_addon4@tests.mozilla.org", "Add-on code should be the first group");
+ is(groups[1].name, "chrome://global", "XUL code should be the second group");
+ is(groups.length, 2, "Should be only two groups.");
+
+ sources = groups[0].sources;
+ is(sources.length, 5, "Should be five sources");
+ ok(sources[0].url.endsWith("/addon4.xpi!/bootstrap.js"), "correct url for bootstrap code");
+ is(sources[0].label, "bootstrap.js", "correct label for bootstrap code");
+ is(sources[1].url, "resource://browser_dbg_addon4/test.jsm", "correct url for addon code");
+ is(sources[1].label, "test.jsm", "correct label for addon code");
+ is(sources[2].url, "chrome://browser_dbg_addon4/content/testxul.js", "correct url for addon tab code");
+ is(sources[2].label, "testxul.js", "correct label for addon tab code");
+ is(sources[3].url, "resource://browser_dbg_addon4/test2.jsm", "correct url for addon code");
+ is(sources[3].label, "test2.jsm", "correct label for addon code");
+ is(sources[4].url, "chrome://browser_dbg_addon4/content/testxul2.js", "correct url for addon tab code");
+ is(sources[4].label, "testxul2.js", "correct label for addon tab code");
+
+ Cu.unload("resource://browser_dbg_addon4/test2.jsm");
+ yield addonDebugger.destroy();
+ yield removeTab(tab1);
+ yield removeTab(tab2);
+ yield removeAddon(addon);
+ finish();
+ });
+}
diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_addon-panels.js b/devtools/client/debugger/test/mochitest/browser_dbg_addon-panels.js
new file mode 100644
index 000000000..aeda501b0
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_addon-panels.js
@@ -0,0 +1,49 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Ensure that only panels that are relevant to the addon debugger
+// display in the toolbox
+
+const ADDON_ID = "jid1-ami3akps3baaeg@jetpack";
+const ADDON_PATH = "addon3.xpi";
+
+var gAddon, gClient, gThreadClient, gDebugger, gSources;
+var PREFS = [
+ ["devtools.canvasdebugger.enabled", true],
+ ["devtools.shadereditor.enabled", true],
+ ["devtools.performance.enabled", true],
+ ["devtools.netmonitor.enabled", true],
+ ["devtools.scratchpad.enabled", true]
+];
+function test() {
+ Task.spawn(function* () {
+ // Store and enable all optional dev tools panels
+ yield pushPrefs(...PREFS);
+
+ let addon = yield addTemporaryAddon(ADDON_PATH);
+ let addonDebugger = yield initAddonDebugger(ADDON_ID);
+
+ // Check only valid tabs are shown
+ let tabs = addonDebugger.frame.contentDocument.getElementById("toolbox-tabs").children;
+ let expectedTabs = ["webconsole", "jsdebugger", "scratchpad"];
+
+ is(tabs.length, expectedTabs.length, "displaying only " + expectedTabs.length + " tabs in addon debugger");
+ Array.forEach(tabs, (tab, i) => {
+ let toolName = expectedTabs[i];
+ is(tab.getAttribute("toolid"), toolName, "displaying " + toolName);
+ });
+
+ // Check no toolbox buttons are shown
+ let buttons = addonDebugger.frame.contentDocument.getElementById("toolbox-buttons").children;
+ Array.forEach(buttons, (btn, i) => {
+ is(btn.hidden, true, "no toolbox buttons for the addon debugger -- " + btn.className);
+ });
+
+ yield addonDebugger.destroy();
+ yield removeAddon(addon);
+
+ finish();
+ });
+}
diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_addon-sources.js b/devtools/client/debugger/test/mochitest/browser_dbg_addon-sources.js
new file mode 100644
index 000000000..eaa4741eb
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_addon-sources.js
@@ -0,0 +1,42 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Ensure that the sources listed when debugging an addon are either from the
+// addon itself, or the SDK, with proper groups and labels.
+
+const ADDON_ID = "jid1-ami3akps3baaeg@jetpack";
+const ADDON_PATH = "addon3.xpi";
+const ADDON_URL = getTemporaryAddonURLFromPath(ADDON_PATH);
+
+var gClient;
+
+function test() {
+ Task.spawn(function* () {
+ let addon = yield addTemporaryAddon(ADDON_PATH);
+ let addonDebugger = yield initAddonDebugger(ADDON_ID);
+
+ is(addonDebugger.title, `Developer Tools - browser_dbg_addon3 - ${ADDON_URL}`,
+ "Saw the right toolbox title.");
+
+ // Check the inital list of sources is correct
+ let groups = yield addonDebugger.getSourceGroups();
+ is(groups[0].name, "jid1-ami3akps3baaeg@jetpack", "Add-on code should be the first group");
+ is(groups[1].name, "Add-on SDK", "Add-on SDK should be the second group");
+ is(groups.length, 2, "Should be only two groups.");
+
+ let sources = groups[0].sources;
+ is(sources.length, 2, "Should be two sources");
+ ok(sources[0].url.endsWith("/addon3.xpi!/bootstrap.js"), "correct url for bootstrap code");
+ is(sources[0].label, "bootstrap.js", "correct label for bootstrap code");
+ is(sources[1].url, "resource://jid1-ami3akps3baaeg-at-jetpack/browser_dbg_addon3/lib/main.js", "correct url for add-on code");
+ is(sources[1].label, "resources/browser_dbg_addon3/lib/main.js", "correct label for add-on code");
+
+ ok(groups[1].sources.length > 10, "SDK modules are listed");
+
+ yield addonDebugger.destroy();
+ yield removeAddon(addon);
+ finish();
+ });
+}
diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_addon-workers-dbg-enabled.js b/devtools/client/debugger/test/mochitest/browser_dbg_addon-workers-dbg-enabled.js
new file mode 100644
index 000000000..158a3d69e
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_addon-workers-dbg-enabled.js
@@ -0,0 +1,41 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Test that the Addon Debugger works when devtools.debugger.workers is enabled.
+// Workers controller cannot be used when debugging an Addon actor.
+
+const ADDON_ID = "jid1-ami3akps3baaeg@jetpack";
+const ADDON_PATH = "addon3.xpi";
+const ADDON_URL = getTemporaryAddonURLFromPath(ADDON_PATH);
+
+function test() {
+ Task.spawn(function* () {
+ info("Enable worker debugging.");
+ yield new Promise(resolve => {
+ SpecialPowers.pushPrefEnv({
+ "set": [["devtools.debugger.workers", true]]
+ }, resolve);
+ });
+
+ let addon = yield addTemporaryAddon(ADDON_PATH);
+ let addonDebugger = yield initAddonDebugger(ADDON_ID);
+
+ is(addonDebugger.title,
+ `Developer Tools - browser_dbg_addon3 - ${ADDON_URL}`,
+ "Saw the right toolbox title.");
+
+ info("Check that groups and sources are displayed.");
+ let groups = yield addonDebugger.getSourceGroups();
+ is(groups.length, 2, "Should be only two groups.");
+ let sources = groups[0].sources;
+ is(sources.length, 2, "Should be two sources");
+
+ yield addonDebugger.destroy();
+ yield removeAddon(addon);
+ finish();
+ });
+}
diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_addonactor.js b/devtools/client/debugger/test/mochitest/browser_dbg_addonactor.js
new file mode 100644
index 000000000..1bee0b933
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_addonactor.js
@@ -0,0 +1,95 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Make sure we can attach to addon actors.
+
+const ADDON3_PATH = "addon3.xpi";
+const ADDON3_ID = "jid1-ami3akps3baaeg@jetpack";
+const ADDON_MODULE_URL = "resource://jid1-ami3akps3baaeg-at-jetpack/browser_dbg_addon3/lib/main.js";
+
+var gAddon, gClient, gThreadClient;
+
+function test() {
+ if (!DebuggerServer.initialized) {
+ DebuggerServer.init();
+ DebuggerServer.addBrowserActors();
+ }
+
+ let transport = DebuggerServer.connectPipe();
+ gClient = new DebuggerClient(transport);
+ gClient.connect().then(([aType, aTraits]) => {
+ is(aType, "browser",
+ "Root actor should identify itself as a browser.");
+
+ installAddon()
+ .then(attachAddonActorForId.bind(null, gClient, ADDON3_ID))
+ .then(attachAddonThread)
+ .then(testDebugger)
+ .then(testSources)
+ .then(() => gClient.close())
+ .then(uninstallAddon)
+ .then(finish)
+ .then(null, aError => {
+ ok(false, "Got an error: " + aError.message + "\n" + aError.stack);
+ });
+ });
+}
+
+function installAddon() {
+ return addTemporaryAddon(ADDON3_PATH).then(aAddon => {
+ gAddon = aAddon;
+ });
+}
+
+function attachAddonThread([aGrip, aResponse]) {
+ info("attached addon actor for Addon ID");
+ let deferred = promise.defer();
+
+ gClient.attachThread(aResponse.threadActor, (aResponse, aThreadClient) => {
+ info("attached thread");
+ gThreadClient = aThreadClient;
+ gThreadClient.resume(deferred.resolve);
+ });
+ return deferred.promise;
+}
+
+function testDebugger() {
+ info("Entering testDebugger");
+ let deferred = promise.defer();
+
+ once(gClient, "paused").then(() => {
+ ok(true, "Should be able to attach to addon actor");
+ gThreadClient.resume(deferred.resolve);
+ });
+
+ Services.obs.notifyObservers(null, "debuggerAttached", null);
+
+ return deferred.promise;
+}
+
+function testSources() {
+ let deferred = promise.defer();
+
+ gThreadClient.getSources(aResponse => {
+ // source URLs contain launch-specific temporary directory path,
+ // hence the ".contains" call.
+ const matches = aResponse.sources.filter(s => s.url.includes(ADDON_MODULE_URL));
+ ok(matches.length > 0,
+ "the main script of the addon is present in the source list");
+ deferred.resolve();
+ });
+
+ return deferred.promise;
+}
+
+function uninstallAddon() {
+ return removeAddon(gAddon);
+}
+
+registerCleanupFunction(function () {
+ gClient = null;
+ gAddon = null;
+ gThreadClient = null;
+});
diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_auto-pretty-print-01.js b/devtools/client/debugger/test/mochitest/browser_dbg_auto-pretty-print-01.js
new file mode 100644
index 000000000..dcad48cf8
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_auto-pretty-print-01.js
@@ -0,0 +1,117 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Test auto pretty printing.
+
+const TAB_URL = EXAMPLE_URL + "doc_auto-pretty-print-01.html";
+
+var gTab, gPanel, gDebugger;
+var gEditor, gSources, gPrefs, gOptions, gView;
+
+var gFirstSource = EXAMPLE_URL + "code_ugly-5.js";
+var gSecondSource = EXAMPLE_URL + "code_ugly-6.js";
+
+var gOriginalPref = Services.prefs.getBoolPref("devtools.debugger.auto-pretty-print");
+
+function test() {
+ let options = {
+ source: gFirstSource,
+ line: 1
+ };
+ initDebugger(TAB_URL, options).then(([aTab,, aPanel]) => {
+ gTab = aTab;
+ gPanel = aPanel;
+ gDebugger = gPanel.panelWin;
+ gEditor = gDebugger.DebuggerView.editor;
+ gSources = gDebugger.DebuggerView.Sources;
+ gPrefs = gDebugger.Prefs;
+ gOptions = gDebugger.DebuggerView.Options;
+ gView = gDebugger.DebuggerView;
+
+ Task.spawn(function* () {
+ testSourceIsUgly();
+
+ enableAutoPrettyPrint();
+ testAutoPrettyPrintOn();
+
+ reload(gPanel);
+ yield waitForSourceShown(gPanel, gFirstSource);
+ testSourceIsUgly();
+ yield waitForSourceShown(gPanel, gFirstSource);
+ testSourceIsPretty();
+ disableAutoPrettyPrint();
+ testAutoPrettyPrintOff();
+
+ let finished = waitForDebuggerEvents(gPanel, gDebugger.EVENTS.SOURCE_SHOWN);
+ gSources.selectedIndex = 1;
+ yield finished;
+
+ testSecondSourceLabel();
+ testSourceIsUgly();
+
+ enableAutoPrettyPrint();
+ yield closeDebuggerAndFinish(gPanel);
+ });
+ });
+}
+
+function testSourceIsUgly() {
+ ok(!gEditor.getText().includes("\n "),
+ "The source shouldn't be pretty printed yet.");
+}
+
+function testSecondSourceLabel() {
+ let source = gSources.selectedItem.attachment.source;
+ ok(source.url === gSecondSource,
+ "Second source url is correct.");
+}
+
+function testProgressBarShown() {
+ const deck = gDebugger.document.getElementById("editor-deck");
+ is(deck.selectedIndex, 2, "The progress bar should be shown");
+}
+
+function testAutoPrettyPrintOn() {
+ is(gPrefs.autoPrettyPrint, true,
+ "The auto-pretty-print pref should be on.");
+ is(gOptions._autoPrettyPrint.getAttribute("checked"), "true",
+ "The Auto pretty print menu item should be checked.");
+}
+
+function disableAutoPrettyPrint() {
+ gOptions._autoPrettyPrint.setAttribute("checked", "false");
+ gOptions._toggleAutoPrettyPrint();
+ gOptions._onPopupHidden();
+}
+
+function enableAutoPrettyPrint() {
+ gOptions._autoPrettyPrint.setAttribute("checked", "true");
+ gOptions._toggleAutoPrettyPrint();
+ gOptions._onPopupHidden();
+}
+
+function testAutoPrettyPrintOff() {
+ is(gPrefs.autoPrettyPrint, false,
+ "The auto-pretty-print pref should be off.");
+ isnot(gOptions._autoPrettyPrint.getAttribute("checked"), "true",
+ "The Auto pretty print menu item should not be checked.");
+}
+
+function testSourceIsPretty() {
+ ok(gEditor.getText().includes("\n "),
+ "The source should be pretty printed.");
+}
+
+registerCleanupFunction(function () {
+ gTab = null;
+ gPanel = null;
+ gDebugger = null;
+ gEditor = null;
+ gSources = null;
+ gOptions = null;
+ gPrefs = null;
+ gView = null;
+ Services.prefs.setBoolPref("devtools.debugger.auto-pretty-print", gOriginalPref);
+});
diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_auto-pretty-print-02.js b/devtools/client/debugger/test/mochitest/browser_dbg_auto-pretty-print-02.js
new file mode 100644
index 000000000..432bc73d2
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_auto-pretty-print-02.js
@@ -0,0 +1,126 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Test that auto pretty printing doesn't accidentally toggle
+ * pretty printing off when we switch to a minified source
+ * that is already pretty printed.
+ */
+
+const TAB_URL = EXAMPLE_URL + "doc_auto-pretty-print-02.html";
+
+var gTab, gDebuggee, gPanel, gDebugger;
+var gEditor, gSources, gPrefs, gOptions, gView;
+
+var gFirstSource = EXAMPLE_URL + "code_ugly-6.js";
+var gSecondSource = EXAMPLE_URL + "code_ugly-7.js";
+
+var gOriginalPref = Services.prefs.getBoolPref("devtools.debugger.auto-pretty-print");
+Services.prefs.setBoolPref("devtools.debugger.auto-pretty-print", true);
+
+function test() {
+ let options = {
+ source: gFirstSource,
+ line: 1
+ };
+ initDebugger(TAB_URL, options).then(([aTab, aDebuggee, aPanel]) => {
+ const gTab = aTab;
+ const gDebuggee = aDebuggee;
+ const gPanel = aPanel;
+ const gDebugger = gPanel.panelWin;
+ const gEditor = gDebugger.DebuggerView.editor;
+ const gSources = gDebugger.DebuggerView.Sources;
+ const gPrefs = gDebugger.Prefs;
+ const gOptions = gDebugger.DebuggerView.Options;
+ const gView = gDebugger.DebuggerView;
+
+ // Should be on by default.
+ testAutoPrettyPrintOn();
+
+ Task.spawn(function* () {
+
+ testSourceIsUgly();
+
+ yield waitForSourceShown(gPanel, gFirstSource);
+ testSourceIsPretty();
+ testPrettyPrintButtonOn();
+
+ // select second source
+ yield selectSecondSource();
+ testSecondSourceLabel();
+
+ // select first source
+ yield selectFirstSource();
+ testFirstSourceLabel();
+ testPrettyPrintButtonOn();
+
+ // Disable auto pretty printing so it does not affect the following tests.
+ yield disableAutoPrettyPrint();
+
+ closeDebuggerAndFinish(gPanel)
+ .then(null, aError => {
+ ok(false, "Got an error: " + DevToolsUtils.safeErrorString(aError));
+ });
+ });
+
+ function selectSecondSource() {
+ let finished = waitForDebuggerEvents(gPanel, gDebugger.EVENTS.SOURCE_SHOWN, 2);
+ gSources.selectedIndex = 1;
+ return finished;
+ }
+
+ function selectFirstSource() {
+ let finished = waitForDebuggerEvents(gPanel, gDebugger.EVENTS.SOURCE_SHOWN);
+ gSources.selectedIndex = 0;
+ return finished;
+ }
+
+ function testSourceIsUgly() {
+ ok(!gEditor.getText().includes("\n "),
+ "The source shouldn't be pretty printed yet.");
+ }
+
+ function testFirstSourceLabel() {
+ let source = gSources.selectedItem.attachment.source;
+ ok(source.url === gFirstSource,
+ "First source url is correct.");
+ }
+
+ function testSecondSourceLabel() {
+ let source = gSources.selectedItem.attachment.source;
+ ok(source.url === gSecondSource,
+ "Second source url is correct.");
+ }
+
+ function testAutoPrettyPrintOn() {
+ is(gPrefs.autoPrettyPrint, true,
+ "The auto-pretty-print pref should be on.");
+ is(gOptions._autoPrettyPrint.getAttribute("checked"), "true",
+ "The Auto pretty print menu item should be checked.");
+ }
+
+ function testPrettyPrintButtonOn() {
+ is(gDebugger.document.getElementById("pretty-print").checked, true,
+ "The button should be checked when the source is selected.");
+ }
+
+ function disableAutoPrettyPrint() {
+ gOptions._autoPrettyPrint.setAttribute("checked", "false");
+ gOptions._toggleAutoPrettyPrint();
+ gOptions._onPopupHidden();
+ info("Disabled auto pretty printing.");
+ }
+
+ function testSourceIsPretty() {
+ ok(gEditor.getText().includes("\n "),
+ "The source should be pretty printed.");
+ }
+
+ registerCleanupFunction(function () {
+ Services.prefs.setBoolPref("devtools.debugger.auto-pretty-print", gOriginalPref);
+ });
+
+ });
+}
diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_auto-pretty-print-03.js b/devtools/client/debugger/test/mochitest/browser_dbg_auto-pretty-print-03.js
new file mode 100644
index 000000000..bf73ef0cc
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_auto-pretty-print-03.js
@@ -0,0 +1,58 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * If auto pretty-printing it enabled, make sure that if
+ * pretty-printing fails that it still properly shows the original
+ * source.
+ */
+
+const TAB_URL = EXAMPLE_URL + "doc_auto-pretty-print-02.html";
+
+var FIRST_SOURCE = EXAMPLE_URL + "code_ugly-6.js";
+var SECOND_SOURCE = EXAMPLE_URL + "code_ugly-7.js";
+
+function test() {
+ let options = {
+ source: FIRST_SOURCE,
+ line: 1
+ };
+ initDebugger(TAB_URL, options).then(([aTab, aDebuggee, aPanel]) => {
+ const gTab = aTab;
+ const gPanel = aPanel;
+ const gDebugger = gPanel.panelWin;
+
+ const gController = gDebugger.DebuggerController;
+ const gEditor = gDebugger.DebuggerView.editor;
+ const constants = gDebugger.require("./content/constants");
+ const queries = gDebugger.require("./content/queries");
+ const actions = bindActionCreators(gPanel);
+
+ Task.spawn(function* () {
+ const secondSource = queries.getSourceByURL(gController.getState(), SECOND_SOURCE);
+ actions.selectSource(secondSource);
+
+ // It should be showing the loading text
+ is(gEditor.getText(), gDebugger.DebuggerView._loadingText,
+ "The editor loading text is shown");
+
+ gController.dispatch({
+ type: constants.TOGGLE_PRETTY_PRINT,
+ status: "error",
+ source: secondSource,
+ });
+
+ is(gEditor.getText(), gDebugger.DebuggerView._loadingText,
+ "The editor loading text is shown");
+
+ yield waitForSourceShown(gPanel, SECOND_SOURCE);
+
+ ok(gEditor.getText().includes("function foo"),
+ "The second source is shown");
+
+ yield closeDebuggerAndFinish(gPanel);
+ });
+ });
+}
diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_bfcache.js b/devtools/client/debugger/test/mochitest/browser_dbg_bfcache.js
new file mode 100644
index 000000000..8b3c9ca34
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_bfcache.js
@@ -0,0 +1,95 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Make sure that the debugger is updated with the correct sources when moving
+ * back and forward in the tab.
+ */
+
+const TAB_URL_1 = EXAMPLE_URL + "doc_script-switching-01.html";
+const TAB_URL_2 = EXAMPLE_URL + "doc_recursion-stack.html";
+
+var gTab, gDebuggee, gPanel, gDebugger;
+var gSources;
+
+const test = Task.async(function* () {
+ info("Starting browser_dbg_bfcache.js's `test`.");
+
+ let options = {
+ source: EXAMPLE_URL + "code_script-switching-01.js",
+ line: 1
+ };
+ ([gTab, gDebuggee, gPanel]) = yield initDebugger(TAB_URL_1, options);
+ gDebugger = gPanel.panelWin;
+ gSources = gDebugger.DebuggerView.Sources;
+
+ yield testFirstPage();
+ yield testLocationChange();
+ yield testBack();
+ yield testForward();
+ return closeDebuggerAndFinish(gPanel);
+});
+
+function testFirstPage() {
+ info("Testing first page.");
+
+ // Spin the event loop before causing the debuggee to pause, to allow
+ // this function to return first.
+ executeSoon(() => gDebuggee.firstCall());
+
+ return waitForSourceAndCaretAndScopes(gPanel, "-02.js", 1)
+ .then(validateFirstPage);
+}
+
+function testLocationChange() {
+ info("Navigating to a different page.");
+
+ return navigateActiveTabTo(gPanel,
+ TAB_URL_2,
+ gDebugger.EVENTS.SOURCES_ADDED)
+ .then(validateSecondPage);
+}
+
+function testBack() {
+ info("Going back.");
+
+ return navigateActiveTabInHistory(gPanel,
+ "back",
+ gDebugger.EVENTS.SOURCES_ADDED)
+ .then(validateFirstPage);
+}
+
+function testForward() {
+ info("Going forward.");
+
+ return navigateActiveTabInHistory(gPanel,
+ "forward",
+ gDebugger.EVENTS.SOURCES_ADDED)
+ .then(validateSecondPage);
+}
+
+function validateFirstPage() {
+ is(gSources.itemCount, 2,
+ "Found the expected number of sources.");
+ ok(gSources.getItemForAttachment(e => e.label == "code_script-switching-01.js"),
+ "Found the first source label.");
+ ok(gSources.getItemForAttachment(e => e.label == "code_script-switching-02.js"),
+ "Found the second source label.");
+}
+
+function validateSecondPage() {
+ is(gSources.itemCount, 1,
+ "Found the expected number of sources.");
+ ok(gSources.getItemForAttachment(e => e.label == "doc_recursion-stack.html"),
+ "Found the single source label.");
+}
+
+registerCleanupFunction(function () {
+ gTab = null;
+ gDebuggee = null;
+ gPanel = null;
+ gDebugger = null;
+ gSources = null;
+});
diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_blackboxing-01.js b/devtools/client/debugger/test/mochitest/browser_dbg_blackboxing-01.js
new file mode 100644
index 000000000..f4ecd5b95
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_blackboxing-01.js
@@ -0,0 +1,57 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Test that if we black box a source and then refresh, it is still black boxed.
+ */
+
+const TAB_URL = EXAMPLE_URL + "doc_binary_search.html";
+
+var gTab, gPanel, gDebugger;
+
+function test() {
+ let options = {
+ source: EXAMPLE_URL + "code_binary_search.coffee",
+ line: 1
+ };
+ initDebugger(TAB_URL, options).then(([aTab,, aPanel]) => {
+ gTab = aTab;
+ gPanel = aPanel;
+ gDebugger = gPanel.panelWin;
+
+ testBlackBoxSource()
+ .then(testBlackBoxReload)
+ .then(() => closeDebuggerAndFinish(gPanel))
+ .then(null, aError => {
+ ok(false, "Got an error: " + aError.message + "\n" + aError.stack);
+ });
+ });
+}
+
+function testBlackBoxSource() {
+ const bbButton = getBlackBoxButton(gPanel);
+ ok(!bbButton.checked, "Should not be black boxed by default");
+
+ return toggleBlackBoxing(gPanel).then(source => {
+ ok(source.isBlackBoxed, "The source should be black boxed now.");
+ ok(bbButton.checked, "The checkbox should no longer be checked.");
+ });
+}
+
+function testBlackBoxReload() {
+ return reloadActiveTab(gPanel, gDebugger.EVENTS.SOURCE_SHOWN).then(() => {
+ const bbButton = getBlackBoxButton(gPanel);
+ const selectedSource = getSelectedSourceElement(gPanel);
+ ok(bbButton.checked, "Should still be black boxed.");
+ ok(selectedSource.classList.contains("black-boxed"),
+ "'black-boxed' class should still be applied");
+ });
+}
+
+registerCleanupFunction(function () {
+ gTab = null;
+ gPanel = null;
+ gDebugger = null;
+});
diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_blackboxing-02.js b/devtools/client/debugger/test/mochitest/browser_dbg_blackboxing-02.js
new file mode 100644
index 000000000..2eca3ec92
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_blackboxing-02.js
@@ -0,0 +1,60 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Test that black boxed frames are compressed into a single frame on the stack
+ * view.
+ */
+
+const TAB_URL = EXAMPLE_URL + "doc_blackboxing.html";
+const BLACKBOXME_URL = EXAMPLE_URL + "code_blackboxing_blackboxme.js";
+
+var gTab, gPanel, gDebugger;
+var gFrames;
+
+function test() {
+ let options = {
+ source: BLACKBOXME_URL,
+ line: 1
+ };
+ initDebugger(TAB_URL, options).then(([aTab,, aPanel]) => {
+ gTab = aTab;
+ gPanel = aPanel;
+ gDebugger = gPanel.panelWin;
+ gFrames = gDebugger.DebuggerView.StackFrames;
+
+ testBlackBoxSource()
+ .then(testBlackBoxStack)
+ .then(() => resumeDebuggerThenCloseAndFinish(gPanel))
+ .then(null, aError => {
+ ok(false, "Got an error: " + aError.message + "\n" + aError.stack);
+ });
+ });
+}
+
+function testBlackBoxSource() {
+ return toggleBlackBoxing(gPanel).then(source => {
+ ok(source.isBlackBoxed, "The source should be black boxed now.");
+ });
+}
+
+function testBlackBoxStack() {
+ let finished = waitForSourceAndCaretAndScopes(gPanel, ".html", 21).then(() => {
+ is(gFrames.itemCount, 3,
+ "Should only get 3 frames.");
+ is(gDebugger.document.querySelectorAll(".dbg-stackframe-black-boxed").length, 1,
+ "And one of them should be the combined black boxed frames.");
+ });
+
+ callInTab(gTab, "runTest");
+ return finished;
+}
+
+registerCleanupFunction(function () {
+ gTab = null;
+ gPanel = null;
+ gDebugger = null;
+ gFrames = null;
+});
diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_blackboxing-03.js b/devtools/client/debugger/test/mochitest/browser_dbg_blackboxing-03.js
new file mode 100644
index 000000000..fa4489ac7
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_blackboxing-03.js
@@ -0,0 +1,65 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Test that black boxed frames are compressed into a single frame on the stack
+ * view when we are already paused.
+ */
+
+const TAB_URL = EXAMPLE_URL + "doc_blackboxing.html";
+const BLACKBOXME_URL = EXAMPLE_URL + "code_blackboxing_blackboxme.js";
+
+var gTab, gPanel, gDebugger;
+var gFrames, gSources;
+
+function test() {
+ let options = {
+ source: BLACKBOXME_URL,
+ line: 1
+ };
+ initDebugger(TAB_URL, options).then(([aTab,, aPanel]) => {
+ gTab = aTab;
+ gPanel = aPanel;
+ gDebugger = gPanel.panelWin;
+ gFrames = gDebugger.DebuggerView.StackFrames;
+ gSources = gDebugger.DebuggerView.Sources;
+
+ waitForSourceAndCaretAndScopes(gPanel, ".html", 21)
+ .then(testBlackBoxStack)
+ .then(testBlackBoxSource)
+ .then(() => resumeDebuggerThenCloseAndFinish(gPanel))
+ .then(null, aError => {
+ ok(false, "Got an error: " + aError.message + "\n" + aError.stack);
+ });
+
+ callInTab(gTab, "runTest");
+ });
+}
+
+function testBlackBoxStack() {
+ is(gFrames.itemCount, 6,
+ "Should get 6 frames.");
+ is(gDebugger.document.querySelectorAll(".dbg-stackframe-black-boxed").length, 0,
+ "And none of them are black boxed.");
+}
+
+function testBlackBoxSource() {
+ return toggleBlackBoxing(gPanel, getSourceActor(gSources, BLACKBOXME_URL)).then(aSource => {
+ ok(aSource.isBlackBoxed, "The source should be black boxed now.");
+
+ is(gFrames.itemCount, 3,
+ "Should only get 3 frames.");
+ is(gDebugger.document.querySelectorAll(".dbg-stackframe-black-boxed").length, 1,
+ "And one of them should be the combined black boxed frames.");
+ });
+}
+
+registerCleanupFunction(function () {
+ gTab = null;
+ gPanel = null;
+ gDebugger = null;
+ gFrames = null;
+ gSources = null;
+});
diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_blackboxing-04.js b/devtools/client/debugger/test/mochitest/browser_dbg_blackboxing-04.js
new file mode 100644
index 000000000..ea9cd84f3
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_blackboxing-04.js
@@ -0,0 +1,65 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Test that we get a stack frame for each black boxed source, not a single one
+ * for all of them.
+ */
+
+const TAB_URL = EXAMPLE_URL + "doc_blackboxing.html";
+const BLACKBOXME_URL = EXAMPLE_URL + "code_blackboxing_blackboxme.js";
+
+var gTab, gPanel, gDebugger;
+var gFrames, gSources;
+
+function test() {
+ let options = {
+ source: BLACKBOXME_URL,
+ line: 1
+ };
+ initDebugger(TAB_URL, options).then(([aTab,, aPanel]) => {
+ gTab = aTab;
+ gPanel = aPanel;
+ gDebugger = gPanel.panelWin;
+ gFrames = gDebugger.DebuggerView.StackFrames;
+ gSources = gDebugger.DebuggerView.Sources;
+
+ blackBoxSources()
+ .then(testBlackBoxStack)
+ .then(() => resumeDebuggerThenCloseAndFinish(gPanel))
+ .then(null, aError => {
+ ok(false, "Got an error: " + aError.message + "\n" + aError.stack);
+ });
+ });
+}
+
+function blackBoxSources() {
+ let finished = waitForThreadEvents(gPanel, "blackboxchange", 3);
+
+ toggleBlackBoxing(gPanel, getSourceActor(gSources, EXAMPLE_URL + "code_blackboxing_one.js"));
+ toggleBlackBoxing(gPanel, getSourceActor(gSources, EXAMPLE_URL + "code_blackboxing_two.js"));
+ toggleBlackBoxing(gPanel, getSourceActor(gSources, EXAMPLE_URL + "code_blackboxing_three.js"));
+ return finished;
+}
+
+function testBlackBoxStack() {
+ let finished = waitForSourceAndCaretAndScopes(gPanel, ".html", 21).then(() => {
+ is(gFrames.itemCount, 4,
+ "Should get 4 frames (one -> two -> three -> doDebuggerStatement).");
+ is(gDebugger.document.querySelectorAll(".dbg-stackframe-black-boxed").length, 3,
+ "And 'one', 'two', and 'three' should each have their own black boxed frame.");
+ });
+
+ callInTab(gTab, "one");
+ return finished;
+}
+
+registerCleanupFunction(function () {
+ gTab = null;
+ gPanel = null;
+ gDebugger = null;
+ gFrames = null;
+ gSources = null;
+});
diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_blackboxing-05.js b/devtools/client/debugger/test/mochitest/browser_dbg_blackboxing-05.js
new file mode 100644
index 000000000..96e2b9873
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_blackboxing-05.js
@@ -0,0 +1,74 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Test that a "this source is blackboxed" message is shown when necessary
+ * and can be properly dismissed.
+ */
+
+const TAB_URL = EXAMPLE_URL + "doc_binary_search.html";
+
+var gTab, gPanel, gDebugger;
+var gDeck;
+
+function test() {
+ let options = {
+ source: EXAMPLE_URL + "code_binary_search.coffee",
+ line: 1
+ };
+ initDebugger(TAB_URL, options).then(([aTab,, aPanel]) => {
+ gTab = aTab;
+ gPanel = aPanel;
+ gDebugger = gPanel.panelWin;
+ gDeck = gDebugger.document.getElementById("editor-deck");
+
+ testSourceEditorShown();
+ toggleBlackBoxing(gPanel)
+ .then(testBlackBoxMessageShown)
+ .then(clickStopBlackBoxingButton)
+ .then(testSourceEditorShownAgain)
+ .then(() => closeDebuggerAndFinish(gPanel))
+ .then(null, aError => {
+ ok(false, "Got an error: " + aError.message + "\n" + aError.stack);
+ });
+ });
+}
+
+function testSourceEditorShown() {
+ is(gDeck.selectedIndex, "0",
+ "The first item in the deck should be selected (the source editor).");
+}
+
+function testBlackBoxMessageShown() {
+ is(gDeck.selectedIndex, "1",
+ "The second item in the deck should be selected (the black box message).");
+}
+
+function clickStopBlackBoxingButton() {
+ // Give the test a chance to finish before triggering the click event.
+ executeSoon(() => getEditorBlackboxMessageButton().click());
+ return waitForDispatch(gPanel, gDebugger.constants.BLACKBOX);
+}
+
+function testSourceEditorShownAgain() {
+ // Wait a tick for the final check to make sure the frontend's click handlers
+ // have finished.
+ return new Promise(resolve => {
+ is(gDeck.selectedIndex, "0",
+ "The first item in the deck should be selected again (the source editor).");
+ resolve();
+ });
+}
+
+function getEditorBlackboxMessageButton() {
+ return gDebugger.document.getElementById("black-boxed-message-button");
+}
+
+registerCleanupFunction(function () {
+ gTab = null;
+ gPanel = null;
+ gDebugger = null;
+ gDeck = null;
+});
diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_blackboxing-06.js b/devtools/client/debugger/test/mochitest/browser_dbg_blackboxing-06.js
new file mode 100644
index 000000000..23a13f4db
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_blackboxing-06.js
@@ -0,0 +1,61 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Test that clicking the black box checkbox when paused doesn't re-select the
+ * currently paused frame's source.
+ */
+
+const TAB_URL = EXAMPLE_URL + "doc_blackboxing.html";
+
+var gTab, gPanel, gDebugger;
+var gSources;
+
+function test() {
+ let options = {
+ source: EXAMPLE_URL + "code_blackboxing_blackboxme.js",
+ line: 1
+ };
+ initDebugger(TAB_URL, options).then(([aTab,, aPanel]) => {
+ gTab = aTab;
+ gPanel = aPanel;
+ gDebugger = gPanel.panelWin;
+ gSources = gDebugger.DebuggerView.Sources;
+
+ waitForCaretAndScopes(gPanel, 21)
+ .then(testBlackBox)
+ .then(() => resumeDebuggerThenCloseAndFinish(gPanel))
+ .then(null, aError => {
+ ok(false, "Got an error: " + aError.message + "\n" + aError.stack);
+ });
+
+ callInTab(gTab, "runTest");
+ });
+}
+
+function testBlackBox() {
+ const selectedActor = gSources.selectedValue;
+
+ let finished = waitForSourceShown(gPanel, "blackboxme.js").then(() => {
+ const newSelectedActor = gSources.selectedValue;
+ isnot(selectedActor, newSelectedActor,
+ "Should not have the same url selected.");
+
+ return toggleBlackBoxing(gPanel).then(() => {
+ is(gSources.selectedValue, newSelectedActor,
+ "The selected source did not change.");
+ });
+ });
+
+ gSources.selectedIndex = 0;
+ return finished;
+}
+
+registerCleanupFunction(function () {
+ gTab = null;
+ gPanel = null;
+ gDebugger = null;
+ gSources = null;
+});
diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_blackboxing-07.js b/devtools/client/debugger/test/mochitest/browser_dbg_blackboxing-07.js
new file mode 100644
index 000000000..1aa6b0bd1
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_blackboxing-07.js
@@ -0,0 +1,53 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Test that if we unblackbox a source which has been automatically blackboxed
+ * and then refresh, it is still unblackboxed.
+ */
+
+const TAB_URL = EXAMPLE_URL + "doc_blackboxing_unblackbox.html";
+
+var gTab, gPanel, gDebugger;
+
+function test() {
+ let options = {
+ source: EXAMPLE_URL + "code_blackboxing_unblackbox.min.js",
+ };
+ initDebugger(TAB_URL, options).then(([aTab,, aPanel]) => {
+ gTab = aTab;
+ gPanel = aPanel;
+ gDebugger = gPanel.panelWin;
+
+ testBlackBoxSource()
+ .then(testBlackBoxReload)
+ .then(() => closeDebuggerAndFinish(gPanel))
+ .then(null, aError => {
+ ok(false, "Got an error: " + aError.message + "\n" + aError.stack);
+ });
+ });
+}
+
+function testBlackBoxSource() {
+ const bbButton = getBlackBoxButton(gPanel);
+ ok(bbButton.checked, "Should be black boxed by default");
+
+ return toggleBlackBoxing(gPanel).then(aSource => {
+ ok(!aSource.isBlackBoxed, "The source should no longer be blackboxed.");
+ });
+}
+
+function testBlackBoxReload() {
+ return reloadActiveTab(gPanel, gDebugger.EVENTS.SOURCE_SHOWN).then(() => {
+ const selectedSource = getSelectedSourceElement(gPanel);
+ ok(!selectedSource.isBlackBoxed, "The source should not be blackboxed.");
+ });
+}
+
+registerCleanupFunction(function () {
+ gTab = null;
+ gPanel = null;
+ gDebugger = null;
+});
diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_breadcrumbs-access.js b/devtools/client/debugger/test/mochitest/browser_dbg_breadcrumbs-access.js
new file mode 100644
index 000000000..596bcd336
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_breadcrumbs-access.js
@@ -0,0 +1,98 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Tests if the stackframe breadcrumbs are keyboard accessible.
+ */
+
+const TAB_URL = EXAMPLE_URL + "doc_script-switching-01.html";
+
+function test() {
+ let gTab, gPanel, gDebugger;
+ let gSources, gFrames;
+
+ let options = {
+ source: EXAMPLE_URL + "code_script-switching-01.js",
+ line: 1
+ };
+ initDebugger(TAB_URL, options).then(([aTab,, aPanel]) => {
+ gTab = aTab;
+ gPanel = aPanel;
+ gDebugger = gPanel.panelWin;
+ gSources = gDebugger.DebuggerView.Sources;
+ gFrames = gDebugger.DebuggerView.StackFrames;
+
+ waitForSourceAndCaretAndScopes(gPanel, "-02.js", 6)
+ .then(checkNavigationWhileNotFocused)
+ .then(focusCurrentStackFrame)
+ .then(checkNavigationWhileFocused)
+ .then(() => resumeDebuggerThenCloseAndFinish(gPanel))
+ .then(null, aError => {
+ ok(false, "Got an error: " + aError.message + "\n" + aError.stack);
+ });
+
+ callInTab(gTab, "firstCall");
+ });
+
+ function checkNavigationWhileNotFocused() {
+ checkState({ frame: 1, source: 1, line: 6 });
+
+ return Task.spawn(function* () {
+ EventUtils.sendKey("DOWN", gDebugger);
+ checkState({ frame: 1, source: 1, line: 7 });
+
+ EventUtils.sendKey("UP", gDebugger);
+ checkState({ frame: 1, source: 1, line: 6 });
+ });
+ }
+
+ function focusCurrentStackFrame() {
+ EventUtils.sendMouseEvent({ type: "mousedown" },
+ gFrames.selectedItem.target,
+ gDebugger);
+ }
+
+ function checkNavigationWhileFocused() {
+ return Task.spawn(function* () {
+ yield promise.all([
+ waitForDebuggerEvents(gPanel, gDebugger.EVENTS.FETCHED_SCOPES),
+ waitForSourceAndCaret(gPanel, "-01.js", 5),
+ EventUtils.sendKey("UP", gDebugger)
+ ]);
+ checkState({ frame: 0, source: 0, line: 5 });
+
+ // Need to refocus the stack frame due to a focus bug in e10s
+ // (See Bug 1205482)
+ focusCurrentStackFrame();
+
+ yield promise.all([
+ waitForDebuggerEvents(gPanel, gDebugger.EVENTS.FETCHED_SCOPES),
+ waitForSourceAndCaret(gPanel, "-02.js", 6),
+ EventUtils.sendKey("END", gDebugger)
+ ]);
+ checkState({ frame: 1, source: 1, line: 6 });
+
+ // Need to refocus the stack frame due to a focus bug in e10s
+ // (See Bug 1205482)
+ focusCurrentStackFrame();
+
+ yield promise.all([
+ waitForDebuggerEvents(gPanel, gDebugger.EVENTS.FETCHED_SCOPES),
+ waitForSourceAndCaret(gPanel, "-01.js", 5),
+ EventUtils.sendKey("HOME", gDebugger)
+ ]);
+ checkState({ frame: 0, source: 0, line: 5 });
+ });
+ }
+
+ function checkState({ frame, source, line, column }) {
+ is(gFrames.selectedIndex, frame,
+ "The currently selected stackframe is incorrect.");
+ is(gSources.selectedIndex, source,
+ "The currently selected source is incorrect.");
+ ok(isCaretPos(gPanel, line, column),
+ "The source editor caret position was incorrect.");
+ }
+}
diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_break-in-anon.js b/devtools/client/debugger/test/mochitest/browser_dbg_break-in-anon.js
new file mode 100644
index 000000000..23d55f4b9
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_break-in-anon.js
@@ -0,0 +1,40 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Make sure anonymous eval scripts can still break with a `debugger`
+ * statement
+ */
+
+const TAB_URL = EXAMPLE_URL + "doc_script-eval.html";
+
+function test() {
+ const options = {
+ source: EXAMPLE_URL + "code_script-eval.js",
+ line: 1
+ };
+ initDebugger(TAB_URL, options).then(([aTab,, aPanel]) => {
+ const gTab = aTab;
+ const gPanel = aPanel;
+ const gDebugger = gPanel.panelWin;
+ const gSources = gDebugger.DebuggerView.Sources;
+
+ return Task.spawn(function* () {
+ is(gSources.values.length, 1, "Should have 1 source");
+
+ callInTab(gTab, "evalSourceWithDebugger");
+ yield waitForDebuggerEvents(gPanel, gDebugger.EVENTS.SOURCE_SHOWN);
+
+ is(gSources.values.length, 2, "Should have 2 sources");
+
+ let item = gSources.getItemForAttachment(e => e.label.indexOf("SCRIPT") === 0);
+ ok(item, "Source label is incorrect.");
+ is(item.attachment.group, gDebugger.L10N.getStr("anonymousSourcesLabel"),
+ "Source group is incorrect");
+
+ yield resumeDebuggerThenCloseAndFinish(gPanel);
+ });
+ });
+}
diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_break-on-dom-01.js b/devtools/client/debugger/test/mochitest/browser_dbg_break-on-dom-01.js
new file mode 100644
index 000000000..82481187a
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_break-on-dom-01.js
@@ -0,0 +1,58 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Whitelisting this test.
+// As part of bug 1077403, the leaking uncaught rejections should be fixed.
+thisTestLeaksUncaughtRejectionsAndShouldBeFixed("[object Object]");
+thisTestLeaksUncaughtRejectionsAndShouldBeFixed(
+ "TypeError: this.transport is null");
+
+/**
+ * Tests that event listeners aren't fetched when the events tab isn't selected.
+ */
+
+const TAB_URL = EXAMPLE_URL + "doc_event-listeners-02.html";
+
+function test() {
+ let options = {
+ source: TAB_URL,
+ line: 1
+ };
+ initDebugger(TAB_URL, options).then(([aTab,, aPanel]) => {
+ let gPanel = aPanel;
+ let gDebugger = aPanel.panelWin;
+ let gView = gDebugger.DebuggerView;
+ let gEvents = gView.EventListeners;
+ let gController = gDebugger.DebuggerController;
+ let constants = gDebugger.require("./content/constants");
+
+ gDebugger.on(gDebugger.EVENTS.EVENT_LISTENERS_FETCHED, () => {
+ ok(false, "Shouldn't have fetched any event listeners.");
+ });
+ gDebugger.on(gDebugger.EVENTS.EVENT_BREAKPOINTS_UPDATED, () => {
+ ok(false, "Shouldn't have updated any event breakpoints.");
+ });
+
+ gView.toggleInstrumentsPane({ visible: true, animated: false });
+
+ is(gView.instrumentsPaneHidden, false,
+ "The instruments pane should be visible now.");
+ is(gView.instrumentsPaneTab, "variables-tab",
+ "The variables tab should be selected by default.");
+
+ Task.spawn(function* () {
+ is(gEvents.itemCount, 0, "There should be no events before reloading.");
+
+ let reloaded = waitForNavigation(gPanel);
+ gDebugger.DebuggerController._target.activeTab.reload();
+
+ is(gEvents.itemCount, 0, "There should be no events while reloading.");
+ yield reloaded;
+ is(gEvents.itemCount, 0, "There should be no events after reloading.");
+
+ yield closeDebuggerAndFinish(aPanel);
+ });
+ });
+}
diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_break-on-dom-02.js b/devtools/client/debugger/test/mochitest/browser_dbg_break-on-dom-02.js
new file mode 100644
index 000000000..dcbb1dca1
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_break-on-dom-02.js
@@ -0,0 +1,135 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Tests that event listeners are fetched when the events tab is selected
+ * or while sources are fetched and the events tab is focused.
+ */
+
+const TAB_URL = EXAMPLE_URL + "doc_event-listeners-02.html";
+
+function test() {
+ let options = {
+ source: TAB_URL,
+ line: 1
+ };
+ initDebugger(TAB_URL, options).then(([aTab,, aPanel]) => {
+ let gPanel = aPanel;
+ let gDebugger = aPanel.panelWin;
+ let gView = gDebugger.DebuggerView;
+ let gEvents = gView.EventListeners;
+ let gController = gDebugger.DebuggerController;
+ let constants = gDebugger.require("./content/constants");
+
+ Task.spawn(function* () {
+ yield testFetchOnFocus();
+ yield testFetchOnReloadWhenFocused();
+ yield testFetchOnReloadWhenNotFocused();
+ yield closeDebuggerAndFinish(aPanel);
+ });
+
+ function testFetchOnFocus() {
+ return Task.spawn(function* () {
+ let fetched = waitForDispatch(aPanel, constants.FETCH_EVENT_LISTENERS);
+
+ gView.toggleInstrumentsPane({ visible: true, animated: false }, 1);
+ is(gView.instrumentsPaneHidden, false,
+ "The instruments pane should be visible now.");
+ is(gView.instrumentsPaneTab, "events-tab",
+ "The events tab should be selected.");
+
+ yield fetched;
+
+ ok(true,
+ "Event listeners were fetched when the events tab was selected");
+ is(gEvents.itemCount, 4,
+ "There should be 4 events displayed in the view.");
+ });
+ }
+
+ function testFetchOnReloadWhenFocused() {
+ return Task.spawn(function* () {
+ let fetched = waitForDispatch(aPanel, constants.FETCH_EVENT_LISTENERS);
+
+ let reloading = once(gDebugger.gTarget, "will-navigate");
+ let reloaded = waitForNavigation(gPanel);
+ gDebugger.DebuggerController._target.activeTab.reload();
+
+ yield reloading;
+
+ is(gEvents.itemCount, 0,
+ "There should be no events displayed in the view while reloading.");
+ ok(true,
+ "Event listeners were removed when the target started navigating.");
+
+ yield reloaded;
+
+ is(gView.instrumentsPaneHidden, false,
+ "The instruments pane should still be visible.");
+ is(gView.instrumentsPaneTab, "events-tab",
+ "The events tab should still be selected.");
+
+ yield fetched;
+
+ is(gEvents.itemCount, 4,
+ "There should be 4 events displayed in the view after reloading.");
+ ok(true,
+ "Event listeners were added back after the target finished navigating.");
+ });
+ }
+
+ function testFetchOnReloadWhenNotFocused() {
+ return Task.spawn(function* () {
+ gController.dispatch({
+ type: gDebugger.services.WAIT_UNTIL,
+ predicate: action => {
+ return (action.type === constants.FETCH_EVENT_LISTENERS ||
+ action.type === constants.UPDATE_EVENT_BREAKPOINTS);
+ },
+ run: (dispatch, getState, action) => {
+ if (action.type === constants.FETCH_EVENT_LISTENERS) {
+ ok(false, "Shouldn't have fetched any event listeners.");
+ }
+ else if (action.type === constants.UPDATE_EVENT_BREAKPOINTS) {
+ ok(false, "Shouldn't have updated any event breakpoints.");
+ }
+ }
+ });
+
+ gView.toggleInstrumentsPane({ visible: true, animated: false }, 0);
+ is(gView.instrumentsPaneHidden, false,
+ "The instruments pane should still be visible.");
+ is(gView.instrumentsPaneTab, "variables-tab",
+ "The variables tab should be selected.");
+
+ let reloading = once(gDebugger.gTarget, "will-navigate");
+ let reloaded = waitForNavigation(gPanel);
+ gDebugger.DebuggerController._target.activeTab.reload();
+
+ yield reloading;
+
+ is(gEvents.itemCount, 0,
+ "There should be no events displayed in the view while reloading.");
+ ok(true,
+ "Event listeners were removed when the target started navigating.");
+
+ yield reloaded;
+
+ is(gView.instrumentsPaneHidden, false,
+ "The instruments pane should still be visible.");
+ is(gView.instrumentsPaneTab, "variables-tab",
+ "The variables tab should still be selected.");
+
+ // Just to be really sure that the events will never ever fire.
+ yield waitForTime(1000);
+
+ is(gEvents.itemCount, 0,
+ "There should be no events displayed in the view after reloading.");
+ ok(true,
+ "Event listeners were not added after the target finished navigating.");
+ });
+ }
+ });
+}
diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_break-on-dom-03.js b/devtools/client/debugger/test/mochitest/browser_dbg_break-on-dom-03.js
new file mode 100644
index 000000000..6d0d9708c
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_break-on-dom-03.js
@@ -0,0 +1,102 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Tests that event listeners are properly displayed in the view.
+ */
+
+const TAB_URL = EXAMPLE_URL + "doc_event-listeners-02.html";
+
+function test() {
+ let options = {
+ source: TAB_URL,
+ line: 1
+ };
+ initDebugger(TAB_URL, options).then(([aTab,, aPanel]) => {
+ let gDebugger = aPanel.panelWin;
+ let gView = gDebugger.DebuggerView;
+ let gEvents = gView.EventListeners;
+ let gController = gDebugger.DebuggerController;
+ let constants = gDebugger.require("./content/constants");
+
+ Task.spawn(function* () {
+ let fetched = waitForDispatch(aPanel, constants.FETCH_EVENT_LISTENERS);
+ gView.toggleInstrumentsPane({ visible: true, animated: false }, 1);
+ yield fetched;
+
+ is(gEvents.widget._parent.querySelectorAll(".side-menu-widget-group").length, 3,
+ "There should be 3 groups shown in the view.");
+
+ let groupCheckboxes = gEvents.widget._parent.querySelectorAll(
+ ".side-menu-widget-group-checkbox");
+ is(groupCheckboxes.length, 3,
+ "There should be a checkbox for each group shown in the view.");
+ for (let cb of groupCheckboxes) {
+ isnot(cb.getAttribute("tooltiptext"), "undefined",
+ "A valid tooltip text should be defined on group checkboxes");
+ }
+
+ is(gEvents.widget._parent.querySelectorAll(".side-menu-widget-item").length, 4,
+ "There should be 4 items shown in the view.");
+ is(gEvents.widget._parent.querySelectorAll(".side-menu-widget-item-checkbox").length, 4,
+ "There should be a checkbox for each item shown in the view.");
+
+ testEventItem(0, "doc_event-listeners-02.html", "change", ["body > input:nth-child(2)"], false);
+ testEventItem(1, "doc_event-listeners-02.html", "click", ["body > button:nth-child(1)"], false);
+ testEventItem(2, "doc_event-listeners-02.html", "keydown", ["window", "body"], false);
+ testEventItem(3, "doc_event-listeners-02.html", "keyup", ["body > input:nth-child(2)"], false);
+
+ testEventGroup("interactionEvents", false);
+ testEventGroup("keyboardEvents", false);
+ testEventGroup("mouseEvents", false);
+
+ is(gEvents.getAllEvents().toString(), "change,click,keydown,keyup",
+ "The getAllEvents() method returns the correct stuff.");
+ is(gEvents.getCheckedEvents().toString(), "",
+ "The getCheckedEvents() method returns the correct stuff.");
+
+ yield ensureThreadClientState(aPanel, "attached");
+ yield closeDebuggerAndFinish(aPanel);
+ });
+
+ function testEventItem(index, label, type, selectors, checked) {
+ let item = gEvents.items[index];
+ let node = item.target;
+
+ ok(item.attachment.url.includes(label),
+ "The event at index " + index + " has the correct url.");
+ is(item.attachment.type, type,
+ "The event at index " + index + " has the correct type.");
+ is(item.attachment.selectors.toString(), selectors,
+ "The event at index " + index + " has the correct selectors.");
+ is(item.attachment.checkboxState, checked,
+ "The event at index " + index + " has the correct checkbox state.");
+
+ let targets = selectors.length > 1
+ ? gDebugger.L10N.getFormatStr("eventNodes", selectors.length)
+ : selectors.toString();
+
+ is(node.querySelector(".dbg-event-listener-type").getAttribute("value"), type,
+ "The correct type is shown for this event.");
+ is(node.querySelector(".dbg-event-listener-targets").getAttribute("value"), targets,
+ "The correct target is shown for this event.");
+ is(node.querySelector(".dbg-event-listener-location").getAttribute("value"), label,
+ "The correct location is shown for this event.");
+ is(node.parentNode.querySelector(".side-menu-widget-item-checkbox").checked, checked,
+ "The correct checkbox state is shown for this event.");
+ }
+
+ function testEventGroup(string, checked) {
+ let name = gDebugger.L10N.getStr(string);
+ let group = gEvents.widget._parent
+ .querySelector(".side-menu-widget-group[name=" + name + "]");
+
+ is(group.querySelector(".side-menu-widget-group-title > .name").value, name,
+ "The correct label is shown for the group named " + name + ".");
+ is(group.querySelector(".side-menu-widget-group-checkbox").checked, checked,
+ "The correct checkbox state is shown for the group named " + name + ".");
+ }
+ });
+}
diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_break-on-dom-04.js b/devtools/client/debugger/test/mochitest/browser_dbg_break-on-dom-04.js
new file mode 100644
index 000000000..3f2b2948e
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_break-on-dom-04.js
@@ -0,0 +1,101 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Tests that checking/unchecking an event listener in the view correctly
+ * causes the active thread to get updated with the new event breakpoints.
+ */
+
+const TAB_URL = EXAMPLE_URL + "doc_event-listeners-02.html";
+
+function test() {
+ let options = {
+ source: TAB_URL,
+ line: 1
+ };
+ initDebugger(TAB_URL, options).then(([aTab,, aPanel]) => {
+ let gDebugger = aPanel.panelWin;
+ let gView = gDebugger.DebuggerView;
+ let gController = gDebugger.DebuggerController;
+ let gEvents = gView.EventListeners;
+ let constants = gDebugger.require("./content/constants");
+
+ Task.spawn(function* () {
+ let fetched = waitForDispatch(aPanel, constants.FETCH_EVENT_LISTENERS);
+ gView.toggleInstrumentsPane({ visible: true, animated: false }, 1);
+ yield fetched;
+
+ testEventItem(0, false);
+ testEventItem(1, false);
+ testEventItem(2, false);
+ testEventItem(3, false);
+ testEventGroup("interactionEvents", false);
+ testEventGroup("keyboardEvents", false);
+ testEventGroup("mouseEvents", false);
+ testEventArrays("change,click,keydown,keyup", "");
+
+ let updated = waitForDispatch(aPanel, constants.UPDATE_EVENT_BREAKPOINTS);
+ EventUtils.sendMouseEvent({ type: "click" }, getItemCheckboxNode(0), gDebugger);
+ yield updated;
+
+ testEventItem(0, true);
+ testEventItem(1, false);
+ testEventItem(2, false);
+ testEventItem(3, false);
+ testEventGroup("interactionEvents", false);
+ testEventGroup("keyboardEvents", false);
+ testEventGroup("mouseEvents", false);
+ testEventArrays("change,click,keydown,keyup", "change");
+
+ updated = waitForDispatch(aPanel, constants.UPDATE_EVENT_BREAKPOINTS);
+ EventUtils.sendMouseEvent({ type: "click" }, getItemCheckboxNode(0), gDebugger);
+ yield updated;
+
+ testEventItem(0, false);
+ testEventItem(1, false);
+ testEventItem(2, false);
+ testEventItem(3, false);
+ testEventGroup("interactionEvents", false);
+ testEventGroup("keyboardEvents", false);
+ testEventGroup("mouseEvents", false);
+ testEventArrays("change,click,keydown,keyup", "");
+
+ yield ensureThreadClientState(aPanel, "attached");
+ yield closeDebuggerAndFinish(aPanel);
+ });
+
+ function getItemCheckboxNode(index) {
+ return gEvents.items[index].target.parentNode
+ .querySelector(".side-menu-widget-item-checkbox");
+ }
+
+ function getGroupCheckboxNode(string) {
+ return gEvents.widget._parent
+ .querySelector(".side-menu-widget-group[name=" + gDebugger.L10N.getStr(string) + "]")
+ .querySelector(".side-menu-widget-group-checkbox");
+ }
+
+ function testEventItem(index, checked) {
+ is(gEvents.attachments[index].checkboxState, checked,
+ "The event at index " + index + " has the correct checkbox state.");
+ is(getItemCheckboxNode(index).checked, checked,
+ "The correct checkbox state is shown for this event.");
+ }
+
+ function testEventGroup(string, checked) {
+ is(getGroupCheckboxNode(string).checked, checked,
+ "The correct checkbox state is shown for the group " + string + ".");
+ }
+
+ function testEventArrays(all, checked) {
+ is(gEvents.getAllEvents().toString(), all,
+ "The getAllEvents() method returns the correct stuff.");
+ is(gEvents.getCheckedEvents().toString(), checked,
+ "The getCheckedEvents() method returns the correct stuff.");
+ is(gController.getState().eventListeners.activeEventNames.toString(), checked,
+ "The correct event names are listed as being active breakpoints.");
+ }
+ });
+}
diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_break-on-dom-05.js b/devtools/client/debugger/test/mochitest/browser_dbg_break-on-dom-05.js
new file mode 100644
index 000000000..d0a552e81
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_break-on-dom-05.js
@@ -0,0 +1,128 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Tests that checking/unchecking an event listener's group in the view will
+ * cause the active thread to get updated with the new event breakpoints for
+ * all children inside that group.
+ */
+
+const TAB_URL = EXAMPLE_URL + "doc_event-listeners-02.html";
+
+function test() {
+ let options = {
+ source: TAB_URL,
+ line: 1
+ };
+ initDebugger(TAB_URL, options).then(([aTab,, aPanel]) => {
+ let gDebugger = aPanel.panelWin;
+ let gView = gDebugger.DebuggerView;
+ let gController = gDebugger.DebuggerController;
+ let gEvents = gView.EventListeners;
+ let constants = gDebugger.require("./content/constants");
+
+ Task.spawn(function* () {
+ let fetched = waitForDispatch(aPanel, constants.FETCH_EVENT_LISTENERS);
+ gView.toggleInstrumentsPane({ visible: true, animated: false }, 1);
+ yield fetched;
+
+ testEventItem(0, false);
+ testEventItem(1, false);
+ testEventItem(2, false);
+ testEventItem(3, false);
+ testEventGroup("interactionEvents", false);
+ testEventGroup("keyboardEvents", false);
+ testEventGroup("mouseEvents", false);
+ testEventArrays("change,click,keydown,keyup", "");
+
+ let updated = waitForDispatch(aPanel, constants.UPDATE_EVENT_BREAKPOINTS);
+ EventUtils.sendMouseEvent({ type: "click" }, getGroupCheckboxNode("interactionEvents"), gDebugger);
+ yield updated;
+
+ testEventItem(0, true);
+ testEventItem(1, false);
+ testEventItem(2, false);
+ testEventItem(3, false);
+ testEventGroup("interactionEvents", true);
+ testEventGroup("keyboardEvents", false);
+ testEventGroup("mouseEvents", false);
+ testEventArrays("change,click,keydown,keyup", "change");
+
+ updated = waitForDispatch(aPanel, constants.UPDATE_EVENT_BREAKPOINTS);
+ EventUtils.sendMouseEvent({ type: "click" }, getGroupCheckboxNode("interactionEvents"), gDebugger);
+ yield updated;
+
+ testEventItem(0, false);
+ testEventItem(1, false);
+ testEventItem(2, false);
+ testEventItem(3, false);
+ testEventGroup("interactionEvents", false);
+ testEventGroup("keyboardEvents", false);
+ testEventGroup("mouseEvents", false);
+ testEventArrays("change,click,keydown,keyup", "");
+
+ updated = waitForDispatch(aPanel, constants.UPDATE_EVENT_BREAKPOINTS);
+ EventUtils.sendMouseEvent({ type: "click" }, getGroupCheckboxNode("keyboardEvents"), gDebugger);
+ yield updated;
+
+ testEventItem(0, false);
+ testEventItem(1, false);
+ testEventItem(2, true);
+ testEventItem(3, true);
+ testEventGroup("interactionEvents", false);
+ testEventGroup("keyboardEvents", true);
+ testEventGroup("mouseEvents", false);
+ testEventArrays("change,click,keydown,keyup", "keydown,keyup");
+
+ updated = waitForDispatch(aPanel, constants.UPDATE_EVENT_BREAKPOINTS);
+ EventUtils.sendMouseEvent({ type: "click" }, getGroupCheckboxNode("keyboardEvents"), gDebugger);
+ yield updated;
+
+ testEventItem(0, false);
+ testEventItem(1, false);
+ testEventItem(2, false);
+ testEventItem(3, false);
+ testEventGroup("interactionEvents", false);
+ testEventGroup("keyboardEvents", false);
+ testEventGroup("mouseEvents", false);
+ testEventArrays("change,click,keydown,keyup", "");
+
+ yield ensureThreadClientState(aPanel, "attached");
+ yield closeDebuggerAndFinish(aPanel);
+ });
+
+ function getItemCheckboxNode(index) {
+ return gEvents.items[index].target.parentNode
+ .querySelector(".side-menu-widget-item-checkbox");
+ }
+
+ function getGroupCheckboxNode(string) {
+ return gEvents.widget._parent
+ .querySelector(".side-menu-widget-group[name=" + gDebugger.L10N.getStr(string) + "]")
+ .querySelector(".side-menu-widget-group-checkbox");
+ }
+
+ function testEventItem(index, checked) {
+ is(gEvents.attachments[index].checkboxState, checked,
+ "The event at index " + index + " has the correct checkbox state.");
+ is(getItemCheckboxNode(index).checked, checked,
+ "The correct checkbox state is shown for this event.");
+ }
+
+ function testEventGroup(string, checked) {
+ is(getGroupCheckboxNode(string).checked, checked,
+ "The correct checkbox state is shown for the group " + string + ".");
+ }
+
+ function testEventArrays(all, checked) {
+ is(gEvents.getAllEvents().toString(), all,
+ "The getAllEvents() method returns the correct stuff.");
+ is(gEvents.getCheckedEvents().toString(), checked,
+ "The getCheckedEvents() method returns the correct stuff.");
+ is(gController.getState().eventListeners.activeEventNames.toString(), checked,
+ "The correct event names are listed as being active breakpoints.");
+ }
+ });
+}
diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_break-on-dom-06.js b/devtools/client/debugger/test/mochitest/browser_dbg_break-on-dom-06.js
new file mode 100644
index 000000000..3197b640a
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_break-on-dom-06.js
@@ -0,0 +1,130 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Tests that the event listener states are preserved in the view after the
+ * target navigates.
+ */
+
+const TAB_URL = EXAMPLE_URL + "doc_event-listeners-02.html";
+
+function test() {
+ let options = {
+ source: TAB_URL,
+ line: 1
+ };
+ initDebugger(TAB_URL, options).then(([aTab,, aPanel]) => {
+ let gDebugger = aPanel.panelWin;
+ let gView = gDebugger.DebuggerView;
+ let gController = gDebugger.DebuggerController;
+ let gEvents = gView.EventListeners;
+ let gBreakpoints = gController.Breakpoints;
+ let constants = gDebugger.require("./content/constants");
+
+ Task.spawn(function* () {
+ let fetched = waitForDispatch(aPanel, constants.FETCH_EVENT_LISTENERS);
+ gView.toggleInstrumentsPane({ visible: true, animated: false }, 1);
+ yield fetched;
+
+ testEventItem(0, false);
+ testEventItem(1, false);
+ testEventItem(2, false);
+ testEventItem(3, false);
+ testEventGroup("interactionEvents", false);
+ testEventGroup("keyboardEvents", false);
+ testEventGroup("mouseEvents", false);
+ testEventArrays("change,click,keydown,keyup", "");
+
+ let updated = waitForDispatch(aPanel, constants.UPDATE_EVENT_BREAKPOINTS);
+ EventUtils.sendMouseEvent({ type: "click" }, getItemCheckboxNode(0), gDebugger);
+ EventUtils.sendMouseEvent({ type: "click" }, getItemCheckboxNode(1), gDebugger);
+ EventUtils.sendMouseEvent({ type: "click" }, getItemCheckboxNode(2), gDebugger);
+ yield updated;
+
+ testEventItem(0, true);
+ testEventItem(1, true);
+ testEventItem(2, true);
+ testEventItem(3, false);
+ testEventGroup("interactionEvents", false);
+ testEventGroup("keyboardEvents", false);
+ testEventGroup("mouseEvents", false);
+ testEventArrays("change,click,keydown,keyup", "change,click,keydown");
+
+ reload(aPanel);
+ yield waitForDispatch(aPanel, constants.FETCH_EVENT_LISTENERS);
+
+ testEventItem(0, true);
+ testEventItem(1, true);
+ testEventItem(2, true);
+ testEventItem(3, false);
+ testEventGroup("interactionEvents", false);
+ testEventGroup("keyboardEvents", false);
+ testEventGroup("mouseEvents", false);
+ testEventArrays("change,click,keydown,keyup", "change,click,keydown");
+
+ updated = waitForDispatch(aPanel, constants.UPDATE_EVENT_BREAKPOINTS);
+ EventUtils.sendMouseEvent({ type: "click" }, getItemCheckboxNode(0), gDebugger);
+ EventUtils.sendMouseEvent({ type: "click" }, getItemCheckboxNode(1), gDebugger);
+ EventUtils.sendMouseEvent({ type: "click" }, getItemCheckboxNode(2), gDebugger);
+ yield updated;
+
+ testEventItem(0, false);
+ testEventItem(1, false);
+ testEventItem(2, false);
+ testEventItem(3, false);
+ testEventGroup("interactionEvents", false);
+ testEventGroup("keyboardEvents", false);
+ testEventGroup("mouseEvents", false);
+ testEventArrays("change,click,keydown,keyup", "");
+
+ reload(aPanel);
+ yield waitForDispatch(aPanel, constants.FETCH_EVENT_LISTENERS);
+
+ testEventItem(0, false);
+ testEventItem(1, false);
+ testEventItem(2, false);
+ testEventItem(3, false);
+ testEventGroup("interactionEvents", false);
+ testEventGroup("keyboardEvents", false);
+ testEventGroup("mouseEvents", false);
+ testEventArrays("change,click,keydown,keyup", "");
+
+ yield ensureThreadClientState(aPanel, "attached");
+ yield closeDebuggerAndFinish(aPanel);
+ });
+
+ function getItemCheckboxNode(index) {
+ return gEvents.items[index].target.parentNode
+ .querySelector(".side-menu-widget-item-checkbox");
+ }
+
+ function getGroupCheckboxNode(string) {
+ return gEvents.widget._parent
+ .querySelector(".side-menu-widget-group[name=" + gDebugger.L10N.getStr(string) + "]")
+ .querySelector(".side-menu-widget-group-checkbox");
+ }
+
+ function testEventItem(index, checked) {
+ is(gEvents.attachments[index].checkboxState, checked,
+ "The event at index " + index + " has the correct checkbox state.");
+ is(getItemCheckboxNode(index).checked, checked,
+ "The correct checkbox state is shown for this event.");
+ }
+
+ function testEventGroup(string, checked) {
+ is(getGroupCheckboxNode(string).checked, checked,
+ "The correct checkbox state is shown for the group " + string + ".");
+ }
+
+ function testEventArrays(all, checked) {
+ is(gEvents.getAllEvents().toString(), all,
+ "The getAllEvents() method returns the correct stuff.");
+ is(gEvents.getCheckedEvents().toString(), checked,
+ "The getCheckedEvents() method returns the correct stuff.");
+ is(gController.getState().eventListeners.activeEventNames.toString(), checked,
+ "The correct event names are listed as being active breakpoints.");
+ }
+ });
+}
diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_break-on-dom-07.js b/devtools/client/debugger/test/mochitest/browser_dbg_break-on-dom-07.js
new file mode 100644
index 000000000..107eab5f8
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_break-on-dom-07.js
@@ -0,0 +1,106 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Tests that system event listeners don't get duplicated in the view.
+ */
+
+function test() {
+ initDebugger().then(([aTab,, aPanel]) => {
+ let gDebugger = aPanel.panelWin;
+ let gView = gDebugger.DebuggerView;
+ let gEvents = gView.EventListeners;
+ let gL10N = gDebugger.L10N;
+
+ is(gEvents.itemCount, 0,
+ "There are no events displayed in the corresponding pane yet.");
+
+ gEvents.addListener({
+ type: "foo",
+ node: { selector: "#first" },
+ function: { url: null }
+ });
+
+ is(gEvents.itemCount, 1,
+ "There was a system event listener added in the view.");
+ is(gEvents.attachments[0].url, gL10N.getStr("eventNative"),
+ "The correct string is used as the event's url.");
+ is(gEvents.attachments[0].type, "foo",
+ "The correct string is used as the event's type.");
+ is(gEvents.attachments[0].selectors.toString(), "#first",
+ "The correct array of selectors is used as the event's target.");
+
+ gEvents.addListener({
+ type: "bar",
+ node: { selector: "#second" },
+ function: { url: null }
+ });
+
+ is(gEvents.itemCount, 2,
+ "There was another system event listener added in the view.");
+ is(gEvents.attachments[1].url, gL10N.getStr("eventNative"),
+ "The correct string is used as the event's url.");
+ is(gEvents.attachments[1].type, "bar",
+ "The correct string is used as the event's type.");
+ is(gEvents.attachments[1].selectors.toString(), "#second",
+ "The correct array of selectors is used as the event's target.");
+
+ gEvents.addListener({
+ type: "foo",
+ node: { selector: "#first" },
+ function: { url: null }
+ });
+
+ is(gEvents.itemCount, 2,
+ "There wasn't another system event listener added in the view.");
+ is(gEvents.attachments[0].url, gL10N.getStr("eventNative"),
+ "The correct string is used as the event's url.");
+ is(gEvents.attachments[0].type, "foo",
+ "The correct string is used as the event's type.");
+ is(gEvents.attachments[0].selectors.toString(), "#first",
+ "The correct array of selectors is used as the event's target.");
+
+ gEvents.addListener({
+ type: "foo",
+ node: { selector: "#second" },
+ function: { url: null }
+ });
+
+ is(gEvents.itemCount, 2,
+ "There still wasn't another system event listener added in the view.");
+ is(gEvents.attachments[0].url, gL10N.getStr("eventNative"),
+ "The correct string is used as the event's url.");
+ is(gEvents.attachments[0].type, "foo",
+ "The correct string is used as the event's type.");
+ is(gEvents.attachments[0].selectors.toString(), "#first,#second",
+ "The correct array of selectors is used as the event's target.");
+
+
+ gEvents.addListener({
+ type: null,
+ node: { selector: "#bogus" },
+ function: { url: null }
+ });
+
+ is(gEvents.itemCount, 2,
+ "No bogus system event listener was added in the view.");
+
+ is(gEvents.attachments[0].url, gL10N.getStr("eventNative"),
+ "The correct string is used as the first event's url.");
+ is(gEvents.attachments[0].type, "foo",
+ "The correct string is used as the first event's type.");
+ is(gEvents.attachments[0].selectors.toString(), "#first,#second",
+ "The correct array of selectors is used as the first event's target.");
+
+ is(gEvents.attachments[1].url, gL10N.getStr("eventNative"),
+ "The correct string is used as the second event's url.");
+ is(gEvents.attachments[1].type, "bar",
+ "The correct string is used as the second event's type.");
+ is(gEvents.attachments[1].selectors.toString(), "#second",
+ "The correct array of selectors is used as the second event's target.");
+
+ closeDebuggerAndFinish(aPanel);
+ });
+}
diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_break-on-dom-08.js b/devtools/client/debugger/test/mochitest/browser_dbg_break-on-dom-08.js
new file mode 100644
index 000000000..c2bad132b
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_break-on-dom-08.js
@@ -0,0 +1,61 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Tests that breaking on an event selects the variables view tab.
+ */
+
+const TAB_URL = EXAMPLE_URL + "doc_event-listeners-02.html";
+
+function test() {
+ let options = {
+ source: TAB_URL,
+ line: 1
+ };
+ initDebugger(TAB_URL, options).then(([aTab,, aPanel]) => {
+ let gTab = aTab;
+ let gDebugger = aPanel.panelWin;
+ let gView = gDebugger.DebuggerView;
+ let gEvents = gView.EventListeners;
+ let gController = gDebugger.DebuggerController;
+ let constants = gDebugger.require("./content/constants");
+
+ Task.spawn(function* () {
+ yield callInTab(gTab, "addBodyClickEventListener");
+
+ let fetched = waitForDispatch(aPanel, constants.FETCH_EVENT_LISTENERS);
+ gView.toggleInstrumentsPane({ visible: true, animated: false }, 1);
+ yield fetched;
+ yield ensureThreadClientState(aPanel, "attached");
+
+ is(gView.instrumentsPaneHidden, false,
+ "The instruments pane should be visible.");
+ is(gView.instrumentsPaneTab, "events-tab",
+ "The events tab should be selected.");
+
+ let updated = waitForDispatch(aPanel, constants.UPDATE_EVENT_BREAKPOINTS);
+ EventUtils.sendMouseEvent({ type: "click" }, getItemCheckboxNode(1), gDebugger);
+ yield updated;
+ yield ensureThreadClientState(aPanel, "attached");
+
+ let paused = waitForCaretAndScopes(aPanel, 48);
+ generateMouseClickInTab(gTab, "content.document.body");
+ yield paused;
+ yield ensureThreadClientState(aPanel, "paused");
+
+ is(gView.instrumentsPaneHidden, false,
+ "The instruments pane should be visible.");
+ is(gView.instrumentsPaneTab, "variables-tab",
+ "The variables tab should be selected.");
+
+ yield resumeDebuggerThenCloseAndFinish(aPanel);
+ });
+
+ function getItemCheckboxNode(index) {
+ return gEvents.items[index].target.parentNode
+ .querySelector(".side-menu-widget-item-checkbox");
+ }
+ });
+}
diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_break-on-dom-event-01.js b/devtools/client/debugger/test/mochitest/browser_dbg_break-on-dom-event-01.js
new file mode 100644
index 000000000..a066b7d6b
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_break-on-dom-event-01.js
@@ -0,0 +1,225 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Tests that the break-on-dom-events request works.
+ */
+
+const TAB_URL = EXAMPLE_URL + "doc_event-listeners-01.html";
+
+var gClient, gThreadClient, gInput, gButton;
+
+function test() {
+ if (!DebuggerServer.initialized) {
+ DebuggerServer.init();
+ DebuggerServer.addBrowserActors();
+ }
+
+ let transport = DebuggerServer.connectPipe();
+ gClient = new DebuggerClient(transport);
+ gClient.connect().then(([aType, aTraits]) => {
+ is(aType, "browser",
+ "Root actor should identify itself as a browser.");
+
+ addTab(TAB_URL)
+ .then(() => attachThreadActorForUrl(gClient, TAB_URL))
+ .then(setupGlobals)
+ .then(pauseDebuggee)
+ .then(testBreakOnAll)
+ .then(testBreakOnDisabled)
+ .then(testBreakOnNone)
+ .then(testBreakOnClick)
+ .then(() => gClient.close())
+ .then(finish)
+ .then(null, aError => {
+ ok(false, "Got an error: " + aError.message + "\n" + aError.stack);
+ });
+ });
+}
+
+function setupGlobals(aThreadClient) {
+ gThreadClient = aThreadClient;
+ gInput = content.document.querySelector("input");
+ gButton = content.document.querySelector("button");
+}
+
+function pauseDebuggee() {
+ let deferred = promise.defer();
+
+ gClient.addOneTimeListener("paused", (aEvent, aPacket) => {
+ is(aPacket.type, "paused",
+ "We should now be paused.");
+ is(aPacket.why.type, "debuggerStatement",
+ "The debugger statement was hit.");
+
+ deferred.resolve();
+ });
+
+ // Spin the event loop before causing the debuggee to pause, to allow
+ // this function to return first.
+ executeSoon(triggerButtonClick);
+
+ return deferred.promise;
+}
+
+// Test pause on all events.
+function testBreakOnAll() {
+ let deferred = promise.defer();
+
+ // Test calling pauseOnDOMEvents from a paused state.
+ gThreadClient.pauseOnDOMEvents("*", (aPacket) => {
+ is(aPacket.error, undefined,
+ "The pause-on-any-event request completed successfully.");
+
+ gClient.addOneTimeListener("paused", (aEvent, aPacket) => {
+ is(aPacket.why.type, "pauseOnDOMEvents",
+ "A hidden breakpoint was hit.");
+ is(aPacket.frame.callee.name, "keyupHandler",
+ "The keyupHandler is entered.");
+
+ gClient.addOneTimeListener("paused", (aEvent, aPacket) => {
+ is(aPacket.why.type, "pauseOnDOMEvents",
+ "A hidden breakpoint was hit.");
+ is(aPacket.frame.callee.name, "clickHandler",
+ "The clickHandler is entered.");
+
+ gClient.addOneTimeListener("paused", (aEvent, aPacket) => {
+ is(aPacket.why.type, "pauseOnDOMEvents",
+ "A hidden breakpoint was hit.");
+ is(aPacket.frame.callee.name, "onchange",
+ "The onchange handler is entered.");
+
+ gThreadClient.resume(deferred.resolve);
+ });
+
+ gThreadClient.resume(triggerInputChange);
+ });
+
+ gThreadClient.resume(triggerButtonClick);
+ });
+
+ gThreadClient.resume(triggerInputKeyup);
+ });
+
+ return deferred.promise;
+}
+
+// Test that removing events from the array disables them.
+function testBreakOnDisabled() {
+ let deferred = promise.defer();
+
+ // Test calling pauseOnDOMEvents from a running state.
+ gThreadClient.pauseOnDOMEvents(["click"], (aPacket) => {
+ is(aPacket.error, undefined,
+ "The pause-on-click-only request completed successfully.");
+
+ gClient.addListener("paused", unexpectedListener);
+
+ // This non-capturing event listener is guaranteed to run after the page's
+ // capturing one had a chance to execute and modify window.foobar.
+ once(gInput, "keyup").then(() => {
+ is(content.wrappedJSObject.foobar, "keyupHandler",
+ "No hidden breakpoint was hit.");
+
+ gClient.removeListener("paused", unexpectedListener);
+ deferred.resolve();
+ });
+
+ triggerInputKeyup();
+ });
+
+ return deferred.promise;
+}
+
+// Test that specifying an empty event array clears all hidden breakpoints.
+function testBreakOnNone() {
+ let deferred = promise.defer();
+
+ // Test calling pauseOnDOMEvents from a running state.
+ gThreadClient.pauseOnDOMEvents([], (aPacket) => {
+ is(aPacket.error, undefined,
+ "The pause-on-none request completed successfully.");
+
+ gClient.addListener("paused", unexpectedListener);
+
+ // This non-capturing event listener is guaranteed to run after the page's
+ // capturing one had a chance to execute and modify window.foobar.
+ once(gInput, "keyup").then(() => {
+ is(content.wrappedJSObject.foobar, "keyupHandler",
+ "No hidden breakpoint was hit.");
+
+ gClient.removeListener("paused", unexpectedListener);
+ deferred.resolve();
+ });
+
+ triggerInputKeyup();
+ });
+
+ return deferred.promise;
+}
+
+// Test pause on a single event.
+function testBreakOnClick() {
+ let deferred = promise.defer();
+
+ // Test calling pauseOnDOMEvents from a running state.
+ gThreadClient.pauseOnDOMEvents(["click"], (aPacket) => {
+ is(aPacket.error, undefined,
+ "The pause-on-click request completed successfully.");
+
+ gClient.addOneTimeListener("paused", (aEvent, aPacket) => {
+ is(aPacket.why.type, "pauseOnDOMEvents",
+ "A hidden breakpoint was hit.");
+ is(aPacket.frame.callee.name, "clickHandler",
+ "The clickHandler is entered.");
+
+ gThreadClient.resume(deferred.resolve);
+ });
+
+ triggerButtonClick();
+ });
+
+ return deferred.promise;
+}
+
+function unexpectedListener() {
+ gClient.removeListener("paused", unexpectedListener);
+ ok(false, "An unexpected hidden breakpoint was hit.");
+ gThreadClient.resume(testBreakOnClick);
+}
+
+function triggerInputKeyup() {
+ // Make sure that the focus is not on the input box so that a focus event
+ // will be triggered.
+ window.focus();
+ gBrowser.selectedBrowser.focus();
+ gButton.focus();
+
+ // Focus the element and wait for focus event.
+ once(gInput, "focus").then(() => {
+ executeSoon(() => {
+ EventUtils.synthesizeKey("e", { shiftKey: 1 }, content);
+ });
+ });
+
+ gInput.focus();
+}
+
+function triggerButtonClick() {
+ EventUtils.sendMouseEvent({ type: "click" }, gButton);
+}
+
+function triggerInputChange() {
+ gInput.focus();
+ gInput.value = "foo";
+ gInput.blur();
+}
+
+registerCleanupFunction(function () {
+ gClient = null;
+ gThreadClient = null;
+ gInput = null;
+ gButton = null;
+});
diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_break-on-dom-event-02.js b/devtools/client/debugger/test/mochitest/browser_dbg_break-on-dom-event-02.js
new file mode 100644
index 000000000..d6d502343
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_break-on-dom-event-02.js
@@ -0,0 +1,105 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Tests that the break-on-dom-events request works even for bound event
+ * listeners and handler objects with 'handleEvent' methods.
+ */
+
+const TAB_URL = EXAMPLE_URL + "doc_event-listeners-03.html";
+
+var gClient, gThreadClient;
+
+function test() {
+ if (!DebuggerServer.initialized) {
+ DebuggerServer.init();
+ DebuggerServer.addBrowserActors();
+ }
+
+ let transport = DebuggerServer.connectPipe();
+ gClient = new DebuggerClient(transport);
+ gClient.connect().then(([aType, aTraits]) => {
+ is(aType, "browser",
+ "Root actor should identify itself as a browser.");
+
+ addTab(TAB_URL)
+ .then(() => attachThreadActorForUrl(gClient, TAB_URL))
+ .then(aThreadClient => gThreadClient = aThreadClient)
+ .then(pauseDebuggee)
+ .then(testBreakOnClick)
+ .then(() => gClient.close())
+ .then(finish)
+ .then(null, aError => {
+ ok(false, "Got an error: " + aError.message + "\n" + aError.stack);
+ });
+ });
+}
+
+function pauseDebuggee() {
+ let deferred = promise.defer();
+
+ gClient.addOneTimeListener("paused", (aEvent, aPacket) => {
+ is(aPacket.type, "paused",
+ "We should now be paused.");
+ is(aPacket.why.type, "debuggerStatement",
+ "The debugger statement was hit.");
+
+ gThreadClient.resume(deferred.resolve);
+ });
+
+ // Spin the event loop before causing the debuggee to pause, to allow
+ // this function to return first.
+ executeSoon(() => triggerButtonClick("initialSetup"));
+
+ return deferred.promise;
+}
+
+// Test pause on a single event.
+function testBreakOnClick() {
+ let deferred = promise.defer();
+
+ // Test calling pauseOnDOMEvents from a running state.
+ gThreadClient.pauseOnDOMEvents(["click"], (aPacket) => {
+ is(aPacket.error, undefined,
+ "The pause-on-click request completed successfully.");
+ let handlers = ["clicker"];
+
+ gClient.addListener("paused", function tester(aEvent, aPacket) {
+ is(aPacket.why.type, "pauseOnDOMEvents",
+ "A hidden breakpoint was hit.");
+
+ switch (handlers.length) {
+ case 1:
+ is(aPacket.frame.where.line, 26, "Found the clicker handler.");
+ handlers.push("handleEventClick");
+ break;
+ case 2:
+ is(aPacket.frame.where.line, 36, "Found the handleEventClick handler.");
+ handlers.push("boundHandleEventClick");
+ break;
+ case 3:
+ is(aPacket.frame.where.line, 46, "Found the boundHandleEventClick handler.");
+ gClient.removeListener("paused", tester);
+ deferred.resolve();
+ }
+
+ gThreadClient.resume(() => triggerButtonClick(handlers.slice(-1)));
+ });
+
+ triggerButtonClick(handlers.slice(-1));
+ });
+
+ return deferred.promise;
+}
+
+function triggerButtonClick(aNodeId) {
+ let button = content.document.getElementById(aNodeId);
+ EventUtils.sendMouseEvent({ type: "click" }, button);
+}
+
+registerCleanupFunction(function () {
+ gClient = null;
+ gThreadClient = null;
+});
diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_break-on-dom-event-03.js b/devtools/client/debugger/test/mochitest/browser_dbg_break-on-dom-event-03.js
new file mode 100644
index 000000000..3ffe830e9
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_break-on-dom-event-03.js
@@ -0,0 +1,97 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Tests that the break-on-dom-events request works for load event listeners.
+ */
+
+const TAB_URL = EXAMPLE_URL + "doc_event-listeners-04.html";
+
+var gClient, gThreadClient;
+
+function test() {
+ if (!DebuggerServer.initialized) {
+ DebuggerServer.init();
+ DebuggerServer.addBrowserActors();
+ }
+
+ let transport = DebuggerServer.connectPipe();
+ gClient = new DebuggerClient(transport);
+ gClient.connect().then(([aType, aTraits]) => {
+ is(aType, "browser",
+ "Root actor should identify itself as a browser.");
+
+ addTab(TAB_URL)
+ .then(() => attachThreadActorForUrl(gClient, TAB_URL))
+ .then(aThreadClient => gThreadClient = aThreadClient)
+ .then(pauseDebuggee)
+ .then(testBreakOnLoad)
+ .then(() => gClient.close())
+ .then(finish)
+ .then(null, aError => {
+ ok(false, "Got an error: " + aError.message + "\n" + aError.stack);
+ });
+ });
+}
+
+function pauseDebuggee() {
+ let deferred = promise.defer();
+
+ gClient.addOneTimeListener("paused", (aEvent, aPacket) => {
+ is(aPacket.type, "paused",
+ "We should now be paused.");
+ is(aPacket.why.type, "debuggerStatement",
+ "The debugger statement was hit.");
+
+ gThreadClient.resume(deferred.resolve);
+ });
+
+ // Spin the event loop before causing the debuggee to pause, to allow
+ // this function to return first.
+ executeSoon(() => triggerButtonClick());
+
+ return deferred.promise;
+}
+
+// Test pause on a load event.
+function testBreakOnLoad() {
+ let deferred = promise.defer();
+
+ // Test calling pauseOnDOMEvents from a running state.
+ gThreadClient.pauseOnDOMEvents(["load"], (aPacket) => {
+ is(aPacket.error, undefined,
+ "The pause-on-load request completed successfully.");
+ let handlers = ["loadHandler"];
+
+ gClient.addListener("paused", function tester(aEvent, aPacket) {
+ is(aPacket.why.type, "pauseOnDOMEvents",
+ "A hidden breakpoint was hit.");
+
+ is(aPacket.frame.where.line, 15, "Found the load event listener.");
+ gClient.removeListener("paused", tester);
+ deferred.resolve();
+
+ gThreadClient.resume(() => triggerButtonClick(handlers.slice(-1)));
+ });
+
+ getTabActorForUrl(gClient, TAB_URL).then(aGrip => {
+ gClient.attachTab(aGrip.actor, (aResponse, aTabClient) => {
+ aTabClient.reload();
+ });
+ });
+ });
+
+ return deferred.promise;
+}
+
+function triggerButtonClick() {
+ let button = content.document.querySelector("button");
+ EventUtils.sendMouseEvent({ type: "click" }, button);
+}
+
+registerCleanupFunction(function () {
+ gClient = null;
+ gThreadClient = null;
+});
diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_break-on-next-console.js b/devtools/client/debugger/test/mochitest/browser_dbg_break-on-next-console.js
new file mode 100644
index 000000000..c30694520
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_break-on-next-console.js
@@ -0,0 +1,61 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Test if 'break on next' functionality works from executions
+ * in content triggered by the console in the toolbox.
+ */
+
+const TAB_URL = EXAMPLE_URL + "doc_script-eval.html";
+
+function test() {
+ let gTab, gPanel, gDebugger;
+ let gSources, gBreakpoints, gTarget, gResumeButton, gResumeKey, gThreadClient;
+
+ let options = {
+ source: EXAMPLE_URL + "code_script-eval.js",
+ line: 1
+ };
+ initDebugger(TAB_URL, options).then(([aTab,, aPanel]) => {
+ gTab = aTab;
+ gPanel = aPanel;
+ gDebugger = gPanel.panelWin;
+ gSources = gDebugger.DebuggerView.Sources;
+ gTarget = gDebugger.gTarget;
+ gThreadClient = gDebugger.gThreadClient;
+ gResumeButton = gDebugger.document.getElementById("resume");
+ gResumeKey = gDebugger.document.getElementById("resumeKey");
+
+ testConsole()
+ .then(() => closeDebuggerAndFinish(gPanel));
+ });
+
+ let testConsole = Task.async(function* () {
+ info("Starting testConsole");
+
+ let oncePaused = gTarget.once("thread-paused");
+ EventUtils.sendMouseEvent({ type: "mousedown" }, gResumeButton, gDebugger);
+ let jsterm = yield getSplitConsole(gDevTools.getToolbox(gPanel.target));
+ let executed = jsterm.execute("1+1");
+ yield oncePaused;
+
+ let updatedFrame = yield waitForDebuggerEvents(gPanel, gDebugger.EVENTS.FETCHED_SCOPES);
+ let variables = gDebugger.DebuggerView.Variables;
+
+ is(variables._store.length, 3, "Correct number of scopes available");
+ is(variables.getScopeAtIndex(0).name, "With scope [Object]",
+ "Paused with correct scope (0)");
+ is(variables.getScopeAtIndex(1).name, "Block scope",
+ "Paused with correct scope (1)");
+ is(variables.getScopeAtIndex(2).name, "Global scope [Window]",
+ "Paused with correct scope (2)");
+
+ let onceResumed = gTarget.once("thread-resumed");
+ EventUtils.sendMouseEvent({ type: "mousedown" }, gResumeButton, gDebugger);
+ yield onceResumed;
+
+ yield executed;
+ });
+}
diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_break-on-next.js b/devtools/client/debugger/test/mochitest/browser_dbg_break-on-next.js
new file mode 100644
index 000000000..53f03c183
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_break-on-next.js
@@ -0,0 +1,103 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Test if 'break on next' functionality works from executions
+ * in content that are triggered by the page.
+ */
+
+const TAB_URL = EXAMPLE_URL + "doc_script-eval.html";
+
+function test() {
+ let gTab, gPanel, gDebugger;
+ let gSources, gBreakpoints, gTarget, gResumeButton, gResumeKey, gThreadClient;
+
+ const options = {
+ source: EXAMPLE_URL + "code_script-eval.js",
+ line: 1
+ };
+ initDebugger(TAB_URL, options).then(([aTab,, aPanel]) => {
+ gTab = aTab;
+ gPanel = aPanel;
+ gDebugger = gPanel.panelWin;
+ gSources = gDebugger.DebuggerView.Sources;
+ gTarget = gDebugger.gTarget;
+ gThreadClient = gDebugger.gThreadClient;
+ gResumeButton = gDebugger.document.getElementById("resume");
+ gResumeKey = gDebugger.document.getElementById("resumeKey");
+
+ testInterval()
+ .then(testEvent)
+ .then(() => closeDebuggerAndFinish(gPanel));
+ });
+
+ // Testing an interval instead of a timeout / rAF because
+ // it's less likely to fail due to timing issues. If the
+ // first callback happens to fire before the break request
+ // happens then we'll just get it next time.
+ let testInterval = Task.async(function* () {
+ info("Starting testInterval");
+
+ yield evalInTab(gTab, `
+ var interval = setInterval(function() {
+ return 1+1;
+ }, 100);
+ `);
+
+ let oncePaused = gTarget.once("thread-paused");
+ EventUtils.sendMouseEvent({ type: "mousedown" }, gResumeButton, gDebugger);
+ yield oncePaused;
+
+ yield waitForDebuggerEvents(gPanel, gDebugger.EVENTS.SOURCE_SHOWN);
+ let variables = gDebugger.DebuggerView.Variables;
+
+ is(variables._store.length, 3, "Correct number of scopes available");
+ is(variables.getScopeAtIndex(0).name, "Function scope [interval<]",
+ "Paused with correct scope (0)");
+ is(variables.getScopeAtIndex(1).name, "Block scope",
+ "Paused with correct scope (1)");
+ is(variables.getScopeAtIndex(2).name, "Global scope [Window]",
+ "Paused with correct scope (2)");
+
+ yield evalInTab(gTab, "clearInterval(interval)");
+ let onceResumed = gTarget.once("thread-resumed");
+ EventUtils.sendMouseEvent({ type: "mousedown" }, gResumeButton, gDebugger);
+ yield onceResumed;
+ });
+
+ let testEvent = Task.async(function* () {
+ info("Starting testEvent");
+
+ let oncePaused = gTarget.once("thread-paused");
+ EventUtils.sendMouseEvent({ type: "mousedown" }, gResumeButton, gDebugger);
+ once(gDebugger.gClient, "willInterrupt").then(() => {
+ generateMouseClickInTab(gTab, "content.document.querySelector('button')");
+ });
+ yield oncePaused;
+
+ yield waitForDebuggerEvents(gPanel, gDebugger.EVENTS.SOURCE_SHOWN);
+ let variables = gDebugger.DebuggerView.Variables;
+
+ is(variables._store.length, 6, "Correct number of scopes available");
+ is(variables.getScopeAtIndex(0).name, "Function scope [onclick]",
+ "Paused with correct scope (0)");
+ // Non-syntactic lexical scope introduced by non-syntactic scope chain.
+ is(variables.getScopeAtIndex(1).name, "Block scope",
+ "Paused with correct scope (1)");
+ is(variables.getScopeAtIndex(2).name, "With scope [HTMLButtonElement]",
+ "Paused with correct scope (2)");
+ is(variables.getScopeAtIndex(3).name, "With scope [HTMLDocument]",
+ "Paused with correct scope (3)");
+ // Global lexical scope.
+ is(variables.getScopeAtIndex(4).name, "Block scope",
+ "Paused with correct scope (4)");
+ is(variables.getScopeAtIndex(5).name, "Global scope [Window]",
+ "Paused with correct scope (5)");
+
+ let onceResumed = gTarget.once("thread-resumed");
+ EventUtils.sendMouseEvent({ type: "mousedown" }, gResumeButton, gDebugger);
+ yield onceResumed;
+ });
+}
diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_break-unselected.js b/devtools/client/debugger/test/mochitest/browser_dbg_break-unselected.js
new file mode 100644
index 000000000..b76a7606a
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_break-unselected.js
@@ -0,0 +1,48 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Test breaking in code and jumping to the debugger before
+ * the debugger UI has been initialized.
+ */
+
+const TAB_URL = EXAMPLE_URL + "doc_inline-debugger-statement.html";
+
+function test() {
+ Task.spawn(function* () {
+ const tab = yield getTab(TAB_URL);
+ const target = TargetFactory.forTab(tab);
+ const toolbox = yield gDevTools.showToolbox(target, "webconsole");
+
+ is(toolbox.currentToolId, "webconsole", "Console is the current panel");
+
+ toolbox.target.on("thread-paused", Task.async(function* () {
+ // Wait for the toolbox to handle the event and switch tools
+ yield waitForTick();
+
+ is(toolbox.currentToolId, "jsdebugger", "Debugger is the current panel");
+
+ // Wait until it's actually fully loaded
+ yield toolbox.loadTool("jsdebugger");
+
+ const panel = toolbox.getCurrentPanel();
+ const queries = panel.panelWin.require("./content/queries");
+ const getState = panel.panelWin.DebuggerController.getState;
+
+ is(panel.panelWin.gThreadClient.state, "paused",
+ "Thread is still paused");
+
+ yield waitForSourceAndCaret(panel, "debugger-statement.html", 16);
+ is(queries.getSelectedSource(getState()).url, TAB_URL,
+ "Selected source is the current tab url");
+ is(queries.getSelectedSourceOpts(getState()).line, 16,
+ "Line 16 is highlighted in the editor");
+
+ resumeDebuggerThenCloseAndFinish(panel);
+ }));
+
+ callInTab(tab, "runDebuggerStatement");
+ });
+}
diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_breakpoints-actual-location.js b/devtools/client/debugger/test/mochitest/browser_dbg_breakpoints-actual-location.js
new file mode 100644
index 000000000..0f880b9cc
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_breakpoints-actual-location.js
@@ -0,0 +1,55 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Bug 737803: Setting a breakpoint in a line without code should move
+ * the icon to the actual location.
+ */
+
+const TAB_URL = EXAMPLE_URL + "doc_script-switching-01.html";
+
+function test() {
+ let options = {
+ source: EXAMPLE_URL + "code_script-switching-01.js",
+ line: 1
+ };
+ initDebugger(TAB_URL, options).then(([aTab,, aPanel]) => {
+ const gTab = aTab;
+ const gPanel = aPanel;
+ const gDebugger = gPanel.panelWin;
+ const gEditor = gDebugger.DebuggerView.editor;
+ const gSources = gDebugger.DebuggerView.Sources;
+ const gController = gDebugger.DebuggerController;
+ const constants = gDebugger.require("./content/constants");
+ const queries = gDebugger.require("./content/queries");
+ const actions = bindActionCreators(gPanel);
+
+ Task.spawn(function* () {
+ yield waitForSourceAndCaretAndScopes(gPanel, "-02.js", 1);
+
+ is(queries.getBreakpoints(gController.getState()).length, 0,
+ "There are no breakpoints in the editor");
+
+ const response = yield actions.addBreakpoint({
+ actor: gSources.selectedValue, line: 4
+ });
+
+ ok(response.actualLocation, "has an actualLocation");
+ is(response.actualLocation.line, 6, "moved to line 6");
+
+ is(queries.getBreakpoints(gController.getState()).length, 1,
+ "There is only one breakpoint in the editor");
+
+ ok(!queries.getBreakpoint(gController.getState(), { actor: gSources.selectedValue, line: 4 }),
+ "There isn't any breakpoint added on an invalid line.");
+ ok(queries.getBreakpoint(gController.getState(), { actor: gSources.selectedValue, line: 6 }),
+ "There isn't any breakpoint added on an invalid line.");
+
+ resumeDebuggerThenCloseAndFinish(gPanel);
+ });
+
+ callInTab(gTab, "firstCall");
+ });
+}
diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_breakpoints-actual-location2.js b/devtools/client/debugger/test/mochitest/browser_dbg_breakpoints-actual-location2.js
new file mode 100644
index 000000000..16082d2cc
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_breakpoints-actual-location2.js
@@ -0,0 +1,88 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Bug 1008372: Setting a breakpoint in a line without code should move
+ * the icon to the actual location, and if a breakpoint already exists
+ * on the new location don't duplicate
+ */
+
+const TAB_URL = EXAMPLE_URL + "doc_breakpoint-move.html";
+
+function test() {
+ let options = {
+ source: TAB_URL,
+ line: 1
+ };
+ initDebugger(TAB_URL, options).then(([aTab,, aPanel]) => {
+ const gTab = aTab;
+ const gPanel = aPanel;
+ const gDebugger = gPanel.panelWin;
+ const gEditor = gDebugger.DebuggerView.editor;
+ const gSources = gDebugger.DebuggerView.Sources;
+ const gController = gDebugger.DebuggerController;
+ const actions = bindActionCreators(gPanel);
+ const constants = gDebugger.require("./content/constants");
+ const queries = gDebugger.require("./content/queries");
+
+ function resumeAndTestBreakpoint(line) {
+ return Task.spawn(function* () {
+ let event = waitForDebuggerEvents(gPanel, gDebugger.EVENTS.FETCHED_SCOPES);
+ doResume(gPanel);
+ yield event;
+ testBreakpoint(line);
+ });
+ }
+
+ function testBreakpoint(line) {
+ let bp = gSources._selectedBreakpoint;
+ ok(bp, "There should be a selected breakpoint on line " + line);
+ is(bp.location.line, line,
+ "The breakpoint on line " + line + " was not hit");
+ }
+
+ Task.spawn(function* () {
+ let onCaretUpdated = waitForCaretAndScopes(gPanel, 16);
+ callInTab(gTab, "ermahgerd");
+ yield onCaretUpdated;
+
+ is(queries.getBreakpoints(gController.getState()).length, 0,
+ "There are no breakpoints in the editor");
+
+ yield actions.addBreakpoint({
+ actor: gSources.selectedValue,
+ line: 19
+ });
+ yield actions.addBreakpoint({
+ actor: gSources.selectedValue,
+ line: 20
+ });
+
+ const response = yield actions.addBreakpoint({
+ actor: gSources.selectedValue,
+ line: 17
+ });
+
+ is(response.actualLocation.line, 19,
+ "Breakpoint client line is new.");
+
+ yield resumeAndTestBreakpoint(19);
+
+ yield actions.removeBreakpoint({
+ actor: gSources.selectedValue,
+ line: 19
+ });
+
+ yield resumeAndTestBreakpoint(20);
+ yield doResume(gPanel);
+
+ callInTab(gTab, "ermahgerd");
+ yield waitForDebuggerEvents(gPanel, gDebugger.EVENTS.FETCHED_SCOPES);
+
+ yield resumeAndTestBreakpoint(20);
+ resumeDebuggerThenCloseAndFinish(gPanel);
+ });
+ });
+}
diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_breakpoints-break-on-last-line-of-script-on-reload.js b/devtools/client/debugger/test/mochitest/browser_dbg_breakpoints-break-on-last-line-of-script-on-reload.js
new file mode 100644
index 000000000..124f8b1c2
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_breakpoints-break-on-last-line-of-script-on-reload.js
@@ -0,0 +1,116 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Bug 978019: Setting a breakpoint on the last line of a Debugger.Script and
+ * reloading should still hit the breakpoint.
+ */
+
+const TAB_URL = EXAMPLE_URL + "doc_breakpoints-break-on-last-line-of-script-on-reload.html";
+const CODE_URL = EXAMPLE_URL + "code_breakpoints-break-on-last-line-of-script-on-reload.js";
+
+function test() {
+ // Debug test slaves are a bit slow at this test.
+ requestLongerTimeout(2);
+
+ let gPanel, gDebugger, gThreadClient, gEvents, gSources;
+
+ const options = {
+ source: CODE_URL,
+ line: 1
+ };
+ initDebugger(TAB_URL, options).then(([aTab,, aPanel]) => {
+ gPanel = aPanel;
+ gDebugger = gPanel.panelWin;
+ gThreadClient = gDebugger.gThreadClient;
+ gEvents = gDebugger.EVENTS;
+ gSources = gDebugger.DebuggerView.Sources;
+ const actions = bindActionCreators(gPanel);
+
+ Task.spawn(function* () {
+ try {
+
+ // Refresh and hit the debugger statement before the location we want to
+ // set our breakpoints. We have to pause before the breakpoint locations
+ // so that GC doesn't get a chance to kick in and collect the IIFE's
+ // script, which would causes us to receive a 'noScript' error from the
+ // server when we try to set the breakpoints.
+ const [paused, ] = yield promise.all([
+ waitForThreadEvents(gPanel, "paused"),
+ reloadActiveTab(gPanel, gEvents.SOURCE_SHOWN),
+ ]);
+
+ is(paused.why.type, "debuggerStatement");
+
+ // Set our breakpoints.
+ const sourceActor = getSourceActor(gSources, CODE_URL);
+ yield promise.all([
+ actions.addBreakpoint({
+ actor: sourceActor,
+ line: 3
+ }),
+ actions.addBreakpoint({
+ actor: sourceActor,
+ line: 4
+ }),
+ actions.addBreakpoint({
+ actor: sourceActor,
+ line: 5
+ })
+ ]);
+
+ // Refresh and hit the debugger statement again.
+ yield promise.all([
+ reloadActiveTab(gPanel, gEvents.SOURCE_SHOWN),
+ waitForCaretAndScopes(gPanel, 1)
+ ]);
+
+ // And we should hit the breakpoints as we resume.
+ yield promise.all([
+ doResume(gPanel),
+ waitForCaretAndScopes(gPanel, 3)
+ ]);
+ yield promise.all([
+ doResume(gPanel),
+ waitForCaretAndScopes(gPanel, 4)
+ ]);
+ yield promise.all([
+ doResume(gPanel),
+ waitForCaretAndScopes(gPanel, 5)
+ ]);
+
+ // Clean up the breakpoints.
+ yield promise.all([
+ actions.removeBreakpoint({ actor: sourceActor, line: 3 }),
+ actions.removeBreakpoint({ actor: sourceActor, line: 4 }),
+ actions.removeBreakpoint({ actor: sourceActor, line: 5 })
+ ]);
+
+ yield resumeDebuggerThenCloseAndFinish(gPanel);
+
+ } catch (e) {
+ DevToolsUtils.reportException(
+ "browser_dbg_breakpoints-break-on-last-line-of-script-on-reload.js",
+ e
+ );
+ ok(false);
+ }
+ });
+ });
+
+ function setBreakpoint(location) {
+ let item = gSources.getItemByValue(getSourceActor(gSources, location.url));
+ let source = gThreadClient.source(item.attachment.source);
+
+ let deferred = promise.defer();
+ source.setBreakpoint(location, ({ error, message }, bpClient) => {
+ if (error) {
+ deferred.reject(error + ": " + message);
+ }
+ deferred.resolve(bpClient);
+ });
+ return deferred.promise;
+ }
+}
diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_breakpoints-button-01.js b/devtools/client/debugger/test/mochitest/browser_dbg_breakpoints-button-01.js
new file mode 100644
index 000000000..25bf1fe06
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_breakpoints-button-01.js
@@ -0,0 +1,55 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Test if the breakpoints toggle button works as advertised.
+ */
+
+const TAB_URL = EXAMPLE_URL + "doc_script-switching-01.html";
+
+function test() {
+ let options = {
+ source: EXAMPLE_URL + "code_script-switching-01.js",
+ line: 1
+ };
+ initDebugger(TAB_URL, options).then(([aTab,, aPanel]) => {
+ const gTab = aTab;
+ const gPanel = aPanel;
+ const gDebugger = gPanel.panelWin;
+ const gSources = gDebugger.DebuggerView.Sources;
+ const actions = bindActionCreators(gPanel);
+ const getState = gDebugger.DebuggerController.getState;
+
+ function checkBreakpointsDisabled(isDisabled, total = 3) {
+ let breakpoints = gDebugger.queries.getBreakpoints(getState());
+
+ is(breakpoints.length, total,
+ "Breakpoints should still be set.");
+ is(breakpoints.filter(bp => bp.disabled === isDisabled).length, total,
+ "Breakpoints should be " + (isDisabled ? "disabled" : "enabled") + ".");
+ }
+
+ Task.spawn(function* () {
+ yield actions.addBreakpoint({ actor: gSources.values[0], line: 5 });
+ yield actions.addBreakpoint({ actor: gSources.values[1], line: 6 });
+ yield actions.addBreakpoint({ actor: gSources.values[1], line: 7 });
+ yield ensureThreadClientState(gPanel, "resumed");
+
+ gSources.toggleBreakpoints();
+ yield waitForDispatch(gPanel, gDebugger.constants.REMOVE_BREAKPOINT, 3);
+ checkBreakpointsDisabled(true);
+
+ const finished = waitForDebuggerEvents(gPanel, gDebugger.EVENTS.BREAKPOINT_ADDED, 3);
+ gSources.toggleBreakpoints();
+ yield waitForDispatch(gPanel, gDebugger.constants.ADD_BREAKPOINT, 3);
+ checkBreakpointsDisabled(false);
+
+ if (gDebugger.gThreadClient.state !== "attached") {
+ yield waitForThreadEvents(gPanel, "resumed");
+ }
+ closeDebuggerAndFinish(gPanel);
+ });
+ });
+}
diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_breakpoints-button-02.js b/devtools/client/debugger/test/mochitest/browser_dbg_breakpoints-button-02.js
new file mode 100644
index 000000000..6a763bf7e
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_breakpoints-button-02.js
@@ -0,0 +1,64 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Test if the breakpoints toggle button works as advertised when there are
+ * some breakpoints already disabled.
+ */
+
+const TAB_URL = EXAMPLE_URL + "doc_script-switching-01.html";
+
+function test() {
+ let options = {
+ source: EXAMPLE_URL + "code_script-switching-01.js",
+ line: 1
+ };
+ initDebugger(TAB_URL, options).then(([aTab,, aPanel]) => {
+ const gTab = aTab;
+ const gPanel = aPanel;
+ const gDebugger = gPanel.panelWin;
+ const gSources = gDebugger.DebuggerView.Sources;
+ const actions = bindActionCreators(gPanel);
+ const getState = gDebugger.DebuggerController.getState;
+
+ function checkBreakpointsDisabled(isDisabled, total = 3) {
+ let breakpoints = gDebugger.queries.getBreakpoints(getState());
+
+ is(breakpoints.length, total,
+ "Breakpoints should still be set.");
+ is(breakpoints.filter(bp => bp.disabled === isDisabled).length, total,
+ "Breakpoints should be " + (isDisabled ? "disabled" : "enabled") + ".");
+ }
+
+ Task.spawn(function* () {
+ yield promise.all([
+ actions.addBreakpoint({ actor: gSources.values[0], line: 5 }),
+ actions.addBreakpoint({ actor: gSources.values[1], line: 6 }),
+ actions.addBreakpoint({ actor: gSources.values[1], line: 7 })
+ ]);
+ if (gDebugger.gThreadClient.state !== "attached") {
+ yield waitForThreadEvents(gPanel, "resumed");
+ }
+
+ yield promise.all([
+ actions.disableBreakpoint({ actor: gSources.values[0], line: 5 }),
+ actions.disableBreakpoint({ actor: gSources.values[1], line: 6 })
+ ]);
+
+ gSources.toggleBreakpoints();
+ yield waitForDispatch(gPanel, gDebugger.constants.REMOVE_BREAKPOINT, 1);
+ checkBreakpointsDisabled(true);
+
+ gSources.toggleBreakpoints();
+ yield waitForDispatch(gPanel, gDebugger.constants.ADD_BREAKPOINT, 3);
+ checkBreakpointsDisabled(false);
+
+ if (gDebugger.gThreadClient.state !== "attached") {
+ yield waitForThreadEvents(gPanel, "resumed");
+ }
+ closeDebuggerAndFinish(gPanel);
+ });
+ });
+}
diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_breakpoints-condition-thrown-message.js b/devtools/client/debugger/test/mochitest/browser_dbg_breakpoints-condition-thrown-message.js
new file mode 100644
index 000000000..cefd429d2
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_breakpoints-condition-thrown-message.js
@@ -0,0 +1,107 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Make sure that the message which breakpoint condition throws
+ * could be displayed on UI correctly
+ */
+
+const TAB_URL = EXAMPLE_URL + "doc_conditional-breakpoints.html";
+
+function test() {
+ let options = {
+ source: TAB_URL,
+ line: 1
+ };
+ initDebugger(TAB_URL, options).then(([aTab,, aPanel]) => {
+ const gTab = aTab;
+ const gPanel = aPanel;
+ const gDebugger = gPanel.panelWin;
+ const gEditor = gDebugger.DebuggerView.editor;
+ const gSources = gDebugger.DebuggerView.Sources;
+ const actions = bindActionCreators(gPanel);
+ const getState = gDebugger.DebuggerController.getState;
+
+ function initialCheck(aCaretLine) {
+ let bp = gDebugger.queries.getBreakpoint(getState(),
+ { actor: gSources.values[0], line: aCaretLine });
+ ok(bp, "There should be a breakpoint on line " + aCaretLine);
+
+ let attachment = gSources._getBreakpoint(bp).attachment;
+ ok(attachment,
+ "There should be a breakpoint on line " + aCaretLine + " in the sources pane.");
+
+ let thrownNode = attachment.view.container.querySelector(".dbg-breakpoint-condition-thrown-message");
+ ok(thrownNode,
+ "The breakpoint item should contain a thrown message node.");
+
+ ok(!attachment.view.container.classList.contains("dbg-breakpoint-condition-thrown"),
+ "The thrown message on line " + aCaretLine + " should be hidden when condition has not been evaluated.");
+ }
+
+ function resumeAndTestThrownMessage(line) {
+ doResume(gPanel);
+
+ return waitForCaretUpdated(gPanel, line).then(() => {
+ // Test that the thrown message is correctly shown.
+ let bp = gDebugger.queries.getBreakpoint(
+ getState(),
+ { actor: gSources.values[0], line: line }
+ );
+ let attachment = gSources._getBreakpoint(bp).attachment;
+ ok(attachment.view.container.classList.contains("dbg-breakpoint-condition-thrown"),
+ "Message on line " + line + " should be shown when condition throws.");
+ });
+ }
+
+ function resumeAndTestNoThrownMessage(line) {
+ doResume(gPanel);
+
+ return waitForCaretUpdated(gPanel, line).then(() => {
+ // test that the thrown message is correctly shown
+ let bp = gDebugger.queries.getBreakpoint(
+ getState(),
+ { actor: gSources.values[0], line: line }
+ );
+ let attachment = gSources._getBreakpoint(bp).attachment;
+ ok(!attachment.view.container.classList.contains("dbg-breakpoint-condition-thrown"),
+ "Message on line " + line + " should be hidden if condition doesn't throw.");
+ });
+ }
+
+ Task.spawn(function* () {
+ let onCaretUpdated = waitForCaretAndScopes(gPanel, 17);
+ callInTab(gTab, "ermahgerd");
+ yield onCaretUpdated;
+
+ yield actions.addBreakpoint({ actor: gSources.selectedValue, line: 18 }, " 1afff");
+ // Close the popup because a SET_BREAKPOINT_CONDITION action is
+ // fired when it's closed, and it sets it on the currently
+ // selected breakpoint and we want to make sure it uses the
+ // current breakpoint. This isn't a problem outside of tests
+ // because any UI interaction will close the popup before the
+ // new breakpoint is added.
+ gSources._hideConditionalPopup();
+ initialCheck(18);
+
+ yield actions.addBreakpoint({ actor: gSources.selectedValue, line: 19 }, "true");
+ gSources._hideConditionalPopup();
+ initialCheck(19);
+
+ yield actions.addBreakpoint({ actor: gSources.selectedValue, line: 20 }, "false");
+ gSources._hideConditionalPopup();
+ initialCheck(20);
+
+ yield actions.addBreakpoint({ actor: gSources.selectedValue, line: 22 }, "randomVar");
+ gSources._hideConditionalPopup();
+ initialCheck(22);
+
+ yield resumeAndTestThrownMessage(18);
+ yield resumeAndTestNoThrownMessage(19);
+ yield resumeAndTestThrownMessage(22);
+ resumeDebuggerThenCloseAndFinish(gPanel);
+ });
+ });
+}
diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_breakpoints-contextmenu-add.js b/devtools/client/debugger/test/mochitest/browser_dbg_breakpoints-contextmenu-add.js
new file mode 100644
index 000000000..2b50d53aa
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_breakpoints-contextmenu-add.js
@@ -0,0 +1,84 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Test adding breakpoints from the source editor context menu
+ */
+
+const TAB_URL = EXAMPLE_URL + "doc_script-switching-01.html";
+
+function test() {
+ let options = {
+ source: EXAMPLE_URL + "code_script-switching-01.js",
+ line: 1
+ };
+ initDebugger(TAB_URL, options).then(([aTab,, aPanel]) => {
+ const gTab = aTab;
+ const gPanel = aPanel;
+ const gDebugger = gPanel.panelWin;
+ const gEditor = gDebugger.DebuggerView.editor;
+ const gSources = gDebugger.DebuggerView.Sources;
+ const gContextMenu = gDebugger.document.getElementById("sourceEditorContextMenu");
+ const queries = gDebugger.require("./content/queries");
+ const actions = bindActionCreators(gPanel);
+ const getState = gDebugger.DebuggerController.getState;
+
+ Task.spawn(function* () {
+ yield waitForSourceAndCaretAndScopes(gPanel, "-02.js", 1);
+
+ is(gDebugger.gThreadClient.state, "paused",
+ "Should only be getting stack frames while paused.");
+ is(queries.getSourceCount(getState()), 2,
+ "Found the expected number of sources.");
+ isnot(gEditor.getText().indexOf("debugger"), -1,
+ "The correct source was loaded initially.");
+ is(gSources.selectedValue, gSources.values[1],
+ "The correct source is selected.");
+
+ ok(gContextMenu,
+ "The source editor's context menupopup is available.");
+
+ gEditor.focus();
+ gEditor.setSelection({ line: 1, ch: 0 }, { line: 1, ch: 10 });
+
+ gContextMenu.openPopup(gEditor.container, "overlap", 0, 0, true, false);
+ gEditor.emit("gutterClick", 6, 2);
+
+ yield once(gContextMenu, "popupshown");
+ is(queries.getBreakpoints(getState()).length, 0, "no breakpoints added");
+
+ let cmd = gContextMenu.querySelector("menuitem[command=addBreakpointCommand]");
+ EventUtils.synthesizeMouseAtCenter(cmd, {}, gDebugger);
+ yield waitForDispatch(gPanel, gDebugger.constants.ADD_BREAKPOINT);
+
+ is(queries.getBreakpoints(getState()).length, 1,
+ "1 breakpoint correctly added");
+ ok(queries.getBreakpoint(getState(),
+ { actor: gSources.values[1], line: 7 }),
+ "Breakpoint on line 7 exists");
+
+ gContextMenu.openPopup(gEditor.container, "overlap", 0, 0, true, false);
+ gEditor.emit("gutterClick", 7, 2);
+
+ yield once(gContextMenu, "popupshown");
+ is(queries.getBreakpoints(getState()).length, 1,
+ "1 breakpoint correctly added");
+
+ cmd = gContextMenu.querySelector("menuitem[command=addConditionalBreakpointCommand]");
+ EventUtils.synthesizeMouseAtCenter(cmd, {}, gDebugger);
+ yield waitForDispatch(gPanel, gDebugger.constants.ADD_BREAKPOINT);
+
+ is(queries.getBreakpoints(getState()).length, 2,
+ "2 breakpoints correctly added");
+ ok(queries.getBreakpoint(getState(),
+ { actor: gSources.values[1], line: 8 }),
+ "Breakpoint on line 8 exists");
+
+ resumeDebuggerThenCloseAndFinish(gPanel);
+ });
+
+ callInTab(gTab, "firstCall");
+ });
+}
diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_breakpoints-contextmenu.js b/devtools/client/debugger/test/mochitest/browser_dbg_breakpoints-contextmenu.js
new file mode 100644
index 000000000..913d32073
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_breakpoints-contextmenu.js
@@ -0,0 +1,252 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Test if the context menu associated with each breakpoint does what it should.
+ */
+
+const TAB_URL = EXAMPLE_URL + "doc_script-switching-01.html";
+
+function test() {
+ // Debug test slaves are a bit slow at this test.
+ requestLongerTimeout(2);
+
+ Task.spawn(function* () {
+ const options = {
+ source: EXAMPLE_URL + "code_script-switching-01.js",
+ line: 1
+ };
+ const [gTab,, gPanel ] = yield initDebugger(TAB_URL, options);
+ const gDebugger = gPanel.panelWin;
+ const gSources = gDebugger.DebuggerView.Sources;
+ const queries = gDebugger.require("./content/queries");
+ const actions = bindActionCreators(gPanel);
+ const getState = gDebugger.DebuggerController.getState;
+
+ const addBreakpoints = Task.async(function* () {
+ yield actions.addBreakpoint({ actor: gSources.values[0], line: 5 });
+ yield actions.addBreakpoint({ actor: gSources.values[1], line: 6 });
+ yield actions.addBreakpoint({ actor: gSources.values[1], line: 7 });
+ yield actions.addBreakpoint({ actor: gSources.values[1], line: 8 });
+ yield actions.addBreakpoint({ actor: gSources.values[1], line: 9 });
+ yield ensureThreadClientState(gPanel, "resumed");
+ gSources.highlightBreakpoint({ actor: gSources.values[1], line: 9 });
+ });
+
+ const pauseAndCheck = Task.async(function* () {
+ let source = queries.getSelectedSource(getState());
+ is(source.url, EXAMPLE_URL + "code_script-switching-02.js",
+ "The currently selected source is incorrect (1).");
+ is(gSources.selectedIndex, 1,
+ "The currently selected source is incorrect (2).");
+ ok(isCaretPos(gPanel, 9),
+ "The editor location is correct before pausing.");
+
+ generateMouseClickInTab(gTab, "content.document.querySelector('button')");
+
+ return waitForSourceAndCaretAndScopes(gPanel, "-01.js", 5).then(() => {
+ let source = queries.getSelectedSource(getState());
+ is(source.url, EXAMPLE_URL + "code_script-switching-01.js",
+ "The currently selected source is incorrect (3).");
+ is(gSources.selectedIndex, 0,
+ "The currently selected source is incorrect (4).");
+ ok(isCaretPos(gPanel, 5),
+ "The editor location is correct after pausing.");
+ });
+ });
+
+ let initialChecks = Task.async(function* () {
+ for (let bp of queries.getBreakpoints(getState())) {
+ ok(bp.actor, "All breakpoint items should have an actor");
+ ok(!bp.disabled, "All breakpoints should initially be enabled.");
+
+ let prefix = "bp-cMenu-"; // "breakpoints context menu"
+ let identifier = queries.makeLocationId(bp.location);
+ let enableSelfId = prefix + "enableSelf-" + identifier + "-menuitem";
+ let disableSelfId = prefix + "disableSelf-" + identifier + "-menuitem";
+
+ // Check to make sure that only the bp context menu is shown when right clicking
+ // this node (Bug 1159276).
+ let breakpointItem = gSources._getBreakpoint(bp);
+ let menu = gDebugger.document.getElementById("bp-mPop-" + identifier);
+ let contextMenuShown = once(gDebugger.document, "popupshown");
+ EventUtils.synthesizeMouseAtCenter(breakpointItem.prebuiltNode, {type: "contextmenu", button: 2}, gDebugger);
+ let event = yield contextMenuShown;
+ is(event.originalTarget.id, menu.id, "The correct context menu was shown");
+ let contextMenuHidden = once(gDebugger.document, "popuphidden");
+ menu.hidePopup();
+ yield contextMenuHidden;
+
+ is(gDebugger.document.getElementById(enableSelfId).getAttribute("hidden"), "true",
+ "The 'Enable breakpoint' context menu item should initially be hidden'.");
+ ok(!gDebugger.document.getElementById(disableSelfId).hasAttribute("hidden"),
+ "The 'Disable breakpoint' context menu item should initially not be hidden'.");
+
+ is(breakpointItem.attachment.view.checkbox.getAttribute("checked"), "true",
+ "All breakpoints should initially have a checked checkbox.");
+ }
+ });
+
+ const checkBreakpointToggleSelf = Task.async(function* (index) {
+ EventUtils.sendMouseEvent({ type: "click" },
+ gDebugger.document.querySelectorAll(".dbg-breakpoint")[index],
+ gDebugger);
+
+ let selectedBreakpoint = gSources._selectedBreakpoint;
+ let selectedBreakpointItem = gSources._getBreakpoint(selectedBreakpoint);
+
+ ok(selectedBreakpoint.actor,
+ "Selected breakpoint should have an actor.");
+ ok(!selectedBreakpoint.disabled,
+ "The breakpoint should not be disabled yet (" + index + ").");
+
+ let prefix = "bp-cMenu-"; // "breakpoints context menu"
+ let identifier = queries.makeLocationId(selectedBreakpoint.location);
+ let enableSelfId = prefix + "enableSelf-" + identifier + "-menuitem";
+ let disableSelfId = prefix + "disableSelf-" + identifier + "-menuitem";
+
+ is(gDebugger.document.getElementById(enableSelfId).getAttribute("hidden"), "true",
+ "The 'Enable breakpoint' context menu item should be hidden'.");
+ ok(!gDebugger.document.getElementById(disableSelfId).hasAttribute("hidden"),
+ "The 'Disable breakpoint' context menu item should not be hidden'.");
+
+ ok(isCaretPos(gPanel, selectedBreakpoint.location.line),
+ "The source editor caret position was incorrect (" + index + ").");
+
+ // Test disabling this breakpoint.
+ gSources._onDisableSelf(selectedBreakpoint.location);
+ yield waitForDispatch(gPanel, gDebugger.constants.REMOVE_BREAKPOINT);
+
+ ok(!!queries.getBreakpoint(getState(), selectedBreakpoint.location).disabled,
+ "The breakpoint should be disabled.");
+
+ ok(!gDebugger.document.getElementById(enableSelfId).hasAttribute("hidden"),
+ "The 'Enable breakpoint' context menu item should not be hidden'.");
+ is(gDebugger.document.getElementById(disableSelfId).getAttribute("hidden"), "true",
+ "The 'Disable breakpoint' context menu item should be hidden'.");
+ ok(!selectedBreakpointItem.attachment.view.checkbox.hasAttribute("checked"),
+ "The breakpoint should now be unchecked.");
+
+ gSources._onEnableSelf(selectedBreakpoint.location);
+ yield waitForDispatch(gPanel, gDebugger.constants.ADD_BREAKPOINT);
+
+ ok(!queries.getBreakpoint(getState(), selectedBreakpoint.location).disabled,
+ "The breakpoint should be enabled.");
+ is(gDebugger.document.getElementById(enableSelfId).getAttribute("hidden"), "true",
+ "The 'Enable breakpoint' context menu item should be hidden'.");
+ ok(!gDebugger.document.getElementById(disableSelfId).hasAttribute("hidden"),
+ "The 'Disable breakpoint' context menu item should not be hidden'.");
+ ok(selectedBreakpointItem.attachment.view.checkbox.hasAttribute("checked"),
+ "The breakpoint should now be checked.");
+ });
+
+ const checkBreakpointToggleOthers = Task.async(function* (index) {
+ EventUtils.sendMouseEvent(
+ { type: "click" },
+ gDebugger.document.querySelectorAll(".dbg-breakpoint")[index],
+ gDebugger
+ );
+
+ // Test disabling other breakpoints.
+ disableOthers();
+ yield waitForDispatch(gPanel, gDebugger.constants.REMOVE_BREAKPOINT, 4);
+
+ let selectedBreakpoint = queries.getBreakpoint(getState(), gSources._selectedBreakpoint.location);
+
+ ok(selectedBreakpoint.actor,
+ "There should be a breakpoint actor.");
+ ok(!selectedBreakpoint.disabled,
+ "The targetted breakpoint should not have been disabled (" + index + ").");
+
+ for (let bp of queries.getBreakpoints(getState())) {
+ if (bp !== selectedBreakpoint) {
+ ok(bp.disabled,
+ "Non-targetted breakpoints should have been disabled.");
+ }
+ }
+
+ // Test re-enabling other breakpoints.
+ enableOthers();
+ yield waitForDispatch(gPanel, gDebugger.constants.ADD_BREAKPOINT, 4);
+ for (let bp of queries.getBreakpoints(getState())) {
+ ok(!bp.disabled, "All breakpoints should be enabled.");
+ }
+
+ // Test disabling all breakpoints.
+ disableAll();
+ yield waitForDispatch(gPanel, gDebugger.constants.REMOVE_BREAKPOINT, 5);
+ for (let bp of queries.getBreakpoints(getState())) {
+ ok(!!bp.disabled, "All breakpoints should be disabled.");
+ }
+
+ // Test re-enabling all breakpoints.
+ enableAll();
+ yield waitForDispatch(gPanel, gDebugger.constants.ADD_BREAKPOINT, 5);
+ for (let bp of queries.getBreakpoints(getState())) {
+ ok(!bp.disabled, "All breakpoints should be enabled.");
+ }
+ });
+
+ const testDeleteAll = Task.async(function* () {
+ // Test deleting all breakpoints.
+ deleteAll();
+ yield waitForDispatch(gPanel, gDebugger.constants.REMOVE_BREAKPOINT, 5);
+
+ ok(!gSources._selectedBreakpoint,
+ "There should be no breakpoint available after removing all breakpoints.");
+
+ for (let bp of queries.getBreakpoints(getState())) {
+ ok(false, "It's a trap!");
+ }
+ });
+
+ function disableOthers() {
+ gSources._onDisableOthers(gSources._selectedBreakpoint.location);
+ }
+ function enableOthers() {
+ gSources._onEnableOthers(gSources._selectedBreakpoint.location);
+ }
+ function disableAll() {
+ gSources._onDisableAll();
+ }
+ function enableAll() {
+ gSources._onEnableAll();
+ }
+ function deleteAll() {
+ gSources._onDeleteAll();
+ }
+
+ yield addBreakpoints();
+ yield initialChecks();
+ yield checkBreakpointToggleSelf(0);
+ yield checkBreakpointToggleOthers(0);
+ yield checkBreakpointToggleSelf(1);
+ yield checkBreakpointToggleOthers(1);
+ yield checkBreakpointToggleSelf(2);
+ yield checkBreakpointToggleOthers(2);
+ yield checkBreakpointToggleSelf(3);
+ yield checkBreakpointToggleOthers(3);
+ yield checkBreakpointToggleSelf(4);
+ yield checkBreakpointToggleOthers(4);
+ yield testDeleteAll();
+
+ yield addBreakpoints();
+ yield initialChecks();
+ yield pauseAndCheck();
+ yield checkBreakpointToggleSelf(0);
+ yield checkBreakpointToggleOthers(0);
+ yield checkBreakpointToggleSelf(1);
+ yield checkBreakpointToggleOthers(1);
+ yield checkBreakpointToggleSelf(2);
+ yield checkBreakpointToggleOthers(2);
+ yield checkBreakpointToggleSelf(3);
+ yield checkBreakpointToggleOthers(3);
+ yield checkBreakpointToggleSelf(4);
+ yield checkBreakpointToggleOthers(4);
+ yield testDeleteAll();
+
+ resumeDebuggerThenCloseAndFinish(gPanel);
+ });
+}
diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_breakpoints-disabled-reload.js b/devtools/client/debugger/test/mochitest/browser_dbg_breakpoints-disabled-reload.js
new file mode 100644
index 000000000..1caf4994f
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_breakpoints-disabled-reload.js
@@ -0,0 +1,124 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Test that disabled breakpoints survive target navigation.
+ */
+
+const TAB_URL = EXAMPLE_URL + "doc_script-switching-01.html";
+
+function test() {
+ let options = {
+ source: EXAMPLE_URL + "code_script-switching-01.js",
+ line: 1
+ };
+ initDebugger(TAB_URL, options).then(([aTab,, aPanel]) => {
+ const gPanel = aPanel;
+ const gTab = aTab;
+ const gDebugger = gPanel.panelWin;
+ const gEvents = gDebugger.EVENTS;
+ const gEditor = gDebugger.DebuggerView.editor;
+ const gSources = gDebugger.DebuggerView.Sources;
+ const queries = gDebugger.require("./content/queries");
+ const actions = bindActionCreators(gPanel);
+ const getState = gDebugger.DebuggerController.getState;
+ let gBreakpointLocation;
+
+ Task.spawn(function* () {
+ gBreakpointLocation = { actor: getSourceActor(gSources, EXAMPLE_URL + "code_script-switching-01.js"),
+ line: 5 };
+
+ yield actions.addBreakpoint(gBreakpointLocation);
+
+ yield ensureThreadClientState(gPanel, "resumed");
+ yield testWhenBreakpointEnabledAndFirstSourceShown();
+
+ gSources._preferredSourceURL = EXAMPLE_URL + "code_script-switching-02.js";
+ yield reloadActiveTab(gPanel, gEvents.SOURCE_SHOWN);
+ yield testWhenBreakpointEnabledAndSecondSourceShown();
+
+ yield actions.disableBreakpoint(gBreakpointLocation);
+ yield reloadActiveTab(gPanel, gEvents.SOURCE_SHOWN);
+
+ yield testWhenBreakpointDisabledAndSecondSourceShown();
+
+ yield actions.enableBreakpoint(gBreakpointLocation);
+ yield reloadActiveTab(gPanel, gEvents.SOURCE_SHOWN);
+ yield testWhenBreakpointEnabledAndSecondSourceShown();
+
+ yield resumeDebuggerThenCloseAndFinish(gPanel);
+ });
+
+ function verifyView({ disabled }) {
+ return Task.spawn(function* () {
+ // It takes a tick for the checkbox in the SideMenuWidget and the
+ // gutter in the editor to get updated.
+ yield waitForTick();
+
+ let breakpoint = queries.getBreakpoint(getState(), gBreakpointLocation);
+ let breakpointItem = gSources._getBreakpoint(breakpoint);
+ is(!!breakpoint.disabled, disabled,
+ "The selected breakpoint state was correct.");
+
+ is(breakpointItem.attachment.view.checkbox.hasAttribute("checked"), !disabled,
+ "The selected breakpoint's checkbox state was correct.");
+ });
+ }
+
+ // All the following executeSoon()'s are required to spin the event loop
+ // before causing the debuggee to pause, to allow functions to yield first.
+
+ function testWhenBreakpointEnabledAndFirstSourceShown() {
+ return Task.spawn(function* () {
+ yield ensureSourceIs(gPanel, "-01.js");
+ yield verifyView({ disabled: false });
+
+ callInTab(gTab, "firstCall");
+ yield waitForDebuggerEvents(gPanel, gEvents.FETCHED_SCOPES);
+ yield ensureSourceIs(gPanel, "-01.js");
+ yield ensureCaretAt(gPanel, 5);
+ yield verifyView({ disabled: false });
+
+ executeSoon(() => gDebugger.gThreadClient.resume());
+ yield waitForSourceAndCaretAndScopes(gPanel, "-02.js", 6);
+ yield verifyView({ disabled: false });
+ });
+ }
+
+ function testWhenBreakpointEnabledAndSecondSourceShown() {
+ return Task.spawn(function* () {
+ yield ensureSourceIs(gPanel, "-02.js", true);
+ yield verifyView({ disabled: false });
+
+ callInTab(gTab, "firstCall");
+ yield waitForSourceAndCaretAndScopes(gPanel, "-01.js", 1);
+ yield verifyView({ disabled: false });
+
+ executeSoon(() => gDebugger.gThreadClient.resume());
+ yield waitForSourceAndCaretAndScopes(gPanel, "-02.js", 6);
+ yield verifyView({ disabled: false });
+ });
+ }
+
+ function testWhenBreakpointDisabledAndSecondSourceShown() {
+ return Task.spawn(function* () {
+ yield ensureSourceIs(gPanel, "-02.js", true);
+ yield verifyView({ disabled: true });
+
+ callInTab(gTab, "firstCall");
+ yield waitForDebuggerEvents(gPanel, gEvents.FETCHED_SCOPES);
+ yield ensureSourceIs(gPanel, "-02.js");
+ yield ensureCaretAt(gPanel, 6);
+ yield verifyView({ disabled: true });
+
+ executeSoon(() => gDebugger.gThreadClient.resume());
+ yield waitForDebuggerEvents(gPanel, gEvents.AFTER_FRAMES_CLEARED);
+ yield ensureSourceIs(gPanel, "-02.js");
+ yield ensureCaretAt(gPanel, 6);
+ yield verifyView({ disabled: true });
+ });
+ }
+ });
+}
diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_breakpoints-editor.js b/devtools/client/debugger/test/mochitest/browser_dbg_breakpoints-editor.js
new file mode 100644
index 000000000..d5232a50d
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_breakpoints-editor.js
@@ -0,0 +1,241 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Bug 723069: Test the debugger breakpoint API and connection to the
+ * source editor.
+ */
+
+const TAB_URL = EXAMPLE_URL + "doc_script-switching-01.html";
+
+function test() {
+ let options = {
+ source: EXAMPLE_URL + "code_script-switching-01.js",
+ line: 1
+ };
+ initDebugger(TAB_URL, options).then(([aTab,, aPanel]) => {
+ const gTab = aTab;
+ const gPanel = aPanel;
+ const gDebugger = gPanel.panelWin;
+ const gEditor = gDebugger.DebuggerView.editor;
+ const gSources = gDebugger.DebuggerView.Sources;
+ const queries = gDebugger.require("./content/queries");
+ const constants = gDebugger.require("./content/constants");
+ const actions = bindActionCreators(gPanel);
+ const getState = gDebugger.DebuggerController.getState;
+
+ Task.spawn(function* () {
+ yield waitForSourceAndCaretAndScopes(gPanel, "-02.js", 1);
+
+ is(gDebugger.gThreadClient.state, "paused",
+ "Should only be getting stack frames while paused.");
+ is(queries.getSourceCount(getState()), 2,
+ "Found the expected number of sources.");
+ is(gEditor.getText().indexOf("debugger"), 166,
+ "The correct source was loaded initially.");
+ is(queries.getSelectedSource(getState()).actor, gSources.values[1],
+ "The correct source is selected.");
+
+ is(queries.getBreakpoints(getState()).length, 0,
+ "No breakpoints currently added.");
+
+ info("Add the first breakpoint.");
+ gEditor.once("breakpointAdded", onEditorBreakpointAddFirst);
+ let location = { actor: gSources.selectedValue, line: 6 };
+ yield actions.addBreakpoint(location);
+ checkFirstBreakpoint(location);
+
+ info("Remove the first breakpoint.");
+ gEditor.once("breakpointRemoved", onEditorBreakpointRemoveFirst);
+ yield actions.removeBreakpoint(location);
+ checkFirstBreakpointRemoved(location);
+ checkBackgroundBreakpoint(yield testBreakpointAddBackground());
+
+ info("Switch to the first source, which is not yet selected");
+ gEditor.once("breakpointAdded", onEditorBreakpointAddSwitch);
+ gEditor.once("change", onEditorTextChanged);
+ actions.selectSource(gSources.items[0].attachment.source);
+ yield waitForDebuggerEvents(gPanel, gDebugger.EVENTS.SOURCE_SHOWN);
+ onReadyForClick();
+ });
+
+ callInTab(gTab, "firstCall");
+
+ let breakpointsAdded = 0;
+ let breakpointsRemoved = 0;
+ let editorBreakpointChanges = 0;
+
+ function onEditorBreakpointAddFirst(aEvent, aLine) {
+ editorBreakpointChanges++;
+
+ ok(aEvent,
+ "breakpoint1 added to the editor.");
+ is(aLine, 5,
+ "Editor breakpoint line is correct.");
+
+ is(gEditor.getBreakpoints().length, 1,
+ "editor.getBreakpoints().length is correct.");
+ }
+
+ function onEditorBreakpointRemoveFirst(aEvent, aLine) {
+ editorBreakpointChanges++;
+
+ ok(aEvent,
+ "breakpoint1 removed from the editor.");
+ is(aLine, 5,
+ "Editor breakpoint line is correct.");
+
+ is(gEditor.getBreakpoints().length, 0,
+ "editor.getBreakpoints().length is correct.");
+ }
+
+ function checkFirstBreakpoint(location) {
+ breakpointsAdded++;
+ const bp = queries.getBreakpoint(getState(), location);
+
+ ok(bp,
+ "breakpoint1 exists");
+ is(bp.location.actor, queries.getSelectedSource(getState()).actor,
+ "breakpoint1 actor is correct.");
+ is(bp.location.line, 6,
+ "breakpoint1 line is correct.");
+
+ is(queries.getBreakpoints(getState()).length, 1,
+ "The list of added breakpoints holds only one breakpoint.");
+
+ is(queries.getSelectedSource(getState()).actor, gSources.values[1],
+ "The second source should be currently selected.");
+ }
+
+ function checkFirstBreakpointRemoved(location) {
+ breakpointsRemoved++;
+ const bp = queries.getBreakpoint(getState(), location);
+ ok(!bp, "breakpoint1 removed");
+ }
+
+ function testBreakpointAddBackground() {
+ is(queries.getBreakpoints(getState()).length, 0,
+ "No breakpoints currently added.");
+
+ is(gSources.values[1], gSources.selectedValue,
+ "The second source should be currently selected.");
+
+ info("Add a breakpoint to the first source, which is not selected.");
+ let location = { actor: gSources.values[0], line: 5 };
+ gEditor.on("breakpointAdded", onEditorBreakpointAddBackgroundTrap);
+ return actions.addBreakpoint(location).then(() => location);
+ }
+
+ function onEditorBreakpointAddBackgroundTrap() {
+ // Trap listener: no breakpoint must be added to the editor when a
+ // breakpoint is added to a source that is not currently selected.
+ editorBreakpointChanges++;
+ ok(false, "breakpoint2 must not be added to the editor.");
+ }
+
+ function checkBackgroundBreakpoint(location) {
+ breakpointsAdded++;
+ const bp = queries.getBreakpoint(getState(), location);
+
+ ok(bp,
+ "breakpoint2 added, client received");
+ is(bp.location.actor, gSources.values[0],
+ "breakpoint2 client url is correct.");
+ is(bp.location.line, 5,
+ "breakpoint2 client line is correct.");
+
+ ok(queries.getBreakpoint(getState(), bp.location),
+ "breakpoint2 found in the list of added breakpoints.");
+
+ is(queries.getBreakpoints(getState()).length, 1,
+ "The list of added breakpoints holds only one breakpoint.");
+
+ is(queries.getSelectedSource(getState()).actor, gSources.values[1],
+ "The second source should be currently selected.");
+
+ // Remove the trap listener.
+ gEditor.off("breakpointAdded", onEditorBreakpointAddBackgroundTrap);
+ }
+
+ function onEditorBreakpointAddSwitch(aEvent, aLine) {
+ editorBreakpointChanges++;
+
+ ok(aEvent,
+ "breakpoint2 added to the editor.");
+ is(aLine, 4,
+ "Editor breakpoint line is correct.");
+
+ is(gEditor.getBreakpoints().length, 1,
+ "editor.getBreakpoints().length is correct");
+ }
+
+ function onEditorTextChanged() {
+ // Wait for the actual text to be shown.
+ if (gEditor.getText() == gDebugger.L10N.getStr("loadingText"))
+ return void gEditor.once("change", onEditorTextChanged);
+
+ is(gEditor.getText().indexOf("debugger"), -1,
+ "The second source is no longer displayed.");
+ is(gEditor.getText().indexOf("firstCall"), 118,
+ "The first source is displayed.");
+
+ is(gSources.values[0], gSources.selectedValue,
+ "The first source should be currently selected.");
+ }
+
+ function onReadyForClick() {
+ info("Remove the second breakpoint using the mouse.");
+ gEditor.once("breakpointRemoved", onEditorBreakpointRemoveSecond);
+
+ let iframe = gEditor.container;
+ let testWin = iframe.ownerDocument.defaultView;
+
+ // Flush the layout for the iframe.
+ info("rect " + iframe.contentDocument.documentElement.getBoundingClientRect());
+
+ let utils = testWin
+ .QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIDOMWindowUtils);
+
+ let coords = gEditor.getCoordsFromPosition({ line: 4, ch: 0 });
+ let rect = iframe.getBoundingClientRect();
+ let left = rect.left + 10;
+ let top = rect.top + coords.top + 4;
+ utils.sendMouseEventToWindow("mousedown", left, top, 0, 1, 0, false, 0, 0);
+ utils.sendMouseEventToWindow("mouseup", left, top, 0, 1, 0, false, 0, 0);
+ }
+
+ function onEditorBreakpointRemoveSecond(aEvent, aLine) {
+ editorBreakpointChanges++;
+
+ ok(aEvent,
+ "breakpoint2 removed from the editor.");
+ is(aLine, 4,
+ "Editor breakpoint line is correct.");
+
+ is(gEditor.getBreakpoints().length, 0,
+ "editor.getBreakpoints().length is correct.");
+
+ waitForDebuggerEvents(gPanel, gDebugger.EVENTS.AFTER_FRAMES_CLEARED).then(() => {
+ finalCheck();
+ closeDebuggerAndFinish(gPanel);
+ });
+
+ gDebugger.gThreadClient.resume();
+ }
+
+ function finalCheck() {
+ is(queries.getBreakpoints(getState()).length, 0,
+ "No breakpoints currently added.");
+
+ is(breakpointsAdded, 2,
+ "Correct number of breakpoints have been added.");
+ is(breakpointsRemoved, 1,
+ "Correct number of breakpoints have been removed.");
+ is(editorBreakpointChanges, 4,
+ "Correct number of editor breakpoint changes.");
+ }
+ });
+}
diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_breakpoints-eval.js b/devtools/client/debugger/test/mochitest/browser_dbg_breakpoints-eval.js
new file mode 100644
index 000000000..795059c70
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_breakpoints-eval.js
@@ -0,0 +1,47 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Test setting breakpoints on an eval script
+ */
+
+const TAB_URL = EXAMPLE_URL + "doc_script-eval.html";
+
+function test() {
+ let options = {
+ source: EXAMPLE_URL + "code_script-eval.js",
+ line: 1
+ };
+ initDebugger(TAB_URL, options).then(([aTab,, aPanel]) => {
+ const gTab = aTab;
+ const gPanel = aPanel;
+ const gDebugger = gPanel.panelWin;
+ const gSources = gDebugger.DebuggerView.Sources;
+ const actions = bindActionCreators(gPanel);
+
+ Task.spawn(function* () {
+ let newSource = waitForDebuggerEvents(gPanel, gDebugger.EVENTS.NEW_SOURCE);
+ callInTab(gTab, "evalSourceWithSourceURL");
+ yield newSource;
+ // Wait for it to be added to the UI
+ yield waitForTick();
+
+ const newSourceActor = getSourceActor(gSources, EXAMPLE_URL + "bar.js");
+ yield actions.addBreakpoint({
+ actor: newSourceActor,
+ line: 2
+ });
+ yield ensureThreadClientState(gPanel, "resumed");
+
+ const paused = waitForThreadEvents(gPanel, "paused");
+ callInTab(gTab, "bar");
+ let frame = (yield paused).frame;
+ is(frame.where.source.actor, newSourceActor, "Should have broken on the eval'ed source");
+ is(frame.where.line, 2, "Should break on line 2");
+
+ yield resumeDebuggerThenCloseAndFinish(gPanel);
+ });
+ });
+}
diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_breakpoints-highlight.js b/devtools/client/debugger/test/mochitest/browser_dbg_breakpoints-highlight.js
new file mode 100644
index 000000000..d04a752ca
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_breakpoints-highlight.js
@@ -0,0 +1,90 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Test if breakpoints are highlighted when they should.
+ */
+
+const TAB_URL = EXAMPLE_URL + "doc_script-switching-01.html";
+
+function test() {
+ let options = {
+ source: EXAMPLE_URL + "code_script-switching-01.js",
+ line: 1
+ };
+ initDebugger(TAB_URL, options).then(([aTab,, aPanel]) => {
+ const gTab = aTab;
+ const gPanel = aPanel;
+ const gDebugger = gPanel.panelWin;
+ const gEditor = gDebugger.DebuggerView.editor;
+ const gSources = gDebugger.DebuggerView.Sources;
+ const queries = gDebugger.require("./content/queries");
+ const actions = bindActionCreators(gPanel);
+ const getState = gDebugger.DebuggerController.getState;
+
+ const addBreakpoints = Task.async(function* () {
+ yield actions.addBreakpoint({ actor: gSources.values[0], line: 5 });
+ yield actions.addBreakpoint({ actor: gSources.values[1], line: 6 });
+ yield actions.addBreakpoint({ actor: gSources.values[1], line: 7 });
+ yield actions.addBreakpoint({ actor: gSources.values[1], line: 8 });
+ yield actions.addBreakpoint({ actor: gSources.values[1], line: 9 });
+ });
+
+ function clickBreakpointAndCheck(aBreakpointIndex, aSourceIndex, aCaretLine) {
+ let finished = waitForCaretUpdated(gPanel, aCaretLine).then(() => {
+ checkHighlight(gSources.values[aSourceIndex], aCaretLine);
+ checkEditorContents(aSourceIndex);
+
+ is(queries.getSelectedSource(getState()).actor,
+ gSources.items[aSourceIndex].value,
+ "The currently selected source value is incorrect (1).");
+ ok(isCaretPos(gPanel, aCaretLine),
+ "The editor caret line and column were incorrect (1).");
+ });
+
+ EventUtils.sendMouseEvent(
+ { type: "click" },
+ gDebugger.document.querySelectorAll(".dbg-breakpoint")[aBreakpointIndex],
+ gDebugger
+ );
+
+ return finished;
+ }
+
+ function checkHighlight(actor, line) {
+ let breakpoint = gSources._selectedBreakpoint;
+ let breakpointItem = gSources._getBreakpoint(breakpoint);
+
+ is(breakpoint.location.actor, actor,
+ "The currently selected breakpoint actor is incorrect.");
+ is(breakpoint.location.line, line,
+ "The currently selected breakpoint line is incorrect.");
+ is(breakpointItem.attachment.actor, actor,
+ "The selected breakpoint item's source location attachment is incorrect.");
+ ok(breakpointItem.target.classList.contains("selected"),
+ "The selected breakpoint item's target should have a selected class.");
+ }
+
+ function checkEditorContents(aSourceIndex) {
+ if (aSourceIndex == 0) {
+ is(gEditor.getText().indexOf("firstCall"), 118,
+ "The first source is correctly displayed.");
+ } else {
+ is(gEditor.getText().indexOf("debugger"), 166,
+ "The second source is correctly displayed.");
+ }
+ }
+
+ Task.spawn(function* () {
+ yield addBreakpoints();
+ yield clickBreakpointAndCheck(0, 0, 5);
+ yield clickBreakpointAndCheck(1, 1, 6);
+ yield clickBreakpointAndCheck(2, 1, 7);
+ yield clickBreakpointAndCheck(3, 1, 8);
+ yield clickBreakpointAndCheck(4, 1, 9);
+ closeDebuggerAndFinish(gPanel);
+ });
+ });
+}
diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_breakpoints-new-script.js b/devtools/client/debugger/test/mochitest/browser_dbg_breakpoints-new-script.js
new file mode 100644
index 000000000..6e0537f82
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_breakpoints-new-script.js
@@ -0,0 +1,92 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Bug 771452: Make sure that setting a breakpoint in an inline source doesn't
+ * add it twice.
+ */
+
+const TAB_URL = EXAMPLE_URL + "doc_inline-script.html";
+
+function test() {
+ let options = {
+ source: TAB_URL,
+ line: 1
+ };
+ initDebugger(TAB_URL, options).then(([aTab,, aPanel]) => {
+ const gTab = aTab;
+ const gPanel = aPanel;
+ const gDebugger = gPanel.panelWin;
+ const gSources = gDebugger.DebuggerView.Sources;
+ const queries = gDebugger.require('./content/queries');
+ const actions = bindActionCreators(gPanel);
+ const getState = gDebugger.DebuggerController.getState;
+
+ function testResume() {
+ const deferred = promise.defer();
+ is(gDebugger.gThreadClient.state, "paused",
+ "The breakpoint wasn't hit yet.");
+
+ gDebugger.gThreadClient.resume(() => {
+ gDebugger.gThreadClient.addOneTimeListener("paused", (aEvent, aPacket) => {
+ waitForDebuggerEvents(gPanel, gDebugger.EVENTS.FETCHED_SCOPES).then(() => {
+ is(aPacket.why.type, "breakpoint",
+ "Execution has advanced to the next breakpoint.");
+ isnot(aPacket.why.type, "debuggerStatement",
+ "The breakpoint was hit before the debugger statement.");
+ ok(isCaretPos(gPanel, 20),
+ "The source editor caret position is incorrect (2).");
+
+ deferred.resolve();
+ });
+ });
+
+ generateMouseClickInTab(gTab, "content.document.querySelector('button')");
+ });
+
+ return deferred.promise;
+ }
+
+ function testBreakpointHit() {
+ const deferred = promise.defer();
+ is(gDebugger.gThreadClient.state, "paused",
+ "The breakpoint was hit.");
+
+ gDebugger.gThreadClient.addOneTimeListener("paused", (aEvent, aPacket) => {
+ waitForDebuggerEvents(gPanel, gDebugger.EVENTS.FETCHED_SCOPES).then(() => {
+ is(aPacket.why.type, "debuggerStatement",
+ "Execution has advanced to the next line.");
+ isnot(aPacket.why.type, "breakpoint",
+ "No ghost breakpoint was hit.");
+ ok(isCaretPos(gPanel, 20),
+ "The source editor caret position is incorrect (3).");
+
+ deferred.resolve();
+ });
+ });
+
+ EventUtils.sendMouseEvent({ type: "mousedown" },
+ gDebugger.document.getElementById("resume"),
+ gDebugger);
+ return deferred.promise;
+ }
+
+ Task.spawn(function(){
+ let onCaretUpdated = waitForCaretAndScopes(gPanel, 16);
+ callInTab(gTab, "runDebuggerStatement");
+ yield onCaretUpdated;
+
+ is(gDebugger.gThreadClient.state, "paused",
+ "The debugger statement was reached.");
+ ok(isCaretPos(gPanel, 16),
+ "The source editor caret position is incorrect (1).");
+
+ yield actions.addBreakpoint({ actor: getSourceActor(gSources, TAB_URL), line: 20 });
+ yield testResume();
+ yield testBreakpointHit();
+ resumeDebuggerThenCloseAndFinish(gPanel);
+ });
+ });
+}
diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_breakpoints-other-tabs.js b/devtools/client/debugger/test/mochitest/browser_dbg_breakpoints-other-tabs.js
new file mode 100644
index 000000000..2763eee95
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_breakpoints-other-tabs.js
@@ -0,0 +1,41 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Make sure that setting a breakpoint in one tab, doesn't cause another tab at
+ * the same source to pause at that location.
+ */
+
+const TAB_URL = EXAMPLE_URL + "doc_breakpoints-other-tabs.html";
+
+var test = Task.async(function* () {
+ const options = {
+ source: EXAMPLE_URL + "code_breakpoints-other-tabs.js",
+ line: 1
+ };
+ const [tab1,, panel1] = yield initDebugger(TAB_URL, options);
+ const [tab2,, panel2] = yield initDebugger(TAB_URL, options);
+ const queries = panel1.panelWin.require("./content/queries");
+ const actions = bindActionCreators(panel1);
+ const getState = panel1.panelWin.DebuggerController.getState;
+
+ const sources = panel1.panelWin.DebuggerView.Sources;
+
+ yield actions.addBreakpoint({
+ actor: queries.getSelectedSource(getState()).actor,
+ line: 2
+ });
+
+ const paused = waitForThreadEvents(panel2, "paused");
+ callInTab(tab2, "testCase");
+ const packet = yield paused;
+
+ is(packet.why.type, "debuggerStatement",
+ "Should have stopped at the debugger statement, not the other tab's breakpoint");
+ is(packet.frame.where.line, 3,
+ "Should have stopped at line 3 (debugger statement), not line 2 (other tab's breakpoint)");
+
+ yield resumeDebuggerThenCloseAndFinish(panel2);
+});
diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_breakpoints-pane.js b/devtools/client/debugger/test/mochitest/browser_dbg_breakpoints-pane.js
new file mode 100644
index 000000000..c576603d4
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_breakpoints-pane.js
@@ -0,0 +1,238 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Bug 723071: Test adding a pane to display the list of breakpoints across
+ * all sources in the debuggee.
+ */
+
+const TAB_URL = EXAMPLE_URL + "doc_script-switching-01.html";
+
+function test() {
+ let options = {
+ source: EXAMPLE_URL + "code_script-switching-01.js",
+ line: 1
+ };
+ initDebugger(TAB_URL, options).then(([aTab,, aPanel]) => {
+ const gTab = aTab;
+ const gPanel = aPanel;
+ const gDebugger = gPanel.panelWin;
+ const gEditor = gDebugger.DebuggerView.editor;
+ const gSources = gDebugger.DebuggerView.Sources;
+ const queries = gDebugger.require("./content/queries");
+ const actions = bindActionCreators(gPanel);
+ const getState = gDebugger.DebuggerController.getState;
+ const { getBreakpoint } = queries;
+
+ let breakpointsAdded = 0;
+ let breakpointsDisabled = 0;
+ let breakpointsRemoved = 0;
+ let breakpointsList;
+
+ const addBreakpoints = Task.async(function* (aIncrementFlag) {
+ const loc1 = { actor: gSources.selectedValue, line: 6 };
+ yield actions.addBreakpoint(loc1);
+ onBreakpointAdd(getBreakpoint(getState(), loc1), {
+ increment: aIncrementFlag,
+ line: 6,
+ text: "debugger;"
+ });
+
+ const loc2 = { actor: gSources.selectedValue, line: 7 };
+ yield actions.addBreakpoint(loc2);
+ onBreakpointAdd(getBreakpoint(getState(), loc2), {
+ increment: aIncrementFlag,
+ line: 7,
+ text: "function foo() {}"
+ });
+
+ const loc3 = {actor: gSources.selectedValue, line: 9 };
+ yield actions.addBreakpoint(loc3);
+ onBreakpointAdd(getBreakpoint(getState(), loc3), {
+ increment: aIncrementFlag,
+ line: 9,
+ text: "foo();"
+ });
+ });
+
+ function disableBreakpoints() {
+ let deferred = promise.defer();
+
+ let nodes = breakpointsList.querySelectorAll(".dbg-breakpoint");
+ info("Nodes to disable: " + breakpointsAdded.length);
+
+ is(nodes.length, breakpointsAdded,
+ "The number of nodes to disable is incorrect.");
+
+ for (let node of nodes) {
+ info("Disabling breakpoint: " + node.id);
+
+ let sourceItem = gSources.getItemForElement(node);
+ let breakpointItem = gSources.getItemForElement.call(sourceItem, node);
+ info("Found data: " + breakpointItem.attachment.toSource());
+
+ actions.disableBreakpoint(breakpointItem.attachment).then(() => {
+ if (++breakpointsDisabled == breakpointsAdded) {
+ deferred.resolve();
+ }
+ });
+ }
+
+ return deferred.promise;
+ }
+
+ function removeBreakpoints() {
+ let deferred = promise.defer();
+
+ let nodes = breakpointsList.querySelectorAll(".dbg-breakpoint");
+ info("Nodes to remove: " + breakpointsAdded.length);
+
+ is(nodes.length, breakpointsAdded,
+ "The number of nodes to remove is incorrect.");
+
+ for (let node of nodes) {
+ info("Removing breakpoint: " + node.id);
+
+ let sourceItem = gSources.getItemForElement(node);
+ let breakpointItem = gSources.getItemForElement.call(sourceItem, node);
+ info("Found data: " + breakpointItem.attachment.toSource());
+
+ actions.removeBreakpoint(breakpointItem.attachment).then(() => {
+ if (++breakpointsRemoved == breakpointsAdded) {
+ deferred.resolve();
+ }
+ });
+ }
+
+ return deferred.promise;
+ }
+
+ function onBreakpointAdd(bp, testData) {
+ if (testData.increment) {
+ breakpointsAdded++;
+ }
+
+ is(breakpointsList.querySelectorAll(".dbg-breakpoint").length, breakpointsAdded,
+ testData.increment
+ ? "Should have added a breakpoint in the pane."
+ : "Should have the same number of breakpoints in the pane.");
+
+ let identifier = queries.makeLocationId(bp.location);
+ let node = gDebugger.document.getElementById("breakpoint-" + identifier);
+ let line = node.getElementsByClassName("dbg-breakpoint-line")[0];
+ let text = node.getElementsByClassName("dbg-breakpoint-text")[0];
+ let check = node.querySelector("checkbox");
+
+ ok(node,
+ "Breakpoint element found successfully.");
+ is(line.getAttribute("value"), testData.line,
+ "The expected information wasn't found in the breakpoint element.");
+ is(text.getAttribute("value"), testData.text,
+ "The expected line text wasn't found in the breakpoint element.");
+ is(check.getAttribute("checked"), "true",
+ "The breakpoint enable checkbox is checked as expected.");
+ }
+
+ Task.spawn(function* () {
+ yield waitForSourceAndCaretAndScopes(gPanel, "-02.js", 1);
+
+ is(gDebugger.gThreadClient.state, "paused",
+ "Should only be getting stack frames while paused.");
+ is(queries.getSourceCount(getState()), 2,
+ "Found the expected number of sources.");
+ is(gEditor.getText().indexOf("debugger"), 166,
+ "The correct source was loaded initially.");
+ is(gSources.selectedValue, gSources.values[1],
+ "The correct source is selected.");
+
+ is(queries.getBreakpoints(getState()).length, 0,
+ "No breakpoints currently added.");
+
+ let breakpointsParent = gSources.widget._parent;
+ breakpointsList = gSources.widget._list;
+
+ is(breakpointsParent.childNodes.length, 1, // one sources list
+ "Found junk in the breakpoints container.");
+ is(breakpointsList.childNodes.length, 1, // one sources group
+ "Found junk in the breakpoints container.");
+ is(breakpointsList.querySelectorAll(".dbg-breakpoint").length, 0,
+ "No breakpoints should be visible at this point.");
+
+ yield addBreakpoints(true);
+
+ is(breakpointsAdded, 3,
+ "Should have added 3 breakpoints so far.");
+ is(breakpointsDisabled, 0,
+ "Shouldn't have disabled anything so far.");
+ is(breakpointsRemoved, 0,
+ "Shouldn't have removed anything so far.");
+
+ is(breakpointsParent.childNodes.length, 1, // one sources list
+ "Found junk in the breakpoints container.");
+ is(breakpointsList.childNodes.length, 1, // one sources group
+ "Found junk in the breakpoints container.");
+ is(breakpointsList.querySelectorAll(".dbg-breakpoint").length, 3,
+ "3 breakpoints should be visible at this point.");
+
+ yield disableBreakpoints();
+
+ is(breakpointsAdded, 3,
+ "Should still have 3 breakpoints added so far.");
+ is(breakpointsDisabled, 3,
+ "Should have 3 disabled breakpoints.");
+ is(breakpointsRemoved, 0,
+ "Shouldn't have removed anything so far.");
+
+ is(breakpointsParent.childNodes.length, 1, // one sources list
+ "Found junk in the breakpoints container.");
+ is(breakpointsList.childNodes.length, 1, // one sources group
+ "Found junk in the breakpoints container.");
+ is(breakpointsList.querySelectorAll(".dbg-breakpoint").length, breakpointsAdded,
+ "Should have the same number of breakpoints in the pane.");
+ is(breakpointsList.querySelectorAll(".dbg-breakpoint").length, breakpointsDisabled,
+ "Should have the same number of disabled breakpoints.");
+
+ yield addBreakpoints();
+
+ is(breakpointsAdded, 3,
+ "Should still have only 3 breakpoints added so far.");
+ is(breakpointsDisabled, 3,
+ "Should still have 3 disabled breakpoints.");
+ is(breakpointsRemoved, 0,
+ "Shouldn't have removed anything so far.");
+
+ is(breakpointsParent.childNodes.length, 1, // one sources list
+ "Found junk in the breakpoints container.");
+ is(breakpointsList.childNodes.length, 1, // one sources group
+ "Found junk in the breakpoints container.");
+ is(breakpointsList.querySelectorAll(".dbg-breakpoint").length, breakpointsAdded,
+ "Since half of the breakpoints already existed, but disabled, " +
+ "only half of the added breakpoints are actually in the pane.");
+
+ yield removeBreakpoints();
+
+ is(breakpointsRemoved, 3,
+ "Should have 3 removed breakpoints.");
+
+ is(breakpointsParent.childNodes.length, 1, // one sources list
+ "Found junk in the breakpoints container.");
+ is(breakpointsList.childNodes.length, 1, // one sources group
+ "Found junk in the breakpoints container.");
+ is(breakpointsList.querySelectorAll(".dbg-breakpoint").length, 0,
+ "No breakpoints should be visible at this point.");
+
+ const cleared = waitForDebuggerEvents(gPanel, gDebugger.EVENTS.AFTER_FRAMES_CLEARED);
+ gDebugger.gThreadClient.resume();
+ yield cleared;
+
+ is(queries.getBreakpoints(getState()).length, 0,
+ "No breakpoints currently added.");
+
+ closeDebuggerAndFinish(gPanel);
+ });
+
+ callInTab(gTab, "firstCall");
+ });
+}
diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_breakpoints-reload.js b/devtools/client/debugger/test/mochitest/browser_dbg_breakpoints-reload.js
new file mode 100644
index 000000000..b86c0ec04
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_breakpoints-reload.js
@@ -0,0 +1,39 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Make sure that setting a breakpoint on code that gets run on load, will get
+ * hit when we reload.
+ */
+
+const TAB_URL = EXAMPLE_URL + "doc_breakpoints-reload.html";
+
+var test = Task.async(function* () {
+ requestLongerTimeout(4);
+
+ const options = {
+ source: TAB_URL,
+ line: 1
+ };
+ const [tab,, panel] = yield initDebugger(TAB_URL, options);
+ const actions = bindActionCreators(panel);
+
+ const sources = panel.panelWin.DebuggerView.Sources;
+ yield actions.addBreakpoint({
+ actor: sources.selectedValue,
+ line: 10 // "break on me" string
+ });
+
+ const paused = waitForThreadEvents(panel, "paused");
+ yield reloadActiveTab(panel, panel.panelWin.EVENTS.SOURCE_SHOWN);
+ const packet = yield paused;
+
+ is(packet.why.type, "breakpoint",
+ "Should have hit the breakpoint after the reload");
+ is(packet.frame.where.line, 10,
+ "Should have stopped at line 10, where we set the breakpoint");
+
+ yield resumeDebuggerThenCloseAndFinish(panel);
+});
diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_bug-896139.js b/devtools/client/debugger/test/mochitest/browser_dbg_bug-896139.js
new file mode 100644
index 000000000..39df159bc
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_bug-896139.js
@@ -0,0 +1,48 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Bug 896139 - Breakpoints not triggering when reloading script.
+ */
+
+const TAB_URL = EXAMPLE_URL + "doc_bug-896139.html";
+const SCRIPT_URL = EXAMPLE_URL + "code_bug-896139.js";
+
+function test() {
+ Task.spawn(function* () {
+ function testBreakpoint() {
+ let promise = waitForDebuggerEvents(panel, win.EVENTS.FETCHED_SCOPES);
+ callInTab(tab, "f");
+ return promise.then(() => doResume(panel));
+ }
+
+ let options = {
+ source: SCRIPT_URL,
+ line: 1
+ };
+ let [tab,, panel] = yield initDebugger(TAB_URL, options);
+ let win = panel.panelWin;
+
+ let Sources = win.DebuggerView.Sources;
+
+ yield panel.addBreakpoint({
+ actor: getSourceActor(win.DebuggerView.Sources, SCRIPT_URL),
+ line: 6
+ });
+
+ // Race condition: the setBreakpoint request sometimes leaves the
+ // debugger in paused state for a bit because we are called before
+ // that request finishes (see bug 1156531 for plans to fix)
+ if (panel.panelWin.gThreadClient.state !== "attached") {
+ yield waitForThreadEvents(panel, "resumed");
+ }
+
+ yield testBreakpoint();
+ yield reloadActiveTab(panel, win.EVENTS.SOURCE_SHOWN);
+ yield testBreakpoint();
+
+ yield closeDebuggerAndFinish(panel);
+ });
+}
diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_chrome-create.js b/devtools/client/debugger/test/mochitest/browser_dbg_chrome-create.js
new file mode 100644
index 000000000..33c21c3d5
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_chrome-create.js
@@ -0,0 +1,64 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Tests that a chrome debugger can be created in a new process.
+ */
+
+var gProcess;
+
+function test() {
+ // Windows XP and 8.1 test slaves are terribly slow at this test.
+ requestLongerTimeout(5);
+ Services.prefs.setBoolPref("devtools.debugger.remote-enabled", true);
+
+ initChromeDebugger(aOnClose).then(aProcess => {
+ gProcess = aProcess;
+
+ info("Starting test...");
+ performTest();
+ });
+}
+
+function performTest() {
+ ok(gProcess._dbgProcess,
+ "The remote debugger process wasn't created properly!");
+ ok(gProcess._dbgProcess.isRunning,
+ "The remote debugger process isn't running!");
+ is(typeof gProcess._dbgProcess.pid, "number",
+ "The remote debugger process doesn't have a pid (?!)");
+
+ info("process location: " + gProcess._dbgProcess.location);
+ info("process pid: " + gProcess._dbgProcess.pid);
+ info("process name: " + gProcess._dbgProcess.processName);
+ info("process sig: " + gProcess._dbgProcess.processSignature);
+
+ ok(gProcess._dbgProfilePath,
+ "The remote debugger profile wasn't created properly!");
+ is(gProcess._dbgProfilePath, OS.Path.join(OS.Constants.Path.profileDir, "chrome_debugger_profile"),
+ "The remote debugger profile isn't where we expect it!");
+
+ info("profile path: " + gProcess._dbgProfilePath);
+
+ gProcess.close();
+}
+
+function aOnClose() {
+ ok(!gProcess._dbgProcess.isRunning,
+ "The remote debugger process isn't closed as it should be!");
+ is(gProcess._dbgProcess.exitValue, (Services.appinfo.OS == "WINNT" ? 0 : 256),
+ "The remote debugger process didn't die cleanly.");
+
+ info("process exit value: " + gProcess._dbgProcess.exitValue);
+
+ info("profile path: " + gProcess._dbgProfilePath);
+
+ finish();
+}
+
+registerCleanupFunction(function () {
+ Services.prefs.clearUserPref("devtools.debugger.remote-enabled");
+ gProcess = null;
+});
diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_chrome-debugging.js b/devtools/client/debugger/test/mochitest/browser_dbg_chrome-debugging.js
new file mode 100644
index 000000000..79e9e6566
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_chrome-debugging.js
@@ -0,0 +1,102 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Tests that chrome debugging works.
+ */
+
+const TAB_URL = EXAMPLE_URL + "doc_inline-debugger-statement.html";
+
+var gClient, gThreadClient;
+var gAttached = promise.defer();
+var gNewGlobal = promise.defer();
+var gNewChromeSource = promise.defer();
+
+var { DevToolsLoader } = Cu.import("resource://devtools/shared/Loader.jsm", {});
+var customLoader = new DevToolsLoader();
+customLoader.invisibleToDebugger = true;
+var { DebuggerServer } = customLoader.require("devtools/server/main");
+
+function test() {
+ if (!DebuggerServer.initialized) {
+ DebuggerServer.init();
+ DebuggerServer.addBrowserActors();
+ }
+ DebuggerServer.allowChromeProcess = true;
+
+ let transport = DebuggerServer.connectPipe();
+ gClient = new DebuggerClient(transport);
+ gClient.connect().then(([aType, aTraits]) => {
+ is(aType, "browser",
+ "Root actor should identify itself as a browser.");
+
+ promise.all([gAttached.promise, gNewGlobal.promise, gNewChromeSource.promise])
+ .then(resumeAndCloseConnection)
+ .then(finish)
+ .then(null, aError => {
+ ok(false, "Got an error: " + aError.message + "\n" + aError.stack);
+ });
+
+ testChromeActor();
+ });
+}
+
+function testChromeActor() {
+ gClient.getProcess().then(aResponse => {
+ gClient.addListener("newGlobal", onNewGlobal);
+
+ let actor = aResponse.form.actor;
+ gClient.attachTab(actor, (response, tabClient) => {
+ tabClient.attachThread(null, (aResponse, aThreadClient) => {
+ gThreadClient = aThreadClient;
+ gThreadClient.addListener("newSource", onNewSource);
+
+ if (aResponse.error) {
+ ok(false, "Couldn't attach to the chrome debugger.");
+ gAttached.reject();
+ } else {
+ ok(true, "Attached to the chrome debugger.");
+ gAttached.resolve();
+
+ // Ensure that a new chrome global will be created.
+ gBrowser.selectedTab = gBrowser.addTab("about:mozilla");
+ }
+ });
+ });
+ });
+}
+
+function onNewGlobal() {
+ ok(true, "Received a new chrome global.");
+
+ gClient.removeListener("newGlobal", onNewGlobal);
+ gNewGlobal.resolve();
+}
+
+function onNewSource(aEvent, aPacket) {
+ if (aPacket.source.url.startsWith("chrome:")) {
+ ok(true, "Received a new chrome source: " + aPacket.source.url);
+
+ gThreadClient.removeListener("newSource", onNewSource);
+ gNewChromeSource.resolve();
+ }
+}
+
+function resumeAndCloseConnection() {
+ let deferred = promise.defer();
+ gThreadClient.resume(() => deferred.resolve(gClient.close()));
+ return deferred.promise;
+}
+
+registerCleanupFunction(function () {
+ gClient = null;
+ gThreadClient = null;
+ gAttached = null;
+ gNewGlobal = null;
+ gNewChromeSource = null;
+
+ customLoader = null;
+ DebuggerServer = null;
+});
diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_clean-exit-window.js b/devtools/client/debugger/test/mochitest/browser_dbg_clean-exit-window.js
new file mode 100644
index 000000000..d09e8c70c
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_clean-exit-window.js
@@ -0,0 +1,86 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Test that closing a window with the debugger in a paused state exits cleanly.
+ */
+
+var gDebuggee, gPanel, gDebugger, gWindow;
+
+const TAB_URL = EXAMPLE_URL + "doc_inline-debugger-statement.html";
+
+function test() {
+ addWindow(TAB_URL)
+ .then(win => initDebugger(TAB_URL, { window: win }))
+ .then(([aTab, aDebuggee, aPanel, aWindow]) => {
+ gDebuggee = aDebuggee;
+ gPanel = aPanel;
+ gDebugger = gPanel.panelWin;
+ gWindow = aWindow;
+
+ return testCleanExit();
+ })
+ .then(null, aError => {
+ ok(false, "Got an error: " + aError.message + "\n" + aError.stack);
+ });
+}
+
+function testCleanExit() {
+ let deferred = promise.defer();
+
+ ok(!!gWindow, "Second window created.");
+
+ gWindow.focus();
+
+ is(Services.wm.getMostRecentWindow("navigator:browser"), gWindow,
+ "The second window is on top.");
+
+ let isActive = promise.defer();
+ let isLoaded = promise.defer();
+
+ promise.all([isActive.promise, isLoaded.promise]).then(() => {
+ waitForSourceAndCaretAndScopes(gPanel, ".html", 16).then(() => {
+ is(gDebugger.gThreadClient.paused, true,
+ "Should be paused after the debugger statement.");
+ gWindow.close();
+ deferred.resolve();
+ finish();
+ });
+
+ gDebuggee.runDebuggerStatement();
+ });
+
+ if (Services.focus.activeWindow != gWindow) {
+ gWindow.addEventListener("activate", function onActivate(aEvent) {
+ if (aEvent.target != gWindow) {
+ return;
+ }
+ gWindow.removeEventListener("activate", onActivate, true);
+ isActive.resolve();
+ }, true);
+ } else {
+ isActive.resolve();
+ }
+
+ if (gWindow.content.location.href != TAB_URL) {
+ gWindow.document.addEventListener("load", function onLoad(aEvent) {
+ if (aEvent.target.documentURI != TAB_URL) {
+ return;
+ }
+ gWindow.document.removeEventListener("load", onLoad, true);
+ isLoaded.resolve();
+ }, true);
+ } else {
+ isLoaded.resolve();
+ }
+ return deferred.promise;
+}
+
+registerCleanupFunction(function () {
+ gWindow = null;
+ gDebuggee = null;
+ gPanel = null;
+ gDebugger = null;
+});
diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_clean-exit.js b/devtools/client/debugger/test/mochitest/browser_dbg_clean-exit.js
new file mode 100644
index 000000000..25cbf550d
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_clean-exit.js
@@ -0,0 +1,44 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Test that closing a tab with the debugger in a paused state exits cleanly.
+ */
+
+var gTab, gPanel, gDebugger;
+
+const TAB_URL = EXAMPLE_URL + "doc_inline-debugger-statement.html";
+
+function test() {
+ const options = {
+ source: TAB_URL,
+ line: 1
+ };
+ initDebugger(TAB_URL, options).then(([aTab,, aPanel]) => {
+ gTab = aTab;
+ gPanel = aPanel;
+ gDebugger = gPanel.panelWin;
+
+ testCleanExit();
+ });
+}
+
+function testCleanExit() {
+ promise.all([
+ waitForSourceAndCaretAndScopes(gPanel, ".html", 16),
+ waitForDebuggerEvents(gPanel, gDebugger.EVENTS.AFTER_FRAMES_REFILLED)
+ ]).then(() => {
+ is(gDebugger.gThreadClient.paused, true,
+ "Should be paused after the debugger statement.");
+ }).then(() => closeDebuggerAndFinish(gPanel, { whilePaused: true }));
+
+ callInTab(gTab, "runDebuggerStatement");
+}
+
+registerCleanupFunction(function () {
+ gTab = null;
+ gPanel = null;
+ gDebugger = null;
+});
diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_closure-inspection.js b/devtools/client/debugger/test/mochitest/browser_dbg_closure-inspection.js
new file mode 100644
index 000000000..739d3b2a7
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_closure-inspection.js
@@ -0,0 +1,153 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+const TAB_URL = EXAMPLE_URL + "doc_closures.html";
+
+// Test that inspecting a closure works as expected.
+
+function test() {
+ let gPanel, gTab, gDebugger;
+
+ let options = {
+ source: TAB_URL,
+ line: 1
+ };
+ initDebugger(TAB_URL, options).then(([aTab,, aPanel]) => {
+ gTab = aTab;
+ gPanel = aPanel;
+ gDebugger = gPanel.panelWin;
+
+ testClosure()
+ .then(() => resumeDebuggerThenCloseAndFinish(gPanel))
+ .then(null, aError => {
+ ok(false, "Got an error: " + aError.message + "\n" + aError.stack);
+ });
+ });
+
+ function testClosure() {
+ generateMouseClickInTab(gTab, "content.document.querySelector('button')");
+
+ return waitForDebuggerEvents(gPanel, gDebugger.EVENTS.FETCHED_SCOPES).then(() => {
+ let gVars = gDebugger.DebuggerView.Variables;
+ let localScope = gVars.getScopeAtIndex(0);
+ let localNodes = localScope.target.querySelector(".variables-view-element-details").childNodes;
+
+ is(localNodes[4].querySelector(".name").getAttribute("value"), "person",
+ "Should have the right property name for |person|.");
+ is(localNodes[4].querySelector(".value").getAttribute("value"), "Object",
+ "Should have the right property value for |person|.");
+
+ // Expand the 'person' tree node. This causes its properties to be
+ // retrieved and displayed.
+ let personNode = gVars.getItemForNode(localNodes[4]);
+ let personFetched = waitForDebuggerEvents(gPanel, gDebugger.EVENTS.FETCHED_PROPERTIES);
+ personNode.expand();
+
+ return personFetched.then(() => {
+ is(personNode.expanded, true,
+ "|person| should be expanded at this point.");
+
+ is(personNode.get("getName").target.querySelector(".name")
+ .getAttribute("value"), "getName",
+ "Should have the right property name for 'getName' in person.");
+ is(personNode.get("getName").target.querySelector(".value")
+ .getAttribute("value"), "_pfactory/<.getName()",
+ "'getName' in person should have the right value.");
+ is(personNode.get("getFoo").target.querySelector(".name")
+ .getAttribute("value"), "getFoo",
+ "Should have the right property name for 'getFoo' in person.");
+ is(personNode.get("getFoo").target.querySelector(".value")
+ .getAttribute("value"), "_pfactory/<.getFoo()",
+ "'getFoo' in person should have the right value.");
+
+ // Expand the function nodes. This causes their properties to be
+ // retrieved and displayed.
+ let getFooNode = personNode.get("getFoo");
+ let getNameNode = personNode.get("getName");
+ let funcsFetched = waitForDebuggerEvents(gPanel, gDebugger.EVENTS.FETCHED_PROPERTIES, 2);
+ let funcClosuresFetched = waitForDebuggerEvents(gPanel, gDebugger.EVENTS.FETCHED_SCOPES, 2);
+ getFooNode.expand();
+ getNameNode.expand();
+
+ return funcsFetched.then(() => {
+ is(getFooNode.expanded, true,
+ "|person.getFoo| should be expanded at this point.");
+ is(getNameNode.expanded, true,
+ "|person.getName| should be expanded at this point.");
+
+ is(getFooNode.get("<Closure>").target.querySelector(".name")
+ .getAttribute("value"), "<Closure>",
+ "Found the closure node for getFoo.");
+ is(getFooNode.get("<Closure>").target.querySelector(".value")
+ .getAttribute("value"), "",
+ "The closure node has no value for getFoo.");
+ is(getNameNode.get("<Closure>").target.querySelector(".name")
+ .getAttribute("value"), "<Closure>",
+ "Found the closure node for getName.");
+ is(getNameNode.get("<Closure>").target.querySelector(".value")
+ .getAttribute("value"), "",
+ "The closure node has no value for getName.");
+
+ // Expand the closure nodes. This causes their environments to be
+ // retrieved and displayed.
+ let getFooClosure = getFooNode.get("<Closure>");
+ let getNameClosure = getNameNode.get("<Closure>");
+ getFooClosure.expand();
+ getNameClosure.expand();
+
+ return funcClosuresFetched.then(() => {
+ is(getFooClosure.expanded, true,
+ "|person.getFoo| closure should be expanded at this point.");
+ is(getNameClosure.expanded, true,
+ "|person.getName| closure should be expanded at this point.");
+
+ is(getFooClosure.get("Function scope [_pfactory]").target.querySelector(".name")
+ .getAttribute("value"), "Function scope [_pfactory]",
+ "Found the function scope node for the getFoo closure.");
+ is(getFooClosure.get("Function scope [_pfactory]").target.querySelector(".value")
+ .getAttribute("value"), "",
+ "The function scope node has no value for the getFoo closure.");
+ is(getNameClosure.get("Function scope [_pfactory]").target.querySelector(".name")
+ .getAttribute("value"), "Function scope [_pfactory]",
+ "Found the function scope node for the getName closure.");
+ is(getNameClosure.get("Function scope [_pfactory]").target.querySelector(".value")
+ .getAttribute("value"), "",
+ "The function scope node has no value for the getName closure.");
+
+ // Expand the scope nodes.
+ let getFooInnerScope = getFooClosure.get("Function scope [_pfactory]");
+ let getNameInnerScope = getNameClosure.get("Function scope [_pfactory]");
+ let innerFuncsFetched = waitForDebuggerEvents(gPanel, gDebugger.EVENTS.FETCHED_PROPERTIES, 2);
+ getFooInnerScope.expand();
+ getNameInnerScope.expand();
+
+ return funcsFetched.then(() => {
+ is(getFooInnerScope.expanded, true,
+ "|person.getFoo| inner scope should be expanded at this point.");
+ is(getNameInnerScope.expanded, true,
+ "|person.getName| inner scope should be expanded at this point.");
+
+ // Only test that each function closes over the necessary variable.
+ // We wouldn't want future SpiderMonkey closure space
+ // optimizations to break this test.
+ is(getFooInnerScope.get("foo").target.querySelector(".name")
+ .getAttribute("value"), "foo",
+ "Found the foo node for the getFoo inner scope.");
+ is(getFooInnerScope.get("foo").target.querySelector(".value")
+ .getAttribute("value"), "10",
+ "The foo node has the expected value.");
+ is(getNameInnerScope.get("name").target.querySelector(".name")
+ .getAttribute("value"), "name",
+ "Found the name node for the getName inner scope.");
+ is(getNameInnerScope.get("name").target.querySelector(".value")
+ .getAttribute("value"), '"Bob"',
+ "The name node has the expected value.");
+ });
+ });
+ });
+ });
+ });
+ }
+}
diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_cmd-blackbox.js b/devtools/client/debugger/test/mochitest/browser_dbg_cmd-blackbox.js
new file mode 100644
index 000000000..06ff8a4f3
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_cmd-blackbox.js
@@ -0,0 +1,117 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Tests that the 'dbg blackbox' and 'dbg unblackbox' commands work as
+ * they should.
+ */
+
+const TEST_URL = EXAMPLE_URL + "doc_blackboxing.html";
+const BLACKBOXME_URL = EXAMPLE_URL + "code_blackboxing_blackboxme.js";
+const BLACKBOXONE_URL = EXAMPLE_URL + "code_blackboxing_one.js";
+const BLACKBOXTWO_URL = EXAMPLE_URL + "code_blackboxing_two.js";
+const BLACKBOXTHREE_URL = EXAMPLE_URL + "code_blackboxing_three.js";
+
+function test() {
+ return Task.spawn(spawnTest).then(finish, helpers.handleError);
+}
+
+function* spawnTest() {
+ let options = yield helpers.openTab(TEST_URL);
+ yield helpers.openToolbar(options);
+
+ let toolbox = yield gDevTools.showToolbox(options.target, "jsdebugger");
+ let panel = toolbox.getCurrentPanel();
+ let constants = panel.panelWin.require("./content/constants");
+
+ yield waitForDebuggerEvents(panel, panel.panelWin.EVENTS.SOURCE_SHOWN);
+
+ function cmd(aTyped, aEventRepeat = 1, aOutput = "") {
+ return promise.all([
+ waitForDispatch(panel, constants.BLACKBOX, aEventRepeat),
+ helpers.audit(options, [{ setup: aTyped, output: aOutput, exec: {} }])
+ ]);
+ }
+
+ // test Black-Box Source
+ yield cmd("dbg blackbox " + BLACKBOXME_URL);
+
+ let bbButton = yield selectSourceAndGetBlackBoxButton(panel, BLACKBOXME_URL);
+ ok(bbButton.checked,
+ "Should be able to black box a specific source.");
+
+ // test Un-Black-Box Source
+ yield cmd("dbg unblackbox " + BLACKBOXME_URL);
+
+ bbButton = yield selectSourceAndGetBlackBoxButton(panel, BLACKBOXME_URL);
+ ok(!bbButton.checked,
+ "Should be able to stop black boxing a specific source.");
+
+ // test Black-Box Glob
+ yield cmd("dbg blackbox --glob *blackboxing_t*.js", 2,
+ [/blackboxing_three\.js/g, /blackboxing_two\.js/g]);
+
+ bbButton = yield selectSourceAndGetBlackBoxButton(panel, BLACKBOXME_URL);
+ ok(!bbButton.checked,
+ "blackboxme should not be black boxed because it doesn't match the glob.");
+ bbButton = yield selectSourceAndGetBlackBoxButton(panel, BLACKBOXONE_URL);
+ ok(!bbButton.checked,
+ "blackbox_one should not be black boxed because it doesn't match the glob.");
+
+ bbButton = yield selectSourceAndGetBlackBoxButton(panel, BLACKBOXTWO_URL);
+ ok(bbButton.checked,
+ "blackbox_two should be black boxed because it matches the glob.");
+ bbButton = yield selectSourceAndGetBlackBoxButton(panel, BLACKBOXTHREE_URL);
+ ok(bbButton.checked,
+ "blackbox_three should be black boxed because it matches the glob.");
+
+ // test Un-Black-Box Glob
+ yield cmd("dbg unblackbox --glob *blackboxing_t*.js", 2);
+
+ bbButton = yield selectSourceAndGetBlackBoxButton(panel, BLACKBOXTWO_URL);
+ ok(!bbButton.checked,
+ "blackbox_two should be un-black boxed because it matches the glob.");
+ bbButton = yield selectSourceAndGetBlackBoxButton(panel, BLACKBOXTHREE_URL);
+ ok(!bbButton.checked,
+ "blackbox_three should be un-black boxed because it matches the glob.");
+
+ // test Black-Box Invert
+ yield cmd("dbg blackbox --invert --glob *blackboxing_t*.js", 3,
+ [/blackboxing_three\.js/g, /blackboxing_two\.js/g]);
+
+ bbButton = yield selectSourceAndGetBlackBoxButton(panel, BLACKBOXME_URL);
+ ok(bbButton.checked,
+ "blackboxme should be black boxed because it doesn't match the glob.");
+ bbButton = yield selectSourceAndGetBlackBoxButton(panel, BLACKBOXONE_URL);
+ ok(bbButton.checked,
+ "blackbox_one should be black boxed because it doesn't match the glob.");
+ bbButton = yield selectSourceAndGetBlackBoxButton(panel, TEST_URL);
+ ok(bbButton.checked,
+ "TEST_URL should be black boxed because it doesn't match the glob.");
+
+ bbButton = yield selectSourceAndGetBlackBoxButton(panel, BLACKBOXTWO_URL);
+ ok(!bbButton.checked,
+ "blackbox_two should not be black boxed because it matches the glob.");
+ bbButton = yield selectSourceAndGetBlackBoxButton(panel, BLACKBOXTHREE_URL);
+ ok(!bbButton.checked,
+ "blackbox_three should not be black boxed because it matches the glob.");
+
+ // test Un-Black-Box Invert
+ yield cmd("dbg unblackbox --invert --glob *blackboxing_t*.js", 3);
+
+ bbButton = yield selectSourceAndGetBlackBoxButton(panel, BLACKBOXME_URL);
+ ok(!bbButton.checked,
+ "blackboxme should be un-black boxed because it does not match the glob.");
+ bbButton = yield selectSourceAndGetBlackBoxButton(panel, BLACKBOXONE_URL);
+ ok(!bbButton.checked,
+ "blackbox_one should be un-black boxed because it does not match the glob.");
+ bbButton = yield selectSourceAndGetBlackBoxButton(panel, TEST_URL);
+ ok(!bbButton.checked,
+ "TEST_URL should be un-black boxed because it doesn't match the glob.");
+
+ yield teardown(panel, { noTabRemoval: true });
+ yield helpers.closeToolbar(options);
+ yield helpers.closeTab(options);
+}
diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_cmd-break.js b/devtools/client/debugger/test/mochitest/browser_dbg_cmd-break.js
new file mode 100644
index 000000000..121bc5e99
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_cmd-break.js
@@ -0,0 +1,225 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Tests that the break commands works as they should.
+ */
+
+const TAB_URL = EXAMPLE_URL + "doc_cmd-break.html";
+var TAB_URL_ACTOR;
+
+function test() {
+ let gPanel, gDebugger, gThreadClient, gSources;
+ let gLineNumber;
+
+ let expectedActorObj = {
+ value: null,
+ message: ""
+ };
+
+ helpers.addTabWithToolbar(TAB_URL, aOptions => {
+ return Task.spawn(function* () {
+ yield helpers.audit(aOptions, [{
+ setup: "break",
+ check: {
+ input: "break",
+ hints: " add line",
+ markup: "IIIII",
+ status: "ERROR",
+ }
+ }]);
+
+ yield helpers.audit(aOptions, [{
+ setup: "break add",
+ check: {
+ input: "break add",
+ hints: " line",
+ markup: "IIIIIVIII",
+ status: "ERROR"
+ }
+ }]);
+
+ yield helpers.audit(aOptions, [{
+ setup: "break add line",
+ check: {
+ input: "break add line",
+ hints: " <file> <line>",
+ markup: "VVVVVVVVVVVVVV",
+ status: "ERROR"
+ }
+ }]);
+
+ yield helpers.audit(aOptions, [{
+ name: "open toolbox",
+ setup: Task.async(function* () {
+ let [aTab, aDebuggee, aPanel] = yield initDebugger(gBrowser.selectedTab);
+
+ // Spin the event loop before causing the debuggee to pause, to allow this
+ // function to return first.
+ executeSoon(() => aDebuggee.firstCall());
+
+ yield waitForSourceAndCaretAndScopes(aPanel, ".html", 1);
+
+ gPanel = aPanel;
+ gDebugger = gPanel.panelWin;
+ gThreadClient = gPanel.panelWin.gThreadClient;
+ gLineNumber = yield ContentTask.spawn(aOptions.browser, {}, function* () {
+ return "" + content.wrappedJSObject.gLineNumber;
+ });
+ gSources = gDebugger.DebuggerView.Sources;
+
+ expectedActorObj.value = getSourceActor(gSources, TAB_URL);
+ }),
+ post: function () {
+ ok(gThreadClient, "Debugger client exists.");
+ is(gLineNumber, 14, "gLineNumber is correct.");
+ },
+ }]);
+
+ yield helpers.audit(aOptions, [{
+ name: "break add line .../doc_cmd-break.html 14",
+ setup: function () {
+ // We have to setup in a function to allow gLineNumber to be initialized.
+ let line = "break add line " + TAB_URL + " " + gLineNumber;
+ return helpers.setInput(aOptions, line);
+ },
+ check: {
+ hints: "",
+ status: "VALID",
+ message: "",
+ args: {
+ file: expectedActorObj,
+ line: { value: 14 }
+ }
+ },
+ exec: {
+ output: "Added breakpoint"
+ }
+ }]);
+
+ yield helpers.audit(aOptions, [{
+ setup: "break add line " + TAB_URL + " 17",
+ check: {
+ hints: "",
+ status: "VALID",
+ message: "",
+ args: {
+ file: expectedActorObj,
+ line: { value: 17 }
+ }
+ },
+ exec: {
+ output: "Added breakpoint"
+ }
+ }]);
+
+ yield helpers.audit(aOptions, [{
+ setup: "break list",
+ check: {
+ input: "break list",
+ hints: "",
+ markup: "VVVVVVVVVV",
+ status: "VALID"
+ },
+ exec: {
+ output: [
+ /Source/, /Remove/,
+ /doc_cmd-break\.html:14/,
+ /doc_cmd-break\.html:17/
+ ]
+ }
+ }]);
+
+ yield helpers.audit(aOptions, [{
+ name: "cleanup",
+ setup: function () {
+ let deferred = promise.defer();
+ gThreadClient.resume(deferred.resolve);
+ return deferred.promise;
+ }
+ }]);
+
+ yield helpers.audit(aOptions, [{
+ setup: "break del 14",
+ check: {
+ input: "break del 14",
+ hints: " -> doc_cmd-break.html:14",
+ markup: "VVVVVVVVVVII",
+ status: "ERROR",
+ args: {
+ breakpoint: {
+ status: "INCOMPLETE",
+ message: "Value required for \u2018breakpoint\u2019."
+ }
+ }
+ }
+ }]);
+
+ yield helpers.audit(aOptions, [{
+ setup: "break del doc_cmd-break.html:14",
+ check: {
+ input: "break del doc_cmd-break.html:14",
+ hints: "",
+ markup: "VVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV",
+ status: "VALID",
+ args: {
+ breakpoint: { arg: " doc_cmd-break.html:14" },
+ }
+ },
+ exec: {
+ output: "Breakpoint removed"
+ }
+ }]);
+
+ yield helpers.audit(aOptions, [{
+ setup: "break list",
+ check: {
+ input: "break list",
+ hints: "",
+ markup: "VVVVVVVVVV",
+ status: "VALID"
+ },
+ exec: {
+ output: [
+ /Source/, /Remove/,
+ /doc_cmd-break\.html:17/
+ ]
+ }
+ }]);
+
+ yield helpers.audit(aOptions, [{
+ setup: "break del doc_cmd-break.html:17",
+ check: {
+ input: "break del doc_cmd-break.html:17",
+ hints: "",
+ markup: "VVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV",
+ status: "VALID",
+ args: {
+ breakpoint: { arg: " doc_cmd-break.html:17" },
+ }
+ },
+ exec: {
+ output: "Breakpoint removed"
+ }
+ }]);
+
+ yield helpers.audit(aOptions, [{
+ setup: "break list",
+ check: {
+ input: "break list",
+ hints: "",
+ markup: "VVVVVVVVVV",
+ status: "VALID"
+ },
+ exec: {
+ output: "No breakpoints set"
+ },
+ post: function () {
+ return teardown(gPanel, { noTabRemoval: true });
+ }
+ }]);
+ });
+ }).then(finish);
+}
diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_cmd-dbg.js b/devtools/client/debugger/test/mochitest/browser_dbg_cmd-dbg.js
new file mode 100644
index 000000000..e843d0cd5
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_cmd-dbg.js
@@ -0,0 +1,102 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Tests that the debugger commands work as they should.
+ */
+
+const TEST_URI = EXAMPLE_URL + "doc_cmd-dbg.html";
+
+function test() {
+ return Task.spawn(function* () {
+ let options = yield helpers.openTab(TEST_URI);
+ yield helpers.openToolbar(options);
+
+ yield helpers.audit(options, [{
+ setup: "dbg open",
+ exec: { output: "" }
+ }]);
+
+ let [gTab, gDebuggee, gPanel] = yield initDebugger(gBrowser.selectedTab);
+ let gDebugger = gPanel.panelWin;
+ let gThreadClient = gDebugger.gThreadClient;
+
+ yield helpers.audit(options, [{
+ setup: "dbg list",
+ exec: { output: /doc_cmd-dbg.html/ }
+ }]);
+
+ let button = gDebuggee.document.querySelector("input[type=button]");
+ let output = gDebuggee.document.querySelector("input[type=text]");
+
+ let cmd = function (aTyped, aState) {
+ return promise.all([
+ waitForThreadEvents(gPanel, aState),
+ helpers.audit(options, [{ setup: aTyped, exec: { output: "" } }])
+ ]);
+ };
+
+ let click = function (aElement, aState) {
+ return promise.all([
+ waitForThreadEvents(gPanel, aState),
+ executeSoon(() => EventUtils.sendMouseEvent({ type: "click" }, aElement, gDebuggee))
+ ]);
+ };
+
+ yield cmd("dbg interrupt", "paused");
+ is(gThreadClient.state, "paused", "Debugger is paused.");
+
+ yield cmd("dbg continue", "resumed");
+ isnot(gThreadClient.state, "paused", "Debugger has continued.");
+
+ yield click(button, "paused");
+ is(gThreadClient.state, "paused", "Debugger is paused again.");
+
+ yield cmd("dbg step in", "paused");
+ yield cmd("dbg step in", "paused");
+ yield cmd("dbg step in", "paused");
+ is(output.value, "step in", "Debugger stepped in.");
+
+ yield cmd("dbg step over", "paused");
+ is(output.value, "step over", "Debugger stepped over.");
+
+ yield cmd("dbg step out", "paused");
+ is(output.value, "step out", "Debugger stepped out.");
+
+ yield cmd("dbg continue", "paused");
+ is(output.value, "dbg continue", "Debugger continued.");
+
+ let closeDebugger = function () {
+ let deferred = promise.defer();
+
+ helpers.audit(options, [{
+ setup: "dbg close",
+ exec: { output: "" }
+ }])
+ .then(() => {
+ let toolbox = gDevTools.getToolbox(options.target);
+ if (!toolbox) {
+ ok(true, "Debugger is closed.");
+ deferred.resolve();
+ } else {
+ toolbox.on("destroyed", () => {
+ ok(true, "Debugger just closed.");
+ deferred.resolve();
+ });
+ }
+ });
+
+ return deferred.promise;
+ };
+
+ // We close the debugger twice to ensure 'dbg close' doesn't error when
+ // toolbox is already closed. See bug 884638 for more info.
+ yield closeDebugger();
+ yield closeDebugger();
+ yield helpers.closeToolbar(options);
+ yield helpers.closeTab(options);
+
+ }).then(finish, helpers.handleError);
+}
diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_conditional-breakpoints-01.js b/devtools/client/debugger/test/mochitest/browser_dbg_conditional-breakpoints-01.js
new file mode 100644
index 000000000..d8f9fca04
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_conditional-breakpoints-01.js
@@ -0,0 +1,218 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Bug 740825: Test the debugger conditional breakpoints.
+ */
+
+const TAB_URL = EXAMPLE_URL + "doc_conditional-breakpoints.html";
+
+function test() {
+ // Linux debug test slaves are a bit slow at this test sometimes.
+ requestLongerTimeout(2);
+
+ let options = {
+ source: TAB_URL,
+ line: 1
+ };
+ initDebugger(TAB_URL, options).then(([aTab,, aPanel]) => {
+ const gTab = aTab;
+ const gPanel = aPanel;
+ const gDebugger = gPanel.panelWin;
+ const gEditor = gDebugger.DebuggerView.editor;
+ const gSources = gDebugger.DebuggerView.Sources;
+ const queries = gDebugger.require("./content/queries");
+ const constants = gDebugger.require("./content/constants");
+ const actions = bindActionCreators(gPanel);
+ const getState = gDebugger.DebuggerController.getState;
+
+ // This test forces conditional breakpoints to be evaluated on the
+ // client-side
+ var client = gPanel.target.client;
+ client.mainRoot.traits.conditionalBreakpoints = false;
+
+ const addBreakpoints = Task.async(function* () {
+ yield actions.addBreakpoint({ actor: gSources.selectedValue, line: 18 },
+ "undefined");
+ yield actions.addBreakpoint({ actor: gSources.selectedValue, line: 19 },
+ "null");
+ yield actions.addBreakpoint({ actor: gSources.selectedValue, line: 20 },
+ "42");
+ yield actions.addBreakpoint({ actor: gSources.selectedValue, line: 21 },
+ "true");
+ yield actions.addBreakpoint({ actor: gSources.selectedValue, line: 22 },
+ "'nasu'");
+ yield actions.addBreakpoint({ actor: gSources.selectedValue, line: 23 },
+ "/regexp/");
+ yield actions.addBreakpoint({ actor: gSources.selectedValue, line: 24 },
+ "({})");
+ yield actions.addBreakpoint({ actor: gSources.selectedValue, line: 25 },
+ "(function() {})");
+ yield actions.addBreakpoint({ actor: gSources.selectedValue, line: 26 },
+ "(function() { return false; })()");
+ yield actions.addBreakpoint({ actor: gSources.selectedValue, line: 27 },
+ "a");
+ yield actions.addBreakpoint({ actor: gSources.selectedValue, line: 28 },
+ "a !== undefined");
+ yield actions.addBreakpoint({ actor: gSources.selectedValue, line: 29 },
+ "b");
+ yield actions.addBreakpoint({ actor: gSources.selectedValue, line: 30 },
+ "a !== null");
+ });
+
+ function resumeAndTestBreakpoint(line) {
+ let finished = waitForCaretUpdated(gPanel, line).then(() => testBreakpoint(line));
+
+ EventUtils.sendMouseEvent({ type: "mousedown" },
+ gDebugger.document.getElementById("resume"),
+ gDebugger);
+
+ return finished;
+ }
+
+ function resumeAndTestNoBreakpoint() {
+ let finished = waitForDebuggerEvents(gPanel, gDebugger.EVENTS.AFTER_FRAMES_CLEARED).then(() => {
+ is(gSources.itemCount, 1,
+ "Found the expected number of sources.");
+ is(gEditor.getText().indexOf("ermahgerd"), 253,
+ "The correct source was loaded initially.");
+ is(gSources.selectedValue, gSources.values[0],
+ "The correct source is selected.");
+
+ ok(gSources.selectedItem,
+ "There should be a selected source in the sources pane.");
+ ok(!gSources._selectedBreakpoint,
+ "There should be no selected breakpoint in the sources pane.");
+ is(gSources._conditionalPopupVisible, false,
+ "The breakpoint conditional expression popup should not be shown.");
+
+ is(gDebugger.document.querySelectorAll(".dbg-stackframe").length, 0,
+ "There should be no visible stackframes.");
+ is(gDebugger.document.querySelectorAll(".dbg-breakpoint").length, 13,
+ "There should be thirteen visible breakpoints.");
+ });
+
+ gDebugger.gThreadClient.resume();
+
+ return finished;
+ }
+
+ function testBreakpoint(line, highlightBreakpoint) {
+ // Highlight the breakpoint only if required.
+ if (highlightBreakpoint) {
+ let finished = waitForCaretUpdated(gPanel, line).then(() => testBreakpoint(line));
+ gSources.highlightBreakpoint({ actor: gSources.selectedValue, line: line });
+ return finished;
+ }
+
+ let selectedActor = gSources.selectedValue;
+ let selectedBreakpoint = gSources._selectedBreakpoint;
+ let selectedBreakpointItem = gSources._getBreakpoint(selectedBreakpoint);
+
+ ok(selectedActor,
+ "There should be a selected item in the sources pane.");
+ ok(selectedBreakpoint,
+ "There should be a selected breakpoint in the sources pane.");
+
+ let source = gSources.selectedItem.attachment.source;
+ let bp = queries.getBreakpoint(getState(), selectedBreakpoint.location);
+
+ ok(bp, "The selected breakpoint exists");
+ is(bp.location.actor, source.actor,
+ "The breakpoint on line " + line + " wasn't added on the correct source.");
+ is(bp.location.line, line,
+ "The breakpoint on line " + line + " wasn't found.");
+ is(!!bp.disabled, false,
+ "The breakpoint on line " + line + " should be enabled.");
+ is(!!selectedBreakpointItem.attachment.openPopup, false,
+ "The breakpoint on line " + line + " should not have opened a popup.");
+ is(gSources._conditionalPopupVisible, false,
+ "The breakpoint conditional expression popup should not have been shown.");
+ isnot(bp.condition, undefined,
+ "The breakpoint on line " + line + " should have a conditional expression.");
+ ok(isCaretPos(gPanel, line),
+ "The editor caret position is not properly set.");
+ }
+
+ const testAfterReload = Task.async(function* () {
+ let selectedActor = gSources.selectedValue;
+ let selectedBreakpoint = gSources._selectedBreakpoint;
+
+ ok(selectedActor,
+ "There should be a selected item in the sources pane after reload.");
+ ok(!selectedBreakpoint,
+ "There should be no selected breakpoint in the sources pane after reload.");
+
+ yield testBreakpoint(18, true);
+ yield testBreakpoint(19, true);
+ yield testBreakpoint(20, true);
+ yield testBreakpoint(21, true);
+ yield testBreakpoint(22, true);
+ yield testBreakpoint(23, true);
+ yield testBreakpoint(24, true);
+ yield testBreakpoint(25, true);
+ yield testBreakpoint(26, true);
+ yield testBreakpoint(27, true);
+ yield testBreakpoint(28, true);
+ yield testBreakpoint(29, true);
+ yield testBreakpoint(30, true);
+
+ is(gSources.itemCount, 1,
+ "Found the expected number of sources.");
+ is(gEditor.getText().indexOf("ermahgerd"), 253,
+ "The correct source was loaded again.");
+ is(gSources.selectedValue, gSources.values[0],
+ "The correct source is selected.");
+
+ ok(gSources.selectedItem,
+ "There should be a selected source in the sources pane.");
+ ok(gSources._selectedBreakpoint,
+ "There should be a selected breakpoint in the sources pane.");
+ is(gSources._conditionalPopupVisible, false,
+ "The breakpoint conditional expression popup should not be shown.");
+ });
+
+ Task.spawn(function* () {
+ let onCaretUpdated = waitForCaretAndScopes(gPanel, 17);
+ callInTab(gTab, "ermahgerd");
+ yield onCaretUpdated;
+
+ yield addBreakpoints();
+
+ is(gDebugger.gThreadClient.state, "paused",
+ "Should only be getting stack frames while paused.");
+ is(queries.getSourceCount(getState()), 1,
+ "Found the expected number of sources.");
+ is(gEditor.getText().indexOf("ermahgerd"), 253,
+ "The correct source was loaded initially.");
+ is(gSources.selectedValue, gSources.values[0],
+ "The correct source is selected.");
+ is(queries.getBreakpoints(getState()).length, 13,
+ "13 breakpoints currently added.");
+
+ yield resumeAndTestBreakpoint(20);
+ yield resumeAndTestBreakpoint(21);
+ yield resumeAndTestBreakpoint(22);
+ yield resumeAndTestBreakpoint(23);
+ yield resumeAndTestBreakpoint(24);
+ yield resumeAndTestBreakpoint(25);
+ yield resumeAndTestBreakpoint(27);
+ yield resumeAndTestBreakpoint(28);
+ yield resumeAndTestBreakpoint(29);
+ yield resumeAndTestBreakpoint(30);
+ yield resumeAndTestNoBreakpoint();
+
+ let sourceShown = waitForSourceShown(gPanel, ".html");
+ reload(gPanel),
+ yield sourceShown;
+
+ testAfterReload();
+
+ // Reset traits back to default value
+ client.mainRoot.traits.conditionalBreakpoints = true;
+ closeDebuggerAndFinish(gPanel);
+ });
+ });
+}
diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_conditional-breakpoints-02.js b/devtools/client/debugger/test/mochitest/browser_dbg_conditional-breakpoints-02.js
new file mode 100644
index 000000000..d3d857753
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_conditional-breakpoints-02.js
@@ -0,0 +1,219 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Bug 740825: Test the debugger conditional breakpoints.
+ */
+
+const TAB_URL = EXAMPLE_URL + "doc_conditional-breakpoints.html";
+
+function test() {
+ let options = {
+ source: TAB_URL,
+ line: 1
+ };
+ initDebugger(TAB_URL, options).then(([aTab,, aPanel]) => {
+ const gTab = aTab;
+ const gPanel = aPanel;
+ const gDebugger = gPanel.panelWin;
+ const gEditor = gDebugger.DebuggerView.editor;
+ const gSources = gDebugger.DebuggerView.Sources;
+ const queries = gDebugger.require("./content/queries");
+ const constants = gDebugger.require("./content/constants");
+ const actions = bindActionCreators(gPanel);
+ const getState = gDebugger.DebuggerController.getState;
+ const CONDITIONAL_POPUP_SHOWN = gDebugger.EVENTS.CONDITIONAL_BREAKPOINT_POPUP_SHOWN;
+
+ // This test forces conditional breakpoints to be evaluated on the
+ // client-side
+ var client = gPanel.target.client;
+ client.mainRoot.traits.conditionalBreakpoints = false;
+
+ function addBreakpoint1() {
+ return actions.addBreakpoint({ actor: gSources.selectedValue, line: 18 });
+ }
+
+ function addBreakpoint2() {
+ let finished = waitForDispatch(gPanel, constants.ADD_BREAKPOINT);
+ setCaretPosition(19);
+ gSources._onCmdAddBreakpoint();
+ return finished;
+ }
+
+ function modBreakpoint2() {
+ setCaretPosition(19);
+ let popupShown = waitForDebuggerEvents(gPanel, CONDITIONAL_POPUP_SHOWN);
+ gSources._onCmdAddConditionalBreakpoint();
+ return popupShown;
+ }
+
+ function* addBreakpoint3() {
+ let finished = waitForDispatch(gPanel, constants.ADD_BREAKPOINT);
+ let popupShown = waitForDebuggerEvents(gPanel, CONDITIONAL_POPUP_SHOWN);
+ setCaretPosition(20);
+ gSources._onCmdAddConditionalBreakpoint();
+ yield finished;
+ yield popupShown;
+ }
+
+ function* modBreakpoint3() {
+ setCaretPosition(20);
+
+ let popupShown = waitForDebuggerEvents(gPanel, CONDITIONAL_POPUP_SHOWN);
+ gSources._onCmdAddConditionalBreakpoint();
+ yield popupShown;
+
+ typeText(gSources._cbTextbox, "bamboocha");
+
+ let finished = waitForDispatch(gPanel, constants.SET_BREAKPOINT_CONDITION);
+ EventUtils.sendKey("RETURN", gDebugger);
+ yield finished;
+ }
+
+ function addBreakpoint4() {
+ let finished = waitForDispatch(gPanel, constants.ADD_BREAKPOINT);
+ setCaretPosition(21);
+ gSources._onCmdAddBreakpoint();
+ return finished;
+ }
+
+ function delBreakpoint4() {
+ let finished = waitForDispatch(gPanel, constants.REMOVE_BREAKPOINT);
+ setCaretPosition(21);
+ gSources._onCmdAddBreakpoint();
+ return finished;
+ }
+
+ function testBreakpoint(aLine, aPopupVisible, aConditionalExpression) {
+ const source = queries.getSelectedSource(getState());
+ ok(source,
+ "There should be a selected item in the sources pane.");
+
+ const bp = queries.getBreakpoint(getState(), {
+ actor: source.actor,
+ line: aLine
+ });
+ const bpItem = gSources._getBreakpoint(bp);
+ ok(bp, "There should be a breakpoint.");
+ ok(bpItem, "There should be a breakpoint in the sources pane.");
+
+ is(bp.location.actor, source.actor,
+ "The breakpoint on line " + aLine + " wasn't added on the correct source.");
+ is(bp.location.line, aLine,
+ "The breakpoint on line " + aLine + " wasn't found.");
+ is(!!bp.disabled, false,
+ "The breakpoint on line " + aLine + " should be enabled.");
+ is(gSources._conditionalPopupVisible, aPopupVisible,
+ "The breakpoint on line " + aLine + " should have a correct popup state (2).");
+ is(bp.condition, aConditionalExpression,
+ "The breakpoint on line " + aLine + " should have a correct conditional expression.");
+ }
+
+ function testNoBreakpoint(aLine) {
+ let selectedActor = gSources.selectedValue;
+ let selectedBreakpoint = gSources._selectedBreakpoint;
+
+ ok(selectedActor,
+ "There should be a selected item in the sources pane for line " + aLine + ".");
+ ok(!selectedBreakpoint,
+ "There should be no selected brekapoint in the sources pane for line " + aLine + ".");
+
+ ok(isCaretPos(gPanel, aLine),
+ "The editor caret position is not properly set.");
+ }
+
+ function setCaretPosition(aLine) {
+ gEditor.setCursor({ line: aLine - 1, ch: 0 });
+ }
+
+ function clickOnBreakpoint(aIndex) {
+ EventUtils.sendMouseEvent({ type: "click" },
+ gDebugger.document.querySelectorAll(".dbg-breakpoint")[aIndex],
+ gDebugger);
+ }
+
+ function waitForConditionUpdate() {
+ // This will close the popup and send another request to update
+ // the condition
+ gSources._hideConditionalPopup();
+ return waitForDispatch(gPanel, constants.SET_BREAKPOINT_CONDITION);
+ }
+
+ Task.spawn(function* () {
+ let onCaretUpdated = waitForCaretAndScopes(gPanel, 17);
+ callInTab(gTab, "ermahgerd");
+ yield onCaretUpdated;
+
+ is(gDebugger.gThreadClient.state, "paused",
+ "Should only be getting stack frames while paused.");
+ is(queries.getSourceCount(getState()), 1,
+ "Found the expected number of sources.");
+ is(gEditor.getText().indexOf("ermahgerd"), 253,
+ "The correct source was loaded initially.");
+ is(gSources.selectedValue, gSources.values[0],
+ "The correct source is selected.");
+
+ is(queries.getBreakpoints(getState()).length, 0,
+ "No breakpoints currently added.");
+
+ yield addBreakpoint1();
+ testBreakpoint(18, false, undefined);
+
+ yield addBreakpoint2();
+ testBreakpoint(19, false, undefined);
+ yield modBreakpoint2();
+ testBreakpoint(19, true, undefined);
+ yield waitForConditionUpdate();
+ yield addBreakpoint3();
+ testBreakpoint(20, true, "");
+ yield waitForConditionUpdate();
+ yield modBreakpoint3();
+ testBreakpoint(20, false, "bamboocha");
+ yield addBreakpoint4();
+ testBreakpoint(21, false, undefined);
+ yield delBreakpoint4();
+
+ setCaretPosition(18);
+ is(gSources._selectedBreakpoint.location.line, 18,
+ "The selected breakpoint is line 18");
+ yield testBreakpoint(18, false, undefined);
+
+ setCaretPosition(19);
+ is(gSources._selectedBreakpoint.location.line, 19,
+ "The selected breakpoint is line 19");
+ yield testBreakpoint(19, false, "");
+
+ setCaretPosition(20);
+ is(gSources._selectedBreakpoint.location.line, 20,
+ "The selected breakpoint is line 20");
+ yield testBreakpoint(20, false, "bamboocha");
+
+ setCaretPosition(17);
+ yield testNoBreakpoint(17);
+
+ setCaretPosition(21);
+ yield testNoBreakpoint(21);
+
+ clickOnBreakpoint(0);
+ is(gSources._selectedBreakpoint.location.line, 18,
+ "The selected breakpoint is line 18");
+ yield testBreakpoint(18, false, undefined);
+
+ clickOnBreakpoint(1);
+ is(gSources._selectedBreakpoint.location.line, 19,
+ "The selected breakpoint is line 19");
+ yield testBreakpoint(19, false, "");
+
+ clickOnBreakpoint(2);
+ is(gSources._selectedBreakpoint.location.line, 20,
+ "The selected breakpoint is line 20");
+ testBreakpoint(20, true, "bamboocha");
+
+ // Reset traits back to default value
+ client.mainRoot.traits.conditionalBreakpoints = true;
+ resumeDebuggerThenCloseAndFinish(gPanel);
+ });
+ });
+}
diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_conditional-breakpoints-03.js b/devtools/client/debugger/test/mochitest/browser_dbg_conditional-breakpoints-03.js
new file mode 100644
index 000000000..fbd9c6ae8
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_conditional-breakpoints-03.js
@@ -0,0 +1,78 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Tests that conditional breakpoint expressions survive disabled breakpoints.
+ */
+
+const TAB_URL = EXAMPLE_URL + "doc_conditional-breakpoints.html";
+
+function test() {
+ initDebugger().then(([aTab,, aPanel]) => {
+ const gTab = aTab;
+ const gPanel = aPanel;
+ const gDebugger = gPanel.panelWin;
+ const gSources = gDebugger.DebuggerView.Sources;
+ const queries = gDebugger.require("./content/queries");
+ const constants = gDebugger.require("./content/constants");
+ const actions = bindActionCreators(gPanel);
+ const getState = gDebugger.DebuggerController.getState;
+
+ // This test forces conditional breakpoints to be evaluated on the
+ // client-side
+ var client = gPanel.target.client;
+ client.mainRoot.traits.conditionalBreakpoints = false;
+
+ function waitForConditionUpdate() {
+ // This will close the popup and send another request to update
+ // the condition
+ gSources._hideConditionalPopup();
+ return waitForDispatch(gPanel, constants.SET_BREAKPOINT_CONDITION);
+ }
+
+ Task.spawn(function* () {
+ let onCaretUpdated = waitForCaretUpdated(gPanel, 17);
+ yield navigateActiveTabTo(gPanel,
+ TAB_URL,
+ gDebugger.EVENTS.SOURCE_SHOWN);
+ callInTab(gTab, "ermahgerd");
+ yield onCaretUpdated;
+
+ const location = { actor: gSources.selectedValue, line: 18 };
+
+ yield actions.addBreakpoint(location, "hello");
+ yield actions.disableBreakpoint(location);
+ yield actions.addBreakpoint(location);
+
+ const bp = queries.getBreakpoint(getState(), location);
+ is(bp.condition, "hello", "The conditional expression is correct.");
+
+ let finished = waitForDebuggerEvents(gPanel, gDebugger.EVENTS.CONDITIONAL_BREAKPOINT_POPUP_SHOWN);
+ EventUtils.sendMouseEvent({ type: "click" },
+ gDebugger.document.querySelector(".dbg-breakpoint"),
+ gDebugger);
+ yield finished;
+
+ const textbox = gDebugger.document.getElementById("conditional-breakpoint-panel-textbox");
+ is(textbox.value, "hello", "The expression is correct (2).");
+
+ yield waitForConditionUpdate();
+ yield actions.disableBreakpoint(location);
+ yield actions.setBreakpointCondition(location, "foo");
+ yield actions.addBreakpoint(location);
+
+ finished = waitForDebuggerEvents(gPanel, gDebugger.EVENTS.CONDITIONAL_BREAKPOINT_POPUP_SHOWN);
+ EventUtils.sendMouseEvent({ type: "click" },
+ gDebugger.document.querySelector(".dbg-breakpoint"),
+ gDebugger);
+ yield finished;
+ is(textbox.value, "foo", "The expression is correct (3).");
+
+ // Reset traits back to default value
+ client.mainRoot.traits.conditionalBreakpoints = true;
+ resumeDebuggerThenCloseAndFinish(gPanel);
+ });
+ });
+}
diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_conditional-breakpoints-04.js b/devtools/client/debugger/test/mochitest/browser_dbg_conditional-breakpoints-04.js
new file mode 100644
index 000000000..55b217405
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_conditional-breakpoints-04.js
@@ -0,0 +1,52 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Make sure that conditional breakpoints with blank expressions
+ * maintain their conditions after enabling them.
+ */
+
+const TAB_URL = EXAMPLE_URL + "doc_conditional-breakpoints.html";
+
+function test() {
+ let options = {
+ source: TAB_URL,
+ line: 1
+ };
+ initDebugger(TAB_URL, options).then(([aTab,, aPanel]) => {
+ const gTab = aTab;
+ const gPanel = aPanel;
+ const gDebugger = gPanel.panelWin;
+ const gSources = gDebugger.DebuggerView.Sources;
+ const queries = gDebugger.require("./content/queries");
+ const constants = gDebugger.require("./content/constants");
+ const actions = bindActionCreators(gPanel);
+ const getState = gDebugger.DebuggerController.getState;
+
+ // This test forces conditional breakpoints to be evaluated on the
+ // client-side
+ var client = gPanel.target.client;
+ client.mainRoot.traits.conditionalBreakpoints = false;
+
+ Task.spawn(function* () {
+ let onCaretUpdated = waitForCaretAndScopes(gPanel, 17);
+ callInTab(gTab, "ermahgerd");
+ yield onCaretUpdated;
+
+ const location = { actor: gSources.selectedValue, line: 18 };
+
+ yield actions.addBreakpoint(location, "");
+ yield actions.disableBreakpoint(location);
+ yield actions.addBreakpoint(location);
+
+ const bp = queries.getBreakpoint(getState(), location);
+ is(bp.condition, "", "The conditional expression is correct.");
+
+ // Reset traits back to default value
+ client.mainRoot.traits.conditionalBreakpoints = true;
+ resumeDebuggerThenCloseAndFinish(gPanel);
+ });
+ });
+}
diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_conditional-breakpoints-05.js b/devtools/client/debugger/test/mochitest/browser_dbg_conditional-breakpoints-05.js
new file mode 100644
index 000000000..f143ef535
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_conditional-breakpoints-05.js
@@ -0,0 +1,141 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Make sure that conditional breakpoints with an exception-throwing expression
+ * could pause on hit
+ */
+
+const TAB_URL = EXAMPLE_URL + "doc_conditional-breakpoints.html";
+
+function test() {
+ let options = {
+ source: TAB_URL,
+ line: 1
+ };
+ initDebugger(TAB_URL, options).then(([aTab,, aPanel]) => {
+ const gTab = aTab;
+ const gPanel = aPanel;
+ const gDebugger = gPanel.panelWin;
+ const gEditor = gDebugger.DebuggerView.editor;
+ const gSources = gDebugger.DebuggerView.Sources;
+ const queries = gDebugger.require("./content/queries");
+ const constants = gDebugger.require("./content/constants");
+ const actions = bindActionCreators(gPanel);
+ const getState = gDebugger.DebuggerController.getState;
+
+ // This test forces conditional breakpoints to be evaluated on the
+ // client-side
+ var client = gPanel.target.client;
+ client.mainRoot.traits.conditionalBreakpoints = false;
+
+ function resumeAndTestBreakpoint(line) {
+ let finished = waitForCaretUpdated(gPanel, line).then(() => testBreakpoint(line));
+ EventUtils.sendMouseEvent({ type: "mousedown" },
+ gDebugger.document.getElementById("resume"),
+ gDebugger);
+
+ return finished;
+ }
+
+ function resumeAndTestNoBreakpoint() {
+ let finished = waitForDebuggerEvents(gPanel, gDebugger.EVENTS.AFTER_FRAMES_CLEARED).then(() => {
+ is(gSources.itemCount, 1,
+ "Found the expected number of sources.");
+ is(gEditor.getText().indexOf("ermahgerd"), 253,
+ "The correct source was loaded initially.");
+ is(gSources.selectedValue, gSources.values[0],
+ "The correct source is selected.");
+
+ ok(gSources.selectedItem,
+ "There should be a selected source in the sources pane.");
+ ok(!gSources._selectedBreakpoint,
+ "There should be no selected breakpoint in the sources pane.");
+ is(gSources._conditionalPopupVisible, false,
+ "The breakpoint conditional expression popup should not be shown.");
+
+ is(gDebugger.document.querySelectorAll(".dbg-stackframe").length, 0,
+ "There should be no visible stackframes.");
+ is(gDebugger.document.querySelectorAll(".dbg-breakpoint").length, 6,
+ "There should be thirteen visible breakpoints.");
+ });
+
+ gDebugger.gThreadClient.resume();
+
+ return finished;
+ }
+
+ function testBreakpoint(line, highlightBreakpoint) {
+ // Highlight the breakpoint only if required.
+ if (highlightBreakpoint) {
+ let finished = waitForCaretUpdated(gPanel, line).then(() => testBreakpoint(line));
+ gSources.highlightBreakpoint({ actor: gSources.selectedValue, line: line });
+ return finished;
+ }
+
+ let selectedActor = gSources.selectedValue;
+ let selectedBreakpoint = gSources._selectedBreakpoint;
+ let selectedBreakpointItem = gSources._getBreakpoint(selectedBreakpoint);
+ let source = queries.getSource(getState(), selectedActor);
+
+ ok(selectedActor,
+ "There should be a selected item in the sources pane.");
+ ok(selectedBreakpoint,
+ "There should be a selected breakpoint.");
+ ok(selectedBreakpointItem,
+ "There should be a selected breakpoint item in the sources pane.");
+
+ is(selectedBreakpoint.location.actor, source.actor,
+ "The breakpoint on line " + line + " wasn't added on the correct source.");
+ is(selectedBreakpoint.location.line, line,
+ "The breakpoint on line " + line + " wasn't found.");
+ is(!!selectedBreakpoint.location.disabled, false,
+ "The breakpoint on line " + line + " should be enabled.");
+ is(gSources._conditionalPopupVisible, false,
+ "The breakpoint conditional expression popup should not have been shown.");
+
+ isnot(selectedBreakpoint.condition, undefined,
+ "The breakpoint on line " + line + " should have a conditional expression.");
+
+ ok(isCaretPos(gPanel, line),
+ "The editor caret position is not properly set.");
+ }
+
+ Task.spawn(function* () {
+ let onCaretUpdated = waitForCaretAndScopes(gPanel, 17);
+ callInTab(gTab, "ermahgerd");
+ yield onCaretUpdated;
+
+ yield actions.addBreakpoint(
+ { actor: gSources.selectedValue, line: 18 }, " 1a"
+ );
+ yield actions.addBreakpoint(
+ { actor: gSources.selectedValue, line: 19 }, "new Error()"
+ );
+ yield actions.addBreakpoint(
+ { actor: gSources.selectedValue, line: 20 }, "true"
+ );
+ yield actions.addBreakpoint(
+ { actor: gSources.selectedValue, line: 21 }, "false"
+ );
+ yield actions.addBreakpoint(
+ { actor: gSources.selectedValue, line: 22 }, "0"
+ );
+ yield actions.addBreakpoint(
+ { actor: gSources.selectedValue, line: 23 }, "randomVar"
+ );
+
+ yield resumeAndTestBreakpoint(18);
+ yield resumeAndTestBreakpoint(19);
+ yield resumeAndTestBreakpoint(20);
+ yield resumeAndTestBreakpoint(23);
+ yield resumeAndTestNoBreakpoint();
+
+ // Reset traits back to default value
+ client.mainRoot.traits.conditionalBreakpoints = true;
+ closeDebuggerAndFinish(gPanel);
+ });
+ });
+}
diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_console-eval.js b/devtools/client/debugger/test/mochitest/browser_dbg_console-eval.js
new file mode 100644
index 000000000..37e0be1b1
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_console-eval.js
@@ -0,0 +1,41 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Breaking in the middle of a script evaluated by the console should
+ * work
+ */
+
+function test() {
+ Task.spawn(function* () {
+ let TAB_URL = EXAMPLE_URL + "doc_empty-tab-01.html";
+ let [,, panel] = yield initDebugger(TAB_URL, { source: null });
+ let dbgWin = panel.panelWin;
+ let sources = dbgWin.DebuggerView.Sources;
+ let frames = dbgWin.DebuggerView.StackFrames;
+ let editor = dbgWin.DebuggerView.editor;
+ let toolbox = gDevTools.getToolbox(panel.target);
+
+ let paused = promise.all([
+ waitForEditorEvents(panel, "cursorActivity"),
+ waitForDebuggerEvents(panel, dbgWin.EVENTS.SOURCE_SHOWN)
+ ]);
+
+ toolbox.once("webconsole-ready", () => {
+ ok(toolbox.splitConsole, "Split console is shown.");
+ let jsterm = toolbox.getPanel("webconsole").hud.jsterm;
+ jsterm.execute("debugger");
+ });
+ EventUtils.synthesizeKey("VK_ESCAPE", {}, dbgWin);
+
+ yield paused;
+ is(sources.selectedItem.attachment.label, "SCRIPT0",
+ "Anonymous source is selected in sources");
+ ok(editor.getText() === "debugger", "Editor has correct text");
+
+ yield toolbox.closeSplitConsole();
+ yield resumeDebuggerThenCloseAndFinish(panel);
+ });
+}
diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_console-named-eval.js b/devtools/client/debugger/test/mochitest/browser_dbg_console-named-eval.js
new file mode 100644
index 000000000..a6c3c96bb
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_console-named-eval.js
@@ -0,0 +1,42 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Breaking in the middle of a named eval script created by the
+ * console should work
+ */
+
+function test() {
+ Task.spawn(runTests);
+}
+
+function* runTests() {
+ let TAB_URL = EXAMPLE_URL + "doc_empty-tab-01.html";
+ let [,, panel] = yield initDebugger(TAB_URL, { source: null });
+ let dbgWin = panel.panelWin;
+ let sources = dbgWin.DebuggerView.Sources;
+ let frames = dbgWin.DebuggerView.StackFrames;
+ let editor = dbgWin.DebuggerView.editor;
+ let toolbox = gDevTools.getToolbox(panel.target);
+
+ let paused = waitForSourceAndCaretAndScopes(panel, "foo.js", 1);
+
+ toolbox.once("webconsole-ready", () => {
+ ok(toolbox.splitConsole, "Split console is shown.");
+ let jsterm = toolbox.getPanel("webconsole").hud.jsterm;
+ jsterm.execute("eval('debugger; //# sourceURL=foo.js')");
+ });
+ EventUtils.synthesizeKey("VK_ESCAPE", {}, dbgWin);
+
+ yield paused;
+ is(sources.selectedItem.attachment.label, "foo.js",
+ "New source is selected in sources");
+ is(sources.selectedItem.attachment.group, "http://example.com",
+ "New source is in the right group");
+ ok(editor.getText() === "debugger; //# sourceURL=foo.js", "Editor has correct text");
+
+ yield toolbox.closeSplitConsole();
+ yield resumeDebuggerThenCloseAndFinish(panel);
+}
diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_controller-evaluate-01.js b/devtools/client/debugger/test/mochitest/browser_dbg_controller-evaluate-01.js
new file mode 100644
index 000000000..9db259b98
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_controller-evaluate-01.js
@@ -0,0 +1,106 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Tests the public evaluation API from the debugger controller.
+ */
+
+const TAB_URL = EXAMPLE_URL + "doc_script-switching-01.html";
+
+function test() {
+ Task.spawn(function* () {
+ const options = {
+ source: EXAMPLE_URL + "code_script-switching-01.js",
+ line: 1
+ };
+ const [tab,, panel] = yield initDebugger(TAB_URL, options);
+ const win = panel.panelWin;
+ const frames = win.DebuggerController.StackFrames;
+ const framesView = win.DebuggerView.StackFrames;
+ const sourcesView = win.DebuggerView.Sources;
+ const editorView = win.DebuggerView.editor;
+ const events = win.EVENTS;
+ const queries = win.require("./content/queries");
+ const constants = win.require("./content/constants");
+ const actions = bindActionCreators(panel);
+ const getState = win.DebuggerController.getState;
+
+ function checkView(frameDepth, selectedSource, caretLine, editorText) {
+ is(win.gThreadClient.state, "paused",
+ "Should only be getting stack frames while paused.");
+ is(framesView.itemCount, 2,
+ "Should have four frames.");
+ is(framesView.selectedDepth, frameDepth,
+ "The correct frame is selected in the widget.");
+ is(sourcesView.selectedIndex, selectedSource,
+ "The correct source is selected in the widget.");
+ ok(isCaretPos(panel, caretLine),
+ "Editor caret location is correct.");
+ is(editorView.getText().search(editorText[0]), editorText[1],
+ "The correct source is not displayed.");
+ }
+
+ // Cache the sources text to avoid having to wait for their
+ // retrieval.
+ const sources = queries.getSources(getState());
+ yield promise.all(Object.keys(sources).map(k => {
+ return actions.loadSourceText(sources[k]);
+ }));
+
+ is(Object.keys(getState().sources.sourcesText).length, 2,
+ "There should be two cached sources in the cache.");
+
+ // Eval while not paused.
+ try {
+ yield frames.evaluate("foo");
+ } catch (error) {
+ is(error.message, "No stack frame available.",
+ "Evaluating shouldn't work while the debuggee isn't paused.");
+ }
+
+ callInTab(tab, "firstCall");
+ yield waitForSourceAndCaretAndScopes(panel, "-02.js", 6);
+ checkView(0, 1, 6, [/secondCall/, 118]);
+
+ // Eval in the topmost frame, while paused.
+ let updatedView = waitForDebuggerEvents(panel, events.FETCHED_SCOPES);
+ let result = yield frames.evaluate("foo");
+ ok(!result.throw, "The evaluation hasn't thrown.");
+ is(result.return.type, "object", "The evaluation return type is correct.");
+ is(result.return.class, "Function", "The evaluation return class is correct.");
+
+ yield updatedView;
+ checkView(0, 1, 6, [/secondCall/, 118]);
+ ok(true, "Evaluating in the topmost frame works properly.");
+
+ // Eval in a different frame, while paused.
+ updatedView = waitForDebuggerEvents(panel, events.FETCHED_SCOPES);
+ try {
+ yield frames.evaluate("foo", { depth: 1 }); // oldest frame
+ } catch (result) {
+ is(result.return.type, "object", "The evaluation thrown type is correct.");
+ is(result.return.class, "Error", "The evaluation thrown class is correct.");
+ ok(!result.return, "The evaluation hasn't returned.");
+ }
+
+ yield updatedView;
+ checkView(0, 1, 6, [/secondCall/, 118]);
+ ok(true, "Evaluating in a custom frame works properly.");
+
+ // Eval in a non-existent frame, while paused.
+ waitForDebuggerEvents(panel, events.FETCHED_SCOPES).then(() => {
+ ok(false, "Shouldn't have updated the view when trying to evaluate " +
+ "an expression in a non-existent stack frame.");
+ });
+ try {
+ yield frames.evaluate("foo", { depth: 4 }); // non-existent frame
+ } catch (error) {
+ is(error.message, "No stack frame available.",
+ "Evaluating shouldn't work if the specified frame doesn't exist.");
+ }
+
+ yield resumeDebuggerThenCloseAndFinish(panel);
+ });
+}
diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_controller-evaluate-02.js b/devtools/client/debugger/test/mochitest/browser_dbg_controller-evaluate-02.js
new file mode 100644
index 000000000..ff4092e1d
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_controller-evaluate-02.js
@@ -0,0 +1,78 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Tests the public evaluation API from the debugger controller.
+ */
+
+const TAB_URL = EXAMPLE_URL + "doc_script-switching-01.html";
+
+function test() {
+ Task.spawn(function* () {
+ const options = {
+ source: EXAMPLE_URL + "code_script-switching-01.js",
+ line: 1
+ };
+ const [tab,, panel] = yield initDebugger(TAB_URL, options);
+ const win = panel.panelWin;
+ const frames = win.DebuggerController.StackFrames;
+ const framesView = win.DebuggerView.StackFrames;
+ const sourcesView = win.DebuggerView.Sources;
+ const editorView = win.DebuggerView.editor;
+ const events = win.EVENTS;
+ const queries = win.require("./content/queries");
+ const constants = win.require("./content/constants");
+ const actions = bindActionCreators(panel);
+ const getState = win.DebuggerController.getState;
+
+ function checkView(selectedFrame, selectedSource, caretLine, editorText) {
+ is(win.gThreadClient.state, "paused",
+ "Should only be getting stack frames while paused.");
+ is(framesView.itemCount, 2,
+ "Should have four frames.");
+ is(framesView.selectedDepth, selectedFrame,
+ "The correct frame is selected in the widget.");
+ is(sourcesView.selectedIndex, selectedSource,
+ "The correct source is selected in the widget.");
+ ok(isCaretPos(panel, caretLine),
+ "Editor caret location is correct.");
+ is(editorView.getText().search(editorText[0]), editorText[1],
+ "The correct source is not displayed.");
+ }
+
+ // Cache the sources text to avoid having to wait for their
+ // retrieval.
+ const sources = queries.getSources(getState());
+ yield promise.all(Object.keys(sources).map(k => {
+ return actions.loadSourceText(sources[k]);
+ }));
+
+ // Allow this generator function to yield first.
+ callInTab(tab, "firstCall");
+ yield waitForSourceAndCaretAndScopes(panel, "-02.js", 6);
+ checkView(0, 1, 6, [/secondCall/, 118]);
+
+ // Change the selected frame and eval inside it.
+ let updatedFrame = waitForDebuggerEvents(panel, events.FETCHED_SCOPES);
+ framesView.selectedDepth = 1; // oldest frame
+ yield updatedFrame;
+ checkView(1, 0, 5, [/firstCall/, 118]);
+
+ let updatedView = waitForDebuggerEvents(panel, events.FETCHED_SCOPES);
+ try {
+ yield frames.evaluate("foo");
+ } catch (result) {
+ is(result.return.type, "object", "The evaluation thrown type is correct.");
+ is(result.return.class, "Error", "The evaluation thrown class is correct.");
+ ok(!result.return, "The evaluation hasn't returned.");
+ }
+
+ yield updatedView;
+ checkView(1, 0, 5, [/firstCall/, 118]);
+ ok(true, "Evaluating while in a user-selected frame works properly.");
+
+ yield resumeDebuggerThenCloseAndFinish(panel);
+ });
+}
diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_debugger-statement.js b/devtools/client/debugger/test/mochitest/browser_dbg_debugger-statement.js
new file mode 100644
index 000000000..7378123f8
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_debugger-statement.js
@@ -0,0 +1,87 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Tests the behavior of the debugger statement.
+ */
+
+const TAB_URL = EXAMPLE_URL + "doc_inline-debugger-statement.html";
+
+var gClient;
+var gTab;
+
+function test() {
+ if (!DebuggerServer.initialized) {
+ DebuggerServer.init();
+ DebuggerServer.addBrowserActors();
+ }
+
+ let transport = DebuggerServer.connectPipe();
+ gClient = new DebuggerClient(transport);
+ gClient.connect().then(([aType, aTraits]) => {
+ is(aType, "browser",
+ "Root actor should identify itself as a browser.");
+
+ addTab(TAB_URL)
+ .then((aTab) => {
+ gTab = aTab;
+ return attachTabActorForUrl(gClient, TAB_URL);
+ })
+ .then(testEarlyDebuggerStatement)
+ .then(testDebuggerStatement)
+ .then(() => gClient.close())
+ .then(finish)
+ .then(null, aError => {
+ ok(false, "Got an error: " + aError.message + "\n" + aError.stack);
+ });
+ });
+}
+
+function testEarlyDebuggerStatement([aGrip, aResponse]) {
+ let deferred = promise.defer();
+
+ let onPaused = function (aEvent, aPacket) {
+ ok(false, "Pause shouldn't be called before we've attached!");
+ deferred.reject();
+ };
+
+ gClient.addListener("paused", onPaused);
+
+ // This should continue without nesting an event loop and calling
+ // the onPaused hook, because we haven't attached yet.
+ callInTab(gTab, "runDebuggerStatement");
+
+ gClient.removeListener("paused", onPaused);
+
+ // Now attach and resume...
+ gClient.request({ to: aResponse.threadActor, type: "attach" }, () => {
+ gClient.request({ to: aResponse.threadActor, type: "resume" }, () => {
+ ok(true, "Pause wasn't called before we've attached.");
+ deferred.resolve([aGrip, aResponse]);
+ });
+ });
+
+ return deferred.promise;
+}
+
+function testDebuggerStatement([aGrip, aResponse]) {
+ let deferred = promise.defer();
+
+ gClient.addListener("paused", (aEvent, aPacket) => {
+ gClient.request({ to: aResponse.threadActor, type: "resume" }, () => {
+ ok(true, "The pause handler was triggered on a debugger statement.");
+ deferred.resolve();
+ });
+ });
+
+ // Reach around the debugging protocol and execute the debugger statement.
+ callInTab(gTab, "runDebuggerStatement");
+
+ return deferred.promise;
+}
+
+registerCleanupFunction(function () {
+ gClient = null;
+});
diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_editor-contextmenu.js b/devtools/client/debugger/test/mochitest/browser_dbg_editor-contextmenu.js
new file mode 100644
index 000000000..b42e3e123
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_editor-contextmenu.js
@@ -0,0 +1,68 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Bug 731394: Test the debugger source editor default context menu.
+ */
+
+const TAB_URL = EXAMPLE_URL + "doc_script-switching-01.html";
+
+function test() {
+ let gTab, gPanel, gDebugger;
+ let gEditor, gSources, gContextMenu;
+
+ let options = {
+ source: EXAMPLE_URL + "code_script-switching-01.js",
+ line: 1
+ };
+ initDebugger(TAB_URL, options).then(([aTab,, aPanel]) => {
+ gTab = aTab;
+ gPanel = aPanel;
+ gDebugger = gPanel.panelWin;
+ gEditor = gDebugger.DebuggerView.editor;
+ gSources = gDebugger.DebuggerView.Sources;
+ gContextMenu = gDebugger.document.getElementById("sourceEditorContextMenu");
+
+ waitForSourceAndCaretAndScopes(gPanel, "-02.js", 1).then(performTest).then(null, info);
+ callInTab(gTab, "firstCall");
+ });
+
+ function performTest() {
+ is(gDebugger.gThreadClient.state, "paused",
+ "Should only be getting stack frames while paused.");
+ is(gSources.itemCount, 2,
+ "Found the expected number of sources.");
+ is(gEditor.getText().indexOf("debugger"), 166,
+ "The correct source was loaded initially.");
+ is(gSources.selectedValue, gSources.values[1],
+ "The correct source is selected.");
+
+ is(gEditor.getText().indexOf("\u263a"), 162,
+ "Unicode characters are converted correctly.");
+
+ ok(gContextMenu,
+ "The source editor's context menupopup is available.");
+ ok(gEditor.getOption("readOnly"),
+ "The source editor is read only.");
+
+ gEditor.focus();
+ gEditor.setSelection({ line: 1, ch: 0 }, { line: 1, ch: 10 });
+
+ once(gContextMenu, "popupshown").then(testContextMenu).then(null, info);
+ gContextMenu.openPopup(gEditor.container, "overlap", 0, 0, true, false);
+ }
+
+ function testContextMenu() {
+ let document = gDebugger.document;
+
+ ok(document.getElementById("editMenuCommands"),
+ "#editMenuCommands found.");
+ ok(!document.getElementById("editMenuKeys"),
+ "#editMenuKeys not found.");
+
+ gContextMenu.hidePopup();
+ resumeDebuggerThenCloseAndFinish(gPanel);
+ }
+}
diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_editor-mode.js b/devtools/client/debugger/test/mochitest/browser_dbg_editor-mode.js
new file mode 100644
index 000000000..5982d9afd
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_editor-mode.js
@@ -0,0 +1,97 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Make sure that updating the editor mode sets the right highlighting engine,
+ * and source URIs with extra query parameters also get the right engine.
+ */
+
+const TAB_URL = EXAMPLE_URL + "doc_editor-mode.html";
+
+var gTab, gPanel, gDebugger;
+var gEditor, gSources;
+
+function test() {
+ let options = {
+ source: EXAMPLE_URL + "code_script-switching-01.js?a=b",
+ line: 1
+ };
+ initDebugger(TAB_URL, options).then(([aTab,, aPanel]) => {
+ gTab = aTab;
+ gPanel = aPanel;
+ gDebugger = gPanel.panelWin;
+ gEditor = gDebugger.DebuggerView.editor;
+ gSources = gDebugger.DebuggerView.Sources;
+
+ waitForSourceAndCaretAndScopes(gPanel, "code_test-editor-mode", 1)
+ .then(testInitialSource)
+ .then(testSwitch1)
+ .then(testSwitch2)
+ .then(() => resumeDebuggerThenCloseAndFinish(gPanel))
+ .then(null, aError => {
+ ok(false, "Got an error: " + aError.message + "\n" + aError.stack);
+ });
+
+ callInTab(gTab, "firstCall");
+ });
+}
+
+function testInitialSource() {
+ is(gSources.itemCount, 3,
+ "Found the expected number of sources.");
+
+ is(gEditor.getMode().name, "text",
+ "Found the expected editor mode.");
+ is(gEditor.getText().search(/firstCall/), -1,
+ "The first source is not displayed.");
+ is(gEditor.getText().search(/debugger/), 135,
+ "The second source is displayed.");
+ is(gEditor.getText().search(/banana/), -1,
+ "The third source is not displayed.");
+
+ let finished = waitForDebuggerEvents(gPanel, gDebugger.EVENTS.SOURCE_SHOWN);
+ gSources.selectedItem = e => e.attachment.label == "code_script-switching-01.js";
+ return finished;
+}
+
+function testSwitch1() {
+ is(gSources.itemCount, 3,
+ "Found the expected number of sources.");
+
+ is(gEditor.getMode().name, "javascript",
+ "Found the expected editor mode.");
+ is(gEditor.getText().search(/firstCall/), 118,
+ "The first source is displayed.");
+ is(gEditor.getText().search(/debugger/), -1,
+ "The second source is not displayed.");
+ is(gEditor.getText().search(/banana/), -1,
+ "The third source is not displayed.");
+
+ let finished = waitForDebuggerEvents(gPanel, gDebugger.EVENTS.SOURCE_SHOWN);
+ gSources.selectedItem = e => e.attachment.label == "doc_editor-mode.html";
+ return finished;
+}
+
+function testSwitch2() {
+ is(gSources.itemCount, 3,
+ "Found the expected number of sources.");
+
+ is(gEditor.getMode().name, "htmlmixed",
+ "Found the expected editor mode.");
+ is(gEditor.getText().search(/firstCall/), -1,
+ "The first source is not displayed.");
+ is(gEditor.getText().search(/debugger/), -1,
+ "The second source is not displayed.");
+ is(gEditor.getText().search(/banana/), 443,
+ "The third source is displayed.");
+}
+
+registerCleanupFunction(function () {
+ gTab = null;
+ gPanel = null;
+ gDebugger = null;
+ gEditor = null;
+ gSources = null;
+});
diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_event-listeners-01.js b/devtools/client/debugger/test/mochitest/browser_dbg_event-listeners-01.js
new file mode 100644
index 000000000..fa24e4507
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_event-listeners-01.js
@@ -0,0 +1,147 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Tests that the eventListeners request works.
+ */
+
+const TAB_URL = EXAMPLE_URL + "doc_event-listeners-01.html";
+
+var gClient;
+var gTab;
+
+function test() {
+ if (!DebuggerServer.initialized) {
+ DebuggerServer.init();
+ DebuggerServer.addBrowserActors();
+ }
+
+ let transport = DebuggerServer.connectPipe();
+ gClient = new DebuggerClient(transport);
+ gClient.connect().then(([aType, aTraits]) => {
+ is(aType, "browser",
+ "Root actor should identify itself as a browser.");
+
+ addTab(TAB_URL)
+ .then((aTab) => {
+ gTab = aTab;
+ return attachThreadActorForUrl(gClient, TAB_URL);
+ })
+ .then(pauseDebuggee)
+ .then(testEventListeners)
+ .then(() => gClient.close())
+ .then(finish)
+ .then(null, aError => {
+ ok(false, "Got an error: " + aError.message + "\n" + aError.stack);
+ });
+ });
+}
+
+function pauseDebuggee(aThreadClient) {
+ let deferred = promise.defer();
+
+ gClient.addOneTimeListener("paused", (aEvent, aPacket) => {
+ is(aPacket.type, "paused",
+ "We should now be paused.");
+ is(aPacket.why.type, "debuggerStatement",
+ "The debugger statement was hit.");
+
+ deferred.resolve(aThreadClient);
+ });
+
+ generateMouseClickInTab(gTab, "content.document.querySelector('button')");
+
+ return deferred.promise;
+}
+
+function testEventListeners(aThreadClient) {
+ let deferred = promise.defer();
+
+ aThreadClient.eventListeners(aPacket => {
+ if (aPacket.error) {
+ let msg = "Error getting event listeners: " + aPacket.message;
+ ok(false, msg);
+ deferred.reject(msg);
+ return;
+ }
+
+ is(aPacket.listeners.length, 3,
+ "Found all event listeners.");
+
+ promise.all(aPacket.listeners.map(listener => {
+ const lDeferred = promise.defer();
+ aThreadClient.pauseGrip(listener.function).getDefinitionSite(aResponse => {
+ if (aResponse.error) {
+ const msg = "Error getting function definition site: " + aResponse.message;
+ ok(false, msg);
+ lDeferred.reject(msg);
+ return;
+ }
+ listener.function.url = aResponse.source.url;
+ lDeferred.resolve(listener);
+ });
+ return lDeferred.promise;
+ })).then(listeners => {
+ let types = [];
+
+ for (let l of listeners) {
+ info("Listener for the " + l.type + " event.");
+ let node = l.node;
+ ok(node, "There is a node property.");
+ ok(node.object, "There is a node object property.");
+ ok(node.selector == "window" ||
+ content.document.querySelectorAll(node.selector).length == 1,
+ "The node property is a unique CSS selector.");
+
+ let func = l.function;
+ ok(func, "There is a function property.");
+ is(func.type, "object", "The function form is of type 'object'.");
+ is(func.class, "Function", "The function form is of class 'Function'.");
+
+ // The onchange handler is an inline string that doesn't have
+ // a URL because it's basically eval'ed
+ if (l.type !== "change") {
+ is(func.url, TAB_URL, "The function url is correct.");
+ }
+
+ is(l.allowsUntrusted, true,
+ "'allowsUntrusted' property has the right value.");
+ is(l.inSystemEventGroup, false,
+ "'inSystemEventGroup' property has the right value.");
+
+ types.push(l.type);
+
+ if (l.type == "keyup") {
+ is(l.capturing, true,
+ "Capturing property has the right value.");
+ is(l.isEventHandler, false,
+ "'isEventHandler' property has the right value.");
+ } else if (l.type == "load") {
+ is(l.capturing, false,
+ "Capturing property has the right value.");
+ is(l.isEventHandler, false,
+ "'isEventHandler' property has the right value.");
+ } else {
+ is(l.capturing, false,
+ "Capturing property has the right value.");
+ is(l.isEventHandler, true,
+ "'isEventHandler' property has the right value.");
+ }
+ }
+
+ ok(types.indexOf("click") != -1, "Found the click handler.");
+ ok(types.indexOf("change") != -1, "Found the change handler.");
+ ok(types.indexOf("keyup") != -1, "Found the keyup handler.");
+
+ aThreadClient.resume(deferred.resolve);
+ });
+ });
+
+ return deferred.promise;
+}
+
+registerCleanupFunction(function () {
+ gClient = null;
+});
diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_event-listeners-02.js b/devtools/client/debugger/test/mochitest/browser_dbg_event-listeners-02.js
new file mode 100644
index 000000000..d7b13e4c5
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_event-listeners-02.js
@@ -0,0 +1,123 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Tests that the eventListeners request works when bound functions are used as
+ * event listeners.
+ */
+
+const TAB_URL = EXAMPLE_URL + "doc_event-listeners-03.html";
+
+var gClient;
+var gTab;
+
+function test() {
+ if (!DebuggerServer.initialized) {
+ DebuggerServer.init();
+ DebuggerServer.addBrowserActors();
+ }
+
+ let transport = DebuggerServer.connectPipe();
+ gClient = new DebuggerClient(transport);
+ gClient.connect().then(([aType, aTraits]) => {
+ is(aType, "browser",
+ "Root actor should identify itself as a browser.");
+
+ addTab(TAB_URL)
+ .then((aTab) => {
+ gTab = aTab;
+ return attachThreadActorForUrl(gClient, TAB_URL);
+ })
+ .then(pauseDebuggee)
+ .then(testEventListeners)
+ .then(() => gClient.close())
+ .then(finish)
+ .then(null, aError => {
+ ok(false, "Got an error: " + aError.message + "\n" + aError.stack);
+ });
+ });
+}
+
+function pauseDebuggee(aThreadClient) {
+ let deferred = promise.defer();
+
+ gClient.addOneTimeListener("paused", (aEvent, aPacket) => {
+ is(aPacket.type, "paused",
+ "We should now be paused.");
+ is(aPacket.why.type, "debuggerStatement",
+ "The debugger statement was hit.");
+
+ deferred.resolve(aThreadClient);
+ });
+
+ generateMouseClickInTab(gTab, "content.document.querySelector('button')");
+
+ return deferred.promise;
+}
+
+function testEventListeners(aThreadClient) {
+ let deferred = promise.defer();
+
+ aThreadClient.eventListeners(aPacket => {
+ if (aPacket.error) {
+ let msg = "Error getting event listeners: " + aPacket.message;
+ ok(false, msg);
+ deferred.reject(msg);
+ return;
+ }
+
+ is(aPacket.listeners.length, 3,
+ "Found all event listeners.");
+
+ promise.all(aPacket.listeners.map(listener => {
+ const lDeferred = promise.defer();
+ aThreadClient.pauseGrip(listener.function).getDefinitionSite(aResponse => {
+ if (aResponse.error) {
+ const msg = "Error getting function definition site: " + aResponse.message;
+ ok(false, msg);
+ lDeferred.reject(msg);
+ return;
+ }
+ listener.function.url = aResponse.source.url;
+ lDeferred.resolve(listener);
+ });
+ return lDeferred.promise;
+ })).then(listeners => {
+ is(listeners.length, 3, "Found three event listeners.");
+ for (let l of listeners) {
+ let node = l.node;
+ ok(node, "There is a node property.");
+ ok(node.object, "There is a node object property.");
+ ok(node.selector == "window" ||
+ content.document.querySelectorAll(node.selector).length == 1,
+ "The node property is a unique CSS selector.");
+
+ let func = l.function;
+ ok(func, "There is a function property.");
+ is(func.type, "object", "The function form is of type 'object'.");
+ is(func.class, "Function", "The function form is of class 'Function'.");
+ is(func.url, TAB_URL, "The function url is correct.");
+
+ is(l.type, "click", "This is a click event listener.");
+ is(l.allowsUntrusted, true,
+ "'allowsUntrusted' property has the right value.");
+ is(l.inSystemEventGroup, false,
+ "'inSystemEventGroup' property has the right value.");
+ is(l.isEventHandler, false,
+ "'isEventHandler' property has the right value.");
+ is(l.capturing, false,
+ "Capturing property has the right value.");
+ }
+
+ aThreadClient.resume(deferred.resolve);
+ });
+ });
+
+ return deferred.promise;
+}
+
+registerCleanupFunction(function () {
+ gClient = null;
+});
diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_event-listeners-03.js b/devtools/client/debugger/test/mochitest/browser_dbg_event-listeners-03.js
new file mode 100644
index 000000000..8e193d8a6
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_event-listeners-03.js
@@ -0,0 +1,82 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Tests that the eventListeners request works when there are event handlers
+ * that the debugger cannot unwrap.
+ */
+
+const TAB_URL = EXAMPLE_URL + "doc_native-event-handler.html";
+
+var gClient;
+var gTab;
+
+function test() {
+ if (!DebuggerServer.initialized) {
+ DebuggerServer.init();
+ DebuggerServer.addBrowserActors();
+ }
+
+ let transport = DebuggerServer.connectPipe();
+ gClient = new DebuggerClient(transport);
+ gClient.connect().then(([aType, aTraits]) => {
+ is(aType, "browser",
+ "Root actor should identify itself as a browser.");
+
+ addTab(TAB_URL)
+ .then((aTab) => {
+ gTab = aTab;
+ return attachThreadActorForUrl(gClient, TAB_URL);
+ })
+ .then(pauseDebuggee)
+ .then(testEventListeners)
+ .then(() => gClient.close())
+ .then(finish)
+ .then(null, aError => {
+ ok(false, "Got an error: " + aError.message + "\n" + aError.stack);
+ });
+ });
+}
+
+function pauseDebuggee(aThreadClient) {
+ let deferred = promise.defer();
+
+ gClient.addOneTimeListener("paused", (aEvent, aPacket) => {
+ is(aPacket.type, "paused",
+ "We should now be paused.");
+ is(aPacket.why.type, "debuggerStatement",
+ "The debugger statement was hit.");
+
+ deferred.resolve(aThreadClient);
+ });
+
+ generateMouseClickInTab(gTab, "content.document.querySelector('button')");
+
+ return deferred.promise;
+}
+
+function testEventListeners(aThreadClient) {
+ let deferred = promise.defer();
+
+ aThreadClient.eventListeners(aPacket => {
+ if (aPacket.error) {
+ let msg = "Error getting event listeners: " + aPacket.message;
+ ok(false, msg);
+ deferred.reject(msg);
+ return;
+ }
+
+ // There are 3 event listeners in the page: button.onclick, window.onload
+ // and one more from the video element controls.
+ is(aPacket.listeners.length, 3, "Found all event listeners.");
+ aThreadClient.resume(deferred.resolve);
+ });
+
+ return deferred.promise;
+}
+
+registerCleanupFunction(function () {
+ gClient = null;
+});
diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_event-listeners-04.js b/devtools/client/debugger/test/mochitest/browser_dbg_event-listeners-04.js
new file mode 100644
index 000000000..f1b0036b3
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_event-listeners-04.js
@@ -0,0 +1,55 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Test that event listeners are properly fetched even if one of the listeners
+ * don't have a Debugger.Source object (bug 942899).
+ *
+ * This test is skipped on debug and e10s builds for following reasons:
+ * - debug: requiring sdk/tabs causes memory leaks when new windows are opened
+ * in tests executed after this one. Bug 1142597.
+ * - e10s: tab.attach is not e10s safe and only works when add-on compatibility
+ * shims are in place. Bug 1146603.
+ */
+
+const TAB_URL = EXAMPLE_URL + "doc_event-listeners-01.html";
+
+function test() {
+ Task.spawn(function* () {
+ let tab = yield addTab(TAB_URL);
+
+ // Create a sandboxed content script the Add-on SDK way. Inspired by bug
+ // 1145996.
+ let tabs = require("sdk/tabs");
+ let sdkTab = [...tabs].find(tab => tab.url === TAB_URL);
+ ok(sdkTab, "Add-on SDK found the loaded tab.");
+
+ info("Attaching an event handler via add-on sdk content scripts.");
+ let worker = sdkTab.attach({
+ contentScript: "document.body.addEventListener('click', e => alert(e))",
+ onError: ok.bind(this, false)
+ });
+
+ let options = {
+ source: TAB_URL,
+ line: 1
+ };
+ let [,, panel, win] = yield initDebugger(tab, options);
+ let dbg = panel.panelWin;
+ let controller = dbg.DebuggerController;
+ let constants = dbg.require("./content/constants");
+ let actions = dbg.require("./content/actions/event-listeners");
+ let fetched = waitForDispatch(panel, constants.FETCH_EVENT_LISTENERS);
+
+ info("Scheduling event listener fetch.");
+ controller.dispatch(actions.fetchEventListeners());
+
+ info("Waiting for updated event listeners to arrive.");
+ yield fetched;
+
+ ok(true, "The listener update did not hang.");
+ closeDebuggerAndFinish(panel);
+ });
+}
diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_file-reload.js b/devtools/client/debugger/test/mochitest/browser_dbg_file-reload.js
new file mode 100644
index 000000000..cb7ceef8f
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_file-reload.js
@@ -0,0 +1,72 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Tests that source contents are invalidated when the target navigates.
+ */
+
+const TAB_URL = EXAMPLE_URL + "doc_random-javascript.html";
+const JS_URL = EXAMPLE_URL + "sjs_random-javascript.sjs";
+
+function test() {
+ let options = {
+ source: JS_URL,
+ line: 1
+ };
+ initDebugger(TAB_URL, options).then(([aTab,, aPanel]) => {
+ const gPanel = aPanel;
+ const gDebugger = aPanel.panelWin;
+ const gEditor = gDebugger.DebuggerView.editor;
+ const gSources = gDebugger.DebuggerView.Sources;
+ const queries = gDebugger.require("./content/queries");
+ const constants = gDebugger.require("./content/constants");
+ const actions = bindActionCreators(gPanel);
+ const getState = gDebugger.DebuggerController.getState;
+
+ Task.spawn(function* () {
+ let source = queries.getSelectedSource(getState());
+
+ is(queries.getSourceCount(getState()), 1,
+ "There should be one source displayed in the view.");
+ is(source.url, JS_URL,
+ "The correct source is currently selected in the view.");
+ ok(gEditor.getText().includes("bacon"),
+ "The currently shown source contains bacon. Mmm, delicious!");
+
+ const { text: firstText } = yield queries.getSourceText(getState(), source.actor);
+ const firstNumber = parseFloat(firstText.match(/\d\.\d+/)[0]);
+
+ is(firstText, gEditor.getText(),
+ "gControllerSources.getText() returned the expected contents.");
+ ok(firstNumber <= 1 && firstNumber >= 0,
+ "The generated number seems to be created correctly.");
+
+ yield reloadActiveTab(aPanel, gDebugger.EVENTS.SOURCE_SHOWN);
+
+ is(queries.getSourceCount(getState()), 1,
+ "There should be one source displayed in the view.");
+ is(source.url, JS_URL,
+ "The correct source is currently selected in the view.");
+ ok(gEditor.getText().includes("bacon"),
+ "The newly shown source contains bacon. Mmm, delicious!");
+
+ source = queries.getSelectedSource(getState());
+ const { text: secondText } = yield queries.getSourceText(getState(), source.actor);
+ const secondNumber = parseFloat(secondText.match(/\d\.\d+/)[0]);
+
+ is(secondText, gEditor.getText(),
+ "gControllerSources.getText() returned the expected contents.");
+ ok(secondNumber <= 1 && secondNumber >= 0,
+ "The generated number seems to be created correctly.");
+
+ isnot(firstText, secondText,
+ "The displayed sources were different across reloads.");
+ isnot(firstNumber, secondNumber,
+ "The displayed sources differences were correct across reloads.");
+
+ yield closeDebuggerAndFinish(aPanel);
+ });
+ });
+}
diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_function-display-name.js b/devtools/client/debugger/test/mochitest/browser_dbg_function-display-name.js
new file mode 100644
index 000000000..fe9ff19eb
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_function-display-name.js
@@ -0,0 +1,68 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Tests that anonymous functions appear in the stack frame list with either
+ * their displayName property or a SpiderMonkey-inferred name.
+ */
+
+const TAB_URL = EXAMPLE_URL + "doc_function-display-name.html";
+
+var gTab, gPanel, gDebugger;
+
+function test() {
+ let options = {
+ source: TAB_URL,
+ line: 1
+ };
+ initDebugger(TAB_URL, options).then(([aTab,, aPanel]) => {
+ gTab = aTab;
+ gPanel = aPanel;
+ gDebugger = gPanel.panelWin;
+
+ testAnonCall();
+ });
+}
+
+function testAnonCall() {
+ let onCaretUpdated = waitForCaretUpdated(gPanel, 15);
+ let onScopes = waitForDebuggerEvents(gPanel, gDebugger.EVENTS.FETCHED_SCOPES);
+ callInTab(gTab, "evalCall");
+ promise.all([onCaretUpdated, onScopes]).then(() => {
+ ok(isCaretPos(gPanel, 15),
+ "The source editor caret position was incorrect.");
+ is(gDebugger.gThreadClient.state, "paused",
+ "Should only be getting stack frames while paused.");
+ is(gDebugger.document.querySelectorAll(".dbg-stackframe").length, 3,
+ "Should have three frames.");
+ is(gDebugger.document.querySelector("#stackframe-0 .dbg-stackframe-title").getAttribute("value"),
+ "anonFunc", "Frame name should be 'anonFunc'.");
+
+ testInferredName();
+ });
+}
+
+function testInferredName() {
+ waitForDebuggerEvents(gPanel, gDebugger.EVENTS.FETCHED_SCOPES).then(() => {
+ ok(isCaretPos(gPanel, 15),
+ "The source editor caret position was incorrect.");
+ is(gDebugger.gThreadClient.state, "paused",
+ "Should only be getting stack frames while paused.");
+ is(gDebugger.document.querySelectorAll(".dbg-stackframe").length, 3,
+ "Should have three frames.");
+ is(gDebugger.document.querySelector("#stackframe-0 .dbg-stackframe-title").getAttribute("value"),
+ "a/<", "Frame name should be 'a/<'.");
+
+ resumeDebuggerThenCloseAndFinish(gPanel);
+ });
+
+ gDebugger.gThreadClient.resume();
+}
+
+registerCleanupFunction(function () {
+ gTab = null;
+ gPanel = null;
+ gDebugger = null;
+});
diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_global-method-override.js b/devtools/client/debugger/test/mochitest/browser_dbg_global-method-override.js
new file mode 100644
index 000000000..ef2018b64
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_global-method-override.js
@@ -0,0 +1,26 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Tests that scripts that override properties of the global object, like
+ * toString don't break the debugger. The test page used to cause the debugger
+ * to throw when trying to attach to the thread actor.
+ */
+
+const TAB_URL = EXAMPLE_URL + "doc_global-method-override.html";
+
+function test() {
+ let options = {
+ source: TAB_URL,
+ line: 1
+ };
+ initDebugger(TAB_URL, options).then(([aTab,, aPanel]) => {
+ let gDebugger = aPanel.panelWin;
+ ok(gDebugger, "Should have a debugger available.");
+ is(gDebugger.gThreadClient.state, "attached", "Debugger should be attached.");
+
+ closeDebuggerAndFinish(aPanel);
+ });
+}
diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_globalactor.js b/devtools/client/debugger/test/mochitest/browser_dbg_globalactor.js
new file mode 100644
index 000000000..3f1533a1f
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_globalactor.js
@@ -0,0 +1,61 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Check extension-added global actor API.
+ */
+
+const ACTORS_URL = CHROME_URL + "testactors.js";
+
+function test() {
+ let gClient;
+
+ if (!DebuggerServer.initialized) {
+ DebuggerServer.init();
+ DebuggerServer.addBrowserActors();
+ }
+
+ DebuggerServer.addActors(ACTORS_URL);
+
+ let transport = DebuggerServer.connectPipe();
+ gClient = new DebuggerClient(transport);
+ gClient.connect().then(([aType, aTraits]) => {
+ is(aType, "browser",
+ "Root actor should identify itself as a browser.");
+
+ gClient.listTabs(aResponse => {
+ let globalActor = aResponse.testGlobalActor1;
+ ok(globalActor, "Found the test tab actor.");
+ ok(globalActor.includes("test_one"),
+ "testGlobalActor1's actorPrefix should be used.");
+
+ gClient.request({ to: globalActor, type: "ping" }, aResponse => {
+ is(aResponse.pong, "pong", "Actor should respond to requests.");
+
+ // Send another ping to see if the same actor is used.
+ gClient.request({ to: globalActor, type: "ping" }, aResponse => {
+ is(aResponse.pong, "pong", "Actor should respond to requests.");
+
+ // Make sure that lazily-created actors are created only once.
+ let count = 0;
+ for (let connID of Object.getOwnPropertyNames(DebuggerServer._connections)) {
+ let conn = DebuggerServer._connections[connID];
+ let actorPrefix = conn._prefix + "test_one";
+ for (let pool of conn._extraPools) {
+ count += Object.keys(pool._actors).filter(e => {
+ return e.startsWith(actorPrefix);
+ }).length;
+ }
+ }
+
+ is(count, 2,
+ "Only two actor exists in all pools. One tab actor and one global.");
+
+ gClient.close().then(finish);
+ });
+ });
+ });
+ });
+}
diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_hide-toolbar-buttons.js b/devtools/client/debugger/test/mochitest/browser_dbg_hide-toolbar-buttons.js
new file mode 100644
index 000000000..2fb9f9ddb
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_hide-toolbar-buttons.js
@@ -0,0 +1,34 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Bug 1093349: Test that the pretty-printing and blackboxing buttons
+ * are hidden if the server doesn't support them
+ */
+
+const TAB_URL = EXAMPLE_URL + "doc_auto-pretty-print-01.html";
+
+var { RootActor } = require("devtools/server/actors/root");
+
+function test() {
+ RootActor.prototype.traits.noBlackBoxing = true;
+ RootActor.prototype.traits.noPrettyPrinting = true;
+
+ let options = {
+ source: EXAMPLE_URL + "code_ugly-5.js",
+ line: 1
+ };
+ initDebugger(TAB_URL, options).then(([aTab, aDebuggee, aPanel]) => {
+ let document = aPanel.panelWin.document;
+ let ppButton = document.querySelector("#pretty-print");
+ let bbButton = document.querySelector("#black-box");
+ let sep = document.querySelector("#sources-toolbar .devtools-separator");
+
+ is(ppButton.style.display, "none", "The pretty-print button is hidden");
+ is(bbButton.style.display, "none", "The blackboxing button is hidden");
+ is(sep.style.display, "none", "The separator is hidden");
+ closeDebuggerAndFinish(aPanel);
+ });
+}
diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_host-layout.js b/devtools/client/debugger/test/mochitest/browser_dbg_host-layout.js
new file mode 100644
index 000000000..82a23cada
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_host-layout.js
@@ -0,0 +1,166 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * This if the debugger's layout is correctly modified when the toolbox's
+ * host changes.
+ */
+
+"use strict";
+
+var gDefaultHostType = Services.prefs.getCharPref("devtools.toolbox.host");
+
+function test() {
+ // test is too slow on some platforms due to the number of test cases
+ requestLongerTimeout(3);
+
+ Task.spawn(function*() {
+ yield testHosts(["bottom", "side", "window:big"], ["horizontal", "vertical", "horizontal"]);
+ yield testHosts(["side", "bottom", "side"], ["vertical", "horizontal", "vertical"]);
+ yield testHosts(["bottom", "side", "bottom"], ["horizontal", "vertical", "horizontal"]);
+ yield testHosts(["side", "window:big", "side"], ["vertical", "horizontal", "vertical"]);
+ yield testHosts(["window:big", "side", "window:big"], ["horizontal", "vertical", "horizontal"]);
+ yield testHosts(["window:small", "bottom", "side"], ["vertical", "horizontal", "vertical"]);
+ yield testHosts(["window:small", "window:big", "window:small"], ["vertical", "horizontal", "vertical"]);
+ finish();
+ });
+}
+
+function testHosts(aHostTypes, aLayoutTypes) {
+ let [firstHost, secondHost, thirdHost] = aHostTypes;
+ let [firstLayout, secondLayout, thirdLayout] = aLayoutTypes;
+
+ Services.prefs.setCharPref("devtools.toolbox.host", getHost(firstHost));
+
+ return Task.spawn(function*() {
+ let [tab, debuggee, panel] = yield initDebugger();
+ if (getHost(firstHost) === "window") {
+ yield resizeToolboxWindow(panel, firstHost);
+ }
+
+ yield testHost(panel, getHost(firstHost), firstLayout);
+ yield switchAndTestHost(tab, panel, secondHost, secondLayout);
+ yield switchAndTestHost(tab, panel, thirdHost, thirdLayout);
+ yield teardown(panel);
+ });
+}
+
+function switchAndTestHost(aTab, aPanel, aHostType, aLayoutType) {
+ let gToolbox = aPanel._toolbox;
+ let gDebugger = aPanel.panelWin;
+
+ return Task.spawn(function*() {
+ let layoutChanged = waitEventOnce(gDebugger, gDebugger.EVENTS.LAYOUT_CHANGED);
+ let hostChanged = gToolbox.switchHost(getHost(aHostType));
+
+ yield hostChanged;
+ info("The toolbox's host has changed.");
+
+ if (getHost(aHostType) === "window") {
+ yield resizeToolboxWindow(aPanel, aHostType);
+ }
+
+ yield layoutChanged;
+ info("The debugger's layout has changed.");
+
+ yield testHost(aPanel, getHost(aHostType), aLayoutType);
+ });
+}
+
+function waitEventOnce(aTarget, aEvent) {
+ let deferred = promise.defer();
+ aTarget.once(aEvent, deferred.resolve);
+ return deferred.promise;
+}
+
+function getHost(host) {
+ if (host.indexOf("window") == 0) {
+ return "window";
+ }
+ return host;
+}
+
+function resizeToolboxWindow(panel, host) {
+ let sizeOption = host.split(":")[1];
+ let win = panel._toolbox.win.parent;
+
+ // should be the same value as BREAKPOINT_SMALL_WINDOW_WIDTH in debugger-view.js
+ let breakpoint = 850;
+
+ if (sizeOption == "big" && win.outerWidth <= breakpoint) {
+ yield resizeAndWaitForLayoutChange(panel, breakpoint + 300);
+ } else if (sizeOption == "small" && win.outerWidth >= breakpoint) {
+ yield resizeAndWaitForLayoutChange(panel, breakpoint - 300);
+ } else {
+ info("Window resize unnecessary for host " + host);
+ }
+}
+
+function resizeAndWaitForLayoutChange(panel, width) {
+ info("Updating toolbox window width to " + width);
+
+ let win = panel._toolbox.win.parent;
+ let gDebugger = panel.panelWin;
+
+ win.resizeTo(width, window.screen.availHeight);
+ yield waitEventOnce(gDebugger, gDebugger.EVENTS.LAYOUT_CHANGED);
+}
+
+function testHost(aPanel, aHostType, aLayoutType) {
+ let gDebugger = aPanel.panelWin;
+ let gView = gDebugger.DebuggerView;
+
+ is(gView._hostType, aHostType,
+ "The default host type should've been set on the panel window (1).");
+ is(gDebugger.gHostType, aHostType,
+ "The default host type should've been set on the panel window (2).");
+
+ is(gView._body.getAttribute("layout"), aLayoutType,
+ "The default host type is present as an attribute on the panel's body.");
+
+ if (aLayoutType == "horizontal") {
+ is(gView._workersAndSourcesPane.parentNode.id, "debugger-widgets",
+ "The workers and sources pane's parent is correct for the horizontal layout.");
+ is(gView._instrumentsPane.parentNode.id, "editor-and-instruments-pane",
+ "The instruments pane's parent is correct for the horizontal layout.");
+ } else {
+ is(gView._workersAndSourcesPane.parentNode.id, "vertical-layout-panes-container",
+ "The workers and sources pane's parent is correct for the vertical layout.");
+ is(gView._instrumentsPane.parentNode.id, "vertical-layout-panes-container",
+ "The instruments pane's parent is correct for the vertical layout.");
+ }
+
+ let widgets = gDebugger.document.getElementById("debugger-widgets").childNodes;
+ let content = gDebugger.document.getElementById("debugger-content").childNodes;
+ let editorPane =
+ gDebugger.document.getElementById("editor-and-instruments-pane").childNodes;
+ let verticalPane =
+ gDebugger.document.getElementById("vertical-layout-panes-container").childNodes;
+
+ if (aLayoutType == "horizontal") {
+ is(widgets.length, 5, // 1 pane, 1 content box, 2 splitters and a phantom box.
+ "Found the correct number of debugger widgets.");
+ is(content.length, 1, // 1 pane
+ "Found the correct number of debugger content.");
+ is(editorPane.length, 3, // 2 panes, 1 splitter
+ "Found the correct number of debugger panes.");
+ is(verticalPane.length, 1, // 1 lonely splitter in the phantom box.
+ "Found the correct number of debugger panes.");
+ } else {
+ is(widgets.length, 4, // 1 content box, 2 splitters and a phantom box.
+ "Found the correct number of debugger widgets.");
+ is(content.length, 1, // 1 pane
+ "Found the correct number of debugger content.");
+ is(editorPane.length, 2, // 1 pane, 1 splitter
+ "Found the correct number of debugger panes.");
+ is(verticalPane.length, 3, // 2 panes and 1 splitter in the phantom box.
+ "Found the correct number of debugger panes.");
+ }
+}
+
+registerCleanupFunction(function() {
+ Services.prefs.setCharPref("devtools.toolbox.host", gDefaultHostType);
+ gDefaultHostType = null;
+});
diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_iframes.js b/devtools/client/debugger/test/mochitest/browser_dbg_iframes.js
new file mode 100644
index 000000000..afc3f9682
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_iframes.js
@@ -0,0 +1,72 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Tests that iframes can be added as debuggees.
+ */
+
+const TAB_URL = EXAMPLE_URL + "doc_iframes.html";
+
+function test() {
+ let gTab, gDebuggee, gPanel, gDebugger;
+ let gIframe, gEditor, gSources, gFrames;
+
+ let options = {
+ source: EXAMPLE_URL + "doc_inline-debugger-statement.html",
+ line: 1
+ };
+ initDebugger(TAB_URL, options).then(([aTab, aDebuggee, aPanel]) => {
+ gTab = aTab;
+ gDebuggee = aDebuggee;
+ gPanel = aPanel;
+ gDebugger = gPanel.panelWin;
+ gIframe = gDebuggee.frames[0];
+ gEditor = gDebugger.DebuggerView.editor;
+ gSources = gDebugger.DebuggerView.Sources;
+ gFrames = gDebugger.DebuggerView.StackFrames;
+
+ checkIframeSource();
+ checkIframePause()
+ .then(() => resumeDebuggerThenCloseAndFinish(gPanel))
+ .then(null, aError => {
+ ok(false, "Got an error: " + aError.message + "\n" + aError.stack);
+ });
+ });
+
+ function checkIframeSource() {
+ is(gDebugger.gThreadClient.paused, false,
+ "Should be running after starting the test.");
+
+ ok(isCaretPos(gPanel, 1),
+ "The source editor caret position was incorrect.");
+ is(gFrames.itemCount, 0,
+ "Should have only no frames.");
+
+ is(gSources.itemCount, 1,
+ "Found the expected number of entries in the sources widget.");
+ is(gEditor.getText().indexOf("debugger"), 348,
+ "The correct source was loaded initially.");
+ is(getSelectedSourceURL(gSources), EXAMPLE_URL + "doc_inline-debugger-statement.html",
+ "The currently selected source value is incorrect (0).");
+ is(gSources.selectedValue, gSources.values[0],
+ "The currently selected source value is incorrect (1).");
+ }
+
+ function checkIframePause() {
+ // Spin the event loop before causing the debuggee to pause, to allow
+ // this function to return first.
+ executeSoon(() => gIframe.runDebuggerStatement());
+
+ return waitForCaretAndScopes(gPanel, 16).then(() => {
+ is(gDebugger.gThreadClient.paused, true,
+ "Should be paused after an interrupt request.");
+
+ ok(isCaretPos(gPanel, 16),
+ "The source editor caret position was incorrect.");
+ is(gFrames.itemCount, 1,
+ "Should have only one frame.");
+ });
+ }
+}
diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_instruments-pane-collapse.js b/devtools/client/debugger/test/mochitest/browser_dbg_instruments-pane-collapse.js
new file mode 100644
index 000000000..31b3318cd
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_instruments-pane-collapse.js
@@ -0,0 +1,167 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Tests that the debugger panes collapse properly.
+ */
+
+const TAB_URL = EXAMPLE_URL + "doc_recursion-stack.html";
+
+var gTab, gPanel, gDebugger;
+var gPrefs, gOptions;
+
+function test() {
+ Task.spawn(function* () {
+ let options = {
+ source: TAB_URL,
+ line: 1
+ };
+
+ let [aTab,, aPanel] = yield initDebugger(TAB_URL, options);
+
+ gTab = aTab;
+ gPanel = aPanel;
+ gDebugger = gPanel.panelWin;
+ gPrefs = gDebugger.Prefs;
+ gOptions = gDebugger.DebuggerView.Options;
+
+ testPanesState();
+
+ gDebugger.DebuggerView.toggleInstrumentsPane({ visible: true, animated: false });
+
+ yield testInstrumentsPaneCollapse();
+ testPanesStartupPref();
+
+ closeDebuggerAndFinish(gPanel);
+ });
+}
+
+function testPanesState() {
+ let instrumentsPane =
+ gDebugger.document.getElementById("instruments-pane");
+ let instrumentsPaneToggleButton =
+ gDebugger.document.getElementById("instruments-pane-toggle");
+
+ ok(instrumentsPane.classList.contains("pane-collapsed") &&
+ instrumentsPaneToggleButton.classList.contains("pane-collapsed"),
+ "The debugger view instruments pane should initially be hidden.");
+ is(gPrefs.panesVisibleOnStartup, false,
+ "The debugger view instruments pane should initially be preffed as hidden.");
+ isnot(gOptions._showPanesOnStartupItem.getAttribute("checked"), "true",
+ "The options menu item should not be checked.");
+}
+
+function* testInstrumentsPaneCollapse () {
+ let instrumentsPane =
+ gDebugger.document.getElementById("instruments-pane");
+ let instrumentsPaneToggleButton =
+ gDebugger.document.getElementById("instruments-pane-toggle");
+
+ let width = parseInt(instrumentsPane.getAttribute("width"));
+ is(width, gPrefs.instrumentsWidth,
+ "The instruments pane has an incorrect width.");
+ is(instrumentsPane.style.marginLeft, "0px",
+ "The instruments pane has an incorrect left margin.");
+ is(instrumentsPane.style.marginRight, "0px",
+ "The instruments pane has an incorrect right margin.");
+ ok(!instrumentsPane.hasAttribute("animated"),
+ "The instruments pane has an incorrect animated attribute.");
+ ok(!instrumentsPane.classList.contains("pane-collapsed") &&
+ !instrumentsPaneToggleButton.classList.contains("pane-collapsed"),
+ "The instruments pane should at this point be visible.");
+
+ // Trigger reflow to make sure the UI is in required state.
+ gDebugger.document.documentElement.getBoundingClientRect();
+
+ gDebugger.DebuggerView.toggleInstrumentsPane({ visible: false, animated: true });
+
+ yield once(instrumentsPane, "transitionend");
+
+ is(gPrefs.panesVisibleOnStartup, false,
+ "The debugger view panes should still initially be preffed as hidden.");
+ isnot(gOptions._showPanesOnStartupItem.getAttribute("checked"), "true",
+ "The options menu item should still not be checked.");
+
+ let margin = -(width + 1) + "px";
+ is(width, gPrefs.instrumentsWidth,
+ "The instruments pane has an incorrect width after collapsing.");
+ is(instrumentsPane.style.marginLeft, margin,
+ "The instruments pane has an incorrect left margin after collapsing.");
+ is(instrumentsPane.style.marginRight, margin,
+ "The instruments pane has an incorrect right margin after collapsing.");
+
+ ok(!instrumentsPane.hasAttribute("animated"),
+ "The instruments pane has an incorrect attribute after an animated collapsing.");
+ ok(instrumentsPane.classList.contains("pane-collapsed") &&
+ instrumentsPaneToggleButton.classList.contains("pane-collapsed"),
+ "The instruments pane should not be visible after collapsing.");
+
+ gDebugger.DebuggerView.toggleInstrumentsPane({ visible: true, animated: false });
+
+ is(gPrefs.panesVisibleOnStartup, false,
+ "The debugger view panes should still initially be preffed as hidden.");
+ isnot(gOptions._showPanesOnStartupItem.getAttribute("checked"), "true",
+ "The options menu item should still not be checked.");
+
+ is(width, gPrefs.instrumentsWidth,
+ "The instruments pane has an incorrect width after uncollapsing.");
+ is(instrumentsPane.style.marginLeft, "0px",
+ "The instruments pane has an incorrect left margin after uncollapsing.");
+ is(instrumentsPane.style.marginRight, "0px",
+ "The instruments pane has an incorrect right margin after uncollapsing.");
+ ok(!instrumentsPane.hasAttribute("animated"),
+ "The instruments pane has an incorrect attribute after an unanimated uncollapsing.");
+ ok(!instrumentsPane.classList.contains("pane-collapsed") &&
+ !instrumentsPaneToggleButton.classList.contains("pane-collapsed"),
+ "The instruments pane should be visible again after uncollapsing.");
+}
+
+function testPanesStartupPref() {
+ let instrumentsPane =
+ gDebugger.document.getElementById("instruments-pane");
+ let instrumentsPaneToggleButton =
+ gDebugger.document.getElementById("instruments-pane-toggle");
+
+ is(gPrefs.panesVisibleOnStartup, false,
+ "The debugger view panes should still initially be preffed as hidden.");
+
+ ok(!instrumentsPane.classList.contains("pane-collapsed") &&
+ !instrumentsPaneToggleButton.classList.contains("pane-collapsed"),
+ "The debugger instruments pane should at this point be visible.");
+ is(gPrefs.panesVisibleOnStartup, false,
+ "The debugger view panes should initially be preffed as hidden.");
+ isnot(gOptions._showPanesOnStartupItem.getAttribute("checked"), "true",
+ "The options menu item should still not be checked.");
+
+ gOptions._showPanesOnStartupItem.setAttribute("checked", "true");
+ gOptions._toggleShowPanesOnStartup();
+
+ ok(!instrumentsPane.classList.contains("pane-collapsed") &&
+ !instrumentsPaneToggleButton.classList.contains("pane-collapsed"),
+ "The debugger instruments pane should at this point be visible.");
+ is(gPrefs.panesVisibleOnStartup, true,
+ "The debugger view panes should now be preffed as visible.");
+ is(gOptions._showPanesOnStartupItem.getAttribute("checked"), "true",
+ "The options menu item should now be checked.");
+
+ gOptions._showPanesOnStartupItem.setAttribute("checked", "false");
+ gOptions._toggleShowPanesOnStartup();
+
+ ok(!instrumentsPane.classList.contains("pane-collapsed") &&
+ !instrumentsPaneToggleButton.classList.contains("pane-collapsed"),
+ "The debugger instruments pane should at this point be visible.");
+ is(gPrefs.panesVisibleOnStartup, false,
+ "The debugger view panes should now be preffed as hidden.");
+ isnot(gOptions._showPanesOnStartupItem.getAttribute("checked"), "true",
+ "The options menu item should now be unchecked.");
+}
+
+registerCleanupFunction(function () {
+ gTab = null;
+ gPanel = null;
+ gDebugger = null;
+ gPrefs = null;
+ gOptions = null;
+});
diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_instruments-pane-collapse_keyboard.js b/devtools/client/debugger/test/mochitest/browser_dbg_instruments-pane-collapse_keyboard.js
new file mode 100644
index 000000000..c26c476cc
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_instruments-pane-collapse_keyboard.js
@@ -0,0 +1,40 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Tests that the debugger panes collapse properly.
+ */
+
+const TAB_URL = EXAMPLE_URL + "doc_recursion-stack.html";
+
+function test() {
+ initDebugger(TAB_URL).then(([aTab,, aPanel]) => {
+ Task.spawn(function* () {
+ let doc = aPanel.panelWin.document;
+ let panel = doc.getElementById("instruments-pane");
+ let button = doc.getElementById("instruments-pane-toggle");
+ ok(panel.classList.contains("pane-collapsed"),
+ "The instruments panel is initially in collapsed state");
+
+ yield togglePane(button, "Press on the toggle button to expand", panel, "VK_RETURN");
+ ok(!panel.classList.contains("pane-collapsed"),
+ "The instruments panel is in the expanded state");
+
+ yield togglePane(button, "Press on the toggle button to collapse", panel, "VK_SPACE");
+ ok(panel.classList.contains("pane-collapsed"),
+ "The instruments panel is in the collapsed state");
+
+ closeDebuggerAndFinish(aPanel);
+ });
+ });
+}
+
+function* togglePane(button, message, pane, keycode) {
+ let onTransitionEnd = once(pane, "transitionend");
+ info(message);
+ button.focus();
+ EventUtils.synthesizeKey(keycode, {});
+ yield onTransitionEnd;
+}
diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_interrupts.js b/devtools/client/debugger/test/mochitest/browser_dbg_interrupts.js
new file mode 100644
index 000000000..d6d61a76b
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_interrupts.js
@@ -0,0 +1,123 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Test resuming from button and keyboard shortcuts.
+ */
+
+const TAB_URL = EXAMPLE_URL + "doc_script-switching-01.html";
+
+function test() {
+ let gTab, gPanel, gDebugger;
+ let gSources, gBreakpoints, gTarget, gResumeButton, gResumeKey, gThreadClient;
+
+ let options = {
+ source: EXAMPLE_URL + "code_script-switching-01.js",
+ line: 1
+ };
+ initDebugger(TAB_URL, options).then(([aTab,, aPanel]) => {
+ gTab = aTab;
+ gPanel = aPanel;
+ gDebugger = gPanel.panelWin;
+ gSources = gDebugger.DebuggerView.Sources;
+ gBreakpoints = gDebugger.DebuggerController.Breakpoints;
+ gTarget = gDebugger.gTarget;
+ gThreadClient = gDebugger.gThreadClient;
+ gResumeButton = gDebugger.document.getElementById("resume");
+ gResumeKey = gDebugger.document.getElementById("resumeKey");
+
+ gTarget.on("thread-paused", failOnPause);
+ addBreakpoints()
+ .then(() => { gTarget.off("thread-paused", failOnPause); })
+ .then(testResumeButton)
+ .then(testResumeKeyboard)
+ .then(() => closeDebuggerAndFinish(gPanel))
+ .then(null, aError => {
+ ok(false, "Got an error: " + aError.message + "\n" + aError.stack);
+ });
+ });
+
+ function failOnPause() {
+ ok(false, "A pause was sent, but it shouldn't have been");
+ }
+
+ function addBreakpoints() {
+ return promise.resolve(null)
+ .then(() => gPanel.addBreakpoint({ actor: gSources.values[0], line: 5 }))
+ .then(() => gPanel.addBreakpoint({ actor: gSources.values[1], line: 6 }))
+ .then(() => gPanel.addBreakpoint({ actor: gSources.values[1], line: 7 }))
+ .then(() => ensureThreadClientState(gPanel, "resumed"));
+ }
+
+ function resume() {
+ let onceResumed = gTarget.once("thread-resumed");
+ gThreadClient.resume();
+ return onceResumed;
+ }
+
+ function testResumeButton() {
+ info("Pressing the resume button, expecting a thread-paused");
+
+ ok(!gResumeButton.hasAttribute("disabled"), "Resume button is not disabled 1");
+ ok(!gResumeButton.hasAttribute("break-on-next"), "Resume button isn't waiting for next execution");
+ ok(!gResumeButton.hasAttribute("checked"), "Resume button is not checked");
+ let oncePaused = gTarget.once("thread-paused");
+
+ // Click the pause button to break on next execution
+ EventUtils.sendMouseEvent({ type: "mousedown" }, gResumeButton, gDebugger);
+ ok(gResumeButton.hasAttribute("disabled"), "Resume button is disabled");
+ ok(gResumeButton.hasAttribute("break-on-next"), "Resume button is waiting for next execution");
+ ok(!gResumeButton.hasAttribute("checked"), "Resume button is not checked");
+
+ // Evaluate a script to fully pause the debugger
+ once(gDebugger.gClient, "willInterrupt").then(() => {
+ evalInTab(gTab, "1+1;");
+ });
+
+ return waitForDebuggerEvents(gPanel, gDebugger.EVENTS.SOURCE_SHOWN)
+ .then(() => {
+ ok(!gResumeButton.hasAttribute("break-on-next"), "Resume button isn't waiting for next execution");
+ is(gResumeButton.getAttribute("checked"), "true", "Resume button is checked");
+ ok(!gResumeButton.hasAttribute("disabled"), "Resume button is not disabled 2");
+ })
+ .then(() => {
+ let p = ensureThreadClientState(gPanel, "resumed");
+ gThreadClient.resume();
+ return p;
+ });
+ }
+
+ function testResumeKeyboard() {
+ let key = gResumeKey.getAttribute("keycode");
+ info("Triggering a pause with keyboard (" + key + "), expecting a thread-paused");
+
+ ok(!gResumeButton.hasAttribute("disabled"), "Resume button is not disabled 3");
+ ok(!gResumeButton.hasAttribute("break-on-next"), "Resume button isn't waiting for next execution");
+ ok(!gResumeButton.hasAttribute("checked"), "Resume button is not checked");
+
+ // Press the key to break on next execution
+ EventUtils.synthesizeKey(key, { }, gDebugger);
+ ok(gResumeButton.hasAttribute("disabled"), "Resume button is disabled");
+ ok(gResumeButton.hasAttribute("break-on-next"), "Resume button is waiting for next execution");
+ ok(!gResumeButton.hasAttribute("checked"), "Resume button is not checked");
+
+ // Evaluate a script to fully pause the debugger
+ once(gDebugger.gClient, "willInterrupt").then(() => {
+ evalInTab(gTab, "1+1;");
+ });
+
+ return waitForDebuggerEvents(gPanel, gDebugger.EVENTS.SOURCE_SHOWN)
+ .then(() => {
+ ok(!gResumeButton.hasAttribute("break-on-next"), "Resume button isn't waiting for next execution");
+ is(gResumeButton.getAttribute("checked"), "true", "Resume button is checked");
+ ok(!gResumeButton.hasAttribute("disabled"), "Resume button is not disabled 4");
+ })
+ .then(() => {
+ let p = ensureThreadClientState(gPanel, "resumed");
+ gThreadClient.resume();
+ return p;
+ });
+ }
+}
diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_jump-to-function-definition.js b/devtools/client/debugger/test/mochitest/browser_dbg_jump-to-function-definition.js
new file mode 100644
index 000000000..71d9c340c
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_jump-to-function-definition.js
@@ -0,0 +1,50 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Tests if the jump to function definition works properly.
+ */
+
+const TAB_URL = EXAMPLE_URL + "doc_function-jump.html";
+const SCRIPT_URI = EXAMPLE_URL + "code_function-jump-01.js";
+
+
+function test() {
+ let gTab, gPanel, gDebugger, gSources;
+
+ let options = {
+ source: EXAMPLE_URL + "code_function-jump-01.js",
+ line: 1
+ };
+ initDebugger(TAB_URL, options).then(([aTab,, aPanel]) => {
+ gTab = aTab;
+ gPanel = aPanel;
+ gDebugger = gPanel.panelWin;
+ gSources = gDebugger.DebuggerView.Sources;
+
+ jumpToFunctionDefinition()
+ .then(() => closeDebuggerAndFinish(gPanel))
+ .then(null, aError => {
+ ok(false, "Got an error: " + aError.message + "\n" + aError.stack);
+ });
+ });
+
+ function jumpToFunctionDefinition() {
+ let callLocation = {line: 5, ch: 0};
+ let editor = gDebugger.DebuggerView.editor;
+ let coords = editor.getCoordsFromPosition(callLocation);
+
+ gDebugger.DebuggerView.Sources._onMouseDown({ clientX: coords.left,
+ clientY: coords.top,
+ metaKey: true });
+
+ let deferred = promise.defer();
+ executeSoon(() => {
+ is(editor.getDebugLocation(), 1, "foo definition should be highlighted");
+ deferred.resolve();
+ });
+ return deferred.promise;
+ }
+}
diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_listaddons.js b/devtools/client/debugger/test/mochitest/browser_dbg_listaddons.js
new file mode 100644
index 000000000..1490c9670
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_listaddons.js
@@ -0,0 +1,112 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Make sure the listAddons request works as specified.
+ */
+const ADDON1_ID = "jid1-oBAwBoE5rSecNg@jetpack";
+const ADDON1_PATH = "addon1.xpi";
+const ADDON2_ID = "jid1-qjtzNGV8xw5h2A@jetpack";
+const ADDON2_PATH = "addon2.xpi";
+
+var gAddon1, gAddon1Actor, gAddon2, gAddon2Actor, gClient;
+
+function test() {
+ if (!DebuggerServer.initialized) {
+ DebuggerServer.init();
+ DebuggerServer.addBrowserActors();
+ }
+
+ let transport = DebuggerServer.connectPipe();
+ gClient = new DebuggerClient(transport);
+ gClient.connect().then(([aType, aTraits]) => {
+ is(aType, "browser",
+ "Root actor should identify itself as a browser.");
+
+ promise.resolve(null)
+ .then(testFirstAddon)
+ .then(testSecondAddon)
+ .then(testRemoveFirstAddon)
+ .then(testRemoveSecondAddon)
+ .then(() => gClient.close())
+ .then(finish)
+ .then(null, aError => {
+ ok(false, "Got an error: " + aError.message + "\n" + aError.stack);
+ });
+ });
+}
+
+function testFirstAddon() {
+ let addonListChanged = false;
+ gClient.addOneTimeListener("addonListChanged", () => {
+ addonListChanged = true;
+ });
+
+ return addTemporaryAddon(ADDON1_PATH).then(aAddon => {
+ gAddon1 = aAddon;
+
+ return getAddonActorForId(gClient, ADDON1_ID).then(aGrip => {
+ ok(!addonListChanged, "Should not yet be notified that list of addons changed.");
+ ok(aGrip, "Should find an addon actor for addon1.");
+ gAddon1Actor = aGrip.actor;
+ });
+ });
+}
+
+function testSecondAddon() {
+ let addonListChanged = false;
+ gClient.addOneTimeListener("addonListChanged", function () {
+ addonListChanged = true;
+ });
+
+ return addTemporaryAddon(ADDON2_PATH).then(aAddon => {
+ gAddon2 = aAddon;
+
+ return getAddonActorForId(gClient, ADDON1_ID).then(aFirstGrip => {
+ return getAddonActorForId(gClient, ADDON2_ID).then(aSecondGrip => {
+ ok(addonListChanged, "Should be notified that list of addons changed.");
+ is(aFirstGrip.actor, gAddon1Actor, "First addon's actor shouldn't have changed.");
+ ok(aSecondGrip, "Should find a addon actor for the second addon.");
+ gAddon2Actor = aSecondGrip.actor;
+ });
+ });
+ });
+}
+
+function testRemoveFirstAddon() {
+ let addonListChanged = false;
+ gClient.addOneTimeListener("addonListChanged", function () {
+ addonListChanged = true;
+ });
+
+ return removeAddon(gAddon1).then(() => {
+ return getAddonActorForId(gClient, ADDON1_ID).then(aGrip => {
+ ok(addonListChanged, "Should be notified that list of addons changed.");
+ ok(!aGrip, "Shouldn't find a addon actor for the first addon anymore.");
+ });
+ });
+}
+
+function testRemoveSecondAddon() {
+ let addonListChanged = false;
+ gClient.addOneTimeListener("addonListChanged", function () {
+ addonListChanged = true;
+ });
+
+ return removeAddon(gAddon2).then(() => {
+ return getAddonActorForId(gClient, ADDON2_ID).then(aGrip => {
+ ok(addonListChanged, "Should be notified that list of addons changed.");
+ ok(!aGrip, "Shouldn't find a addon actor for the second addon anymore.");
+ });
+ });
+}
+
+registerCleanupFunction(function () {
+ gAddon1 = null;
+ gAddon1Actor = null;
+ gAddon2 = null;
+ gAddon2Actor = null;
+ gClient = null;
+});
diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_listtabs-01.js b/devtools/client/debugger/test/mochitest/browser_dbg_listtabs-01.js
new file mode 100644
index 000000000..dc804713b
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_listtabs-01.js
@@ -0,0 +1,98 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Make sure the listTabs request works as specified.
+ */
+
+const TAB1_URL = EXAMPLE_URL + "doc_empty-tab-01.html";
+const TAB2_URL = EXAMPLE_URL + "doc_empty-tab-02.html";
+
+var gTab1, gTab1Actor, gTab2, gTab2Actor, gClient;
+
+function test() {
+ if (!DebuggerServer.initialized) {
+ DebuggerServer.init();
+ DebuggerServer.addBrowserActors();
+ }
+
+ let transport = DebuggerServer.connectPipe();
+ gClient = new DebuggerClient(transport);
+ gClient.connect().then(([aType, aTraits]) => {
+ is(aType, "browser",
+ "Root actor should identify itself as a browser.");
+
+ promise.resolve(null)
+ .then(testFirstTab)
+ .then(testSecondTab)
+ .then(testRemoveTab)
+ .then(testAttachRemovedTab)
+ .then(() => gClient.close())
+ .then(finish)
+ .then(null, aError => {
+ ok(false, "Got an error: " + aError.message + "\n" + aError.stack);
+ });
+ });
+}
+
+function testFirstTab() {
+ return addTab(TAB1_URL).then(aTab => {
+ gTab1 = aTab;
+
+ return getTabActorForUrl(gClient, TAB1_URL).then(aGrip => {
+ ok(aGrip, "Should find a tab actor for the first tab.");
+ gTab1Actor = aGrip.actor;
+ });
+ });
+}
+
+function testSecondTab() {
+ return addTab(TAB2_URL).then(aTab => {
+ gTab2 = aTab;
+
+ return getTabActorForUrl(gClient, TAB1_URL).then(aFirstGrip => {
+ return getTabActorForUrl(gClient, TAB2_URL).then(aSecondGrip => {
+ is(aFirstGrip.actor, gTab1Actor, "First tab's actor shouldn't have changed.");
+ ok(aSecondGrip, "Should find a tab actor for the second tab.");
+ gTab2Actor = aSecondGrip.actor;
+ });
+ });
+ });
+}
+
+function testRemoveTab() {
+ return removeTab(gTab1).then(() => {
+ return getTabActorForUrl(gClient, TAB1_URL).then(aGrip => {
+ ok(!aGrip, "Shouldn't find a tab actor for the first tab anymore.");
+ });
+ });
+}
+
+function testAttachRemovedTab() {
+ return removeTab(gTab2).then(() => {
+ let deferred = promise.defer();
+
+ gClient.addListener("paused", (aEvent, aPacket) => {
+ ok(false, "Attaching to an exited tab actor shouldn't generate a pause.");
+ deferred.reject();
+ });
+
+ gClient.request({ to: gTab2Actor, type: "attach" }, aResponse => {
+ is(aResponse.error, "connectionClosed",
+ "Connection is gone since the tab was removed.");
+ deferred.resolve();
+ });
+
+ return deferred.promise;
+ });
+}
+
+registerCleanupFunction(function () {
+ gTab1 = null;
+ gTab1Actor = null;
+ gTab2 = null;
+ gTab2Actor = null;
+ gClient = null;
+});
diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_listtabs-02.js b/devtools/client/debugger/test/mochitest/browser_dbg_listtabs-02.js
new file mode 100644
index 000000000..f696b6cb0
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_listtabs-02.js
@@ -0,0 +1,219 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Make sure the root actor's live tab list implementation works as specified.
+ */
+
+var { BrowserTabList } = require("devtools/server/actors/webbrowser");
+
+var gTestPage = "data:text/html;charset=utf-8," + encodeURIComponent(
+ "<title>JS Debugger BrowserTabList test page</title><body>Yo.</body>");
+
+// The tablist object whose behavior we observe.
+var gTabList;
+var gFirstActor, gActorA;
+var gTabA, gTabB, gTabC;
+var gNewWindow;
+
+// Stock onListChanged handler.
+var onListChangedCount = 0;
+function onListChangedHandler() {
+ onListChangedCount++;
+}
+
+function test() {
+ if (!DebuggerServer.initialized) {
+ DebuggerServer.init();
+ DebuggerServer.addBrowserActors();
+ }
+
+ gTabList = new BrowserTabList("fake DebuggerServerConnection");
+ gTabList._testing = true;
+ gTabList.onListChanged = onListChangedHandler;
+
+ checkSingleTab()
+ .then(addTabA)
+ .then(testTabA)
+ .then(addTabB)
+ .then(testTabB)
+ .then(removeTabA)
+ .then(testTabClosed)
+ .then(addTabC)
+ .then(testTabC)
+ .then(removeTabC)
+ .then(testNewWindow)
+ .then(removeNewWindow)
+ .then(testWindowClosed)
+ .then(removeTabB)
+ .then(checkSingleTab)
+ .then(finishUp);
+}
+
+function checkSingleTab() {
+ return gTabList.getList().then(aTabActors => {
+ is(aTabActors.length, 1, "initial tab list: contains initial tab");
+ gFirstActor = aTabActors[0];
+ is(gFirstActor.url, "about:blank", "initial tab list: initial tab URL is 'about:blank'");
+ is(gFirstActor.title, "New Tab", "initial tab list: initial tab title is 'New Tab'");
+ });
+}
+
+function addTabA() {
+ return addTab(gTestPage).then(aTab => {
+ gTabA = aTab;
+ });
+}
+
+function testTabA() {
+ is(onListChangedCount, 1, "onListChanged handler call count");
+
+ return gTabList.getList().then(aTabActors => {
+ let tabActors = new Set(aTabActors);
+ is(tabActors.size, 2, "gTabA opened: two tabs in list");
+ ok(tabActors.has(gFirstActor), "gTabA opened: initial tab present");
+
+ info("actors: " + [...tabActors].map(a => a.url));
+ gActorA = [...tabActors].filter(a => a !== gFirstActor)[0];
+ ok(gActorA.url.match(/^data:text\/html;/), "gTabA opened: new tab URL");
+ is(gActorA.title, "JS Debugger BrowserTabList test page", "gTabA opened: new tab title");
+ });
+}
+
+function addTabB() {
+ return addTab(gTestPage).then(aTab => {
+ gTabB = aTab;
+ });
+}
+
+function testTabB() {
+ is(onListChangedCount, 2, "onListChanged handler call count");
+
+ return gTabList.getList().then(aTabActors => {
+ let tabActors = new Set(aTabActors);
+ is(tabActors.size, 3, "gTabB opened: three tabs in list");
+ });
+}
+
+function removeTabA() {
+ let deferred = promise.defer();
+
+ once(gBrowser.tabContainer, "TabClose").then(aEvent => {
+ ok(!aEvent.detail.adoptedBy, "This was a normal tab close");
+
+ // Let the actor's TabClose handler finish first.
+ executeSoon(deferred.resolve);
+ }, false);
+
+ removeTab(gTabA);
+ return deferred.promise;
+}
+
+function testTabClosed() {
+ is(onListChangedCount, 3, "onListChanged handler call count");
+
+ gTabList.getList().then(aTabActors => {
+ let tabActors = new Set(aTabActors);
+ is(tabActors.size, 2, "gTabA closed: two tabs in list");
+ ok(tabActors.has(gFirstActor), "gTabA closed: initial tab present");
+
+ info("actors: " + [...tabActors].map(a => a.url));
+ gActorA = [...tabActors].filter(a => a !== gFirstActor)[0];
+ ok(gActorA.url.match(/^data:text\/html;/), "gTabA closed: new tab URL");
+ is(gActorA.title, "JS Debugger BrowserTabList test page", "gTabA closed: new tab title");
+ });
+}
+
+function addTabC() {
+ return addTab(gTestPage).then(aTab => {
+ gTabC = aTab;
+ });
+}
+
+function testTabC() {
+ is(onListChangedCount, 4, "onListChanged handler call count");
+
+ gTabList.getList().then(aTabActors => {
+ let tabActors = new Set(aTabActors);
+ is(tabActors.size, 3, "gTabC opened: three tabs in list");
+ });
+}
+
+function removeTabC() {
+ let deferred = promise.defer();
+
+ once(gBrowser.tabContainer, "TabClose").then(aEvent => {
+ ok(aEvent.detail.adoptedBy, "This was a tab closed by moving");
+
+ // Let the actor's TabClose handler finish first.
+ executeSoon(deferred.resolve);
+ }, false);
+
+ gNewWindow = gBrowser.replaceTabWithWindow(gTabC);
+ return deferred.promise;
+}
+
+function testNewWindow() {
+ is(onListChangedCount, 5, "onListChanged handler call count");
+
+ return gTabList.getList().then(aTabActors => {
+ let tabActors = new Set(aTabActors);
+ is(tabActors.size, 3, "gTabC closed: three tabs in list");
+ ok(tabActors.has(gFirstActor), "gTabC closed: initial tab present");
+
+ info("actors: " + [...tabActors].map(a => a.url));
+ gActorA = [...tabActors].filter(a => a !== gFirstActor)[0];
+ ok(gActorA.url.match(/^data:text\/html;/), "gTabC closed: new tab URL");
+ is(gActorA.title, "JS Debugger BrowserTabList test page", "gTabC closed: new tab title");
+ });
+}
+
+function removeNewWindow() {
+ let deferred = promise.defer();
+
+ once(gNewWindow, "unload").then(aEvent => {
+ ok(!aEvent.detail, "This was a normal window close");
+
+ // Let the actor's TabClose handler finish first.
+ executeSoon(deferred.resolve);
+ }, false);
+
+ gNewWindow.close();
+ return deferred.promise;
+}
+
+function testWindowClosed() {
+ is(onListChangedCount, 6, "onListChanged handler call count");
+
+ return gTabList.getList().then(aTabActors => {
+ let tabActors = new Set(aTabActors);
+ is(tabActors.size, 2, "gNewWindow closed: two tabs in list");
+ ok(tabActors.has(gFirstActor), "gNewWindow closed: initial tab present");
+
+ info("actors: " + [...tabActors].map(a => a.url));
+ gActorA = [...tabActors].filter(a => a !== gFirstActor)[0];
+ ok(gActorA.url.match(/^data:text\/html;/), "gNewWindow closed: new tab URL");
+ is(gActorA.title, "JS Debugger BrowserTabList test page", "gNewWindow closed: new tab title");
+ });
+}
+
+function removeTabB() {
+ let deferred = promise.defer();
+
+ once(gBrowser.tabContainer, "TabClose").then(aEvent => {
+ ok(!aEvent.detail.adoptedBy, "This was a normal tab close");
+
+ // Let the actor's TabClose handler finish first.
+ executeSoon(deferred.resolve);
+ }, false);
+
+ removeTab(gTabB);
+ return deferred.promise;
+}
+
+function finishUp() {
+ gTabList = gFirstActor = gActorA = gTabA = gTabB = gTabC = gNewWindow = null;
+ finish();
+}
diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_listtabs-03.js b/devtools/client/debugger/test/mochitest/browser_dbg_listtabs-03.js
new file mode 100644
index 000000000..d5584dcdb
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_listtabs-03.js
@@ -0,0 +1,61 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Make sure the listTabs request works as specified.
+ */
+
+const TAB1_URL = EXAMPLE_URL + "doc_empty-tab-01.html";
+
+var gTab1, gTab1Actor, gTab2, gTab2Actor, gClient;
+
+function test() {
+ if (!DebuggerServer.initialized) {
+ DebuggerServer.init();
+ DebuggerServer.addBrowserActors();
+ }
+
+ let transport = DebuggerServer.connectPipe();
+ gClient = new DebuggerClient(transport);
+ gClient.connect().then(Task.async(function* ([aType, aTraits]) {
+ is(aType, "browser",
+ "Root actor should identify itself as a browser.");
+ let tab = yield addTab(TAB1_URL);
+
+ let { tabs } = yield gClient.listTabs();
+ is(tabs.length, 2, "Should be two tabs");
+ let tabGrip = tabs.filter(a => a.url == TAB1_URL).pop();
+ ok(tabGrip, "Should have an actor for the tab");
+
+ let response = yield gClient.request({ to: tabGrip.actor, type: "attach" });
+ is(response.type, "tabAttached", "Should have attached");
+
+ response = yield gClient.listTabs();
+ tabs = response.tabs;
+
+ response = yield gClient.request({ to: tabGrip.actor, type: "detach" });
+ is(response.type, "detached", "Should have detached");
+
+ let newGrip = tabs.filter(a => a.url == TAB1_URL).pop();
+ is(newGrip.actor, tabGrip.actor, "Should have the same actor for the same tab");
+
+ response = yield gClient.request({ to: tabGrip.actor, type: "attach" });
+ is(response.type, "tabAttached", "Should have attached");
+ response = yield gClient.request({ to: tabGrip.actor, type: "detach" });
+ is(response.type, "detached", "Should have detached");
+
+ yield removeTab(tab);
+ yield gClient.close();
+ finish();
+ }));
+}
+
+registerCleanupFunction(function () {
+ gTab1 = null;
+ gTab1Actor = null;
+ gTab2 = null;
+ gTab2Actor = null;
+ gClient = null;
+});
diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_listworkers.js b/devtools/client/debugger/test/mochitest/browser_dbg_listworkers.js
new file mode 100644
index 000000000..315acc231
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_listworkers.js
@@ -0,0 +1,59 @@
+var TAB_URL = EXAMPLE_URL + "doc_listworkers-tab.html";
+var WORKER1_URL = "code_listworkers-worker1.js";
+var WORKER2_URL = "code_listworkers-worker2.js";
+
+function test() {
+ Task.spawn(function* () {
+ DebuggerServer.init();
+ DebuggerServer.addBrowserActors();
+
+ let client = new DebuggerClient(DebuggerServer.connectPipe());
+ yield connect(client);
+
+ let tab = yield addTab(TAB_URL);
+ let { tabs } = yield listTabs(client);
+ let [, tabClient] = yield attachTab(client, findTab(tabs, TAB_URL));
+
+ let { workers } = yield listWorkers(tabClient);
+ is(workers.length, 0);
+
+ executeSoon(() => {
+ evalInTab(tab, "var worker1 = new Worker('" + WORKER1_URL + "');");
+ });
+ yield waitForWorkerListChanged(tabClient);
+
+ ({ workers } = yield listWorkers(tabClient));
+ is(workers.length, 1);
+ is(workers[0].url, WORKER1_URL);
+
+ executeSoon(() => {
+ evalInTab(tab, "var worker2 = new Worker('" + WORKER2_URL + "');");
+ });
+ yield waitForWorkerListChanged(tabClient);
+
+ ({ workers } = yield listWorkers(tabClient));
+ is(workers.length, 2);
+ is(workers[0].url, WORKER1_URL);
+ is(workers[1].url, WORKER2_URL);
+
+ executeSoon(() => {
+ evalInTab(tab, "worker1.terminate()");
+ });
+ yield waitForWorkerListChanged(tabClient);
+
+ ({ workers } = yield listWorkers(tabClient));
+ is(workers.length, 1);
+ is(workers[0].url, WORKER2_URL);
+
+ executeSoon(() => {
+ evalInTab(tab, "worker2.terminate()");
+ });
+ yield waitForWorkerListChanged(tabClient);
+
+ ({ workers } = yield listWorkers(tabClient));
+ is(workers.length, 0);
+
+ yield close(client);
+ finish();
+ });
+}
diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_location-changes-01-simple.js b/devtools/client/debugger/test/mochitest/browser_dbg_location-changes-01-simple.js
new file mode 100644
index 000000000..261881835
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_location-changes-01-simple.js
@@ -0,0 +1,60 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Make sure that changing the tab location URL works.
+ */
+
+const TAB_URL = EXAMPLE_URL + "doc_recursion-stack.html";
+
+function test() {
+ let options = {
+ source: TAB_URL,
+ line: 1
+ };
+ initDebugger(TAB_URL, options).then(([aTab,, aPanel]) => {
+ const gTab = aTab;
+ const gPanel = aPanel;
+ const gDebugger = gPanel.panelWin;
+ const gEditor = gDebugger.DebuggerView.editor;
+ const gSources = gDebugger.DebuggerView.Sources;
+ const gFrames = gDebugger.DebuggerView.StackFrames;
+ const constants = gDebugger.require("./content/constants");
+
+ Task.spawn(function* () {
+ let onCaretUpdated = waitForCaretAndScopes(gPanel, 14);
+ callInTab(gTab, "simpleCall");
+ yield onCaretUpdated;
+
+ is(gDebugger.gThreadClient.state, "paused",
+ "Should only be getting stack frames while paused.");
+
+ is(gFrames.itemCount, 1,
+ "Should have only one frame.");
+
+ is(gSources.itemCount, 1,
+ "Found the expected number of entries in the sources widget.");
+
+ isnot(gSources.selectedValue, null,
+ "There should be a selected source value.");
+ isnot(gEditor.getText().length, 0,
+ "The source editor should have some text displayed.");
+ isnot(gEditor.getText(), gDebugger.L10N.getStr("loadingText"),
+ "The source editor text should not be 'Loading...'");
+
+ is(gDebugger.document.querySelectorAll("#sources .side-menu-widget-empty-notice-container").length, 0,
+ "The sources widget should not display any notice at this point (1).");
+ is(gDebugger.document.querySelectorAll("#sources .side-menu-widget-empty-notice").length, 0,
+ "The sources widget should not display any notice at this point (2).");
+ is(gDebugger.document.querySelector("#sources .side-menu-widget-empty-notice > label"), null,
+ "The sources widget should not display a notice at this point (3).");
+
+ yield doResume(gPanel);
+ navigateActiveTabTo(gPanel, "about:blank");
+ yield waitForDispatch(gPanel, constants.UNLOAD);
+ closeDebuggerAndFinish(gPanel);
+ });
+ });
+}
diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_location-changes-02-blank.js b/devtools/client/debugger/test/mochitest/browser_dbg_location-changes-02-blank.js
new file mode 100644
index 000000000..10c7d98ba
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_location-changes-02-blank.js
@@ -0,0 +1,57 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Make sure that changing the tab location URL to a page with no sources works.
+ */
+
+const TAB_URL = EXAMPLE_URL + "doc_recursion-stack.html";
+
+function test() {
+ let options = {
+ source: TAB_URL,
+ line: 1
+ };
+ initDebugger(TAB_URL, options).then(([aTab,, aPanel]) => {
+ const gTab = aTab;
+ const gPanel = aPanel;
+ const gDebugger = gPanel.panelWin;
+ const gEditor = gDebugger.DebuggerView.editor;
+ const gSources = gDebugger.DebuggerView.Sources;
+ const gFrames = gDebugger.DebuggerView.StackFrames;
+ const constants = gDebugger.require("./content/constants");
+
+ Task.spawn(function* () {
+ let onCaretUpdated = waitForCaretUpdated(gPanel, 14);
+ callInTab(gTab, "simpleCall");
+ yield onCaretUpdated;
+
+ navigateActiveTabTo(gPanel, "about:blank");
+ yield waitForNavigation(gPanel);
+
+ isnot(gDebugger.gThreadClient.state, "paused",
+ "Should not be paused after a tab navigation.");
+
+ is(gFrames.itemCount, 0,
+ "Should have no frames.");
+
+ is(gSources.itemCount, 0,
+ "Found no entries in the sources widget.");
+
+ is(gSources.selectedValue, "",
+ "There should be no selected source value.");
+ is(gEditor.getText().length, 0,
+ "The source editor should not have any text displayed.");
+
+ is(gDebugger.document.querySelectorAll("#sources .side-menu-widget-empty-text").length, 1,
+ "The sources widget should now display a notice (1).");
+ is(gDebugger.document.querySelectorAll("#sources .side-menu-widget-empty-text")[0].getAttribute("value"),
+ gDebugger.L10N.getStr("noSourcesText"),
+ "The sources widget should now display a notice (2).");
+
+ closeDebuggerAndFinish(gPanel);
+ });
+ });
+}
diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_location-changes-03-new.js b/devtools/client/debugger/test/mochitest/browser_dbg_location-changes-03-new.js
new file mode 100644
index 000000000..878c7be81
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_location-changes-03-new.js
@@ -0,0 +1,59 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Make sure that changing the tab location URL to a page with other sources works.
+ */
+
+const TAB_URL_1 = EXAMPLE_URL + "doc_recursion-stack.html";
+const TAB_URL_2 = EXAMPLE_URL + "doc_iframes.html";
+
+function test() {
+ let options = {
+ source: TAB_URL_1,
+ line: 1
+ };
+ initDebugger(TAB_URL_1, options).then(([aTab, aDebuggee, aPanel]) => {
+ const gTab = aTab;
+ const gDebuggee = aDebuggee;
+ const gPanel = aPanel;
+ const gDebugger = gPanel.panelWin;
+ const gEditor = gDebugger.DebuggerView.editor;
+ const gSources = gDebugger.DebuggerView.Sources;
+ const gFrames = gDebugger.DebuggerView.StackFrames;
+ const constants = gDebugger.require("./content/constants");
+
+ Task.spawn(function* () {
+ let onCaretUpdated = waitForCaretUpdated(gPanel, 14);
+ callInTab(gTab, "simpleCall");
+ yield onCaretUpdated;
+
+ const startedLoading = waitForNextDispatch(gDebugger.DebuggerController,
+ constants.LOAD_SOURCE_TEXT);
+ navigateActiveTabTo(gPanel, TAB_URL_2, gDebugger.EVENTS.SOURCE_SHOWN);
+ yield startedLoading;
+
+ isnot(gDebugger.gThreadClient.state, "paused",
+ "Should not be paused after a tab navigation.");
+ is(gFrames.itemCount, 0,
+ "Should have no frames.");
+ is(gSources.itemCount, 1,
+ "Found the expected number of entries in the sources widget.");
+
+ is(getSelectedSourceURL(gSources), EXAMPLE_URL + "doc_inline-debugger-statement.html",
+ "There should be a selected source value.");
+ isnot(gEditor.getText().length, 0,
+ "The source editor should have some text displayed.");
+ is(gEditor.getText(), gDebugger.L10N.getStr("loadingText"),
+ "The source editor text should be 'Loading...'");
+
+ is(gDebugger.document.querySelectorAll("#sources .side-menu-widget-empty-text").length, 0,
+ "The sources widget should not display any notice at this point.");
+
+ yield waitForDispatch(gPanel, constants.LOAD_SOURCE_TEXT);
+ closeDebuggerAndFinish(gPanel);
+ });
+ });
+}
diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_location-changes-04-breakpoint.js b/devtools/client/debugger/test/mochitest/browser_dbg_location-changes-04-breakpoint.js
new file mode 100644
index 000000000..493796720
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_location-changes-04-breakpoint.js
@@ -0,0 +1,165 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Make sure that reloading a page with a breakpoint set does not cause it to
+ * fire more than once.
+ */
+
+const TAB_URL = EXAMPLE_URL + "doc_included-script.html";
+const SOURCE_URL = EXAMPLE_URL + "code_location-changes.js";
+
+function test() {
+ const options = {
+ source: SOURCE_URL,
+ line: 1
+ };
+ initDebugger(TAB_URL, options).then(([aTab, aDebuggee, aPanel]) => {
+ const gTab = aTab;
+ const gDebuggee = aDebuggee;
+ const gPanel = aPanel;
+ const gDebugger = gPanel.panelWin;
+ const gEditor = gDebugger.DebuggerView.editor;
+ const gSources = gDebugger.DebuggerView.Sources;
+ const queries = gDebugger.require("./content/queries");
+ const actions = bindActionCreators(gPanel);
+ const getState = gDebugger.DebuggerController.getState;
+
+ function clickButtonAndPause() {
+ const paused = waitForPause(gDebugger.gThreadClient);
+ BrowserTestUtils.synthesizeMouse("button", 2, 2, {}, gBrowser.selectedBrowser);
+ return paused;
+ }
+
+ Task.spawn(function* () {
+ let onCaretUpdated = waitForCaretUpdated(gPanel, 17);
+ callInTab(gTab, "runDebuggerStatement");
+ yield onCaretUpdated;
+
+ const location = { actor: getSourceActor(gSources, SOURCE_URL), line: 5 };
+ yield actions.addBreakpoint(location);
+
+ const caretUpdated = waitForSourceAndCaret(gPanel, ".js", 5);
+ gSources.highlightBreakpoint(location);
+ yield caretUpdated;
+ ok(true, "Switched to the desired function when adding a breakpoint");
+
+ is(gDebugger.gThreadClient.state, "paused",
+ "The breakpoint was hit (1).");
+ is(getSelectedSourceURL(gSources), SOURCE_URL,
+ "The currently shown source is correct (1).");
+ ok(isCaretPos(gPanel, 5),
+ "The source editor caret position is correct (1).");
+
+ yield doResume(gPanel);
+
+ isnot(gDebugger.gThreadClient.state, "paused",
+ "The breakpoint was not hit yet (2).");
+ is(getSelectedSourceURL(gSources), SOURCE_URL,
+ "The currently shown source is correct (2).");
+ ok(isCaretPos(gPanel, 5),
+ "The source editor caret position is correct (2).");
+
+ let packet = yield clickButtonAndPause();
+ is(packet.why.type, "breakpoint",
+ "Execution has advanced to the breakpoint.");
+ isnot(packet.why.type, "debuggerStatement",
+ "The breakpoint was hit before the debugger statement.");
+ yield ensureCaretAt(gPanel, 5, 1, true);
+
+ is(gDebugger.gThreadClient.state, "paused",
+ "The breakpoint was hit (3).");
+ is(getSelectedSourceURL(gSources), SOURCE_URL,
+ "The currently shown source is incorrect (3).");
+ ok(isCaretPos(gPanel, 5),
+ "The source editor caret position is incorrect (3).");
+
+ let paused = waitForPause(gDebugger.gThreadClient);
+ gDebugger.gThreadClient.resume();
+ packet = yield paused;
+
+ is(packet.why.type, "debuggerStatement",
+ "Execution has advanced to the next line.");
+ isnot(packet.why.type, "breakpoint",
+ "No ghost breakpoint was hit.");
+
+ yield ensureCaretAt(gPanel, 6, 1, true);
+
+ is(gDebugger.gThreadClient.state, "paused",
+ "The debugger statement was hit (4).");
+ is(getSelectedSourceURL(gSources), SOURCE_URL,
+ "The currently shown source is incorrect (4).");
+ ok(isCaretPos(gPanel, 6),
+ "The source editor caret position is incorrect (4).");
+
+ yield promise.all([
+ reload(gPanel),
+ waitForDebuggerEvents(gPanel, gDebugger.EVENTS.SOURCE_SHOWN)
+ ]);
+
+ isnot(gDebugger.gThreadClient.state, "paused",
+ "The breakpoint wasn't hit yet (5).");
+ is(getSelectedSourceURL(gSources), SOURCE_URL,
+ "The currently shown source is incorrect (5).");
+ ok(isCaretPos(gPanel, 1),
+ "The source editor caret position is incorrect (5).");
+
+ paused = waitForPause(gDebugger.gThreadClient);
+ clickButtonAndPause();
+ packet = yield paused;
+ is(packet.why.type, "breakpoint",
+ "Execution has advanced to the breakpoint.");
+ isnot(packet.why.type, "debuggerStatement",
+ "The breakpoint was hit before the debugger statement.");
+ yield ensureCaretAt(gPanel, 5, 1, true);
+
+ is(gDebugger.gThreadClient.state, "paused",
+ "The breakpoint was hit (6).");
+ is(getSelectedSourceURL(gSources), SOURCE_URL,
+ "The currently shown source is incorrect (6).");
+ ok(isCaretPos(gPanel, 5),
+ "The source editor caret position is incorrect (6).");
+
+ paused = waitForPause(gDebugger.gThreadClient);
+ gDebugger.gThreadClient.resume();
+ packet = yield paused;
+
+ is(packet.why.type, "debuggerStatement",
+ "Execution has advanced to the next line.");
+ isnot(packet.why.type, "breakpoint",
+ "No ghost breakpoint was hit.");
+
+ yield ensureCaretAt(gPanel, 6, 1, true);
+
+ is(gDebugger.gThreadClient.state, "paused",
+ "The debugger statement was hit (7).");
+ is(getSelectedSourceURL(gSources), SOURCE_URL,
+ "The currently shown source is incorrect (7).");
+ ok(isCaretPos(gPanel, 6),
+ "The source editor caret position is incorrect (7).");
+
+ let sourceShown = waitForDebuggerEvents(gPanel, gDebugger.EVENTS.SOURCE_SHOWN);
+ // Click the second source in the list.
+ yield actions.selectSource(getSourceForm(gSources, TAB_URL));
+ yield sourceShown;
+ is(gEditor.getText().indexOf("debugger"), 447,
+ "The correct source is shown in the source editor.");
+ is(gEditor.getBreakpoints().length, 0,
+ "No breakpoints should be shown for the second source.");
+ yield ensureCaretAt(gPanel, 1, 1, true);
+
+ sourceShown = waitForDebuggerEvents(gPanel, gDebugger.EVENTS.SOURCE_SHOWN);
+ yield actions.selectSource(getSourceForm(gSources, SOURCE_URL));
+ yield sourceShown;
+ is(gEditor.getText().indexOf("debugger"), 148,
+ "The correct source is shown in the source editor.");
+ is(gEditor.getBreakpoints().length, 1,
+ "One breakpoint should be shown for the first source.");
+
+ yield ensureCaretAt(gPanel, 6, 1, true);
+ resumeDebuggerThenCloseAndFinish(gPanel);
+ });
+ });
+}
diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_multiple-windows.js b/devtools/client/debugger/test/mochitest/browser_dbg_multiple-windows.js
new file mode 100644
index 000000000..b0bb1834c
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_multiple-windows.js
@@ -0,0 +1,165 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Make sure that the debugger attaches to the right tab when multiple windows
+ * are open.
+ */
+
+const TAB1_URL = EXAMPLE_URL + "doc_script-switching-01.html";
+const TAB2_URL = EXAMPLE_URL + "doc_script-switching-02.html";
+
+var gNewTab, gNewWindow;
+var gClient;
+
+function test() {
+ if (!DebuggerServer.initialized) {
+ DebuggerServer.init();
+ DebuggerServer.addBrowserActors();
+ }
+
+ let transport = DebuggerServer.connectPipe();
+ gClient = new DebuggerClient(transport);
+ gClient.connect().then(([aType, aTraits]) => {
+ is(aType, "browser",
+ "Root actor should identify itself as a browser.");
+
+ promise.resolve(null)
+ .then(() => addTab(TAB1_URL))
+ .then(testFirstTab)
+ .then(() => addWindow(TAB2_URL))
+ .then(testNewWindow)
+ .then(testFocusFirst)
+ .then(testRemoveTab)
+ .then(() => gClient.close())
+ .then(finish)
+ .then(null, aError => {
+ ok(false, "Got an error: " + aError.message + "\n" + aError.stack);
+ });
+ });
+}
+
+function testFirstTab(aTab) {
+ let deferred = promise.defer();
+
+ gNewTab = aTab;
+ ok(!!gNewTab, "Second tab created.");
+
+ gClient.listTabs(aResponse => {
+ let tabActor = aResponse.tabs.filter(aGrip => aGrip.url == TAB1_URL).pop();
+ ok(tabActor,
+ "Should find a tab actor for the first tab.");
+
+ is(aResponse.selected, 1,
+ "The first tab is selected.");
+
+ deferred.resolve();
+ });
+
+ return deferred.promise;
+}
+
+function testNewWindow(aWindow) {
+ let deferred = promise.defer();
+
+ gNewWindow = aWindow;
+ ok(!!gNewWindow, "Second window created.");
+
+ gNewWindow.focus();
+
+ let topWindow = Services.wm.getMostRecentWindow("navigator:browser");
+ is(topWindow, gNewWindow,
+ "The second window is on top.");
+
+ let isActive = promise.defer();
+ let isLoaded = promise.defer();
+
+ promise.all([isActive.promise, isLoaded.promise]).then(() => {
+ gClient.listTabs(aResponse => {
+ is(aResponse.selected, 2,
+ "The second tab is selected.");
+
+ deferred.resolve();
+ });
+ });
+
+ if (Services.focus.activeWindow != gNewWindow) {
+ gNewWindow.addEventListener("activate", function onActivate(aEvent) {
+ if (aEvent.target != gNewWindow) {
+ return;
+ }
+ gNewWindow.removeEventListener("activate", onActivate, true);
+ isActive.resolve();
+ }, true);
+ } else {
+ isActive.resolve();
+ }
+
+ let contentLocation = gNewWindow.content.location.href;
+ if (contentLocation != TAB2_URL) {
+ gNewWindow.document.addEventListener("load", function onLoad(aEvent) {
+ if (aEvent.target.documentURI != TAB2_URL) {
+ return;
+ }
+ gNewWindow.document.removeEventListener("load", onLoad, true);
+ isLoaded.resolve();
+ }, true);
+ } else {
+ isLoaded.resolve();
+ }
+
+ return deferred.promise;
+}
+
+function testFocusFirst() {
+ let deferred = promise.defer();
+
+ once(window.content, "focus").then(() => {
+ gClient.listTabs(aResponse => {
+ is(aResponse.selected, 1,
+ "The first tab is selected after focusing on it.");
+
+ deferred.resolve();
+ });
+ });
+
+ window.content.focus();
+
+ return deferred.promise;
+}
+
+function testRemoveTab() {
+ let deferred = promise.defer();
+
+ gNewWindow.close();
+
+ // give it time to close
+ executeSoon(function () { continue_remove_tab(deferred); });
+ return deferred.promise;
+}
+
+function continue_remove_tab(deferred)
+{
+ removeTab(gNewTab);
+
+ gClient.listTabs(aResponse => {
+ // Verify that tabs are no longer included in listTabs.
+ let foundTab1 = aResponse.tabs.some(aGrip => aGrip.url == TAB1_URL);
+ let foundTab2 = aResponse.tabs.some(aGrip => aGrip.url == TAB2_URL);
+ ok(!foundTab1, "Tab1 should be gone.");
+ ok(!foundTab2, "Tab2 should be gone.");
+
+ is(aResponse.selected, 0,
+ "The original tab is selected.");
+
+ deferred.resolve();
+ });
+}
+
+registerCleanupFunction(function () {
+ gNewTab = null;
+ gNewWindow = null;
+ gClient = null;
+});
diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_navigation.js b/devtools/client/debugger/test/mochitest/browser_dbg_navigation.js
new file mode 100644
index 000000000..df48601e6
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_navigation.js
@@ -0,0 +1,75 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Check tab attach/navigation.
+ */
+
+const TAB1_URL = EXAMPLE_URL + "doc_empty-tab-01.html";
+const TAB2_URL = EXAMPLE_URL + "doc_empty-tab-02.html";
+
+var gClient;
+
+function test() {
+ if (!DebuggerServer.initialized) {
+ DebuggerServer.init();
+ DebuggerServer.addBrowserActors();
+ }
+
+ let transport = DebuggerServer.connectPipe();
+ gClient = new DebuggerClient(transport);
+ gClient.connect().then(([aType, aTraits]) => {
+ is(aType, "browser",
+ "Root actor should identify itself as a browser.");
+
+ addTab(TAB1_URL)
+ .then(() => attachTabActorForUrl(gClient, TAB1_URL))
+ .then(testNavigate)
+ .then(testDetach)
+ .then(finish)
+ .then(null, aError => {
+ ok(false, "Got an error: " + aError.message + "\n" + aError.stack);
+ });
+ });
+}
+
+function testNavigate([aGrip, aResponse]) {
+ let outstanding = [promise.defer(), promise.defer()];
+
+ gClient.addListener("tabNavigated", function onTabNavigated(aEvent, aPacket) {
+ is(aPacket.url, TAB2_URL,
+ "Got a tab navigation notification.");
+
+ if (aPacket.state == "start") {
+ ok(true, "Tab started to navigate.");
+ outstanding[0].resolve();
+ } else {
+ ok(true, "Tab finished navigating.");
+ gClient.removeListener("tabNavigated", onTabNavigated);
+ outstanding[1].resolve();
+ }
+ });
+
+ gBrowser.selectedBrowser.loadURI(TAB2_URL);
+ return promise.all(outstanding.map(e => e.promise))
+ .then(() => aGrip.actor);
+}
+
+function testDetach(aActor) {
+ let deferred = promise.defer();
+
+ gClient.addOneTimeListener("tabDetached", (aType, aPacket) => {
+ ok(true, "Got a tab detach notification.");
+ is(aPacket.from, aActor, "tab detach message comes from the expected actor");
+ deferred.resolve(gClient.close());
+ });
+
+ removeTab(gBrowser.selectedTab);
+ return deferred.promise;
+}
+
+registerCleanupFunction(function () {
+ gClient = null;
+});
diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_no-dangling-breakpoints.js b/devtools/client/debugger/test/mochitest/browser_dbg_no-dangling-breakpoints.js
new file mode 100644
index 000000000..b55e0132d
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_no-dangling-breakpoints.js
@@ -0,0 +1,25 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Bug 1201008 - Make sure you can't set a breakpoint in a blank
+ * editor
+ */
+
+function test() {
+ initDebugger('data:text/html,hi', { source: null }).then(([aTab,, aPanel]) => {
+ const gPanel = aPanel;
+ const gDebugger = gPanel.panelWin;
+
+ Task.spawn(function* () {
+ const editor = gDebugger.DebuggerView.editor;
+ editor.emit("gutterClick", 0);
+ is(editor.getBreakpoints().length, 0,
+ "A breakpoint should not exist");
+
+ closeDebuggerAndFinish(gPanel);
+ });
+ });
+}
diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_no-page-sources.js b/devtools/client/debugger/test/mochitest/browser_dbg_no-page-sources.js
new file mode 100644
index 000000000..ff7e6f04a
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_no-page-sources.js
@@ -0,0 +1,54 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Make sure the right text shows when the page has no sources.
+ */
+
+const TAB_URL = EXAMPLE_URL + "doc_no-page-sources.html";
+
+var gTab, gDebuggee, gPanel, gDebugger;
+var gEditor, gSources;
+
+function test() {
+ initDebugger(TAB_URL, { source: null }).then(([aTab, aDebuggee, aPanel]) => {
+ gTab = aTab;
+ gDebuggee = aDebuggee;
+ gPanel = aPanel;
+ gDebugger = gPanel.panelWin;
+ gEditor = gDebugger.DebuggerView.editor;
+ gSources = gDebugger.DebuggerView.Sources;
+ const constants = gDebugger.require("./content/constants");
+
+ reloadActiveTab(gPanel);
+ waitForNavigation(gPanel)
+ .then(testSourcesEmptyText)
+ .then(() => closeDebuggerAndFinish(gPanel))
+ .then(null, aError => {
+ ok(false, "Got an error: " + aError.message + "\n" + aError.stack);
+ });
+ });
+}
+
+function testSourcesEmptyText() {
+ is(gSources.itemCount, 0,
+ "Found no entries in the sources widget.");
+
+ is(gEditor.getText().length, 0,
+ "The source editor should not have any text displayed.");
+
+ is(gDebugger.document.querySelector("#sources .side-menu-widget-empty-text").getAttribute("value"),
+ gDebugger.L10N.getStr("noSourcesText"),
+ "The sources widget should now display 'This page has no sources'.");
+}
+
+registerCleanupFunction(function () {
+ gTab = null;
+ gDebuggee = null;
+ gPanel = null;
+ gDebugger = null;
+ gEditor = null;
+ gSources = null;
+});
diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_on-pause-highlight.js b/devtools/client/debugger/test/mochitest/browser_dbg_on-pause-highlight.js
new file mode 100644
index 000000000..07e2360af
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_on-pause-highlight.js
@@ -0,0 +1,86 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Tests that debugger's tab is highlighted when it is paused and not the
+ * currently selected tool.
+ */
+
+const TAB_URL = EXAMPLE_URL + "doc_recursion-stack.html";
+
+var gTab, gPanel, gDebugger;
+var gToolbox, gToolboxTab;
+
+function test() {
+ let options = {
+ source: TAB_URL,
+ line: 1
+ };
+ initDebugger(TAB_URL, options).then(([aTab,, aPanel]) => {
+ gTab = aTab;
+ gPanel = aPanel;
+ gDebugger = gPanel.panelWin;
+ gToolbox = gPanel._toolbox;
+ gToolboxTab = gToolbox.doc.getElementById("toolbox-tab-jsdebugger");
+
+ testPause();
+ });
+}
+
+function testPause() {
+ is(gDebugger.gThreadClient.paused, false,
+ "Should be running after starting test.");
+
+ gDebugger.gThreadClient.addOneTimeListener("paused", () => {
+ gToolbox.selectTool("webconsole").then(() => {
+ ok(gToolboxTab.hasAttribute("highlighted") &&
+ gToolboxTab.getAttribute("highlighted") == "true",
+ "The highlighted class is present");
+ ok(!gToolboxTab.hasAttribute("selected") ||
+ gToolboxTab.getAttribute("selected") != "true",
+ "The tab is not selected");
+ }).then(() => gToolbox.selectTool("jsdebugger")).then(() => {
+ ok(gToolboxTab.hasAttribute("highlighted") &&
+ gToolboxTab.getAttribute("highlighted") == "true",
+ "The highlighted class is present");
+ ok(gToolboxTab.hasAttribute("selected") &&
+ gToolboxTab.getAttribute("selected") == "true",
+ "...and the tab is selected, so the glow will not be present.");
+ }).then(testResume);
+ });
+
+ EventUtils.sendMouseEvent({ type: "mousedown" },
+ gDebugger.document.getElementById("resume"),
+ gDebugger);
+
+ // Evaluate a script to fully pause the debugger
+ once(gDebugger.gClient, "willInterrupt").then(() => {
+ evalInTab(gTab, "1+1;");
+ });
+}
+
+function testResume() {
+ gDebugger.gThreadClient.addOneTimeListener("resumed", () => {
+ gToolbox.selectTool("webconsole").then(() => {
+ ok(!gToolboxTab.classList.contains("highlighted"),
+ "The highlighted class is not present now after the resume");
+ ok(!gToolboxTab.hasAttribute("selected") ||
+ gToolboxTab.getAttribute("selected") != "true",
+ "The tab is not selected");
+ }).then(() => closeDebuggerAndFinish(gPanel));
+ });
+
+ EventUtils.sendMouseEvent({ type: "mousedown" },
+ gDebugger.document.getElementById("resume"),
+ gDebugger);
+}
+
+registerCleanupFunction(function () {
+ gTab = null;
+ gPanel = null;
+ gDebugger = null;
+ gToolbox = null;
+ gToolboxTab = null;
+});
diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_on-pause-raise.js b/devtools/client/debugger/test/mochitest/browser_dbg_on-pause-raise.js
new file mode 100644
index 000000000..6f6f15247
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_on-pause-raise.js
@@ -0,0 +1,120 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Tests that the toolbox is raised when the debugger gets paused.
+ */
+
+const TAB_URL = EXAMPLE_URL + "doc_recursion-stack.html";
+
+add_task(function *() {
+ let options = {
+ source: TAB_URL,
+ line: 1
+ };
+ let [tab,, panel] = yield initDebugger(TAB_URL, options);
+ let panelWin = panel.panelWin;
+ let toolbox = panel._toolbox;
+ let toolboxTab = toolbox.doc.getElementById("toolbox-tab-jsdebugger");
+
+ let newTab = yield addTab(TAB_URL);
+ isnot(newTab, tab,
+ "The newly added tab is different from the debugger's tab.");
+ is(gBrowser.selectedTab, newTab,
+ "Debugger's tab is not the selected tab.");
+
+ info("Run tests against bottom host.");
+ yield testPause();
+ yield testResume();
+
+ // testResume selected the console, select back the debugger.
+ yield toolbox.selectTool("jsdebugger");
+
+ info("Switching to a toolbox window host.");
+ yield toolbox.switchHost(Toolbox.HostType.WINDOW);
+
+ info("Run tests against window host.");
+ yield testPause();
+ yield testResume();
+
+ info("Cleanup after the test.");
+ yield toolbox.switchHost(Toolbox.HostType.BOTTOM);
+ yield closeDebuggerAndFinish(panel);
+
+ function* testPause() {
+ is(panelWin.gThreadClient.paused, false,
+ "Should be running after starting the test.");
+
+ let onFocus, onTabSelect;
+ if (toolbox.hostType == Toolbox.HostType.WINDOW) {
+ onFocus = new Promise(done => {
+ toolbox.win.parent.addEventListener("focus", function onFocus() {
+ toolbox.win.parent.removeEventListener("focus", onFocus, true);
+ done();
+ }, true);
+ });
+ } else {
+ onTabSelect = new Promise(done => {
+ tab.parentNode.addEventListener("TabSelect", function listener({type}) {
+ tab.parentNode.removeEventListener(type, listener);
+ done();
+ });
+ });
+ }
+
+ let onPaused = waitForPause(panelWin.gThreadClient);
+
+ // Evaluate a script to fully pause the debugger
+ evalInTab(tab, "debugger;");
+
+ yield onPaused;
+ yield onFocus;
+ yield onTabSelect;
+
+ if (toolbox.hostType != Toolbox.HostType.WINDOW) {
+ is(gBrowser.selectedTab, tab,
+ "Debugger's tab got selected.");
+ }
+
+ yield toolbox.selectTool("webconsole");
+ ok(toolboxTab.hasAttribute("highlighted") &&
+ toolboxTab.getAttribute("highlighted") == "true",
+ "The highlighted class is present");
+ ok(!toolboxTab.hasAttribute("selected") ||
+ toolboxTab.getAttribute("selected") != "true",
+ "The tab is not selected");
+ yield toolbox.selectTool("jsdebugger");
+ ok(toolboxTab.hasAttribute("highlighted") &&
+ toolboxTab.getAttribute("highlighted") == "true",
+ "The highlighted class is present");
+ ok(toolboxTab.hasAttribute("selected") &&
+ toolboxTab.getAttribute("selected") == "true",
+ "...and the tab is selected, so the glow will not be present.");
+ }
+
+ function* testResume() {
+ let onPaused = waitForEvent(panelWin.gThreadClient, "resumed");
+
+ EventUtils.sendMouseEvent({ type: "mousedown" },
+ panelWin.document.getElementById("resume"),
+ panelWin);
+
+ yield onPaused;
+
+ yield toolbox.selectTool("webconsole");
+ ok(!toolboxTab.hasAttribute("highlighted") ||
+ toolboxTab.getAttribute("highlighted") != "true",
+ "The highlighted class is not present now after the resume");
+ ok(!toolboxTab.hasAttribute("selected") ||
+ toolboxTab.getAttribute("selected") != "true",
+ "The tab is not selected");
+ }
+});
+
+registerCleanupFunction(function () {
+ // Revert to the default toolbox host, so that the following tests proceed
+ // normally and not inside a non-default host.
+ Services.prefs.setCharPref("devtools.toolbox.host", Toolbox.HostType.BOTTOM);
+});
diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_optimized-out-vars.js b/devtools/client/debugger/test/mochitest/browser_dbg_optimized-out-vars.js
new file mode 100644
index 000000000..ba60a0068
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_optimized-out-vars.js
@@ -0,0 +1,50 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Test that optimized out variables aren't present in the variables view.
+
+function test() {
+ Task.spawn(function* () {
+ const TAB_URL = EXAMPLE_URL + "doc_closure-optimized-out.html";
+ let gDebugger, sources;
+
+ let options = {
+ source: TAB_URL,
+ line: 1
+ };
+ let [tab,, panel] = yield initDebugger(TAB_URL, options);
+ gDebugger = panel.panelWin;
+ sources = gDebugger.DebuggerView.Sources;
+
+ yield panel.addBreakpoint({ actor: sources.values[0],
+ line: 18 });
+ yield ensureThreadClientState(panel, "resumed");
+
+ // Spin the event loop before causing the debuggee to pause, to allow
+ // this function to return first.
+ generateMouseClickInTab(tab, "content.document.querySelector('button')");
+
+ yield waitForDebuggerEvents(panel, gDebugger.EVENTS.FETCHED_SCOPES);
+ let gVars = gDebugger.DebuggerView.Variables;
+ let outerScope = gVars.getScopeAtIndex(1);
+ outerScope.expand();
+
+ let upvarVar = outerScope.get("upvar");
+ ok(upvarVar, "The variable `upvar` is shown.");
+ is(upvarVar.target.querySelector(".value").getAttribute("value"),
+ gDebugger.L10N.getStr("variablesViewOptimizedOut"),
+ "Should show the optimized out message for upvar.");
+
+ let argVar = outerScope.get("arg");
+ is(argVar.target.querySelector(".name").getAttribute("value"), "arg",
+ "Should have the right property name for |arg|.");
+ is(argVar.target.querySelector(".value").getAttribute("value"), 42,
+ "Should have the right property value for |arg|.");
+
+ yield resumeDebuggerThenCloseAndFinish(panel);
+ }).then(null, aError => {
+ ok(false, "Got an error: " + aError.message + "\n" + aError.stack);
+ });
+}
diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_panel-size.js b/devtools/client/debugger/test/mochitest/browser_dbg_panel-size.js
new file mode 100644
index 000000000..32e2df0c7
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_panel-size.js
@@ -0,0 +1,88 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Test that the sources and instruments panels widths are properly
+ * remembered when the debugger closes.
+ */
+
+const TAB_URL = EXAMPLE_URL + "doc_recursion-stack.html";
+
+function test() {
+ let gTab, gPanel, gDebugger;
+ let gPrefs, gSources, gInstruments;
+
+ let options = {
+ source: TAB_URL,
+ line: 1
+ };
+ initDebugger(TAB_URL, options).then(([aTab,, aPanel]) => {
+ gTab = aTab;
+ gPanel = aPanel;
+ gDebugger = gPanel.panelWin;
+ gPrefs = gDebugger.Prefs;
+ gSources = gDebugger.document.getElementById("workers-and-sources-pane");
+ gInstruments = gDebugger.document.getElementById("instruments-pane");
+
+ performTest();
+ });
+
+ function performTest() {
+ let preferredWsw = Services.prefs.getIntPref("devtools.debugger.ui.panes-workers-and-sources-width");
+ let preferredIw = Services.prefs.getIntPref("devtools.debugger.ui.panes-instruments-width");
+ let someWidth1, someWidth2;
+
+ do {
+ someWidth1 = parseInt(Math.random() * 200) + 100;
+ someWidth2 = parseInt(Math.random() * 300) + 100;
+ } while ((someWidth1 == preferredWsw) || (someWidth2 == preferredIw));
+
+ info("Preferred sources width: " + preferredWsw);
+ info("Preferred instruments width: " + preferredIw);
+ info("Generated sources width: " + someWidth1);
+ info("Generated instruments width: " + someWidth2);
+
+ ok(gPrefs.workersAndSourcesWidth,
+ "The debugger preferences should have a saved workersAndSourcesWidth value.");
+ ok(gPrefs.instrumentsWidth,
+ "The debugger preferences should have a saved instrumentsWidth value.");
+
+ is(gPrefs.workersAndSourcesWidth, preferredWsw,
+ "The debugger preferences should have a correct workersAndSourcesWidth value.");
+ is(gPrefs.instrumentsWidth, preferredIw,
+ "The debugger preferences should have a correct instrumentsWidth value.");
+
+ is(gSources.getAttribute("width"), gPrefs.workersAndSourcesWidth,
+ "The workers and sources pane width should be the same as the preferred value.");
+ is(gInstruments.getAttribute("width"), gPrefs.instrumentsWidth,
+ "The instruments pane width should be the same as the preferred value.");
+
+ gSources.setAttribute("width", someWidth1);
+ gInstruments.setAttribute("width", someWidth2);
+
+ is(gPrefs.workersAndSourcesWidth, preferredWsw,
+ "The workers and sources pane width pref should still be the same as the preferred value.");
+ is(gPrefs.instrumentsWidth, preferredIw,
+ "The instruments pane width pref should still be the same as the preferred value.");
+
+ isnot(gSources.getAttribute("width"), gPrefs.workersAndSourcesWidth,
+ "The workers and sources pane width should not be the preferred value anymore.");
+ isnot(gInstruments.getAttribute("width"), gPrefs.instrumentsWidth,
+ "The instruments pane width should not be the preferred value anymore.");
+
+ teardown(gPanel).then(() => {
+ is(gPrefs.workersAndSourcesWidth, someWidth1,
+ "The workers and sources pane width should have been saved by now.");
+ is(gPrefs.instrumentsWidth, someWidth2,
+ "The instruments pane width should have been saved by now.");
+
+ // Cleanup after ourselves!
+ Services.prefs.setIntPref("devtools.debugger.ui.panes-workers-and-sources-width", preferredWsw);
+ Services.prefs.setIntPref("devtools.debugger.ui.panes-instruments-width", preferredIw);
+
+ finish();
+ });
+ }
+}
diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_parser-01.js b/devtools/client/debugger/test/mochitest/browser_dbg_parser-01.js
new file mode 100644
index 000000000..8481e2d5f
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_parser-01.js
@@ -0,0 +1,33 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Check that simple JS can be parsed and cached with the reflection API.
+ */
+
+function test() {
+ let { Parser } = Cu.import("resource://devtools/shared/Parser.jsm", {});
+
+ let source = "let x = 42;";
+ let parser = new Parser();
+ let first = parser.get(source);
+ let second = parser.get(source);
+
+ isnot(first, second,
+ "The two syntax trees should be different.");
+
+ let third = parser.get(source, "url");
+ let fourth = parser.get(source, "url");
+
+ isnot(first, third,
+ "The new syntax trees should be different than the old ones.");
+ is(third, fourth,
+ "The new syntax trees were cached once an identifier was specified.");
+
+ is(parser.errors.length, 0,
+ "There should be no errors logged when parsing.");
+
+ finish();
+}
diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_parser-02.js b/devtools/client/debugger/test/mochitest/browser_dbg_parser-02.js
new file mode 100644
index 000000000..6cf41b380
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_parser-02.js
@@ -0,0 +1,30 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Check that syntax errors are reported correctly.
+ */
+
+function test() {
+ let { Parser } = Cu.import("resource://devtools/shared/Parser.jsm", {});
+
+ let source = "let x + 42;";
+ let parser = new Parser();
+ // Don't pollute the logs with exceptions that we are going to check anyhow.
+ parser.logExceptions = false;
+ let parsed = parser.get(source);
+
+ ok(parsed,
+ "An object should be returned even though the source had a syntax error.");
+
+ is(parser.errors.length, 1,
+ "There should be one error logged when parsing.");
+ is(parser.errors[0].name, "SyntaxError",
+ "The correct exception was caught.");
+ is(parser.errors[0].message, "missing ; before statement",
+ "The correct exception was caught.");
+
+ finish();
+}
diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_parser-03.js b/devtools/client/debugger/test/mochitest/browser_dbg_parser-03.js
new file mode 100644
index 000000000..439df705b
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_parser-03.js
@@ -0,0 +1,79 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Check that JS inside HTML can be separated and parsed correctly.
+ */
+
+function test() {
+ let { Parser } = Cu.import("resource://devtools/shared/Parser.jsm", {});
+
+ let source = [
+ "<!doctype html>",
+ "<head>",
+ "<script>",
+ "let a = 42;",
+ "</script>",
+ "<script type='text/javascript'>",
+ "let b = 42;",
+ "</script>",
+ "<script type='text/javascript;version=1.8'>",
+ "let c = 42;",
+ "</script>",
+ "</head>"
+ ].join("\n");
+ let parser = new Parser();
+ let parsed = parser.get(source);
+
+ ok(parsed,
+ "HTML code should be parsed correctly.");
+ is(parser.errors.length, 0,
+ "There should be no errors logged when parsing.");
+
+ is(parsed.scriptCount, 3,
+ "There should be 3 scripts parsed in the parent HTML source.");
+
+ is(parsed.getScriptInfo(0).toSource(), "({start:-1, length:-1, index:-1})",
+ "There is no script at the beginning of the parent source.");
+ is(parsed.getScriptInfo(source.length - 1).toSource(), "({start:-1, length:-1, index:-1})",
+ "There is no script at the end of the parent source.");
+
+ is(parsed.getScriptInfo(source.indexOf("let a")).toSource(), "({start:31, length:13, index:0})",
+ "The first script was located correctly.");
+ is(parsed.getScriptInfo(source.indexOf("let b")).toSource(), "({start:85, length:13, index:1})",
+ "The second script was located correctly.");
+ is(parsed.getScriptInfo(source.indexOf("let c")).toSource(), "({start:151, length:13, index:2})",
+ "The third script was located correctly.");
+
+ is(parsed.getScriptInfo(source.indexOf("let a") - 1).toSource(), "({start:31, length:13, index:0})",
+ "The left edge of the first script was interpreted correctly.");
+ is(parsed.getScriptInfo(source.indexOf("let b") - 1).toSource(), "({start:85, length:13, index:1})",
+ "The left edge of the second script was interpreted correctly.");
+ is(parsed.getScriptInfo(source.indexOf("let c") - 1).toSource(), "({start:151, length:13, index:2})",
+ "The left edge of the third script was interpreted correctly.");
+
+ is(parsed.getScriptInfo(source.indexOf("let a") - 2).toSource(), "({start:-1, length:-1, index:-1})",
+ "The left outside of the first script was interpreted correctly.");
+ is(parsed.getScriptInfo(source.indexOf("let b") - 2).toSource(), "({start:-1, length:-1, index:-1})",
+ "The left outside of the second script was interpreted correctly.");
+ is(parsed.getScriptInfo(source.indexOf("let c") - 2).toSource(), "({start:-1, length:-1, index:-1})",
+ "The left outside of the third script was interpreted correctly.");
+
+ is(parsed.getScriptInfo(source.indexOf("let a") + 12).toSource(), "({start:31, length:13, index:0})",
+ "The right edge of the first script was interpreted correctly.");
+ is(parsed.getScriptInfo(source.indexOf("let b") + 12).toSource(), "({start:85, length:13, index:1})",
+ "The right edge of the second script was interpreted correctly.");
+ is(parsed.getScriptInfo(source.indexOf("let c") + 12).toSource(), "({start:151, length:13, index:2})",
+ "The right edge of the third script was interpreted correctly.");
+
+ is(parsed.getScriptInfo(source.indexOf("let a") + 13).toSource(), "({start:-1, length:-1, index:-1})",
+ "The right outside of the first script was interpreted correctly.");
+ is(parsed.getScriptInfo(source.indexOf("let b") + 13).toSource(), "({start:-1, length:-1, index:-1})",
+ "The right outside of the second script was interpreted correctly.");
+ is(parsed.getScriptInfo(source.indexOf("let c") + 13).toSource(), "({start:-1, length:-1, index:-1})",
+ "The right outside of the third script was interpreted correctly.");
+
+ finish();
+}
diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_parser-04.js b/devtools/client/debugger/test/mochitest/browser_dbg_parser-04.js
new file mode 100644
index 000000000..b6ae0dfb6
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_parser-04.js
@@ -0,0 +1,58 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Check that faulty JS inside HTML can be separated and identified correctly.
+ */
+
+function test() {
+ let { Parser } = Cu.import("resource://devtools/shared/Parser.jsm", {});
+
+ let source = [
+ "<!doctype html>",
+ "<head>",
+ "<SCRIPT>",
+ "let a + 42;",
+ "</SCRIPT>",
+ "<script type='text/javascript'>",
+ "let b = 42;",
+ "</SCRIPT>",
+ "<script type='text/javascript;version=1.8'>",
+ "let c + 42;",
+ "</SCRIPT>",
+ "</head>"
+ ].join("\n");
+ let parser = new Parser();
+ // Don't pollute the logs with exceptions that we are going to check anyhow.
+ parser.logExceptions = false;
+ let parsed = parser.get(source);
+
+ ok(parsed,
+ "HTML code should be parsed correctly.");
+ is(parser.errors.length, 2,
+ "There should be two errors logged when parsing.");
+
+ is(parser.errors[0].name, "SyntaxError",
+ "The correct first exception was caught.");
+ is(parser.errors[0].message, "missing ; before statement",
+ "The correct first exception was caught.");
+
+ is(parser.errors[1].name, "SyntaxError",
+ "The correct second exception was caught.");
+ is(parser.errors[1].message, "missing ; before statement",
+ "The correct second exception was caught.");
+
+ is(parsed.scriptCount, 1,
+ "There should be 1 script parsed in the parent HTML source.");
+
+ is(parsed.getScriptInfo(source.indexOf("let a")).toSource(), "({start:-1, length:-1, index:-1})",
+ "The first script shouldn't be considered valid.");
+ is(parsed.getScriptInfo(source.indexOf("let b")).toSource(), "({start:85, length:13, index:0})",
+ "The second script was located correctly.");
+ is(parsed.getScriptInfo(source.indexOf("let c")).toSource(), "({start:-1, length:-1, index:-1})",
+ "The third script shouldn't be considered valid.");
+
+ finish();
+}
diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_parser-05.js b/devtools/client/debugger/test/mochitest/browser_dbg_parser-05.js
new file mode 100644
index 000000000..b34d3952a
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_parser-05.js
@@ -0,0 +1,45 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Check that JS code containing strings that might look like <script> tags
+ * inside an HTML source is parsed correctly.
+ */
+
+function test() {
+ let { Parser } = Cu.import("resource://devtools/shared/Parser.jsm", {});
+
+ let source = [
+ "let a = [];",
+ "a.push('<script>');",
+ "a.push('var a = 42;');",
+ "a.push('</script>');",
+ "a.push('<script type=\"text/javascript\">');",
+ "a.push('var b = 42;');",
+ "a.push('</script>');",
+ "a.push('<script type=\"text/javascript;version=1.8\">');",
+ "a.push('var c = 42;');",
+ "a.push('</script>');"
+ ].join("\n");
+ let parser = new Parser();
+ let parsed = parser.get(source);
+
+ ok(parsed,
+ "The javascript code should be parsed correctly.");
+ is(parser.errors.length, 0,
+ "There should be no errors logged when parsing.");
+
+ is(parsed.scriptCount, 1,
+ "There should be 1 script parsed in the parent source.");
+
+ is(parsed.getScriptInfo(source.indexOf("let a")).toSource(), "({start:0, length:261, index:0})",
+ "The script location is correct (1).");
+ is(parsed.getScriptInfo(source.indexOf("<script>")).toSource(), "({start:0, length:261, index:0})",
+ "The script location is correct (2).");
+ is(parsed.getScriptInfo(source.indexOf("</script>")).toSource(), "({start:0, length:261, index:0})",
+ "The script location is correct (3).");
+
+ finish();
+}
diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_parser-06.js b/devtools/client/debugger/test/mochitest/browser_dbg_parser-06.js
new file mode 100644
index 000000000..4e5583e00
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_parser-06.js
@@ -0,0 +1,80 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Check that some potentially problematic identifier nodes have the
+ * right location information attached.
+ */
+
+function test() {
+ let { Parser, ParserHelpers, SyntaxTreeVisitor } =
+ Cu.import("resource://devtools/shared/Parser.jsm", {});
+
+ function verify(source, predicate, [sline, scol], [eline, ecol]) {
+ let ast = Parser.reflectionAPI.parse(source);
+ let node = SyntaxTreeVisitor.filter(ast, predicate).pop();
+ let loc = ParserHelpers.getNodeLocation(node);
+
+ is(loc.start.toSource(), { line: sline, column: scol }.toSource(),
+ "The start location was correct for the identifier in: '" + source + "'.");
+ is(loc.end.toSource(), { line: eline, column: ecol }.toSource(),
+ "The end location was correct for the identifier in: '" + source + "'.");
+ }
+
+ // FunctionDeclarations and FunctionExpressions.
+
+ // The location is unavailable for the identifier node "foo".
+ verify("function foo(){}", e => e.name == "foo", [1, 9], [1, 12]);
+ verify("\nfunction\nfoo\n(\n)\n{\n}\n", e => e.name == "foo", [3, 0], [3, 3]);
+
+ verify("({bar:function foo(){}})", e => e.name == "foo", [1, 15], [1, 18]);
+ verify("(\n{\nbar\n:\nfunction\nfoo\n(\n)\n{\n}\n}\n)", e => e.name == "foo", [6, 0], [6, 3]);
+
+ // Just to be sure, check the identifier node "bar" as well.
+ verify("({bar:function foo(){}})", e => e.name == "bar", [1, 2], [1, 5]);
+ verify("(\n{\nbar\n:\nfunction\nfoo\n(\n)\n{\n}\n}\n)", e => e.name == "bar", [3, 0], [3, 3]);
+
+ // MemberExpressions.
+
+ // The location is unavailable for the identifier node "bar".
+ verify("foo.bar", e => e.name == "bar", [1, 4], [1, 7]);
+ verify("\nfoo\n.\nbar\n", e => e.name == "bar", [4, 0], [4, 3]);
+
+ // Just to be sure, check the identifier node "foo" as well.
+ verify("foo.bar", e => e.name == "foo", [1, 0], [1, 3]);
+ verify("\nfoo\n.\nbar\n", e => e.name == "foo", [2, 0], [2, 3]);
+
+ // VariableDeclarator
+
+ // The location is incorrect for the identifier node "foo".
+ verify("let foo = bar", e => e.name == "foo", [1, 4], [1, 7]);
+ verify("\nlet\nfoo\n=\nbar\n", e => e.name == "foo", [3, 0], [3, 3]);
+
+ // Just to be sure, check the identifier node "bar" as well.
+ verify("let foo = bar", e => e.name == "bar", [1, 10], [1, 13]);
+ verify("\nlet\nfoo\n=\nbar\n", e => e.name == "bar", [5, 0], [5, 3]);
+
+ // Just to be sure, check AssignmentExpreesions as well.
+ verify("foo = bar", e => e.name == "foo", [1, 0], [1, 3]);
+ verify("\nfoo\n=\nbar\n", e => e.name == "foo", [2, 0], [2, 3]);
+ verify("foo = bar", e => e.name == "bar", [1, 6], [1, 9]);
+ verify("\nfoo\n=\nbar\n", e => e.name == "bar", [4, 0], [4, 3]);
+
+ // LabeledStatement, BreakStatement and ContinueStatement, because it's 1968 again
+
+ verify("foo: bar", e => e.name == "foo", [1, 0], [1, 3]);
+ verify("\nfoo\n:\nbar\n", e => e.name == "foo", [2, 0], [2, 3]);
+
+ verify("foo: for(;;) break foo", e => e.name == "foo", [1, 19], [1, 22]);
+ verify("\nfoo\n:\nfor(\n;\n;\n)\nbreak\nfoo\n", e => e.name == "foo", [9, 0], [9, 3]);
+
+ verify("foo: bar", e => e.name == "foo", [1, 0], [1, 3]);
+ verify("\nfoo\n:\nbar\n", e => e.name == "foo", [2, 0], [2, 3]);
+
+ verify("foo: for(;;) continue foo", e => e.name == "foo", [1, 22], [1, 25]);
+ verify("\nfoo\n:\nfor(\n;\n;\n)\ncontinue\nfoo\n", e => e.name == "foo", [9, 0], [9, 3]);
+
+ finish();
+}
diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_parser-07.js b/devtools/client/debugger/test/mochitest/browser_dbg_parser-07.js
new file mode 100644
index 000000000..bea913a9e
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_parser-07.js
@@ -0,0 +1,57 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Check that nodes with locaiton information attached can be properly
+ * verified for containing lines and columns.
+ */
+
+function test() {
+ let { ParserHelpers } = Cu.import("resource://devtools/shared/Parser.jsm", {});
+
+ let node1 = { loc: {
+ start: { line: 1, column: 10 },
+ end: { line: 10, column: 1 }
+ }};
+ let node2 = { loc: {
+ start: { line: 1, column: 10 },
+ end: { line: 1, column: 20 }
+ }};
+
+ ok(ParserHelpers.nodeContainsLine(node1, 1), "1st check.");
+ ok(ParserHelpers.nodeContainsLine(node1, 5), "2nd check.");
+ ok(ParserHelpers.nodeContainsLine(node1, 10), "3rd check.");
+
+ ok(!ParserHelpers.nodeContainsLine(node1, 0), "4th check.");
+ ok(!ParserHelpers.nodeContainsLine(node1, 11), "5th check.");
+
+ ok(ParserHelpers.nodeContainsLine(node2, 1), "6th check.");
+ ok(!ParserHelpers.nodeContainsLine(node2, 0), "7th check.");
+ ok(!ParserHelpers.nodeContainsLine(node2, 2), "8th check.");
+
+ ok(!ParserHelpers.nodeContainsPoint(node1, 1, 10), "9th check.");
+ ok(!ParserHelpers.nodeContainsPoint(node1, 10, 1), "10th check.");
+
+ ok(!ParserHelpers.nodeContainsPoint(node1, 0, 10), "11th check.");
+ ok(!ParserHelpers.nodeContainsPoint(node1, 11, 1), "12th check.");
+
+ ok(!ParserHelpers.nodeContainsPoint(node1, 1, 9), "13th check.");
+ ok(!ParserHelpers.nodeContainsPoint(node1, 10, 2), "14th check.");
+
+ ok(ParserHelpers.nodeContainsPoint(node2, 1, 10), "15th check.");
+ ok(ParserHelpers.nodeContainsPoint(node2, 1, 15), "16th check.");
+ ok(ParserHelpers.nodeContainsPoint(node2, 1, 20), "17th check.");
+
+ ok(!ParserHelpers.nodeContainsPoint(node2, 0, 10), "18th check.");
+ ok(!ParserHelpers.nodeContainsPoint(node2, 2, 20), "19th check.");
+
+ ok(!ParserHelpers.nodeContainsPoint(node2, 0, 9), "20th check.");
+ ok(!ParserHelpers.nodeContainsPoint(node2, 2, 21), "21th check.");
+
+ ok(!ParserHelpers.nodeContainsPoint(node2, 1, 9), "22th check.");
+ ok(!ParserHelpers.nodeContainsPoint(node2, 1, 21), "23th check.");
+
+ finish();
+}
diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_parser-08.js b/devtools/client/debugger/test/mochitest/browser_dbg_parser-08.js
new file mode 100644
index 000000000..624f3c293
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_parser-08.js
@@ -0,0 +1,291 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Test that inferring anonymous function information is done correctly.
+ */
+
+function test() {
+ let { Parser, ParserHelpers, SyntaxTreeVisitor } =
+ Cu.import("resource://devtools/shared/Parser.jsm", {});
+
+ function verify(source, predicate, details) {
+ let { name, chain } = details;
+ let [[sline, scol], [eline, ecol]] = details.loc;
+ let ast = Parser.reflectionAPI.parse(source);
+ let node = SyntaxTreeVisitor.filter(ast, predicate).pop();
+ let info = ParserHelpers.inferFunctionExpressionInfo(node);
+
+ is(info.name, name,
+ "The function expression assignment property name is correct.");
+ is(chain ? info.chain.toSource() : info.chain, chain ? chain.toSource() : chain,
+ "The function expression assignment property chain is correct.");
+ is(info.loc.start.toSource(), { line: sline, column: scol }.toSource(),
+ "The start location was correct for the identifier in: '" + source + "'.");
+ is(info.loc.end.toSource(), { line: eline, column: ecol }.toSource(),
+ "The end location was correct for the identifier in: '" + source + "'.");
+ }
+
+ // VariableDeclarator
+
+ verify("var foo=function(){}", e => e.type == "FunctionExpression", {
+ name: "foo",
+ chain: null,
+ loc: [[1, 4], [1, 7]]
+ });
+ verify("\nvar\nfoo\n=\nfunction\n(\n)\n{\n}\n", e => e.type == "FunctionExpression", {
+ name: "foo",
+ chain: null,
+ loc: [[3, 0], [3, 3]]
+ });
+
+ // AssignmentExpression
+
+ verify("foo=function(){}", e => e.type == "FunctionExpression",
+ { name: "foo", chain: [], loc: [[1, 0], [1, 3]] });
+
+ verify("\nfoo\n=\nfunction\n(\n)\n{\n}\n", e => e.type == "FunctionExpression",
+ { name: "foo", chain: [], loc: [[2, 0], [2, 3]] });
+
+ verify("foo.bar=function(){}", e => e.type == "FunctionExpression",
+ { name: "bar", chain: ["foo"], loc: [[1, 0], [1, 7]] });
+
+ verify("\nfoo.bar\n=\nfunction\n(\n)\n{\n}\n", e => e.type == "FunctionExpression",
+ { name: "bar", chain: ["foo"], loc: [[2, 0], [2, 7]] });
+
+ verify("this.foo=function(){}", e => e.type == "FunctionExpression",
+ { name: "foo", chain: ["this"], loc: [[1, 0], [1, 8]] });
+
+ verify("\nthis.foo\n=\nfunction\n(\n)\n{\n}\n", e => e.type == "FunctionExpression",
+ { name: "foo", chain: ["this"], loc: [[2, 0], [2, 8]] });
+
+ verify("this.foo.bar=function(){}", e => e.type == "FunctionExpression",
+ { name: "bar", chain: ["this", "foo"], loc: [[1, 0], [1, 12]] });
+
+ verify("\nthis.foo.bar\n=\nfunction\n(\n)\n{\n}\n", e => e.type == "FunctionExpression",
+ { name: "bar", chain: ["this", "foo"], loc: [[2, 0], [2, 12]] });
+
+ verify("foo.this.bar=function(){}", e => e.type == "FunctionExpression",
+ { name: "bar", chain: ["foo", "this"], loc: [[1, 0], [1, 12]] });
+
+ verify("\nfoo.this.bar\n=\nfunction\n(\n)\n{\n}\n", e => e.type == "FunctionExpression",
+ { name: "bar", chain: ["foo", "this"], loc: [[2, 0], [2, 12]] });
+
+ // ObjectExpression
+
+ verify("({foo:function(){}})", e => e.type == "FunctionExpression",
+ { name: "foo", chain: [], loc: [[1, 2], [1, 5]] });
+
+ verify("(\n{\nfoo\n:\nfunction\n(\n)\n{\n}\n}\n)", e => e.type == "FunctionExpression",
+ { name: "foo", chain: [], loc: [[3, 0], [3, 3]] });
+
+ verify("({foo:{bar:function(){}}})", e => e.type == "FunctionExpression",
+ { name: "bar", chain: ["foo"], loc: [[1, 7], [1, 10]] });
+
+ verify("(\n{\nfoo\n:\n{\nbar\n:\nfunction\n(\n)\n{\n}\n}\n}\n)", e => e.type == "FunctionExpression",
+ { name: "bar", chain: ["foo"], loc: [[6, 0], [6, 3]] });
+
+ // AssignmentExpression + ObjectExpression
+
+ verify("foo={bar:function(){}}", e => e.type == "FunctionExpression",
+ { name: "bar", chain: ["foo"], loc: [[1, 5], [1, 8]] });
+
+ verify("\nfoo\n=\n{\nbar\n:\nfunction\n(\n)\n{\n}\n}\n", e => e.type == "FunctionExpression",
+ { name: "bar", chain: ["foo"], loc: [[5, 0], [5, 3]] });
+
+ verify("foo={bar:{baz:function(){}}}", e => e.type == "FunctionExpression",
+ { name: "baz", chain: ["foo", "bar"], loc: [[1, 10], [1, 13]] });
+
+ verify("\nfoo\n=\n{\nbar\n:\n{\nbaz\n:\nfunction\n(\n)\n{\n}\n}\n}\n", e => e.type == "FunctionExpression",
+ { name: "baz", chain: ["foo", "bar"], loc: [[8, 0], [8, 3]] });
+
+ verify("nested.foo={bar:function(){}}", e => e.type == "FunctionExpression",
+ { name: "bar", chain: ["nested", "foo"], loc: [[1, 12], [1, 15]] });
+
+ verify("\nnested.foo\n=\n{\nbar\n:\nfunction\n(\n)\n{\n}\n}\n", e => e.type == "FunctionExpression",
+ { name: "bar", chain: ["nested", "foo"], loc: [[5, 0], [5, 3]] });
+
+ verify("nested.foo={bar:{baz:function(){}}}", e => e.type == "FunctionExpression",
+ { name: "baz", chain: ["nested", "foo", "bar"], loc: [[1, 17], [1, 20]] });
+
+ verify("\nnested.foo\n=\n{\nbar\n:\n{\nbaz\n:\nfunction\n(\n)\n{\n}\n}\n}\n", e => e.type == "FunctionExpression",
+ { name: "baz", chain: ["nested", "foo", "bar"], loc: [[8, 0], [8, 3]] });
+
+ verify("this.foo={bar:function(){}}", e => e.type == "FunctionExpression",
+ { name: "bar", chain: ["this", "foo"], loc: [[1, 10], [1, 13]] });
+
+ verify("\nthis.foo\n=\n{\nbar\n:\nfunction\n(\n)\n{\n}\n}\n", e => e.type == "FunctionExpression",
+ { name: "bar", chain: ["this", "foo"], loc: [[5, 0], [5, 3]] });
+
+ verify("this.foo={bar:{baz:function(){}}}", e => e.type == "FunctionExpression",
+ { name: "baz", chain: ["this", "foo", "bar"], loc: [[1, 15], [1, 18]] });
+
+ verify("\nthis.foo\n=\n{\nbar\n:\n{\nbaz\n:\nfunction\n(\n)\n{\n}\n}\n}\n", e => e.type == "FunctionExpression",
+ { name: "baz", chain: ["this", "foo", "bar"], loc: [[8, 0], [8, 3]] });
+
+ verify("this.nested.foo={bar:function(){}}", e => e.type == "FunctionExpression",
+ { name: "bar", chain: ["this", "nested", "foo"], loc: [[1, 17], [1, 20]] });
+
+ verify("\nthis.nested.foo\n=\n{\nbar\n:\nfunction\n(\n)\n{\n}\n}\n", e => e.type == "FunctionExpression",
+ { name: "bar", chain: ["this", "nested", "foo"], loc: [[5, 0], [5, 3]] });
+
+ verify("this.nested.foo={bar:{baz:function(){}}}", e => e.type == "FunctionExpression",
+ { name: "baz", chain: ["this", "nested", "foo", "bar"], loc: [[1, 22], [1, 25]] });
+
+ verify("\nthis.nested.foo\n=\n{\nbar\n:\n{\nbaz\n:\nfunction\n(\n)\n{\n}\n}\n}\n", e => e.type == "FunctionExpression",
+ { name: "baz", chain: ["this", "nested", "foo", "bar"], loc: [[8, 0], [8, 3]] });
+
+ verify("nested.this.foo={bar:function(){}}", e => e.type == "FunctionExpression",
+ { name: "bar", chain: ["nested", "this", "foo"], loc: [[1, 17], [1, 20]] });
+
+ verify("\nnested.this.foo\n=\n{\nbar\n:\nfunction\n(\n)\n{\n}\n}\n", e => e.type == "FunctionExpression",
+ { name: "bar", chain: ["nested", "this", "foo"], loc: [[5, 0], [5, 3]] });
+
+ verify("nested.this.foo={bar:{baz:function(){}}}", e => e.type == "FunctionExpression",
+ { name: "baz", chain: ["nested", "this", "foo", "bar"], loc: [[1, 22], [1, 25]] });
+
+ verify("\nnested.this.foo\n=\n{\nbar\n:\n{\nbaz\n:\nfunction\n(\n)\n{\n}\n}\n}\n", e => e.type == "FunctionExpression",
+ { name: "baz", chain: ["nested", "this", "foo", "bar"], loc: [[8, 0], [8, 3]] });
+
+ // VariableDeclarator + AssignmentExpression + ObjectExpression
+
+ verify("let foo={bar:function(){}}", e => e.type == "FunctionExpression",
+ { name: "bar", chain: ["foo"], loc: [[1, 9], [1, 12]] });
+
+ verify("\nlet\nfoo\n=\n{\nbar\n:\nfunction\n(\n)\n{\n}\n}\n", e => e.type == "FunctionExpression",
+ { name: "bar", chain: ["foo"], loc: [[6, 0], [6, 3]] });
+
+ verify("let foo={bar:{baz:function(){}}}", e => e.type == "FunctionExpression",
+ { name: "baz", chain: ["foo", "bar"], loc: [[1, 14], [1, 17]] });
+
+ verify("\nlet\nfoo\n=\n{\nbar\n:\n{\nbaz\n:\nfunction\n(\n)\n{\n}\n}\n}\n", e => e.type == "FunctionExpression",
+ { name: "baz", chain: ["foo", "bar"], loc: [[9, 0], [9, 3]] });
+
+ // New/CallExpression + AssignmentExpression + ObjectExpression
+
+ verify("foo({bar:function(){}})", e => e.type == "FunctionExpression",
+ { name: "bar", chain: [], loc: [[1, 5], [1, 8]] });
+
+ verify("\nfoo\n(\n{\nbar\n:\nfunction\n(\n)\n{\n}\n}\n)\n", e => e.type == "FunctionExpression",
+ { name: "bar", chain: [], loc: [[5, 0], [5, 3]] });
+
+ verify("foo({bar:{baz:function(){}}})", e => e.type == "FunctionExpression",
+ { name: "baz", chain: ["bar"], loc: [[1, 10], [1, 13]] });
+
+ verify("\nfoo\n(\n{\nbar\n:\n{\nbaz\n:\nfunction\n(\n)\n{\n}\n}\n}\n)\n", e => e.type == "FunctionExpression",
+ { name: "baz", chain: ["bar"], loc: [[8, 0], [8, 3]] });
+
+ verify("nested.foo({bar:function(){}})", e => e.type == "FunctionExpression",
+ { name: "bar", chain: [], loc: [[1, 12], [1, 15]] });
+
+ verify("\nnested.foo\n(\n{\nbar\n:\nfunction\n(\n)\n{\n}\n}\n)\n", e => e.type == "FunctionExpression",
+ { name: "bar", chain: [], loc: [[5, 0], [5, 3]] });
+
+ verify("nested.foo({bar:{baz:function(){}}})", e => e.type == "FunctionExpression",
+ { name: "baz", chain: ["bar"], loc: [[1, 17], [1, 20]] });
+
+ verify("\nnested.foo\n(\n{\nbar\n:\n{\nbaz\n:\nfunction\n(\n)\n{\n}\n}\n}\n)\n", e => e.type == "FunctionExpression",
+ { name: "baz", chain: ["bar"], loc: [[8, 0], [8, 3]] });
+
+ verify("this.foo({bar:function(){}})", e => e.type == "FunctionExpression",
+ { name: "bar", chain: [], loc: [[1, 10], [1, 13]] });
+
+ verify("\nthis.foo\n(\n{\nbar\n:\nfunction\n(\n)\n{\n}\n}\n)\n", e => e.type == "FunctionExpression",
+ { name: "bar", chain: [], loc: [[5, 0], [5, 3]] });
+
+ verify("this.foo({bar:{baz:function(){}}})", e => e.type == "FunctionExpression",
+ { name: "baz", chain: ["bar"], loc: [[1, 15], [1, 18]] });
+
+ verify("\nthis.foo\n(\n{\nbar\n:\n{\nbaz\n:\nfunction\n(\n)\n{\n}\n}\n}\n)\n", e => e.type == "FunctionExpression",
+ { name: "baz", chain: ["bar"], loc: [[8, 0], [8, 3]] });
+
+ verify("this.nested.foo({bar:function(){}})", e => e.type == "FunctionExpression",
+ { name: "bar", chain: [], loc: [[1, 17], [1, 20]] });
+
+ verify("\nthis.nested.foo\n(\n{\nbar\n:\nfunction\n(\n)\n{\n}\n}\n)\n", e => e.type == "FunctionExpression",
+ { name: "bar", chain: [], loc: [[5, 0], [5, 3]] });
+
+ verify("this.nested.foo({bar:{baz:function(){}}})", e => e.type == "FunctionExpression",
+ { name: "baz", chain: ["bar"], loc: [[1, 22], [1, 25]] });
+
+ verify("\nthis.nested.foo\n(\n{\nbar\n:\n{\nbaz\n:\nfunction\n(\n)\n{\n}\n}\n}\n)\n", e => e.type == "FunctionExpression",
+ { name: "baz", chain: ["bar"], loc: [[8, 0], [8, 3]] });
+
+ verify("nested.this.foo({bar:function(){}})", e => e.type == "FunctionExpression",
+ { name: "bar", chain: [], loc: [[1, 17], [1, 20]] });
+
+ verify("\nnested.this.foo\n(\n{\nbar\n:\nfunction\n(\n)\n{\n}\n}\n)\n", e => e.type == "FunctionExpression",
+ { name: "bar", chain: [], loc: [[5, 0], [5, 3]] });
+
+ verify("nested.this.foo({bar:{baz:function(){}}})", e => e.type == "FunctionExpression",
+ { name: "baz", chain: ["bar"], loc: [[1, 22], [1, 25]] });
+
+ verify("\nnested.this.foo\n(\n{\nbar\n:\n{\nbaz\n:\nfunction\n(\n)\n{\n}\n}\n}\n)\n", e => e.type == "FunctionExpression",
+ { name: "baz", chain: ["bar"], loc: [[8, 0], [8, 3]] });
+
+ // New/CallExpression + VariableDeclarator + AssignmentExpression + ObjectExpression
+
+ verify("let target=foo({bar:function(){}})", e => e.type == "FunctionExpression",
+ { name: "bar", chain: ["target"], loc: [[1, 16], [1, 19]] });
+
+ verify("\nlet\ntarget=\nfoo\n(\n{\nbar\n:\nfunction\n(\n)\n{\n}\n}\n)\n", e => e.type == "FunctionExpression",
+ { name: "bar", chain: ["target"], loc: [[7, 0], [7, 3]] });
+
+ verify("let target=foo({bar:{baz:function(){}}})", e => e.type == "FunctionExpression",
+ { name: "baz", chain: ["target", "bar"], loc: [[1, 21], [1, 24]] });
+
+ verify("\nlet\ntarget=\nfoo\n(\n{\nbar\n:\n{\nbaz\n:\nfunction\n(\n)\n{\n}\n}\n}\n)\n", e => e.type == "FunctionExpression",
+ { name: "baz", chain: ["target", "bar"], loc: [[10, 0], [10, 3]] });
+
+ verify("let target=nested.foo({bar:function(){}})", e => e.type == "FunctionExpression",
+ { name: "bar", chain: ["target"], loc: [[1, 23], [1, 26]] });
+
+ verify("\nlet\ntarget=\nnested.foo\n(\n{\nbar\n:\nfunction\n(\n)\n{\n}\n}\n)\n", e => e.type == "FunctionExpression",
+ { name: "bar", chain: ["target"], loc: [[7, 0], [7, 3]] });
+
+ verify("let target=nested.foo({bar:{baz:function(){}}})", e => e.type == "FunctionExpression",
+ { name: "baz", chain: ["target", "bar"], loc: [[1, 28], [1, 31]] });
+
+ verify("\nlet\ntarget=\nnested.foo\n(\n{\nbar\n:\n{\nbaz\n:\nfunction\n(\n)\n{\n}\n}\n}\n)\n", e => e.type == "FunctionExpression",
+ { name: "baz", chain: ["target", "bar"], loc: [[10, 0], [10, 3]] });
+
+ verify("let target=this.foo({bar:function(){}})", e => e.type == "FunctionExpression",
+ { name: "bar", chain: ["target"], loc: [[1, 21], [1, 24]] });
+
+ verify("\nlet\ntarget=\nthis.foo\n(\n{\nbar\n:\nfunction\n(\n)\n{\n}\n}\n)\n", e => e.type == "FunctionExpression",
+ { name: "bar", chain: ["target"], loc: [[7, 0], [7, 3]] });
+
+ verify("let target=this.foo({bar:{baz:function(){}}})", e => e.type == "FunctionExpression",
+ { name: "baz", chain: ["target", "bar"], loc: [[1, 26], [1, 29]] });
+
+ verify("\nlet\ntarget=\nthis.foo\n(\n{\nbar\n:\n{\nbaz\n:\nfunction\n(\n)\n{\n}\n}\n}\n)\n", e => e.type == "FunctionExpression",
+ { name: "baz", chain: ["target", "bar"], loc: [[10, 0], [10, 3]] });
+
+ verify("let target=this.nested.foo({bar:function(){}})", e => e.type == "FunctionExpression",
+ { name: "bar", chain: ["target"], loc: [[1, 28], [1, 31]] });
+
+ verify("\nlet\ntarget=\nthis.nested.foo\n(\n{\nbar\n:\nfunction\n(\n)\n{\n}\n}\n)\n", e => e.type == "FunctionExpression",
+ { name: "bar", chain: ["target"], loc: [[7, 0], [7, 3]] });
+
+ verify("let target=this.nested.foo({bar:{baz:function(){}}})", e => e.type == "FunctionExpression",
+ { name: "baz", chain: ["target", "bar"], loc: [[1, 33], [1, 36]] });
+
+ verify("\nlet\ntarget=\nthis.nested.foo\n(\n{\nbar\n:\n{\nbaz\n:\nfunction\n(\n)\n{\n}\n}\n}\n)\n", e => e.type == "FunctionExpression",
+ { name: "baz", chain: ["target", "bar"], loc: [[10, 0], [10, 3]] });
+
+ verify("let target=nested.this.foo({bar:function(){}})", e => e.type == "FunctionExpression",
+ { name: "bar", chain: ["target"], loc: [[1, 28], [1, 31]] });
+
+ verify("\nlet\ntarget=\nnested.this.foo\n(\n{\nbar\n:\nfunction\n(\n)\n{\n}\n}\n)\n", e => e.type == "FunctionExpression",
+ { name: "bar", chain: ["target"], loc: [[7, 0], [7, 3]] });
+
+ verify("let target=nested.this.foo({bar:{baz:function(){}}})", e => e.type == "FunctionExpression",
+ { name: "baz", chain: ["target", "bar"], loc: [[1, 33], [1, 36]] });
+
+ verify("\nlet\ntarget=\nnested.this.foo\n(\n{\nbar\n:\n{\nbaz\n:\nfunction\n(\n)\n{\n}\n}\n}\n)\n", e => e.type == "FunctionExpression",
+ { name: "baz", chain: ["target", "bar"], loc: [[10, 0], [10, 3]] });
+
+ finish();
+}
diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_parser-09.js b/devtools/client/debugger/test/mochitest/browser_dbg_parser-09.js
new file mode 100644
index 000000000..2e0ac3b89
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_parser-09.js
@@ -0,0 +1,292 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Test that inferring anonymous function information is done correctly
+ * from arrow expressions.
+ */
+
+function test() {
+ let { Parser, ParserHelpers, SyntaxTreeVisitor } =
+ Cu.import("resource://devtools/shared/Parser.jsm", {});
+
+ function verify(source, predicate, details) {
+ let { name, chain } = details;
+ let [[sline, scol], [eline, ecol]] = details.loc;
+ let ast = Parser.reflectionAPI.parse(source);
+ let node = SyntaxTreeVisitor.filter(ast, predicate).pop();
+ let info = ParserHelpers.inferFunctionExpressionInfo(node);
+
+ is(info.name, name,
+ "The function expression assignment property name is correct.");
+ is(chain ? info.chain.toSource() : info.chain, chain ? chain.toSource() : chain,
+ "The function expression assignment property chain is correct.");
+ is(info.loc.start.toSource(), { line: sline, column: scol }.toSource(),
+ "The start location was correct for the identifier in: '" + source + "'.");
+ is(info.loc.end.toSource(), { line: eline, column: ecol }.toSource(),
+ "The end location was correct for the identifier in: '" + source + "'.");
+ }
+
+ // VariableDeclarator
+
+ verify("var foo=()=>{}", e => e.type == "ArrowFunctionExpression", {
+ name: "foo",
+ chain: null,
+ loc: [[1, 4], [1, 7]]
+ });
+ verify("\nvar\nfoo\n=\n(\n)=>\n{\n}\n", e => e.type == "ArrowFunctionExpression", {
+ name: "foo",
+ chain: null,
+ loc: [[3, 0], [3, 3]]
+ });
+
+ // AssignmentExpression
+
+ verify("foo=()=>{}", e => e.type == "ArrowFunctionExpression",
+ { name: "foo", chain: [], loc: [[1, 0], [1, 3]] });
+
+ verify("\nfoo\n=\n(\n)=>\n{\n}\n", e => e.type == "ArrowFunctionExpression",
+ { name: "foo", chain: [], loc: [[2, 0], [2, 3]] });
+
+ verify("foo.bar=()=>{}", e => e.type == "ArrowFunctionExpression",
+ { name: "bar", chain: ["foo"], loc: [[1, 0], [1, 7]] });
+
+ verify("\nfoo.bar\n=\n(\n)=>\n{\n}\n", e => e.type == "ArrowFunctionExpression",
+ { name: "bar", chain: ["foo"], loc: [[2, 0], [2, 7]] });
+
+ verify("this.foo=()=>{}", e => e.type == "ArrowFunctionExpression",
+ { name: "foo", chain: ["this"], loc: [[1, 0], [1, 8]] });
+
+ verify("\nthis.foo\n=\n(\n)=>\n{\n}\n", e => e.type == "ArrowFunctionExpression",
+ { name: "foo", chain: ["this"], loc: [[2, 0], [2, 8]] });
+
+ verify("this.foo.bar=()=>{}", e => e.type == "ArrowFunctionExpression",
+ { name: "bar", chain: ["this", "foo"], loc: [[1, 0], [1, 12]] });
+
+ verify("\nthis.foo.bar\n=\n(\n)=>\n{\n}\n", e => e.type == "ArrowFunctionExpression",
+ { name: "bar", chain: ["this", "foo"], loc: [[2, 0], [2, 12]] });
+
+ verify("foo.this.bar=()=>{}", e => e.type == "ArrowFunctionExpression",
+ { name: "bar", chain: ["foo", "this"], loc: [[1, 0], [1, 12]] });
+
+ verify("\nfoo.this.bar\n=\n(\n)=>\n{\n}\n", e => e.type == "ArrowFunctionExpression",
+ { name: "bar", chain: ["foo", "this"], loc: [[2, 0], [2, 12]] });
+
+ // ObjectExpression
+
+ verify("({foo:()=>{}})", e => e.type == "ArrowFunctionExpression",
+ { name: "foo", chain: [], loc: [[1, 2], [1, 5]] });
+
+ verify("(\n{\nfoo\n:\n(\n)=>\n{\n}\n}\n)", e => e.type == "ArrowFunctionExpression",
+ { name: "foo", chain: [], loc: [[3, 0], [3, 3]] });
+
+ verify("({foo:{bar:()=>{}}})", e => e.type == "ArrowFunctionExpression",
+ { name: "bar", chain: ["foo"], loc: [[1, 7], [1, 10]] });
+
+ verify("(\n{\nfoo\n:\n{\nbar\n:\n(\n)=>\n{\n}\n}\n}\n)", e => e.type == "ArrowFunctionExpression",
+ { name: "bar", chain: ["foo"], loc: [[6, 0], [6, 3]] });
+
+ // AssignmentExpression + ObjectExpression
+
+ verify("foo={bar:()=>{}}", e => e.type == "ArrowFunctionExpression",
+ { name: "bar", chain: ["foo"], loc: [[1, 5], [1, 8]] });
+
+ verify("\nfoo\n=\n{\nbar\n:\n(\n)=>\n{\n}\n}\n", e => e.type == "ArrowFunctionExpression",
+ { name: "bar", chain: ["foo"], loc: [[5, 0], [5, 3]] });
+
+ verify("foo={bar:{baz:()=>{}}}", e => e.type == "ArrowFunctionExpression",
+ { name: "baz", chain: ["foo", "bar"], loc: [[1, 10], [1, 13]] });
+
+ verify("\nfoo\n=\n{\nbar\n:\n{\nbaz\n:\n(\n)=>\n{\n}\n}\n}\n", e => e.type == "ArrowFunctionExpression",
+ { name: "baz", chain: ["foo", "bar"], loc: [[8, 0], [8, 3]] });
+
+ verify("nested.foo={bar:()=>{}}", e => e.type == "ArrowFunctionExpression",
+ { name: "bar", chain: ["nested", "foo"], loc: [[1, 12], [1, 15]] });
+
+ verify("\nnested.foo\n=\n{\nbar\n:\n(\n)=>\n{\n}\n}\n", e => e.type == "ArrowFunctionExpression",
+ { name: "bar", chain: ["nested", "foo"], loc: [[5, 0], [5, 3]] });
+
+ verify("nested.foo={bar:{baz:()=>{}}}", e => e.type == "ArrowFunctionExpression",
+ { name: "baz", chain: ["nested", "foo", "bar"], loc: [[1, 17], [1, 20]] });
+
+ verify("\nnested.foo\n=\n{\nbar\n:\n{\nbaz\n:\n(\n)=>\n{\n}\n}\n}\n", e => e.type == "ArrowFunctionExpression",
+ { name: "baz", chain: ["nested", "foo", "bar"], loc: [[8, 0], [8, 3]] });
+
+ verify("this.foo={bar:()=>{}}", e => e.type == "ArrowFunctionExpression",
+ { name: "bar", chain: ["this", "foo"], loc: [[1, 10], [1, 13]] });
+
+ verify("\nthis.foo\n=\n{\nbar\n:\n(\n)=>\n{\n}\n}\n", e => e.type == "ArrowFunctionExpression",
+ { name: "bar", chain: ["this", "foo"], loc: [[5, 0], [5, 3]] });
+
+ verify("this.foo={bar:{baz:()=>{}}}", e => e.type == "ArrowFunctionExpression",
+ { name: "baz", chain: ["this", "foo", "bar"], loc: [[1, 15], [1, 18]] });
+
+ verify("\nthis.foo\n=\n{\nbar\n:\n{\nbaz\n:\n(\n)=>\n{\n}\n}\n}\n", e => e.type == "ArrowFunctionExpression",
+ { name: "baz", chain: ["this", "foo", "bar"], loc: [[8, 0], [8, 3]] });
+
+ verify("this.nested.foo={bar:()=>{}}", e => e.type == "ArrowFunctionExpression",
+ { name: "bar", chain: ["this", "nested", "foo"], loc: [[1, 17], [1, 20]] });
+
+ verify("\nthis.nested.foo\n=\n{\nbar\n:\n(\n)=>\n{\n}\n}\n", e => e.type == "ArrowFunctionExpression",
+ { name: "bar", chain: ["this", "nested", "foo"], loc: [[5, 0], [5, 3]] });
+
+ verify("this.nested.foo={bar:{baz:()=>{}}}", e => e.type == "ArrowFunctionExpression",
+ { name: "baz", chain: ["this", "nested", "foo", "bar"], loc: [[1, 22], [1, 25]] });
+
+ verify("\nthis.nested.foo\n=\n{\nbar\n:\n{\nbaz\n:\n(\n)=>\n{\n}\n}\n}\n", e => e.type == "ArrowFunctionExpression",
+ { name: "baz", chain: ["this", "nested", "foo", "bar"], loc: [[8, 0], [8, 3]] });
+
+ verify("nested.this.foo={bar:()=>{}}", e => e.type == "ArrowFunctionExpression",
+ { name: "bar", chain: ["nested", "this", "foo"], loc: [[1, 17], [1, 20]] });
+
+ verify("\nnested.this.foo\n=\n{\nbar\n:\n(\n)=>\n{\n}\n}\n", e => e.type == "ArrowFunctionExpression",
+ { name: "bar", chain: ["nested", "this", "foo"], loc: [[5, 0], [5, 3]] });
+
+ verify("nested.this.foo={bar:{baz:()=>{}}}", e => e.type == "ArrowFunctionExpression",
+ { name: "baz", chain: ["nested", "this", "foo", "bar"], loc: [[1, 22], [1, 25]] });
+
+ verify("\nnested.this.foo\n=\n{\nbar\n:\n{\nbaz\n:\n(\n)=>\n{\n}\n}\n}\n", e => e.type == "ArrowFunctionExpression",
+ { name: "baz", chain: ["nested", "this", "foo", "bar"], loc: [[8, 0], [8, 3]] });
+
+ // VariableDeclarator + AssignmentExpression + ObjectExpression
+
+ verify("let foo={bar:()=>{}}", e => e.type == "ArrowFunctionExpression",
+ { name: "bar", chain: ["foo"], loc: [[1, 9], [1, 12]] });
+
+ verify("\nlet\nfoo\n=\n{\nbar\n:\n(\n)=>\n{\n}\n}\n", e => e.type == "ArrowFunctionExpression",
+ { name: "bar", chain: ["foo"], loc: [[6, 0], [6, 3]] });
+
+ verify("let foo={bar:{baz:()=>{}}}", e => e.type == "ArrowFunctionExpression",
+ { name: "baz", chain: ["foo", "bar"], loc: [[1, 14], [1, 17]] });
+
+ verify("\nlet\nfoo\n=\n{\nbar\n:\n{\nbaz\n:\n(\n)=>\n{\n}\n}\n}\n", e => e.type == "ArrowFunctionExpression",
+ { name: "baz", chain: ["foo", "bar"], loc: [[9, 0], [9, 3]] });
+
+ // New/CallExpression + AssignmentExpression + ObjectExpression
+
+ verify("foo({bar:()=>{}})", e => e.type == "ArrowFunctionExpression",
+ { name: "bar", chain: [], loc: [[1, 5], [1, 8]] });
+
+ verify("\nfoo\n(\n{\nbar\n:\n(\n)=>\n{\n}\n}\n)\n", e => e.type == "ArrowFunctionExpression",
+ { name: "bar", chain: [], loc: [[5, 0], [5, 3]] });
+
+ verify("foo({bar:{baz:()=>{}}})", e => e.type == "ArrowFunctionExpression",
+ { name: "baz", chain: ["bar"], loc: [[1, 10], [1, 13]] });
+
+ verify("\nfoo\n(\n{\nbar\n:\n{\nbaz\n:\n(\n)=>\n{\n}\n}\n}\n)\n", e => e.type == "ArrowFunctionExpression",
+ { name: "baz", chain: ["bar"], loc: [[8, 0], [8, 3]] });
+
+ verify("nested.foo({bar:()=>{}})", e => e.type == "ArrowFunctionExpression",
+ { name: "bar", chain: [], loc: [[1, 12], [1, 15]] });
+
+ verify("\nnested.foo\n(\n{\nbar\n:\n(\n)=>\n{\n}\n}\n)\n", e => e.type == "ArrowFunctionExpression",
+ { name: "bar", chain: [], loc: [[5, 0], [5, 3]] });
+
+ verify("nested.foo({bar:{baz:()=>{}}})", e => e.type == "ArrowFunctionExpression",
+ { name: "baz", chain: ["bar"], loc: [[1, 17], [1, 20]] });
+
+ verify("\nnested.foo\n(\n{\nbar\n:\n{\nbaz\n:\n(\n)=>\n{\n}\n}\n}\n)\n", e => e.type == "ArrowFunctionExpression",
+ { name: "baz", chain: ["bar"], loc: [[8, 0], [8, 3]] });
+
+ verify("this.foo({bar:()=>{}})", e => e.type == "ArrowFunctionExpression",
+ { name: "bar", chain: [], loc: [[1, 10], [1, 13]] });
+
+ verify("\nthis.foo\n(\n{\nbar\n:\n(\n)=>\n{\n}\n}\n)\n", e => e.type == "ArrowFunctionExpression",
+ { name: "bar", chain: [], loc: [[5, 0], [5, 3]] });
+
+ verify("this.foo({bar:{baz:()=>{}}})", e => e.type == "ArrowFunctionExpression",
+ { name: "baz", chain: ["bar"], loc: [[1, 15], [1, 18]] });
+
+ verify("\nthis.foo\n(\n{\nbar\n:\n{\nbaz\n:\n(\n)=>\n{\n}\n}\n}\n)\n", e => e.type == "ArrowFunctionExpression",
+ { name: "baz", chain: ["bar"], loc: [[8, 0], [8, 3]] });
+
+ verify("this.nested.foo({bar:()=>{}})", e => e.type == "ArrowFunctionExpression",
+ { name: "bar", chain: [], loc: [[1, 17], [1, 20]] });
+
+ verify("\nthis.nested.foo\n(\n{\nbar\n:\n(\n)=>\n{\n}\n}\n)\n", e => e.type == "ArrowFunctionExpression",
+ { name: "bar", chain: [], loc: [[5, 0], [5, 3]] });
+
+ verify("this.nested.foo({bar:{baz:()=>{}}})", e => e.type == "ArrowFunctionExpression",
+ { name: "baz", chain: ["bar"], loc: [[1, 22], [1, 25]] });
+
+ verify("\nthis.nested.foo\n(\n{\nbar\n:\n{\nbaz\n:\n(\n)=>\n{\n}\n}\n}\n)\n", e => e.type == "ArrowFunctionExpression",
+ { name: "baz", chain: ["bar"], loc: [[8, 0], [8, 3]] });
+
+ verify("nested.this.foo({bar:()=>{}})", e => e.type == "ArrowFunctionExpression",
+ { name: "bar", chain: [], loc: [[1, 17], [1, 20]] });
+
+ verify("\nnested.this.foo\n(\n{\nbar\n:\n(\n)=>\n{\n}\n}\n)\n", e => e.type == "ArrowFunctionExpression",
+ { name: "bar", chain: [], loc: [[5, 0], [5, 3]] });
+
+ verify("nested.this.foo({bar:{baz:()=>{}}})", e => e.type == "ArrowFunctionExpression",
+ { name: "baz", chain: ["bar"], loc: [[1, 22], [1, 25]] });
+
+ verify("\nnested.this.foo\n(\n{\nbar\n:\n{\nbaz\n:\n(\n)=>\n{\n}\n}\n}\n)\n", e => e.type == "ArrowFunctionExpression",
+ { name: "baz", chain: ["bar"], loc: [[8, 0], [8, 3]] });
+
+ // New/CallExpression + VariableDeclarator + AssignmentExpression + ObjectExpression
+
+ verify("let target=foo({bar:()=>{}})", e => e.type == "ArrowFunctionExpression",
+ { name: "bar", chain: ["target"], loc: [[1, 16], [1, 19]] });
+
+ verify("\nlet\ntarget=\nfoo\n(\n{\nbar\n:\n(\n)=>\n{\n}\n}\n)\n", e => e.type == "ArrowFunctionExpression",
+ { name: "bar", chain: ["target"], loc: [[7, 0], [7, 3]] });
+
+ verify("let target=foo({bar:{baz:()=>{}}})", e => e.type == "ArrowFunctionExpression",
+ { name: "baz", chain: ["target", "bar"], loc: [[1, 21], [1, 24]] });
+
+ verify("\nlet\ntarget=\nfoo\n(\n{\nbar\n:\n{\nbaz\n:\n(\n)=>\n{\n}\n}\n}\n)\n", e => e.type == "ArrowFunctionExpression",
+ { name: "baz", chain: ["target", "bar"], loc: [[10, 0], [10, 3]] });
+
+ verify("let target=nested.foo({bar:()=>{}})", e => e.type == "ArrowFunctionExpression",
+ { name: "bar", chain: ["target"], loc: [[1, 23], [1, 26]] });
+
+ verify("\nlet\ntarget=\nnested.foo\n(\n{\nbar\n:\n(\n)=>\n{\n}\n}\n)\n", e => e.type == "ArrowFunctionExpression",
+ { name: "bar", chain: ["target"], loc: [[7, 0], [7, 3]] });
+
+ verify("let target=nested.foo({bar:{baz:()=>{}}})", e => e.type == "ArrowFunctionExpression",
+ { name: "baz", chain: ["target", "bar"], loc: [[1, 28], [1, 31]] });
+
+ verify("\nlet\ntarget=\nnested.foo\n(\n{\nbar\n:\n{\nbaz\n:\n(\n)=>\n{\n}\n}\n}\n)\n", e => e.type == "ArrowFunctionExpression",
+ { name: "baz", chain: ["target", "bar"], loc: [[10, 0], [10, 3]] });
+
+ verify("let target=this.foo({bar:()=>{}})", e => e.type == "ArrowFunctionExpression",
+ { name: "bar", chain: ["target"], loc: [[1, 21], [1, 24]] });
+
+ verify("\nlet\ntarget=\nthis.foo\n(\n{\nbar\n:\n(\n)=>\n{\n}\n}\n)\n", e => e.type == "ArrowFunctionExpression",
+ { name: "bar", chain: ["target"], loc: [[7, 0], [7, 3]] });
+
+ verify("let target=this.foo({bar:{baz:()=>{}}})", e => e.type == "ArrowFunctionExpression",
+ { name: "baz", chain: ["target", "bar"], loc: [[1, 26], [1, 29]] });
+
+ verify("\nlet\ntarget=\nthis.foo\n(\n{\nbar\n:\n{\nbaz\n:\n(\n)=>\n{\n}\n}\n}\n)\n", e => e.type == "ArrowFunctionExpression",
+ { name: "baz", chain: ["target", "bar"], loc: [[10, 0], [10, 3]] });
+
+ verify("let target=this.nested.foo({bar:()=>{}})", e => e.type == "ArrowFunctionExpression",
+ { name: "bar", chain: ["target"], loc: [[1, 28], [1, 31]] });
+
+ verify("\nlet\ntarget=\nthis.nested.foo\n(\n{\nbar\n:\n(\n)=>\n{\n}\n}\n)\n", e => e.type == "ArrowFunctionExpression",
+ { name: "bar", chain: ["target"], loc: [[7, 0], [7, 3]] });
+
+ verify("let target=this.nested.foo({bar:{baz:()=>{}}})", e => e.type == "ArrowFunctionExpression",
+ { name: "baz", chain: ["target", "bar"], loc: [[1, 33], [1, 36]] });
+
+ verify("\nlet\ntarget=\nthis.nested.foo\n(\n{\nbar\n:\n{\nbaz\n:\n(\n)=>\n{\n}\n}\n}\n)\n", e => e.type == "ArrowFunctionExpression",
+ { name: "baz", chain: ["target", "bar"], loc: [[10, 0], [10, 3]] });
+
+ verify("let target=nested.this.foo({bar:()=>{}})", e => e.type == "ArrowFunctionExpression",
+ { name: "bar", chain: ["target"], loc: [[1, 28], [1, 31]] });
+
+ verify("\nlet\ntarget=\nnested.this.foo\n(\n{\nbar\n:\n(\n)=>\n{\n}\n}\n)\n", e => e.type == "ArrowFunctionExpression",
+ { name: "bar", chain: ["target"], loc: [[7, 0], [7, 3]] });
+
+ verify("let target=nested.this.foo({bar:{baz:()=>{}}})", e => e.type == "ArrowFunctionExpression",
+ { name: "baz", chain: ["target", "bar"], loc: [[1, 33], [1, 36]] });
+
+ verify("\nlet\ntarget=\nnested.this.foo\n(\n{\nbar\n:\n{\nbaz\n:\n(\n)=>\n{\n}\n}\n}\n)\n", e => e.type == "ArrowFunctionExpression",
+ { name: "baz", chain: ["target", "bar"], loc: [[10, 0], [10, 3]] });
+
+ finish();
+}
diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_parser-10.js b/devtools/client/debugger/test/mochitest/browser_dbg_parser-10.js
new file mode 100644
index 000000000..d340f9f9c
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_parser-10.js
@@ -0,0 +1,129 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Test that creating an evaluation string for certain nodes works properly.
+ */
+
+function test() {
+ let { Parser, ParserHelpers, SyntaxTreeVisitor } =
+ Cu.import("resource://devtools/shared/Parser.jsm", {});
+
+ function verify(source, predicate, string) {
+ let ast = Parser.reflectionAPI.parse(source);
+ let node = SyntaxTreeVisitor.filter(ast, predicate).pop();
+ let info = ParserHelpers.getIdentifierEvalString(node);
+ is(info, string, "The identifier evaluation string is correct.");
+ }
+
+ // Indentifier or Literal
+
+ verify("foo", e => e.type == "Identifier", "foo");
+ verify("undefined", e => e.type == "Identifier", "undefined");
+ verify("null", e => e.type == "Literal", "null");
+ verify("42", e => e.type == "Literal", "42");
+ verify("true", e => e.type == "Literal", "true");
+ verify("\"nasu\"", e => e.type == "Literal", "\"nasu\"");
+
+ // MemberExpression or ThisExpression
+
+ verify("this", e => e.type == "ThisExpression", "this");
+ verify("foo.bar", e => e.name == "foo", "foo");
+ verify("foo.bar", e => e.name == "bar", "foo.bar");
+
+ // MemberExpression + ThisExpression
+
+ verify("this.foo.bar", e => e.type == "ThisExpression", "this");
+ verify("this.foo.bar", e => e.name == "foo", "this.foo");
+ verify("this.foo.bar", e => e.name == "bar", "this.foo.bar");
+
+ verify("foo.this.bar", e => e.name == "foo", "foo");
+ verify("foo.this.bar", e => e.name == "this", "foo.this");
+ verify("foo.this.bar", e => e.name == "bar", "foo.this.bar");
+
+ // ObjectExpression + VariableDeclarator
+
+ verify("let foo={bar:baz}", e => e.name == "baz", "baz");
+ verify("let foo={bar:undefined}", e => e.name == "undefined", "undefined");
+ verify("let foo={bar:null}", e => e.type == "Literal", "null");
+ verify("let foo={bar:42}", e => e.type == "Literal", "42");
+ verify("let foo={bar:true}", e => e.type == "Literal", "true");
+ verify("let foo={bar:\"nasu\"}", e => e.type == "Literal", "\"nasu\"");
+ verify("let foo={bar:this}", e => e.type == "ThisExpression", "this");
+
+ verify("let foo={bar:{nested:baz}}", e => e.name == "baz", "baz");
+ verify("let foo={bar:{nested:undefined}}", e => e.name == "undefined", "undefined");
+ verify("let foo={bar:{nested:null}}", e => e.type == "Literal", "null");
+ verify("let foo={bar:{nested:42}}", e => e.type == "Literal", "42");
+ verify("let foo={bar:{nested:true}}", e => e.type == "Literal", "true");
+ verify("let foo={bar:{nested:\"nasu\"}}", e => e.type == "Literal", "\"nasu\"");
+ verify("let foo={bar:{nested:this}}", e => e.type == "ThisExpression", "this");
+
+ verify("let foo={bar:baz}", e => e.name == "bar", "foo.bar");
+ verify("let foo={bar:baz}", e => e.name == "foo", "foo");
+
+ verify("let foo={bar:{nested:baz}}", e => e.name == "nested", "foo.bar.nested");
+ verify("let foo={bar:{nested:baz}}", e => e.name == "bar", "foo.bar");
+ verify("let foo={bar:{nested:baz}}", e => e.name == "foo", "foo");
+
+ // ObjectExpression + MemberExpression
+
+ verify("parent.foo={bar:baz}", e => e.name == "bar", "parent.foo.bar");
+ verify("parent.foo={bar:baz}", e => e.name == "foo", "parent.foo");
+ verify("parent.foo={bar:baz}", e => e.name == "parent", "parent");
+
+ verify("parent.foo={bar:{nested:baz}}", e => e.name == "nested", "parent.foo.bar.nested");
+ verify("parent.foo={bar:{nested:baz}}", e => e.name == "bar", "parent.foo.bar");
+ verify("parent.foo={bar:{nested:baz}}", e => e.name == "foo", "parent.foo");
+ verify("parent.foo={bar:{nested:baz}}", e => e.name == "parent", "parent");
+
+ verify("this.foo={bar:{nested:baz}}", e => e.name == "nested", "this.foo.bar.nested");
+ verify("this.foo={bar:{nested:baz}}", e => e.name == "bar", "this.foo.bar");
+ verify("this.foo={bar:{nested:baz}}", e => e.name == "foo", "this.foo");
+ verify("this.foo={bar:{nested:baz}}", e => e.type == "ThisExpression", "this");
+
+ verify("this.parent.foo={bar:{nested:baz}}", e => e.name == "nested", "this.parent.foo.bar.nested");
+ verify("this.parent.foo={bar:{nested:baz}}", e => e.name == "bar", "this.parent.foo.bar");
+ verify("this.parent.foo={bar:{nested:baz}}", e => e.name == "foo", "this.parent.foo");
+ verify("this.parent.foo={bar:{nested:baz}}", e => e.name == "parent", "this.parent");
+ verify("this.parent.foo={bar:{nested:baz}}", e => e.type == "ThisExpression", "this");
+
+ verify("parent.this.foo={bar:{nested:baz}}", e => e.name == "nested", "parent.this.foo.bar.nested");
+ verify("parent.this.foo={bar:{nested:baz}}", e => e.name == "bar", "parent.this.foo.bar");
+ verify("parent.this.foo={bar:{nested:baz}}", e => e.name == "foo", "parent.this.foo");
+ verify("parent.this.foo={bar:{nested:baz}}", e => e.name == "this", "parent.this");
+ verify("parent.this.foo={bar:{nested:baz}}", e => e.name == "parent", "parent");
+
+ // FunctionExpression
+
+ verify("function foo(){}", e => e.name == "foo", "foo");
+ verify("var foo=function(){}", e => e.name == "foo", "foo");
+ verify("var foo=function bar(){}", e => e.name == "bar", "bar");
+
+ // New/CallExpression
+
+ verify("foo()", e => e.name == "foo", "foo");
+ verify("new foo()", e => e.name == "foo", "foo");
+
+ verify("foo(bar)", e => e.name == "bar", "bar");
+ verify("foo(bar, baz)", e => e.name == "baz", "baz");
+ verify("foo(undefined)", e => e.name == "undefined", "undefined");
+ verify("foo(null)", e => e.type == "Literal", "null");
+ verify("foo(42)", e => e.type == "Literal", "42");
+ verify("foo(true)", e => e.type == "Literal", "true");
+ verify("foo(\"nasu\")", e => e.type == "Literal", "\"nasu\"");
+ verify("foo(this)", e => e.type == "ThisExpression", "this");
+
+ // New/CallExpression + ObjectExpression + MemberExpression
+
+ verify("fun(this.parent.foo={bar:{nested:baz}})", e => e.name == "nested", "this.parent.foo.bar.nested");
+ verify("fun(this.parent.foo={bar:{nested:baz}})", e => e.name == "bar", "this.parent.foo.bar");
+ verify("fun(this.parent.foo={bar:{nested:baz}})", e => e.name == "foo", "this.parent.foo");
+ verify("fun(this.parent.foo={bar:{nested:baz}})", e => e.name == "parent", "this.parent");
+ verify("fun(this.parent.foo={bar:{nested:baz}})", e => e.type == "ThisExpression", "this");
+ verify("fun(this.parent.foo={bar:{nested:baz}})", e => e.name == "fun", "fun");
+
+ finish();
+}
diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_parser-11.js b/devtools/client/debugger/test/mochitest/browser_dbg_parser-11.js
new file mode 100644
index 000000000..ee2b4c89d
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_parser-11.js
@@ -0,0 +1,41 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Checks if self-closing <script/> tags are parsed by Parser.jsm
+ */
+
+function test() {
+ let { Parser } = Cu.import("resource://devtools/shared/Parser.jsm", {});
+
+ let source = [
+ '<script type="text/javascript" src="chrome://foo.js"/>',
+ '<script type="application/javascript;version=1.8" src="chrome://baz.js"/>',
+ '<script async defer src="chrome://foobar.js"/>',
+ '<script type="application/javascript"/>"hello third"',
+ '<script type="application/javascript">"hello fourth"</script>',
+ ].join("\n");
+ let parser = new Parser();
+ let parsed = parser.get(source);
+
+ is(parser.errors.length, 0,
+ "There should be no errors logged when parsing.");
+ is(parsed.scriptCount, 5,
+ "There should be 5 scripts parsed in the parent HTML source.");
+
+ is(parsed.getScriptInfo(source.indexOf("foo.js\"/>") + 1).toSource(), "({start:-1, length:-1, index:-1})",
+ "the first script is empty");
+ is(parsed.getScriptInfo(source.indexOf("baz.js\"/>") + 1).toSource(), "({start:-1, length:-1, index:-1})",
+ "the second script is empty");
+ is(parsed.getScriptInfo(source.indexOf("foobar.js\"/>") + 1).toSource(), "({start:-1, length:-1, index:-1})",
+ "the third script is empty");
+
+ is(parsed.getScriptInfo(source.indexOf("hello third!")).toSource(), "({start:-1, length:-1, index:-1})",
+ "Inline script on self-closing tag not considered a script");
+ is(parsed.getScriptInfo(source.indexOf("hello fourth")).toSource(), "({start:267, length:14, index:4})",
+ "The fourth script was located correctly.");
+
+ finish();
+}
diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_parser-computed-name.js b/devtools/client/debugger/test/mochitest/browser_dbg_parser-computed-name.js
new file mode 100644
index 000000000..085f9781b
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_parser-computed-name.js
@@ -0,0 +1,32 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Test that template strings are correctly processed.
+ */
+
+"use strict";
+
+function test() {
+ let { Parser, SyntaxTreeVisitor } =
+ Cu.import("resource://devtools/shared/Parser.jsm", {});
+
+ let ast = Parser.reflectionAPI.parse("({ [i]: 1 })");
+ let nodes = SyntaxTreeVisitor.filter(ast, e => e.type == "ComputedName");
+ ok(nodes && nodes.length === 1, "Found the ComputedName node");
+
+ let name = nodes[0].name;
+ ok(name, "The ComputedName node has a name property");
+ is(name.type, "Identifier", "The name has a correct type");
+ is(name.name, "i", "The name has a correct name");
+
+ let identNodes = SyntaxTreeVisitor.filter(ast, e => e.type == "Identifier");
+ ok(identNodes && identNodes.length === 1, "Found the Identifier node");
+
+ is(identNodes[0].type, "Identifier", "The identifier has a correct type");
+ is(identNodes[0].name, "i", "The identifier has a correct name");
+
+ finish();
+}
diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_parser-function-defaults.js b/devtools/client/debugger/test/mochitest/browser_dbg_parser-function-defaults.js
new file mode 100644
index 000000000..55fac4055
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_parser-function-defaults.js
@@ -0,0 +1,31 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Test that function default arguments are correctly processed.
+ */
+
+"use strict";
+
+function test() {
+ let { Parser, ParserHelpers, SyntaxTreeVisitor } =
+ Cu.import("resource://devtools/shared/Parser.jsm", {});
+
+ function verify(source, predicate, string) {
+ let ast = Parser.reflectionAPI.parse(source);
+ let node = SyntaxTreeVisitor.filter(ast, predicate).pop();
+ let info = ParserHelpers.getIdentifierEvalString(node);
+ is(info, string, "The identifier evaluation string is correct.");
+ }
+
+ // FunctionDeclaration
+ verify("function foo(a, b='b') {}", e => e.type == "Literal", "\"b\"");
+ // FunctionExpression
+ verify("let foo=function(a, b='b') {}", e => e.type == "Literal", "\"b\"");
+ // ArrowFunctionExpression
+ verify("let foo=(a, b='b')=> {}", e => e.type == "Literal", "\"b\"");
+
+ finish();
+}
diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_parser-spread-expression.js b/devtools/client/debugger/test/mochitest/browser_dbg_parser-spread-expression.js
new file mode 100644
index 000000000..4b7d93d2f
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_parser-spread-expression.js
@@ -0,0 +1,32 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Test that spread expressions work both in arrays and function calls.
+ */
+
+"use strict";
+
+function test() {
+ let { Parser, SyntaxTreeVisitor } =
+ Cu.import("resource://devtools/shared/Parser.jsm", {});
+
+ const SCRIPTS = ["[...a]", "foo(...a)"];
+
+ for (let script of SCRIPTS) {
+ info(`Testing spread expression in '${script}'`);
+ let ast = Parser.reflectionAPI.parse(script);
+ let nodes = SyntaxTreeVisitor.filter(ast,
+ e => e.type == "SpreadExpression");
+ ok(nodes && nodes.length === 1, "Found the SpreadExpression node");
+
+ let expr = nodes[0].expression;
+ ok(expr, "The SpreadExpression node has the sub-expression");
+ is(expr.type, "Identifier", "The sub-expression is an Identifier");
+ is(expr.name, "a", "The sub-expression identifier has a correct name");
+ }
+
+ finish();
+}
diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_parser-template-strings.js b/devtools/client/debugger/test/mochitest/browser_dbg_parser-template-strings.js
new file mode 100644
index 000000000..6ee271137
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_parser-template-strings.js
@@ -0,0 +1,29 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Test that template strings are correctly processed.
+ */
+
+"use strict";
+
+function test() {
+ let { Parser, SyntaxTreeVisitor } =
+ Cu.import("resource://devtools/shared/Parser.jsm", {});
+
+ let ast = Parser.reflectionAPI.parse("`foo${i}bar`");
+ let nodes = SyntaxTreeVisitor.filter(ast, e => e.type == "TemplateLiteral");
+ ok(nodes && nodes.length === 1, "Found the TemplateLiteral node");
+
+ let elements = nodes[0].elements;
+ ok(elements, "The TemplateLiteral node has elements");
+ is(elements.length, 3, "There are 3 elements in the literal");
+
+ ["Literal", "Identifier", "Literal"].forEach((type, i) => {
+ is(elements[i].type, type, `Element at index ${i} is '${type}'`);
+ });
+
+ finish();
+}
diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_pause-exceptions-01.js b/devtools/client/debugger/test/mochitest/browser_dbg_pause-exceptions-01.js
new file mode 100644
index 000000000..92cd7e4bc
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_pause-exceptions-01.js
@@ -0,0 +1,246 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Make sure that pausing on exceptions works.
+ */
+
+const TAB_URL = EXAMPLE_URL + "doc_pause-exceptions.html";
+
+var gTab, gPanel, gDebugger;
+var gFrames, gVariables, gPrefs, gOptions;
+
+function test() {
+ requestLongerTimeout(2);
+ let options = {
+ source: TAB_URL,
+ line: 1
+ };
+ initDebugger(TAB_URL, options).then(([aTab,, aPanel]) => {
+ gTab = aTab;
+ gPanel = aPanel;
+ gDebugger = gPanel.panelWin;
+ gFrames = gDebugger.DebuggerView.StackFrames;
+ gVariables = gDebugger.DebuggerView.Variables;
+ gPrefs = gDebugger.Prefs;
+ gOptions = gDebugger.DebuggerView.Options;
+
+ is(gPrefs.pauseOnExceptions, false,
+ "The pause-on-exceptions pref should be disabled by default.");
+ isnot(gOptions._pauseOnExceptionsItem.getAttribute("checked"), "true",
+ "The pause-on-exceptions menu item should not be checked.");
+
+ testPauseOnExceptionsDisabled()
+ .then(enablePauseOnExceptions)
+ .then(disableIgnoreCaughtExceptions)
+ .then(testPauseOnExceptionsEnabled)
+ .then(disablePauseOnExceptions)
+ .then(enableIgnoreCaughtExceptions)
+ .then(() => closeDebuggerAndFinish(gPanel))
+ .then(null, aError => {
+ ok(false, "Got an error: " + aError.message + "\n" + aError.stack);
+ });
+ });
+}
+
+function testPauseOnExceptionsDisabled() {
+ let finished = waitForCaretAndScopes(gPanel, 26).then(() => {
+ info("Testing disabled pause-on-exceptions.");
+
+ is(gDebugger.gThreadClient.state, "paused",
+ "Should only be getting stack frames while paused (1).");
+ ok(isCaretPos(gPanel, 26),
+ "Should be paused on the debugger statement (1).");
+
+ let innerScope = gVariables.getScopeAtIndex(0);
+ let innerNodes = innerScope.target.querySelector(".variables-view-element-details").childNodes;
+
+ is(gFrames.itemCount, 1,
+ "Should have one frame.");
+ is(gVariables._store.length, 4,
+ "Should have four scopes.");
+
+ is(innerNodes[0].querySelector(".name").getAttribute("value"), "this",
+ "Should have the right property name for 'this'.");
+ is(innerNodes[0].querySelector(".value").getAttribute("value"), "<button>",
+ "Should have the right property value for 'this'.");
+
+ let finished = waitForDebuggerEvents(gPanel, gDebugger.EVENTS.AFTER_FRAMES_CLEARED).then(() => {
+ isnot(gDebugger.gThreadClient.state, "paused",
+ "Should not be paused after resuming.");
+ ok(isCaretPos(gPanel, 26),
+ "Should be idle on the debugger statement.");
+
+ ok(true, "Frames were cleared, debugger didn't pause again.");
+ });
+
+ EventUtils.sendMouseEvent({ type: "mousedown" },
+ gDebugger.document.getElementById("resume"),
+ gDebugger);
+
+ return finished;
+ });
+
+ generateMouseClickInTab(gTab, "content.document.querySelector('button')");
+
+ return finished;
+}
+
+function testPauseOnExceptionsEnabled() {
+ let finished = waitForCaretAndScopes(gPanel, 19).then(() => {
+ info("Testing enabled pause-on-exceptions.");
+
+ is(gDebugger.gThreadClient.state, "paused",
+ "Should only be getting stack frames while paused.");
+ ok(isCaretPos(gPanel, 19),
+ "Should be paused on the debugger statement.");
+
+ let innerScope = gVariables.getScopeAtIndex(0);
+ let innerNodes = innerScope.target.querySelector(".variables-view-element-details").childNodes;
+
+ is(gFrames.itemCount, 1,
+ "Should have one frame.");
+ is(gVariables._store.length, 4,
+ "Should have four scopes.");
+
+ is(innerNodes[0].querySelector(".name").getAttribute("value"), "<exception>",
+ "Should have the right property name for <exception>.");
+ is(innerNodes[0].querySelector(".value").getAttribute("value"), "Error",
+ "Should have the right property value for <exception>.");
+
+ let finished = waitForCaretAndScopes(gPanel, 26).then(() => {
+ info("Testing enabled pause-on-exceptions and resumed after pause.");
+
+ is(gDebugger.gThreadClient.state, "paused",
+ "Should only be getting stack frames while paused.");
+ ok(isCaretPos(gPanel, 26),
+ "Should be paused on the debugger statement.");
+
+ let innerScope = gVariables.getScopeAtIndex(0);
+ let innerNodes = innerScope.target.querySelector(".variables-view-element-details").childNodes;
+
+ is(gFrames.itemCount, 1,
+ "Should have one frame.");
+ is(gVariables._store.length, 4,
+ "Should have four scopes.");
+
+ is(innerNodes[0].querySelector(".name").getAttribute("value"), "this",
+ "Should have the right property name for 'this'.");
+ is(innerNodes[0].querySelector(".value").getAttribute("value"), "<button>",
+ "Should have the right property value for 'this'.");
+
+ let finished = waitForDebuggerEvents(gPanel, gDebugger.EVENTS.AFTER_FRAMES_CLEARED).then(() => {
+ isnot(gDebugger.gThreadClient.state, "paused",
+ "Should not be paused after resuming.");
+ ok(isCaretPos(gPanel, 26),
+ "Should be idle on the debugger statement.");
+
+ ok(true, "Frames were cleared, debugger didn't pause again.");
+ });
+
+ EventUtils.sendMouseEvent({ type: "mousedown" },
+ gDebugger.document.getElementById("resume"),
+ gDebugger);
+
+ return finished;
+ });
+
+ EventUtils.sendMouseEvent({ type: "mousedown" },
+ gDebugger.document.getElementById("resume"),
+ gDebugger);
+
+ return finished;
+ });
+
+ generateMouseClickInTab(gTab, "content.document.querySelector('button')");
+
+ return finished;
+}
+
+function enablePauseOnExceptions() {
+ let deferred = promise.defer();
+
+ gDebugger.gThreadClient.addOneTimeListener("resumed", () => {
+ is(gPrefs.pauseOnExceptions, true,
+ "The pause-on-exceptions pref should now be enabled.");
+ is(gOptions._pauseOnExceptionsItem.getAttribute("checked"), "true",
+ "The pause-on-exceptions menu item should now be checked.");
+
+ ok(true, "Pausing on exceptions was enabled.");
+ deferred.resolve();
+ });
+
+ gOptions._pauseOnExceptionsItem.setAttribute("checked", "true");
+ gOptions._togglePauseOnExceptions();
+
+ return deferred.promise;
+}
+
+function disablePauseOnExceptions() {
+ let deferred = promise.defer();
+
+ gDebugger.gThreadClient.addOneTimeListener("resumed", () => {
+ is(gPrefs.pauseOnExceptions, false,
+ "The pause-on-exceptions pref should now be disabled.");
+ isnot(gOptions._pauseOnExceptionsItem.getAttribute("checked"), "true",
+ "The pause-on-exceptions menu item should now be unchecked.");
+
+ ok(true, "Pausing on exceptions was disabled.");
+ deferred.resolve();
+ });
+
+ gOptions._pauseOnExceptionsItem.setAttribute("checked", "false");
+ gOptions._togglePauseOnExceptions();
+
+ return deferred.promise;
+}
+
+function enableIgnoreCaughtExceptions() {
+ let deferred = promise.defer();
+
+ gDebugger.gThreadClient.addOneTimeListener("resumed", () => {
+ is(gPrefs.ignoreCaughtExceptions, true,
+ "The ignore-caught-exceptions pref should now be enabled.");
+ is(gOptions._ignoreCaughtExceptionsItem.getAttribute("checked"), "true",
+ "The ignore-caught-exceptions menu item should now be checked.");
+
+ ok(true, "Ignore caught exceptions was enabled.");
+ deferred.resolve();
+ });
+
+ gOptions._ignoreCaughtExceptionsItem.setAttribute("checked", "true");
+ gOptions._toggleIgnoreCaughtExceptions();
+
+ return deferred.promise;
+}
+
+function disableIgnoreCaughtExceptions() {
+ let deferred = promise.defer();
+
+ gDebugger.gThreadClient.addOneTimeListener("resumed", () => {
+ is(gPrefs.ignoreCaughtExceptions, false,
+ "The ignore-caught-exceptions pref should now be disabled.");
+ isnot(gOptions._ignoreCaughtExceptionsItem.getAttribute("checked"), "true",
+ "The ignore-caught-exceptions menu item should now be unchecked.");
+
+ ok(true, "Ignore caught exceptions was disabled.");
+ deferred.resolve();
+ });
+
+ gOptions._ignoreCaughtExceptionsItem.setAttribute("checked", "false");
+ gOptions._toggleIgnoreCaughtExceptions();
+
+ return deferred.promise;
+}
+
+registerCleanupFunction(function () {
+ gTab = null;
+ gPanel = null;
+ gDebugger = null;
+ gFrames = null;
+ gVariables = null;
+ gPrefs = null;
+ gOptions = null;
+});
diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_pause-exceptions-02.js b/devtools/client/debugger/test/mochitest/browser_dbg_pause-exceptions-02.js
new file mode 100644
index 000000000..21b28ce26
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_pause-exceptions-02.js
@@ -0,0 +1,204 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Make sure that pausing on exceptions works after reload.
+ */
+
+const TAB_URL = EXAMPLE_URL + "doc_pause-exceptions.html";
+
+var gTab, gPanel, gDebugger;
+var gFrames, gVariables, gPrefs, gOptions;
+
+function test() {
+ let options = {
+ source: TAB_URL,
+ line: 1
+ };
+ initDebugger(TAB_URL, options).then(([aTab,, aPanel]) => {
+ gTab = aTab;
+ gPanel = aPanel;
+ gDebugger = gPanel.panelWin;
+ gFrames = gDebugger.DebuggerView.StackFrames;
+ gVariables = gDebugger.DebuggerView.Variables;
+ gPrefs = gDebugger.Prefs;
+ gOptions = gDebugger.DebuggerView.Options;
+
+ is(gPrefs.pauseOnExceptions, false,
+ "The pause-on-exceptions pref should be disabled by default.");
+ isnot(gOptions._pauseOnExceptionsItem.getAttribute("checked"), "true",
+ "The pause-on-exceptions menu item should not be checked.");
+
+ enablePauseOnExceptions()
+ .then(disableIgnoreCaughtExceptions)
+ .then(() => reloadActiveTab(gPanel, gDebugger.EVENTS.SOURCE_SHOWN))
+ .then(() => {
+ generateMouseClickInTab(gTab, "content.document.querySelector('button')");
+ })
+ .then(testPauseOnExceptionsAfterReload)
+ .then(disablePauseOnExceptions)
+ .then(enableIgnoreCaughtExceptions)
+ .then(() => closeDebuggerAndFinish(gPanel))
+ .then(null, aError => {
+ ok(false, "Got an error: " + aError.message + "\n" + aError.stack);
+ });
+ });
+}
+
+function testPauseOnExceptionsAfterReload() {
+ let finished = waitForCaretAndScopes(gPanel, 19).then(() => {
+ info("Testing enabled pause-on-exceptions.");
+
+ is(gDebugger.gThreadClient.state, "paused",
+ "Should only be getting stack frames while paused.");
+ ok(isCaretPos(gPanel, 19),
+ "Should be paused on the debugger statement.");
+
+ let innerScope = gVariables.getScopeAtIndex(0);
+ let innerNodes = innerScope.target.querySelector(".variables-view-element-details").childNodes;
+
+ is(gFrames.itemCount, 1,
+ "Should have one frame.");
+ is(gVariables._store.length, 4,
+ "Should have four scopes.");
+
+ is(innerNodes[0].querySelector(".name").getAttribute("value"), "<exception>",
+ "Should have the right property name for <exception>.");
+ is(innerNodes[0].querySelector(".value").getAttribute("value"), "Error",
+ "Should have the right property value for <exception>.");
+
+ let finished = waitForCaretAndScopes(gPanel, 26).then(() => {
+ info("Testing enabled pause-on-exceptions and resumed after pause.");
+
+ is(gDebugger.gThreadClient.state, "paused",
+ "Should only be getting stack frames while paused.");
+ ok(isCaretPos(gPanel, 26),
+ "Should be paused on the debugger statement.");
+
+ let innerScope = gVariables.getScopeAtIndex(0);
+ let innerNodes = innerScope.target.querySelector(".variables-view-element-details").childNodes;
+
+ is(gFrames.itemCount, 1,
+ "Should have one frame.");
+ is(gVariables._store.length, 4,
+ "Should have four scopes.");
+
+ is(innerNodes[0].querySelector(".name").getAttribute("value"), "this",
+ "Should have the right property name for 'this'.");
+ is(innerNodes[0].querySelector(".value").getAttribute("value"), "<button>",
+ "Should have the right property value for 'this'.");
+
+ let finished = waitForDebuggerEvents(gPanel, gDebugger.EVENTS.AFTER_FRAMES_CLEARED).then(() => {
+ isnot(gDebugger.gThreadClient.state, "paused",
+ "Should not be paused after resuming.");
+ ok(isCaretPos(gPanel, 26),
+ "Should be idle on the debugger statement.");
+
+ ok(true, "Frames were cleared, debugger didn't pause again.");
+ });
+
+ EventUtils.sendMouseEvent({ type: "mousedown" },
+ gDebugger.document.getElementById("resume"),
+ gDebugger);
+
+ return finished;
+ });
+
+ EventUtils.sendMouseEvent({ type: "mousedown" },
+ gDebugger.document.getElementById("resume"),
+ gDebugger);
+
+ return finished;
+ });
+
+ generateMouseClickInTab(gTab, "content.document.querySelector('button')");
+
+ return finished;
+}
+
+function enablePauseOnExceptions() {
+ let deferred = promise.defer();
+
+ gDebugger.gThreadClient.addOneTimeListener("resumed", () => {
+ is(gPrefs.pauseOnExceptions, true,
+ "The pause-on-exceptions pref should now be enabled.");
+ is(gOptions._pauseOnExceptionsItem.getAttribute("checked"), "true",
+ "The pause-on-exceptions menu item should now be checked.");
+
+ ok(true, "Pausing on exceptions was enabled.");
+ deferred.resolve();
+ });
+
+ gOptions._pauseOnExceptionsItem.setAttribute("checked", "true");
+ gOptions._togglePauseOnExceptions();
+ return deferred.promise;
+}
+
+function disablePauseOnExceptions() {
+ let deferred = promise.defer();
+
+ gDebugger.gThreadClient.addOneTimeListener("resumed", () => {
+ is(gPrefs.pauseOnExceptions, false,
+ "The pause-on-exceptions pref should now be disabled.");
+ isnot(gOptions._pauseOnExceptionsItem.getAttribute("checked"), "true",
+ "The pause-on-exceptions menu item should now be unchecked.");
+
+ ok(true, "Pausing on exceptions was disabled.");
+ deferred.resolve();
+ });
+
+ gOptions._pauseOnExceptionsItem.setAttribute("checked", "false");
+ gOptions._togglePauseOnExceptions();
+
+ return deferred.promise;
+}
+
+function enableIgnoreCaughtExceptions() {
+ let deferred = promise.defer();
+
+ gDebugger.gThreadClient.addOneTimeListener("resumed", () => {
+ is(gPrefs.ignoreCaughtExceptions, true,
+ "The ignore-caught-exceptions pref should now be enabled.");
+ is(gOptions._ignoreCaughtExceptionsItem.getAttribute("checked"), "true",
+ "The ignore-caught-exceptions menu item should now be checked.");
+
+ ok(true, "Ignore caught exceptions was enabled.");
+ deferred.resolve();
+ });
+
+ gOptions._ignoreCaughtExceptionsItem.setAttribute("checked", "true");
+ gOptions._toggleIgnoreCaughtExceptions();
+
+ return deferred.promise;
+}
+
+function disableIgnoreCaughtExceptions() {
+ let deferred = promise.defer();
+
+ gDebugger.gThreadClient.addOneTimeListener("resumed", () => {
+ is(gPrefs.ignoreCaughtExceptions, false,
+ "The ignore-caught-exceptions pref should now be disabled.");
+ isnot(gOptions._ignoreCaughtExceptionsItem.getAttribute("checked"), "true",
+ "The ignore-caught-exceptions menu item should now be unchecked.");
+
+ ok(true, "Ignore caught exceptions was disabled.");
+ deferred.resolve();
+ });
+
+ gOptions._ignoreCaughtExceptionsItem.setAttribute("checked", "false");
+ gOptions._toggleIgnoreCaughtExceptions();
+
+ return deferred.promise;
+}
+
+registerCleanupFunction(function () {
+ gTab = null;
+ gPanel = null;
+ gDebugger = null;
+ gFrames = null;
+ gVariables = null;
+ gPrefs = null;
+ gOptions = null;
+});
diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_pause-no-step.js b/devtools/client/debugger/test/mochitest/browser_dbg_pause-no-step.js
new file mode 100644
index 000000000..d4e8a05d2
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_pause-no-step.js
@@ -0,0 +1,94 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Tests that pausing / stepping is only enabled when there is a
+ * location.
+ */
+
+const TAB_URL = EXAMPLE_URL + "doc_pause-exceptions.html";
+
+var gTab, gPanel, gDebugger;
+var gResumeButton, gStepOverButton, gStepOutButton, gStepInButton;
+var gResumeKey, gFrames;
+
+function test() {
+ let options = {
+ source: TAB_URL,
+ line: 1
+ };
+ initDebugger(TAB_URL, options).then(([aTab,, aPanel]) => {
+ gTab = aTab;
+ gPanel = aPanel;
+ gDebugger = gPanel.panelWin;
+ gResumeButton = gDebugger.document.getElementById("resume");
+ gStepOverButton = gDebugger.document.getElementById("step-over");
+ gStepInButton = gDebugger.document.getElementById("step-in");
+ gStepOutButton = gDebugger.document.getElementById("step-out");
+ gResumeKey = gDebugger.document.getElementById("resumeKey");
+ gFrames = gDebugger.DebuggerView.StackFrames;
+
+ testPause();
+ });
+}
+
+function testPause() {
+ ok(!gDebugger.gThreadClient.paused, "Should be running after starting the test.");
+ ok(gStepOutButton.disabled, "Stepping out button should be disabled");
+ ok(gStepInButton.disabled, "Stepping in button should be disabled");
+ ok(gStepOverButton.disabled, "Stepping over button should be disabled");
+
+ gDebugger.gThreadClient.addOneTimeListener("paused", () => {
+ ok(gDebugger.gThreadClient.paused,
+ "Should be paused after an interrupt request.");
+
+ ok(!gStepOutButton.disabled, "Stepping out button should be enabled");
+ ok(!gStepInButton.disabled, "Stepping in button should be enabled");
+ ok(!gStepOverButton.disabled, "Stepping over button should be enabled");
+
+ waitForDebuggerEvents(gPanel, gDebugger.EVENTS.AFTER_FRAMES_REFILLED).then(() => {
+ is(gFrames.itemCount, 1,
+ "Should have 1 frame from the evalInTab call.");
+ gDebugger.gThreadClient.resume(testBreakAtLocation);
+ });
+
+ });
+
+ EventUtils.sendMouseEvent({ type: "mousedown" }, gResumeButton, gDebugger);
+
+ ok(!gDebugger.gThreadClient.paused,
+ "Shouldn't be paused until the next script is executed.");
+ ok(gStepOutButton.disabled, "Stepping out button should be disabled");
+ ok(gStepInButton.disabled, "Stepping in button should be disabled");
+ ok(gStepOverButton.disabled, "Stepping over button should be disabled");
+
+ // Evaluate a script to fully pause the debugger
+ once(gDebugger.gClient, "willInterrupt").then(() => {
+ evalInTab(gTab, "1+1;");
+ });
+}
+
+function testBreakAtLocation() {
+ gDebugger.gThreadClient.addOneTimeListener("paused", () => {
+ ok(!gStepOutButton.disabled, "Stepping out button should be enabled");
+ ok(!gStepInButton.disabled, "Stepping in button should be enabled");
+ ok(!gStepOverButton.disabled, "Stepping over button should be enabled");
+
+ resumeDebuggerThenCloseAndFinish(gPanel);
+ });
+
+ BrowserTestUtils.synthesizeMouseAtCenter("button", {}, gBrowser.selectedBrowser);
+}
+
+registerCleanupFunction(function () {
+ gPanel = null;
+ gDebugger = null;
+ gResumeButton = null;
+ gStepOverButton = null;
+ gStepInButton = null;
+ gStepOutButton = null;
+ gResumeKey = null;
+ gFrames = null;
+});
diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_pause-resume.js b/devtools/client/debugger/test/mochitest/browser_dbg_pause-resume.js
new file mode 100644
index 000000000..e9aaebe55
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_pause-resume.js
@@ -0,0 +1,91 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Tests if pausing and resuming in the main loop works properly.
+ */
+
+const TAB_URL = EXAMPLE_URL + "doc_pause-exceptions.html";
+
+var gTab, gPanel, gDebugger;
+var gResumeButton, gResumeKey, gFrames;
+
+function test() {
+ let options = {
+ source: TAB_URL,
+ line: 1
+ };
+ initDebugger(TAB_URL, options).then(([aTab,, aPanel]) => {
+ gTab = aTab;
+ gPanel = aPanel;
+ gDebugger = gPanel.panelWin;
+ gResumeButton = gDebugger.document.getElementById("resume");
+ gResumeKey = gDebugger.document.getElementById("resumeKey");
+ gFrames = gDebugger.DebuggerView.StackFrames;
+
+ testPause();
+ });
+}
+
+function testPause() {
+ is(gDebugger.gThreadClient.paused, false,
+ "Should be running after starting the test.");
+
+ is(gResumeButton.getAttribute("tooltiptext"),
+ gDebugger.L10N.getFormatStr("pauseButtonTooltip",
+ gDebugger.ShortcutUtils.prettifyShortcut(gResumeKey)),
+ "Button tooltip should be 'pause' when running.");
+
+ gDebugger.gThreadClient.addOneTimeListener("paused", () => {
+ is(gDebugger.gThreadClient.paused, true,
+ "Should be paused after an interrupt request.");
+
+ is(gResumeButton.getAttribute("tooltiptext"),
+ gDebugger.L10N.getFormatStr("resumeButtonTooltip",
+ gDebugger.ShortcutUtils.prettifyShortcut(gResumeKey)),
+ "Button tooltip should be 'resume' when paused.");
+
+ is(gFrames.itemCount, 0,
+ "Should have no frames when paused in the main loop.");
+
+ testResume();
+ });
+
+ EventUtils.sendMouseEvent({ type: "mousedown" }, gResumeButton, gDebugger);
+
+ is(gResumeButton.getAttribute("tooltiptext"),
+ gDebugger.L10N.getFormatStr("pausePendingButtonTooltip"),
+ "Button tooltip should be 'waiting for execution' when breaking on nex.");
+
+ // Evaluate a script to fully pause the debugger
+ once(gDebugger.gClient, "willInterrupt").then(() => {
+ evalInTab(gTab, "1+1;");
+ });
+}
+
+function testResume() {
+ gDebugger.gThreadClient.addOneTimeListener("resumed", () => {
+ is(gDebugger.gThreadClient.paused, false,
+ "Should be paused after an interrupt request.");
+
+ is(gResumeButton.getAttribute("tooltiptext"),
+ gDebugger.L10N.getFormatStr("pauseButtonTooltip",
+ gDebugger.ShortcutUtils.prettifyShortcut(gResumeKey)),
+ "Button tooltip should be pause when running.");
+
+ closeDebuggerAndFinish(gPanel);
+ });
+
+ EventUtils.sendMouseEvent({ type: "mousedown" }, gResumeButton, gDebugger);
+}
+
+registerCleanupFunction(function () {
+ gTab = null;
+ gPanel = null;
+ gDebugger = null;
+ gResumeButton = null;
+ gResumeKey = null;
+ gFrames = null;
+});
diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_pause-warning.js b/devtools/client/debugger/test/mochitest/browser_dbg_pause-warning.js
new file mode 100644
index 000000000..bcd2599dc
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_pause-warning.js
@@ -0,0 +1,109 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Tests if a warning is shown in the inspector when debugger is paused.
+ */
+
+const TAB_URL = EXAMPLE_URL + "doc_inline-script.html";
+
+var gTab, gPanel, gDebugger;
+var gTarget, gToolbox;
+
+function test() {
+ let options = {
+ source: TAB_URL,
+ line: 1
+ };
+ initDebugger(TAB_URL, options).then(([aTab,, aPanel]) => {
+ gTab = aTab;
+ gPanel = aPanel;
+ gDebugger = gPanel.panelWin;
+ gTarget = gPanel.target;
+ gToolbox = gPanel._toolbox;
+
+ testPause();
+ });
+}
+
+function testPause() {
+ gDebugger.gThreadClient.addOneTimeListener("paused", () => {
+ ok(gDebugger.gThreadClient.paused,
+ "threadClient.paused has been updated to true.");
+
+ gToolbox.once("inspector-selected").then(inspector => {
+ inspector.once("inspector-updated").then(testNotificationIsUp1);
+ });
+ gToolbox.selectTool("inspector");
+ });
+
+ EventUtils.sendMouseEvent({ type: "mousedown" },
+ gDebugger.document.getElementById("resume"),
+ gDebugger);
+
+ // Evaluate a script to fully pause the debugger
+ once(gDebugger.gClient, "willInterrupt").then(() => {
+ evalInTab(gTab, "1+1;");
+ });
+}
+
+function testNotificationIsUp1() {
+ let notificationBox = gToolbox.getNotificationBox();
+ let notification = notificationBox.getNotificationWithValue("inspector-script-paused");
+
+ ok(notification,
+ "Inspector notification is present (1).");
+
+ gToolbox.once("jsdebugger-selected", testNotificationIsHidden);
+ gToolbox.selectTool("jsdebugger");
+}
+
+function testNotificationIsHidden() {
+ let notificationBox = gToolbox.getNotificationBox();
+ let notification = notificationBox.getNotificationWithValue("inspector-script-paused");
+
+ ok(!notification,
+ "Inspector notification is hidden (2).");
+
+ gToolbox.once("inspector-selected", testNotificationIsUp2);
+ gToolbox.selectTool("inspector");
+}
+
+function testNotificationIsUp2() {
+ let notificationBox = gToolbox.getNotificationBox();
+ let notification = notificationBox.getNotificationWithValue("inspector-script-paused");
+
+ ok(notification,
+ "Inspector notification is present again (3).");
+
+ testResume();
+}
+
+function testResume() {
+ gDebugger.gThreadClient.addOneTimeListener("resumed", () => {
+ ok(!gDebugger.gThreadClient.paused,
+ "threadClient.paused has been updated to false.");
+
+ let notificationBox = gToolbox.getNotificationBox();
+ let notification = notificationBox.getNotificationWithValue("inspector-script-paused");
+
+ ok(!notification,
+ "Inspector notification was removed once debugger resumed.");
+
+ closeDebuggerAndFinish(gPanel);
+ });
+
+ EventUtils.sendMouseEvent({ type: "mousedown" },
+ gDebugger.document.getElementById("resume"),
+ gDebugger);
+}
+
+registerCleanupFunction(function () {
+ gTab = null;
+ gPanel = null;
+ gDebugger = null;
+ gTarget = null;
+ gToolbox = null;
+});
diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_paused-keybindings.js b/devtools/client/debugger/test/mochitest/browser_dbg_paused-keybindings.js
new file mode 100644
index 000000000..ebffbdd9d
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_paused-keybindings.js
@@ -0,0 +1,50 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Test that keybindings still work when the content window is paused and
+// the tab is selected again.
+
+function test() {
+ Task.spawn(function* () {
+ const TAB_URL = EXAMPLE_URL + "doc_inline-script.html";
+ let gDebugger, searchBox;
+
+ let options = {
+ source: TAB_URL,
+ line: 1
+ };
+ let [tab, debuggee, panel] = yield initDebugger(TAB_URL, options);
+ gDebugger = panel.panelWin;
+ searchBox = gDebugger.DebuggerView.Filtering._searchbox;
+
+ let onCaretUpdated = ensureCaretAt(panel, 20, 1, true);
+ let onThreadPaused = ensureThreadClientState(panel, "paused");
+ ContentTask.spawn(tab.linkedBrowser, {}, function* () {
+ content.document.querySelector("button").click();
+ });
+ yield onCaretUpdated;
+ yield onThreadPaused
+
+ // Now open a tab and close it.
+ let tab2 = yield addTab(TAB_URL);
+ yield waitForTick();
+ yield removeTab(tab2);
+ yield ensureCaretAt(panel, 20);
+
+ // Try to use the Cmd-L keybinding to see if it still works.
+ let caretMove = ensureCaretAt(panel, 15, 1, true);
+ // Wait a tick for the editor focus event to occur first.
+ executeSoon(function () {
+ EventUtils.synthesizeKey("l", { accelKey: true });
+ EventUtils.synthesizeKey("1", {});
+ EventUtils.synthesizeKey("5", {});
+ });
+ yield caretMove;
+
+ yield resumeDebuggerThenCloseAndFinish(panel);
+ }).then(null, aError => {
+ ok(false, "Got an error: " + aError.message + "\n" + aError.stack);
+ });
+}
diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_post-page.js b/devtools/client/debugger/test/mochitest/browser_dbg_post-page.js
new file mode 100644
index 000000000..9d7d418de
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_post-page.js
@@ -0,0 +1,53 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Tests that source contents are invalidated when the target navigates.
+ */
+
+const TAB_URL = EXAMPLE_URL + "sjs_post-page.sjs";
+
+const FORM = "<form method=\"POST\"><input type=\"submit\"></form>";
+const GET_CONTENT = "<script>\"GET\";</script>" + FORM;
+const POST_CONTENT = "<script>\"POST\";</script>" + FORM;
+
+add_task(function* () {
+ let options = {
+ source: TAB_URL,
+ line: 1
+ };
+ let [tab,, panel] = yield initDebugger(TAB_URL, options);
+ let win = panel.panelWin;
+ let editor = win.DebuggerView.editor;
+ let queries = win.require("./content/queries");
+ let getState = win.DebuggerController.getState;
+
+ let source = queries.getSelectedSource(getState());
+
+ is(queries.getSourceCount(getState()), 1,
+ "There should be one source displayed in the view.");
+ is(source.url, TAB_URL,
+ "The correct source is currently selected in the view.");
+ is(editor.getText(), GET_CONTENT,
+ "The currently shown source contains bacon. Mmm, delicious!");
+
+ // Submit the form and wait for debugger update
+ let onSourceUpdated = waitForSourceShown(panel, TAB_URL);
+ yield ContentTask.spawn(tab.linkedBrowser, null, function () {
+ content.document.querySelector("input[type=\"submit\"]").click();
+ });
+ yield onSourceUpdated;
+
+ // Verify that the source updates to the POST page content
+ source = queries.getSelectedSource(getState());
+ is(queries.getSourceCount(getState()), 1,
+ "There should be one source displayed in the view.");
+ is(source.url, TAB_URL,
+ "The correct source is currently selected in the view.");
+ is(editor.getText(), POST_CONTENT,
+ "The currently shown source contains bacon. Mmm, delicious!");
+
+ yield closeDebuggerAndFinish(panel);
+});
diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_pretty-print-01.js b/devtools/client/debugger/test/mochitest/browser_dbg_pretty-print-01.js
new file mode 100644
index 000000000..be6cb76f7
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_pretty-print-01.js
@@ -0,0 +1,52 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Make sure that clicking the pretty print button prettifies the source.
+ */
+
+const TAB_URL = EXAMPLE_URL + "doc_pretty-print.html";
+
+function test() {
+ // Wait for debugger panel to be fully set and break on debugger statement
+ let options = {
+ source: EXAMPLE_URL + "code_ugly.js",
+ line: 2
+ };
+ initDebugger(TAB_URL, options).then(([aTab,, aPanel]) => {
+ const gTab = aTab;
+ const gPanel = aPanel;
+ const gDebugger = gPanel.panelWin;
+ const gEditor = gDebugger.DebuggerView.editor;
+ const gSources = gDebugger.DebuggerView.Sources;
+ const queries = gDebugger.require("./content/queries");
+ const constants = gDebugger.require("./content/constants");
+ const actions = bindActionCreators(gPanel);
+ const getState = gDebugger.DebuggerController.getState;
+
+ Task.spawn(function* () {
+ ok(!gEditor.getText().includes("\n "),
+ "The source shouldn't be pretty printed yet.");
+
+ const finished = waitForSourceShown(gPanel, "code_ugly.js");
+ gDebugger.document.getElementById("pretty-print").click();
+ const deck = gDebugger.document.getElementById("editor-deck");
+ is(deck.selectedIndex, 2, "The progress bar should be shown");
+ yield finished;
+
+ ok(gEditor.getText().includes("\n "),
+ "The source should be pretty printed.");
+ is(deck.selectedIndex, 0, "The editor should be shown");
+
+ const source = queries.getSelectedSource(getState());
+ const { loading, text } = queries.getSourceText(getState(), source.actor);
+ ok(!loading, "Source text is not loading");
+ ok(text.includes("\n "),
+ "Subsequent calls to getText return the pretty printed source.");
+
+ resumeDebuggerThenCloseAndFinish(gPanel);
+ });
+ });
+}
diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_pretty-print-02.js b/devtools/client/debugger/test/mochitest/browser_dbg_pretty-print-02.js
new file mode 100644
index 000000000..4cbdfc9a8
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_pretty-print-02.js
@@ -0,0 +1,41 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Make sure that right clicking and selecting the pretty print context menu
+ * item prettifies the source.
+ */
+
+const TAB_URL = EXAMPLE_URL + "doc_pretty-print.html";
+
+function test() {
+ // Wait for debugger panel to be fully set and break on debugger statement
+ let options = {
+ source: EXAMPLE_URL + "code_ugly.js",
+ line: 2
+ };
+ initDebugger(TAB_URL, options).then(([aTab,, aPanel]) => {
+ const gTab = aTab;
+ const gPanel = aPanel;
+ const gDebugger = gPanel.panelWin;
+ const gEditor = gDebugger.DebuggerView.editor;
+ const gContextMenu = gDebugger.document.getElementById("sourceEditorContextMenu");
+
+ Task.spawn(function* () {
+ const finished = waitForSourceShown(gPanel, "code_ugly.js");
+ once(gContextMenu, "popupshown").then(() => {
+ const menuItem = gDebugger.document.getElementById("se-dbg-cMenu-prettyPrint");
+ menuItem.click();
+ });
+ gContextMenu.openPopup(gEditor.container, "overlap", 0, 0, true, false);
+ yield finished;
+
+ ok(gEditor.getText().includes("\n "),
+ "The source should be pretty printed.");
+
+ resumeDebuggerThenCloseAndFinish(gPanel);
+ });
+ });
+}
diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_pretty-print-03.js b/devtools/client/debugger/test/mochitest/browser_dbg_pretty-print-03.js
new file mode 100644
index 000000000..13de30ac0
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_pretty-print-03.js
@@ -0,0 +1,40 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Make sure that we have the correct line selected after pretty printing.
+ */
+
+const TAB_URL = EXAMPLE_URL + "doc_pretty-print.html";
+
+function test() {
+ // Wait for debugger panel to be fully set and break on debugger statement
+ let options = {
+ source: EXAMPLE_URL + "code_ugly.js",
+ line: 2
+ };
+ initDebugger(TAB_URL, options).then(([aTab,, aPanel]) => {
+ const gTab = aTab;
+ const gPanel = aPanel;
+ const gDebugger = gPanel.panelWin;
+
+ Task.spawn(function* () {
+ yield doResume(gPanel);
+
+ const paused = waitForPause(gDebugger.gThreadClient);
+ callInTab(gTab, "foo");
+ yield paused;
+
+ const finished = promise.all([
+ waitForSourceShown(gPanel, "code_ugly.js"),
+ waitForCaretUpdated(gPanel, 7)
+ ]);
+ gDebugger.document.getElementById("pretty-print").click();
+ yield finished;
+
+ resumeDebuggerThenCloseAndFinish(gPanel);
+ });
+ });
+}
diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_pretty-print-04.js b/devtools/client/debugger/test/mochitest/browser_dbg_pretty-print-04.js
new file mode 100644
index 000000000..a45aca91e
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_pretty-print-04.js
@@ -0,0 +1,50 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Tests that the function searching works with pretty printed sources.
+ */
+
+const TAB_URL = EXAMPLE_URL + "doc_pretty-print.html";
+
+function test() {
+ // Wait for debugger panel to be fully set and break on debugger statement
+ let options = {
+ source: EXAMPLE_URL + "code_ugly.js",
+ line: 2
+ };
+ initDebugger(TAB_URL, options).then(([aTab,, aPanel]) => {
+ const gTab = aTab;
+ const gPanel = aPanel;
+ const gDebugger = gPanel.panelWin;
+ const gSearchBox = gDebugger.DebuggerView.Filtering._searchbox;
+
+ Task.spawn(function* () {
+ let popupShown = promise.defer();
+ once(gDebugger, "popupshown").then(() => {
+ ok(isCaretPos(gPanel, 2, 10),
+ "The bar function's non-pretty-printed location should be shown.");
+ popupShown.resolve();
+ });
+ setText(gSearchBox, "@bar");
+ yield popupShown.promise;
+
+ const finished = waitForSourceShown(gPanel, "code_ugly.js");
+ gDebugger.document.getElementById("pretty-print").click();
+ yield finished;
+
+ popupShown = promise.defer();
+ once(gDebugger, "popupshown").then(() => {
+ ok(isCaretPos(gPanel, 6, 10),
+ "The bar function's pretty printed location should be shown.");
+ popupShown.resolve();
+ });
+ setText(gSearchBox, "@bar");
+ yield popupShown.promise;
+
+ resumeDebuggerThenCloseAndFinish(gPanel);
+ });
+ });
+}
diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_pretty-print-05.js b/devtools/client/debugger/test/mochitest/browser_dbg_pretty-print-05.js
new file mode 100644
index 000000000..de1198103
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_pretty-print-05.js
@@ -0,0 +1,66 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Make sure that prettifying HTML sources doesn't do anything.
+ */
+
+const TAB_URL = EXAMPLE_URL + "doc_included-script.html";
+const SCRIPT_URL = EXAMPLE_URL + "code_location-changes.js";
+
+function test() {
+ let options = {
+ source: SCRIPT_URL,
+ line: 1
+ };
+ initDebugger(TAB_URL, options).then(([aTab,, aPanel]) => {
+ const gTab = aTab;
+ const gPanel = aPanel;
+ const gDebugger = gPanel.panelWin;
+ const gEditor = gDebugger.DebuggerView.editor;
+ const gSources = gDebugger.DebuggerView.Sources;
+ const queries = gDebugger.require("./content/queries");
+ const constants = gDebugger.require("./content/constants");
+ const actions = bindActionCreators(gPanel);
+ const getState = gDebugger.DebuggerController.getState;
+
+ Task.spawn(function* () {
+ // Now, select the html page
+ const sourceShown = waitForSourceShown(gPanel, TAB_URL);
+ gSources.selectedValue = getSourceActor(gSources, TAB_URL);
+ yield sourceShown;
+
+ // From this point onward, the source editor's text should never change.
+ gEditor.once("change", () => {
+ ok(false, "The source editor text shouldn't have changed.");
+ });
+
+ is(getSelectedSourceURL(gSources), TAB_URL,
+ "The correct source is currently selected.");
+ ok(gEditor.getText().includes("myFunction"),
+ "The source shouldn't be pretty printed yet.");
+
+ const source = queries.getSelectedSource(getState());
+ try {
+ yield actions.togglePrettyPrint(source);
+ ok(false, "An error occurred while pretty-printing");
+ }
+ catch (err) {
+ is(err.message, "Can't prettify non-javascript files.",
+ "The promise was correctly rejected with a meaningful message.");
+ }
+
+ const { text } = yield queries.getSourceText(getState(), source.actor);
+ is(getSelectedSourceURL(gSources), TAB_URL,
+ "The correct source is still selected.");
+ ok(gEditor.getText().includes("myFunction"),
+ "The displayed source hasn't changed.");
+ ok(text.includes("myFunction"),
+ "The cached source text wasn't altered in any way.");
+
+ yield closeDebuggerAndFinish(gPanel);
+ });
+ });
+}
diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_pretty-print-06.js b/devtools/client/debugger/test/mochitest/browser_dbg_pretty-print-06.js
new file mode 100644
index 000000000..608df3140
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_pretty-print-06.js
@@ -0,0 +1,80 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Make sure that prettifying JS sources with type errors works as expected.
+ */
+
+const TAB_URL = EXAMPLE_URL + "doc_included-script.html";
+const JS_URL = EXAMPLE_URL + "code_location-changes.js";
+
+function test() {
+ let options = {
+ source: JS_URL,
+ line: 1
+ };
+ initDebugger(TAB_URL, options).then(([aTab,, aPanel]) => {
+ const gTab = aTab;
+ const gPanel = aPanel;
+ const gDebugger = gPanel.panelWin;
+ const gClient = gDebugger.gClient;
+ const gEditor = gDebugger.DebuggerView.editor;
+ const gSources = gDebugger.DebuggerView.Sources;
+ const queries = gDebugger.require("./content/queries");
+ const constants = gDebugger.require("./content/constants");
+ const actions = bindActionCreators(gPanel);
+ const getState = gDebugger.DebuggerController.getState;
+ let gPrettyPrinted = false;
+
+ // We can't feed javascript files with syntax errors to the debugger,
+ // because they will never run, thus sometimes getting gc'd before the
+ // debugger is opened, or even before the target finishes navigating.
+ // Make the client lie about being able to parse perfectly fine code.
+ gClient.request = (function (aOriginalRequestMethod) {
+ return function (aPacket, aCallback) {
+ if (aPacket.type == "prettyPrint") {
+ gPrettyPrinted = true;
+ return promise.reject({ error: "prettyPrintError" });
+ }
+ return aOriginalRequestMethod(aPacket, aCallback);
+ };
+ }(gClient.request));
+
+ Task.spawn(function* () {
+ // From this point onward, the source editor's text should never change.
+ gEditor.once("change", () => {
+ ok(false, "The source editor text shouldn't have changed.");
+ });
+
+ is(getSelectedSourceURL(gSources), JS_URL,
+ "The correct source is currently selected.");
+ ok(gEditor.getText().includes("myFunction"),
+ "The source shouldn't be pretty printed yet.");
+
+ const source = queries.getSelectedSource(getState());
+ try {
+ yield actions.togglePrettyPrint(source);
+ ok(false, "The promise for a prettified source should be rejected!");
+ } catch (error) {
+ ok(error.error, "Error came from a RDP request");
+ ok(error.error.includes("prettyPrintError"),
+ "The promise was correctly rejected with a meaningful message.");
+ }
+
+ const { text } = yield queries.getSourceText(getState(), source.actor);
+ is(getSelectedSourceURL(gSources), JS_URL,
+ "The correct source is still selected.");
+ ok(gEditor.getText().includes("myFunction"),
+ "The displayed source hasn't changed.");
+ ok(text.includes("myFunction"),
+ "The cached source text wasn't altered in any way.");
+
+ is(gPrettyPrinted, true,
+ "The hijacked pretty print method was executed.");
+
+ yield closeDebuggerAndFinish(gPanel);
+ });
+ });
+}
diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_pretty-print-07.js b/devtools/client/debugger/test/mochitest/browser_dbg_pretty-print-07.js
new file mode 100644
index 000000000..4776d16ba
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_pretty-print-07.js
@@ -0,0 +1,62 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Test basic pretty printing functionality. Would be an xpcshell test, except
+// for bug 921252.
+
+var gTab, gPanel, gClient, gThreadClient, gSource;
+
+const TAB_URL = EXAMPLE_URL + "doc_pretty-print-2.html";
+
+function test() {
+ let options = {
+ source: EXAMPLE_URL + "code_ugly-2.js",
+ line: 1
+ };
+ initDebugger(TAB_URL, options).then(([aTab,, aPanel]) => {
+ gTab = aTab;
+ gPanel = aPanel;
+ gClient = gPanel.panelWin.gClient;
+ gThreadClient = gPanel.panelWin.DebuggerController.activeThread;
+
+ findSource();
+ });
+}
+
+function findSource() {
+ gThreadClient.getSources(({ error, sources }) => {
+ ok(!error);
+ sources = sources.filter(s => s.url.includes("code_ugly-2.js"));
+ is(sources.length, 1);
+ gSource = sources[0];
+ prettyPrintSource();
+ });
+}
+
+function prettyPrintSource() {
+ gThreadClient.source(gSource).prettyPrint(4, testPrettyPrinted);
+}
+
+function testPrettyPrinted({ error, source }) {
+ ok(!error, "Should not get an error while pretty-printing");
+ ok(source.includes("\n "),
+ "Source should be pretty-printed");
+ disablePrettyPrint();
+}
+
+function disablePrettyPrint() {
+ gThreadClient.source(gSource).disablePrettyPrint(testUgly);
+}
+
+function testUgly({ error, source }) {
+ ok(!error, "Should not get an error while disabling pretty-printing");
+ ok(!source.includes("\n "),
+ "Source should not be pretty after disabling pretty-printing");
+ closeDebuggerAndFinish(gPanel);
+}
+
+registerCleanupFunction(function () {
+ gTab = gPanel = gClient = gThreadClient = gSource = null;
+});
diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_pretty-print-08.js b/devtools/client/debugger/test/mochitest/browser_dbg_pretty-print-08.js
new file mode 100644
index 000000000..e49910972
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_pretty-print-08.js
@@ -0,0 +1,99 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Test stepping through pretty printed sources.
+
+var gTab, gPanel, gClient, gThreadClient, gSource;
+
+const TAB_URL = EXAMPLE_URL + "doc_pretty-print-2.html";
+
+function test() {
+ let options = {
+ source: EXAMPLE_URL + "code_ugly-2.js",
+ line: 1
+ };
+ initDebugger(TAB_URL, options).then(([aTab,, aPanel]) => {
+ gTab = aTab;
+ gPanel = aPanel;
+ gClient = gPanel.panelWin.gClient;
+ gThreadClient = gPanel.panelWin.DebuggerController.activeThread;
+
+ findSource();
+ });
+}
+
+const BP_LOCATION = {
+ line: 5,
+ // column: 0
+};
+
+function findSource() {
+ gThreadClient.getSources(({ error, sources }) => {
+ ok(!error, "error should exist");
+ sources = sources.filter(s => s.url.includes("code_ugly-3.js"));
+ is(sources.length, 1, "sources.length should be 1");
+ [gSource] = sources;
+ BP_LOCATION.actor = gSource.actor;
+
+ prettyPrintSource(sources[0]);
+ });
+}
+
+function prettyPrintSource(source) {
+ gThreadClient.source(gSource).prettyPrint(2, runCode);
+}
+
+function runCode({ error }) {
+ ok(!error);
+ gClient.addOneTimeListener("paused", testDbgStatement);
+ callInTab(gTab, "main3");
+}
+
+function testDbgStatement(event, { why, frame }) {
+ is(why.type, "debuggerStatement");
+ const { source, line, column } = frame.where;
+ is(source.actor, BP_LOCATION.actor, "source.actor should be the right actor");
+ is(line, 3, "the line should be 3");
+ setBreakpoint();
+}
+
+function setBreakpoint() {
+ gThreadClient.source(gSource).setBreakpoint(
+ { line: BP_LOCATION.line,
+ column: BP_LOCATION.column },
+ ({ error, actualLocation }) => {
+ ok(!error, "error should not exist");
+ ok(!actualLocation, "actualLocation should not exist");
+ testStepping();
+ }
+ );
+}
+
+function testStepping() {
+ gClient.addOneTimeListener("paused", (event, { why, frame }) => {
+ is(why.type, "resumeLimit");
+ const { source, line } = frame.where;
+ is(source.actor, BP_LOCATION.actor, "source.actor should be the right actor");
+ is(line, 4, "the line should be 4");
+ testHitBreakpoint();
+ });
+ gThreadClient.stepIn();
+}
+
+function testHitBreakpoint() {
+ gClient.addOneTimeListener("paused", (event, { why, frame }) => {
+ is(why.type, "breakpoint");
+ const { source, line } = frame.where;
+ is(source.actor, BP_LOCATION.actor, "source.actor should be the right actor");
+ is(line, BP_LOCATION.line, "the line should the right line");
+
+ resumeDebuggerThenCloseAndFinish(gPanel);
+ });
+ gThreadClient.resume();
+}
+
+registerCleanupFunction(function () {
+ gTab = gPanel = gClient = gThreadClient = gSource = null;
+});
diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_pretty-print-09.js b/devtools/client/debugger/test/mochitest/browser_dbg_pretty-print-09.js
new file mode 100644
index 000000000..16eab24ea
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_pretty-print-09.js
@@ -0,0 +1,92 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Test pretty printing source mapped sources.
+
+var gClient;
+var gThreadClient;
+var gSource;
+
+var gTab, gPanel, gClient, gThreadClient, gSource;
+
+const TAB_URL = EXAMPLE_URL + "doc_pretty-print-2.html";
+
+function test() {
+ let options = {
+ source: EXAMPLE_URL + "code_ugly-2.js",
+ line: 1
+ };
+ initDebugger(TAB_URL, options).then(([aTab,, aPanel]) => {
+ gTab = aTab;
+ gPanel = aPanel;
+ gClient = gPanel.panelWin.gClient;
+ gThreadClient = gPanel.panelWin.DebuggerController.activeThread;
+
+ findSource();
+ });
+}
+
+const dataUrl = s => "data:text/javascript," + s;
+
+// These should match the instructions in code_ugly-4.js.
+const A = "function a(){b()}";
+const A_URL = dataUrl(A);
+const B = "function b(){debugger}";
+const B_URL = dataUrl(B);
+
+function findSource() {
+ gThreadClient.getSources(({ error, sources }) => {
+ ok(!error);
+ sources = sources.filter(s => s.url === B_URL);
+ is(sources.length, 1);
+ gSource = sources[0];
+ prettyPrint();
+ });
+}
+
+function prettyPrint() {
+ gThreadClient.source(gSource).prettyPrint(2, runCode);
+}
+
+function runCode({ error }) {
+ ok(!error);
+ gClient.addOneTimeListener("paused", testDbgStatement);
+ callInTab(gTab, "a");
+}
+
+function testDbgStatement(event, { frame, why }) {
+ is(why.type, "debuggerStatement");
+ const { source, line } = frame.where;
+ is(source.url, B_URL);
+ is(line, 2);
+
+ disablePrettyPrint();
+}
+
+function disablePrettyPrint() {
+ gThreadClient.source(gSource).disablePrettyPrint(testUgly);
+}
+
+function testUgly({ error, source }) {
+ ok(!error);
+ ok(!source.includes("\n "));
+ getFrame();
+}
+
+function getFrame() {
+ gThreadClient.getFrames(0, 1, testFrame);
+}
+
+function testFrame({ frames: [frame] }) {
+ const { source, line } = frame.where;
+ is(source.url, B_URL);
+ is(line, 1);
+
+ resumeDebuggerThenCloseAndFinish(gPanel);
+}
+
+registerCleanupFunction(function () {
+ gTab = gPanel = gClient = gThreadClient = null;
+});
diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_pretty-print-10.js b/devtools/client/debugger/test/mochitest/browser_dbg_pretty-print-10.js
new file mode 100644
index 000000000..862d553fb
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_pretty-print-10.js
@@ -0,0 +1,48 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Make sure that we disable the pretty print button for black boxed sources,
+ * and that clicking it doesn't do anything.
+ */
+
+const TAB_URL = EXAMPLE_URL + "doc_pretty-print.html";
+
+function test() {
+ // Wait for debugger panel to be fully set and break on debugger statement
+ let options = {
+ source: EXAMPLE_URL + "code_ugly.js",
+ line: 2
+ };
+ initDebugger(TAB_URL, options).then(([aTab,, aPanel]) => {
+ const gTab = aTab;
+ const gPanel = aPanel;
+ const gDebugger = gPanel.panelWin;
+ const gEditor = gDebugger.DebuggerView.editor;
+ const gSources = gDebugger.DebuggerView.Sources;
+ const queries = gDebugger.require("./content/queries");
+ const getState = gDebugger.DebuggerController.getState;
+
+ Task.spawn(function* () {
+ ok(!gEditor.getText().includes("\n "),
+ "The source shouldn't be pretty printed yet.");
+
+ yield toggleBlackBoxing(gPanel);
+
+ // Wait a tick before clicking to make sure the frontend's blackboxchange
+ // handlers have finished.
+ yield waitForTick();
+ gDebugger.document.getElementById("pretty-print").click();
+ // Make sure the text updates
+ yield waitForTick();
+
+ const source = queries.getSelectedSource(getState());
+ const { text } = queries.getSourceText(getState(), source.actor);
+ ok(!text.includes("\n "));
+
+ resumeDebuggerThenCloseAndFinish(gPanel);
+ });
+ });
+}
diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_pretty-print-11.js b/devtools/client/debugger/test/mochitest/browser_dbg_pretty-print-11.js
new file mode 100644
index 000000000..9e8bebc3f
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_pretty-print-11.js
@@ -0,0 +1,65 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Make sure that pretty printing is maintained across refreshes.
+ */
+
+const TAB_URL = EXAMPLE_URL + "doc_pretty-print.html";
+
+var gTab, gPanel, gDebugger;
+var gEditor, gSources;
+
+function test() {
+ // Wait for debugger panel to be fully set and break on debugger statement
+ let options = {
+ source: EXAMPLE_URL + "code_ugly.js",
+ line: 2
+ };
+ initDebugger(TAB_URL, options).then(([aTab,, aPanel]) => {
+ gTab = aTab;
+ gPanel = aPanel;
+ gDebugger = gPanel.panelWin;
+ gEditor = gDebugger.DebuggerView.editor;
+ gSources = gDebugger.DebuggerView.Sources;
+
+ testSourceIsUgly();
+ const finished = waitForCaretUpdated(gPanel, 7);
+ clickPrettyPrintButton();
+ finished.then(testSourceIsPretty)
+ .then(() => {
+ const finished = waitForCaretUpdated(gPanel, 7);
+ const reloaded = reloadActiveTab(gPanel, gDebugger.EVENTS.SOURCE_SHOWN);
+ return Promise.all([finished, reloaded]);
+ })
+ .then(testSourceIsPretty)
+ .then(() => resumeDebuggerThenCloseAndFinish(gPanel))
+ .then(null, aError => {
+ ok(false, "Got an error: " + DevToolsUtils.safeErrorString(aError));
+ });
+ });
+}
+
+function testSourceIsUgly() {
+ ok(!gEditor.getText().includes("\n "),
+ "The source shouldn't be pretty printed yet.");
+}
+
+function clickPrettyPrintButton() {
+ gDebugger.document.getElementById("pretty-print").click();
+}
+
+function testSourceIsPretty() {
+ ok(gEditor.getText().includes("\n "),
+ "The source should be pretty printed.");
+}
+
+registerCleanupFunction(function () {
+ gTab = null;
+ gPanel = null;
+ gDebugger = null;
+ gEditor = null;
+ gSources = null;
+});
diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_pretty-print-12.js b/devtools/client/debugger/test/mochitest/browser_dbg_pretty-print-12.js
new file mode 100644
index 000000000..57ce54fc5
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_pretty-print-12.js
@@ -0,0 +1,51 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Make sure that we don't leave the pretty print button checked when we fail to
+ * pretty print a source (because it isn't a JS file, for example).
+ */
+
+const TAB_URL = EXAMPLE_URL + "doc_blackboxing.html";
+const SCRIPT_URL = EXAMPLE_URL + "code_blackboxing_blackboxme.js";
+
+function test() {
+ let options = {
+ source: SCRIPT_URL,
+ line: 1
+ };
+ initDebugger(TAB_URL, options).then(([aTab,, aPanel]) => {
+ const gTab = aTab;
+ const gPanel = aPanel;
+ const gDebugger = gPanel.panelWin;
+ const gEditor = gDebugger.DebuggerView.editor;
+ const gSources = gDebugger.DebuggerView.Sources;
+ const queries = gDebugger.require("./content/queries");
+ const constants = gDebugger.require("./content/constants");
+ const actions = bindActionCreators(gPanel);
+ const getState = gDebugger.DebuggerController.getState;
+
+ Task.spawn(function* () {
+ const source = getSourceForm(gSources, TAB_URL);
+ let shown = ensureSourceIs(gPanel, TAB_URL, true);
+ actions.selectSource(source);
+ yield shown;
+
+ try {
+ yield actions.togglePrettyPrint(source);
+ ok(false, "An error occurred while pretty-printing");
+ }
+ catch (err) {
+ is(err.message, "Can't prettify non-javascript files.",
+ "The promise was correctly rejected with a meaningful message.");
+ }
+
+ is(gDebugger.document.getElementById("pretty-print").checked, false,
+ "The button shouldn't be checked after trying to pretty print a non-js file.");
+
+ closeDebuggerAndFinish(gPanel);
+ });
+ });
+}
diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_pretty-print-13.js b/devtools/client/debugger/test/mochitest/browser_dbg_pretty-print-13.js
new file mode 100644
index 000000000..7409f88f5
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_pretty-print-13.js
@@ -0,0 +1,53 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Make sure that clicking the pretty print button prettifies the source, even
+ * when the source URL does not end in ".js", but the content type is
+ * JavaScript.
+ */
+
+const TAB_URL = EXAMPLE_URL + "doc_pretty-print-3.html";
+
+function test() {
+ // Wait for debugger panel to be fully set and break on debugger statement
+ let options = {
+ source: EXAMPLE_URL + "code_ugly-8",
+ line: 2
+ };
+ initDebugger(TAB_URL, options).then(([aTab,, aPanel]) => {
+ const gTab = aTab;
+ const gPanel = aPanel;
+ const gDebugger = gPanel.panelWin;
+ const gEditor = gDebugger.DebuggerView.editor;
+ const gSources = gDebugger.DebuggerView.Sources;
+ const queries = gDebugger.require("./content/queries");
+ const constants = gDebugger.require("./content/constants");
+ const actions = bindActionCreators(gPanel);
+ const getState = gDebugger.DebuggerController.getState;
+
+ Task.spawn(function* () {
+ ok(!gEditor.getText().includes("\n "),
+ "The source shouldn't be pretty printed yet.");
+
+ const finished = waitForSourceShown(gPanel, "code_ugly-8");
+ gDebugger.document.getElementById("pretty-print").click();
+ const deck = gDebugger.document.getElementById("editor-deck");
+ is(deck.selectedIndex, 2, "The progress bar should be shown");
+ yield finished;
+
+ ok(gEditor.getText().includes("\n "),
+ "The source should be pretty printed.");
+ is(deck.selectedIndex, 0, "The editor should be shown");
+
+ const source = queries.getSelectedSource(getState());
+ const { text } = queries.getSourceText(getState(), source.actor);
+ ok(text.includes("\n "),
+ "Subsequent calls to getText return the pretty printed source.");
+
+ resumeDebuggerThenCloseAndFinish(gPanel);
+ })
+ });
+}
diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_pretty-print-on-paused.js b/devtools/client/debugger/test/mochitest/browser_dbg_pretty-print-on-paused.js
new file mode 100644
index 000000000..12e3a20fc
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_pretty-print-on-paused.js
@@ -0,0 +1,69 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Test that pretty printing when the debugger is paused does not switch away
+ * from the selected source.
+ */
+
+const TAB_URL = EXAMPLE_URL + "doc_pretty-print-on-paused.html";
+
+var gTab, gPanel, gDebugger, gThreadClient, gSources;
+
+const SECOND_SOURCE_VALUE = EXAMPLE_URL + "code_ugly-2.js";
+
+function test() {
+ // Wait for debugger panel to be fully set and break on debugger statement
+ let options = {
+ source: EXAMPLE_URL + "code_script-switching-02.js",
+ line: 1
+ };
+ initDebugger(TAB_URL, options).then(([aTab,, aPanel]) => {
+ gTab = aTab;
+ gPanel = aPanel;
+ gDebugger = gPanel.panelWin;
+ gThreadClient = gDebugger.gThreadClient;
+ gSources = gDebugger.DebuggerView.Sources;
+
+ Task.spawn(function* () {
+ try {
+ yield doInterrupt(gPanel);
+
+ let source = gThreadClient.source(getSourceForm(gSources, SECOND_SOURCE_VALUE));
+ yield source.setBreakpoint({
+ line: 6
+ });
+ yield doResume(gPanel);
+
+ const bpHit = waitForCaretAndScopes(gPanel, 6);
+ callInTab(gTab, "secondCall");
+ yield bpHit;
+
+ info("Switch to the second source.");
+ const sourceShown = waitForSourceShown(gPanel, SECOND_SOURCE_VALUE);
+ gSources.selectedValue = getSourceActor(gSources, SECOND_SOURCE_VALUE);
+ yield sourceShown;
+
+ info("Pretty print the source.");
+ const prettyPrinted = waitForSourceShown(gPanel, SECOND_SOURCE_VALUE);
+ gDebugger.document.getElementById("pretty-print").click();
+ yield prettyPrinted;
+
+ yield resumeDebuggerThenCloseAndFinish(gPanel);
+ } catch (e) {
+ DevToolsUtils.reportException("browser_dbg_pretty-print-on-paused.js", e);
+ ok(false, "Got an error: " + DevToolsUtils.safeErrorString(e));
+ }
+ });
+ });
+}
+
+registerCleanupFunction(function () {
+ gTab = null;
+ gPanel = null;
+ gDebugger = null;
+ gThreadClient = null;
+ gSources = null;
+});
diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_progress-listener-bug.js b/devtools/client/debugger/test/mochitest/browser_dbg_progress-listener-bug.js
new file mode 100644
index 000000000..e5aac615a
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_progress-listener-bug.js
@@ -0,0 +1,89 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Tests that the debugger does show up even if a progress listener reads the
+ * WebProgress argument's DOMWindow property in onStateChange() (bug 771655).
+ */
+
+var gTab, gPanel, gDebugger;
+var gOldListener;
+
+const TAB_URL = EXAMPLE_URL + "doc_inline-script.html";
+
+function test() {
+ installListener();
+
+ let options = {
+ source: TAB_URL,
+ line: 1
+ };
+ initDebugger(TAB_URL, options).then(([aTab,, aPanel]) => {
+ gTab = aTab;
+ gPanel = aPanel;
+ gDebugger = gPanel.panelWin;
+
+ is(!!gDebugger.DebuggerController._startup, true,
+ "Controller should be initialized after starting the test.");
+
+ testPause();
+ });
+}
+
+function testPause() {
+ let onCaretUpdated = waitForCaretUpdated(gPanel, 16);
+ callInTab(gTab, "runDebuggerStatement");
+ onCaretUpdated.then(() => {
+ is(gDebugger.gThreadClient.state, "paused",
+ "The debugger statement was reached.");
+
+ resumeDebuggerThenCloseAndFinish(gPanel);
+ });
+}
+
+// This is taken almost verbatim from bug 771655.
+function installListener() {
+ if ("_testPL" in window) {
+ gOldListener = _testPL;
+
+ Cc["@mozilla.org/docloaderservice;1"]
+ .getService(Ci.nsIWebProgress)
+ .removeProgressListener(_testPL);
+ }
+
+ window._testPL = {
+ START_DOC: Ci.nsIWebProgressListener.STATE_START |
+ Ci.nsIWebProgressListener.STATE_IS_DOCUMENT,
+ onStateChange: function (wp, req, stateFlags, status) {
+ if ((stateFlags & this.START_DOC) === this.START_DOC) {
+ // This DOMWindow access triggers the unload event.
+ wp.DOMWindow;
+ }
+ },
+ QueryInterface: function (iid) {
+ if (iid.equals(Ci.nsISupportsWeakReference) ||
+ iid.equals(Ci.nsIWebProgressListener))
+ return this;
+ throw Cr.NS_ERROR_NO_INTERFACE;
+ }
+ };
+
+ Cc["@mozilla.org/docloaderservice;1"]
+ .getService(Ci.nsIWebProgress)
+ .addProgressListener(_testPL, Ci.nsIWebProgress.NOTIFY_STATE_REQUEST);
+}
+
+registerCleanupFunction(function () {
+ if (gOldListener) {
+ window._testPL = gOldListener;
+ } else {
+ delete window._testPL;
+ }
+
+ gTab = null;
+ gPanel = null;
+ gDebugger = null;
+ gOldListener = null;
+});
diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_promises-allocation-stack.js b/devtools/client/debugger/test/mochitest/browser_dbg_promises-allocation-stack.js
new file mode 100644
index 000000000..2d55c3a8d
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_promises-allocation-stack.js
@@ -0,0 +1,87 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Test that we can get a stack to a promise's allocation point.
+ */
+
+"use strict";
+
+const TAB_URL = EXAMPLE_URL + "doc_promise-get-allocation-stack.html";
+const { PromisesFront } = require("devtools/shared/fronts/promises");
+var events = require("sdk/event/core");
+
+function test() {
+ Task.spawn(function* () {
+ DebuggerServer.init();
+ DebuggerServer.addBrowserActors();
+
+ let options = {
+ source: TAB_URL,
+ line: 1
+ };
+ const [ tab,, panel ] = yield initDebugger(TAB_URL, options);
+
+ let client = new DebuggerClient(DebuggerServer.connectPipe());
+ yield connect(client);
+
+ let { tabs } = yield listTabs(client);
+ let targetTab = findTab(tabs, TAB_URL);
+ yield attachTab(client, targetTab);
+
+ yield testGetAllocationStack(client, targetTab, tab);
+
+ yield close(client);
+ yield closeDebuggerAndFinish(panel);
+ }).then(null, error => {
+ ok(false, "Got an error: " + error.message + "\n" + error.stack);
+ });
+}
+
+function* testGetAllocationStack(client, form, tab) {
+ let front = PromisesFront(client, form);
+
+ yield front.attach();
+ yield front.listPromises();
+
+ // Get the grip for promise p
+ let onNewPromise = new Promise(resolve => {
+ events.on(front, "new-promises", promises => {
+ for (let p of promises) {
+ if (p.preview.ownProperties.name &&
+ p.preview.ownProperties.name.value === "p") {
+ resolve(p);
+ }
+ }
+ });
+ });
+
+ callInTab(tab, "makePromises");
+
+ let grip = yield onNewPromise;
+ ok(grip, "Found our promise p");
+
+ let objectClient = new ObjectClient(client, grip);
+ ok(objectClient, "Got Object Client");
+
+ yield new Promise(resolve => {
+ objectClient.getPromiseAllocationStack(response => {
+ ok(response.allocationStack.length, "Got promise allocation stack.");
+
+ for (let stack of response.allocationStack) {
+ is(stack.source.url, TAB_URL, "Got correct source URL.");
+ is(stack.functionDisplayName, "makePromises",
+ "Got correct function display name.");
+ is(typeof stack.line, "number", "Expect stack line to be a number.");
+ is(typeof stack.column, "number",
+ "Expect stack column to be a number.");
+ }
+
+ resolve();
+ });
+ });
+
+ yield front.detach();
+}
diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_promises-chrome-allocation-stack.js b/devtools/client/debugger/test/mochitest/browser_dbg_promises-chrome-allocation-stack.js
new file mode 100644
index 000000000..48e9ab229
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_promises-chrome-allocation-stack.js
@@ -0,0 +1,100 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Test that we can get a stack to a promise's allocation point in the chrome
+ * process.
+ */
+
+"use strict";
+
+const SOURCE_URL = "browser_dbg_promises-chrome-allocation-stack.js";
+const PromisesFront = require("devtools/shared/fronts/promises");
+var events = require("sdk/event/core");
+
+const STACK_DATA = [
+ { functionDisplayName: "test/</<" },
+ { functionDisplayName: "testGetAllocationStack" },
+];
+
+function test() {
+ Task.spawn(function* () {
+ requestLongerTimeout(10);
+
+ DebuggerServer.init();
+ DebuggerServer.addBrowserActors();
+ DebuggerServer.allowChromeProcess = true;
+
+ let client = new DebuggerClient(DebuggerServer.connectPipe());
+ yield connect(client);
+ let chrome = yield client.getProcess();
+ let [, tabClient] = yield attachTab(client, chrome.form);
+ yield tabClient.attachThread();
+
+ yield testGetAllocationStack(client, chrome.form, () => {
+ let p = new Promise(() => {});
+ p.name = "p";
+ let q = p.then();
+ q.name = "q";
+ let r = p.then(null, () => {});
+ r.name = "r";
+ });
+
+ yield close(client);
+ finish();
+ }).then(null, error => {
+ ok(false, "Got an error: " + error.message + "\n" + error.stack);
+ });
+}
+
+function* testGetAllocationStack(client, form, makePromises) {
+ let front = PromisesFront(client, form);
+
+ yield front.attach();
+ yield front.listPromises();
+
+ // Get the grip for promise p
+ let onNewPromise = new Promise(resolve => {
+ events.on(front, "new-promises", promises => {
+ for (let p of promises) {
+ if (p.preview.ownProperties.name &&
+ p.preview.ownProperties.name.value === "p") {
+ resolve(p);
+ }
+ }
+ });
+ });
+
+ makePromises();
+
+ let grip = yield onNewPromise;
+ ok(grip, "Found our promise p");
+
+ let objectClient = new ObjectClient(client, grip);
+ ok(objectClient, "Got Object Client");
+
+ yield new Promise(resolve => {
+ objectClient.getPromiseAllocationStack(response => {
+ ok(response.allocationStack.length, "Got promise allocation stack.");
+
+ for (let i = 0; i < STACK_DATA.length; i++) {
+ let data = STACK_DATA[i];
+ let stack = response.allocationStack[i];
+
+ ok(stack.source.url.startsWith("chrome:"), "Got a chrome source URL");
+ ok(stack.source.url.endsWith(SOURCE_URL), "Got correct source URL.");
+ is(stack.functionDisplayName, data.functionDisplayName,
+ "Got correct function display name.");
+ is(typeof stack.line, "number", "Expect stack line to be a number.");
+ is(typeof stack.column, "number",
+ "Expect stack column to be a number.");
+ }
+
+ resolve();
+ });
+ });
+
+ yield front.detach();
+}
diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_promises-fulfillment-stack.js b/devtools/client/debugger/test/mochitest/browser_dbg_promises-fulfillment-stack.js
new file mode 100644
index 000000000..a5f592eb6
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_promises-fulfillment-stack.js
@@ -0,0 +1,106 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Test that we can get a stack to a promise's fulfillment point.
+ */
+
+"use strict";
+
+const TAB_URL = EXAMPLE_URL + "doc_promise-get-fulfillment-stack.html";
+const { PromisesFront } = require("devtools/shared/fronts/promises");
+var events = require("sdk/event/core");
+
+const TEST_DATA = [
+ {
+ functionDisplayName: "returnPromise/<",
+ line: 19,
+ column: 37
+ },
+ {
+ functionDisplayName: "returnPromise",
+ line: 19,
+ column: 14
+ },
+ {
+ functionDisplayName: "makePromise",
+ line: 14,
+ column: 15
+ },
+];
+
+function test() {
+ Task.spawn(function* () {
+ DebuggerServer.init();
+ DebuggerServer.addBrowserActors();
+
+ let options = {
+ source: TAB_URL,
+ line: 1
+ };
+ const [ tab,, panel ] = yield initDebugger(TAB_URL, options);
+
+ let client = new DebuggerClient(DebuggerServer.connectPipe());
+ yield connect(client);
+
+ let { tabs } = yield listTabs(client);
+ let targetTab = findTab(tabs, TAB_URL);
+ yield attachTab(client, targetTab);
+
+ yield testGetFulfillmentStack(client, targetTab, tab);
+
+ yield close(client);
+ yield closeDebuggerAndFinish(panel);
+ }).then(null, error => {
+ ok(false, "Got an error: " + error.message + "\n" + error.stack);
+ });
+}
+
+function* testGetFulfillmentStack(client, form, tab) {
+ let front = PromisesFront(client, form);
+
+ yield front.attach();
+ yield front.listPromises();
+
+ // Get the grip for promise p
+ let onNewPromise = new Promise(resolve => {
+ events.on(front, "new-promises", promises => {
+ for (let p of promises) {
+ if (p.preview.ownProperties.name &&
+ p.preview.ownProperties.name.value === "p") {
+ resolve(p);
+ }
+ }
+ });
+ });
+
+ callInTab(tab, "makePromise");
+
+ let grip = yield onNewPromise;
+ ok(grip, "Found our promise p");
+
+ let objectClient = new ObjectClient(client, grip);
+ ok(objectClient, "Got Object Client");
+
+ yield new Promise(resolve => {
+ objectClient.getPromiseFulfillmentStack(response => {
+ ok(response.fulfillmentStack.length, "Got promise allocation stack.");
+
+ for (let i = 0; i < TEST_DATA.length; i++) {
+ let stack = response.fulfillmentStack[i];
+ let data = TEST_DATA[i];
+ is(stack.source.url, TAB_URL, "Got correct source URL.");
+ is(stack.functionDisplayName, data.functionDisplayName,
+ "Got correct function display name.");
+ is(stack.line, data.line, "Got correct stack line number.");
+ is(stack.column, data.column, "Got correct stack column number.");
+ }
+
+ resolve();
+ });
+ });
+
+ yield front.detach();
+}
diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_promises-rejection-stack.js b/devtools/client/debugger/test/mochitest/browser_dbg_promises-rejection-stack.js
new file mode 100644
index 000000000..9434024e3
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_promises-rejection-stack.js
@@ -0,0 +1,106 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Test that we can get a stack to a promise's rejection point.
+ */
+
+"use strict";
+
+const TAB_URL = EXAMPLE_URL + "doc_promise-get-rejection-stack.html";
+const { PromisesFront } = require("devtools/shared/fronts/promises");
+var events = require("sdk/event/core");
+
+const TEST_DATA = [
+ {
+ functionDisplayName: "returnPromise/<",
+ line: 19,
+ column: 47
+ },
+ {
+ functionDisplayName: "returnPromise",
+ line: 19,
+ column: 14
+ },
+ {
+ functionDisplayName: "makePromise",
+ line: 14,
+ column: 15
+ },
+];
+
+function test() {
+ Task.spawn(function* () {
+ DebuggerServer.init();
+ DebuggerServer.addBrowserActors();
+
+ let options = {
+ source: TAB_URL,
+ line: 1
+ };
+ const [ tab,, panel ] = yield initDebugger(TAB_URL, options);
+
+ let client = new DebuggerClient(DebuggerServer.connectPipe());
+ yield connect(client);
+
+ let { tabs } = yield listTabs(client);
+ let targetTab = findTab(tabs, TAB_URL);
+ yield attachTab(client, targetTab);
+
+ yield testGetRejectionStack(client, targetTab, tab);
+
+ yield close(client);
+ yield closeDebuggerAndFinish(panel);
+ }).then(null, error => {
+ ok(false, "Got an error: " + error.message + "\n" + error.stack);
+ });
+}
+
+function* testGetRejectionStack(client, form, tab) {
+ let front = PromisesFront(client, form);
+
+ yield front.attach();
+ yield front.listPromises();
+
+ // Get the grip for promise p
+ let onNewPromise = new Promise(resolve => {
+ events.on(front, "new-promises", promises => {
+ for (let p of promises) {
+ if (p.preview.ownProperties.name &&
+ p.preview.ownProperties.name.value === "p") {
+ resolve(p);
+ }
+ }
+ });
+ });
+
+ callInTab(tab, "makePromise");
+
+ let grip = yield onNewPromise;
+ ok(grip, "Found our promise p");
+
+ let objectClient = new ObjectClient(client, grip);
+ ok(objectClient, "Got Object Client");
+
+ yield new Promise(resolve => {
+ objectClient.getPromiseRejectionStack(response => {
+ ok(response.rejectionStack.length, "Got promise allocation stack.");
+
+ for (let i = 0; i < TEST_DATA.length; i++) {
+ let stack = response.rejectionStack[i];
+ let data = TEST_DATA[i];
+ is(stack.source.url, TAB_URL, "Got correct source URL.");
+ is(stack.functionDisplayName, data.functionDisplayName,
+ "Got correct function display name.");
+ is(stack.line, data.line, "Got correct stack line number.");
+ is(stack.column, data.column, "Got correct stack column number.");
+ }
+
+ resolve();
+ });
+ });
+
+ yield front.detach();
+}
diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_reload-preferred-script-02.js b/devtools/client/debugger/test/mochitest/browser_dbg_reload-preferred-script-02.js
new file mode 100644
index 000000000..75e7cfc1c
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_reload-preferred-script-02.js
@@ -0,0 +1,50 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Tests if the preferred source is shown when a page is loaded and
+ * the preferred source is specified after another source might have been shown.
+ */
+
+const TAB_URL = EXAMPLE_URL + "doc_script-switching-01.html";
+const PREFERRED_URL = EXAMPLE_URL + "code_script-switching-02.js";
+
+var gTab, gPanel, gDebugger;
+var gSources;
+
+function test() {
+ let options = {
+ source: EXAMPLE_URL + "code_script-switching-01.js",
+ line: 1
+ };
+ initDebugger(TAB_URL, options).then(([aTab,, aPanel]) => {
+ gTab = aTab;
+ gPanel = aPanel;
+ gDebugger = gPanel.panelWin;
+ gSources = gDebugger.DebuggerView.Sources;
+
+ waitForSourceShown(gPanel, PREFERRED_URL).then(finishTest);
+ gSources.preferredSource = getSourceActor(gSources, PREFERRED_URL);
+ });
+}
+
+function finishTest() {
+ info("Currently preferred source: " + gSources.preferredValue);
+ info("Currently selected source: " + gSources.selectedValue);
+
+ is(getSourceURL(gSources, gSources.preferredValue), PREFERRED_URL,
+ "The preferred source url wasn't set correctly.");
+ is(getSourceURL(gSources, gSources.selectedValue), PREFERRED_URL,
+ "The selected source isn't the correct one.");
+
+ closeDebuggerAndFinish(gPanel);
+}
+
+registerCleanupFunction(function () {
+ gTab = null;
+ gPanel = null;
+ gDebugger = null;
+ gSources = null;
+});
diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_reload-preferred-script-03.js b/devtools/client/debugger/test/mochitest/browser_dbg_reload-preferred-script-03.js
new file mode 100644
index 000000000..d286a5072
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_reload-preferred-script-03.js
@@ -0,0 +1,62 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Tests if the preferred source is shown when a page is loaded and
+ * the preferred source is specified after another source was definitely shown.
+ */
+
+const TAB_URL = EXAMPLE_URL + "doc_script-switching-01.html";
+const FIRST_URL = EXAMPLE_URL + "code_script-switching-01.js";
+const SECOND_URL = EXAMPLE_URL + "code_script-switching-02.js";
+
+var gTab, gPanel, gDebugger;
+var gSources;
+
+function test() {
+ let options = {
+ source: FIRST_URL,
+ line: 1
+ };
+ initDebugger(TAB_URL, options).then(([aTab,, aPanel]) => {
+ gTab = aTab;
+ gPanel = aPanel;
+ gDebugger = gPanel.panelWin;
+ gSources = gDebugger.DebuggerView.Sources;
+
+ testSource(undefined, FIRST_URL);
+ switchToSource(SECOND_URL)
+ .then(() => testSource(SECOND_URL))
+ .then(() => switchToSource(FIRST_URL))
+ .then(() => testSource(FIRST_URL))
+ .then(() => closeDebuggerAndFinish(gPanel))
+ .then(null, aError => {
+ ok(false, "Got an error: " + aError.message + "\n" + aError.stack);
+ });
+ });
+}
+
+function testSource(aPreferredUrl, aSelectedUrl = aPreferredUrl) {
+ info("Currently preferred source: " + gSources.preferredValue);
+ info("Currently selected source: " + gSources.selectedValue);
+
+ is(getSourceURL(gSources, gSources.preferredValue), aPreferredUrl,
+ "The preferred source url wasn't set correctly.");
+ is(getSourceURL(gSources, gSources.selectedValue), aSelectedUrl,
+ "The selected source isn't the correct one.");
+}
+
+function switchToSource(aUrl) {
+ let finished = waitForSourceShown(gPanel, aUrl);
+ gSources.preferredSource = getSourceActor(gSources, aUrl);
+ return finished;
+}
+
+registerCleanupFunction(function () {
+ gTab = null;
+ gPanel = null;
+ gDebugger = null;
+ gSources = null;
+});
diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_reload-same-script.js b/devtools/client/debugger/test/mochitest/browser_dbg_reload-same-script.js
new file mode 100644
index 000000000..754599418
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_reload-same-script.js
@@ -0,0 +1,88 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Tests if the same source is shown after a page is reloaded.
+ */
+
+const TAB_URL = EXAMPLE_URL + "doc_script-switching-01.html";
+const FIRST_URL = EXAMPLE_URL + "code_script-switching-01.js";
+const SECOND_URL = EXAMPLE_URL + "code_script-switching-02.js";
+
+function test() {
+ // Debug test slaves are a bit slow at this test.
+ requestLongerTimeout(2);
+
+ let options = {
+ source: FIRST_URL,
+ line: 1
+ };
+ initDebugger(TAB_URL, options).then(([aTab,, aPanel]) => {
+ const gTab = aTab;
+ const gPanel = aPanel;
+ const gDebugger = aPanel.panelWin;
+ const gTarget = gDebugger.gTarget;
+ const gSources = gDebugger.DebuggerView.Sources;
+ const queries = gDebugger.require("./content/queries");
+ const actions = bindActionCreators(gPanel);
+ const getState = gDebugger.DebuggerController.getState;
+ let gStep = 0;
+
+ function reloadPage() {
+ const navigated = waitForNavigation(gPanel);
+ reload(gPanel);
+ return navigated;
+ }
+
+ function switchAndReload(aUrl) {
+ actions.selectSource(getSourceForm(gSources, aUrl));
+ return reloadPage();
+ }
+
+ function testCurrentSource(aUrl, aExpectedUrl = aUrl) {
+ const prefSource = getSourceURL(gSources, gSources.preferredValue);
+ const selSource = getSourceURL(gSources, gSources.selectedValue);
+
+ info("Currently preferred source: '" + prefSource + "'.");
+ info("Currently selected source: '" + selSource + "'.");
+
+ is(prefSource, aExpectedUrl,
+ "The preferred source url wasn't set correctly (" + gStep + ").");
+ is(selSource, aUrl,
+ "The selected source isn't the correct one (" + gStep + ").");
+ }
+
+ function performTest() {
+ switch (gStep++) {
+ case 0:
+ testCurrentSource(FIRST_URL, null);
+ reloadPage().then(performTest);
+ break;
+ case 1:
+ testCurrentSource(FIRST_URL);
+ reloadPage().then(performTest);
+ break;
+ case 2:
+ testCurrentSource(FIRST_URL);
+ switchAndReload(SECOND_URL).then(performTest);
+ break;
+ case 3:
+ testCurrentSource(SECOND_URL);
+ reloadPage().then(performTest);
+ break;
+ case 4:
+ testCurrentSource(SECOND_URL);
+ reloadPage().then(performTest);
+ break;
+ case 5:
+ testCurrentSource(SECOND_URL);
+ closeDebuggerAndFinish(gPanel);
+ break;
+ }
+ }
+
+ performTest();
+ });
+}
diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_scripts-switching-01.js b/devtools/client/debugger/test/mochitest/browser_dbg_scripts-switching-01.js
new file mode 100644
index 000000000..b213040c0
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_scripts-switching-01.js
@@ -0,0 +1,162 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Make sure that switching the displayed source in the UI works as advertised.
+ */
+
+const TAB_URL = EXAMPLE_URL + "doc_script-switching-01.html";
+
+function test() {
+ let options = {
+ source: EXAMPLE_URL + "code_script-switching-01.js",
+ line: 1
+ };
+ initDebugger(TAB_URL, options).then(([aTab,, aPanel]) => {
+ const gTab = aTab;
+ const gPanel = aPanel;
+ const gDebugger = gPanel.panelWin;
+ const gEditor = gDebugger.DebuggerView.editor;
+ const gSources = gDebugger.DebuggerView.Sources;
+
+ const gLabel1 = "code_script-switching-01.js";
+ const gLabel2 = "code_script-switching-02.js";
+
+ function testSourcesDisplay() {
+ let deferred = promise.defer();
+
+ is(gSources.itemCount, 2,
+ "Found the expected number of sources. (1)");
+
+ is(gSources.items[0].target.querySelector(".dbg-source-item").getAttribute("tooltiptext"),
+ EXAMPLE_URL + "code_script-switching-01.js",
+ "The correct tooltip text is displayed for the first source. (1)");
+ is(gSources.items[1].target.querySelector(".dbg-source-item").getAttribute("tooltiptext"),
+ EXAMPLE_URL + "code_script-switching-02.js",
+ "The correct tooltip text is displayed for the second source. (1)");
+
+ ok(getSourceActor(gSources, EXAMPLE_URL + gLabel1),
+ "First source url is incorrect. (1)");
+ ok(getSourceActor(gSources, EXAMPLE_URL + gLabel2),
+ "Second source url is incorrect. (1)");
+
+ ok(gSources.getItemForAttachment(e => e.label == gLabel1),
+ "First source label is incorrect. (1)");
+ ok(gSources.getItemForAttachment(e => e.label == gLabel2),
+ "Second source label is incorrect. (1)");
+
+ ok(gSources.selectedItem,
+ "There should be a selected item in the sources pane. (1)");
+ is(getSelectedSourceURL(gSources), EXAMPLE_URL + gLabel2,
+ "The selected value is the sources pane is incorrect. (1)");
+
+ is(gEditor.getText().search(/firstCall/), -1,
+ "The first source is not displayed. (1)");
+ is(gEditor.getText().search(/debugger/), 166,
+ "The second source is displayed. (1)");
+
+ ok(isCaretPos(gPanel, 6),
+ "Editor caret location is correct. (1)");
+
+ // The editor's debug location takes a tick to update.
+ is(gEditor.getDebugLocation(), 5,
+ "Editor debugger location is correct. (1)");
+ ok(gEditor.hasLineClass(5, "debug-line"),
+ "The debugged line is highlighted appropriately (1).");
+
+ waitForDebuggerEvents(gPanel, gDebugger.EVENTS.SOURCE_SHOWN).then(deferred.resolve);
+ gSources.selectedIndex = 0;
+
+ return deferred.promise;
+ }
+
+ function testSwitchPaused1() {
+ let deferred = promise.defer();
+
+ ok(gSources.selectedItem,
+ "There should be a selected item in the sources pane. (2)");
+ is(getSelectedSourceURL(gSources), EXAMPLE_URL + gLabel1,
+ "The selected value is the sources pane is incorrect. (2)");
+
+ is(gEditor.getText().search(/firstCall/), 118,
+ "The first source is displayed. (2)");
+ is(gEditor.getText().search(/debugger/), -1,
+ "The second source is not displayed. (2)");
+
+ // The editor's debug location takes a tick to update.
+ ok(isCaretPos(gPanel, 1),
+ "Editor caret location is correct. (2)");
+ is(gEditor.getDebugLocation(), null,
+ "Editor debugger location is correct. (2)");
+ ok(!gEditor.hasLineClass(5, "debug-line"),
+ "The debugged line highlight was removed. (2)");
+
+ waitForDebuggerEvents(gPanel, gDebugger.EVENTS.SOURCE_SHOWN).then(deferred.resolve);
+ gSources.selectedIndex = 1;
+ return deferred.promise;
+ }
+
+ function testSwitchPaused2() {
+ let deferred = promise.defer();
+
+ ok(gSources.selectedItem,
+ "There should be a selected item in the sources pane. (3)");
+ is(getSelectedSourceURL(gSources), EXAMPLE_URL + gLabel2,
+ "The selected value is the sources pane is incorrect. (3)");
+
+ is(gEditor.getText().search(/firstCall/), -1,
+ "The first source is not displayed. (3)");
+ is(gEditor.getText().search(/debugger/), 166,
+ "The second source is displayed. (3)");
+
+ ok(isCaretPos(gPanel, 6),
+ "Editor caret location is correct. (3)");
+ is(gEditor.getDebugLocation(), 5,
+ "Editor debugger location is correct. (3)");
+ ok(gEditor.hasLineClass(5, "debug-line"),
+ "The debugged line is highlighted appropriately (3).");
+
+ // Step out twice.
+ waitForThreadEvents(gPanel, "paused").then(() => {
+ waitForDebuggerEvents(gPanel, gDebugger.EVENTS.SOURCE_SHOWN).then(deferred.resolve);
+ gDebugger.gThreadClient.stepOut();
+ });
+ gDebugger.gThreadClient.stepOut();
+
+ return deferred.promise;
+ }
+
+ function testSwitchRunning() {
+ ok(gSources.selectedItem,
+ "There should be a selected item in the sources pane. (4)");
+ is(getSelectedSourceURL(gSources), EXAMPLE_URL + gLabel1,
+ "The selected value is the sources pane is incorrect. (4)");
+
+ is(gEditor.getText().search(/firstCall/), 118,
+ "The first source is displayed. (4)");
+ is(gEditor.getText().search(/debugger/), -1,
+ "The second source is not displayed. (4)");
+
+ ok(isCaretPos(gPanel, 6),
+ "Editor caret location is correct. (4)");
+ is(gEditor.getDebugLocation(), 5,
+ "Editor debugger location is correct. (4)");
+ ok(gEditor.hasLineClass(5, "debug-line"),
+ "The debugged line is highlighted appropriately (3). (4)");
+ }
+
+ Task.spawn(function* () {
+ const shown = waitForSourceAndCaretAndScopes(gPanel, "-02.js", 1);
+ callInTab(gTab, "firstCall");
+ yield shown;
+
+ yield testSourcesDisplay();
+ yield testSwitchPaused1();
+ yield testSwitchPaused2();
+ yield testSwitchRunning();
+ resumeDebuggerThenCloseAndFinish(gPanel);
+ });
+ });
+}
diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_scripts-switching-02.js b/devtools/client/debugger/test/mochitest/browser_dbg_scripts-switching-02.js
new file mode 100644
index 000000000..0d524db2c
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_scripts-switching-02.js
@@ -0,0 +1,163 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Make sure that switching the displayed source in the UI works as advertised.
+ */
+
+const TAB_URL = EXAMPLE_URL + "doc_script-switching-02.html";
+
+var gLabel1 = "code_script-switching-01.js";
+var gLabel2 = "code_script-switching-02.js";
+var gParams = "?foo=bar,baz|lol";
+
+function test() {
+ let options = {
+ source: EXAMPLE_URL + "code_script-switching-01.js",
+ line: 1
+ };
+ initDebugger(TAB_URL, options).then(([aTab,, aPanel]) => {
+ const gTab = aTab;
+ const gPanel = aPanel;
+ const gDebugger = gPanel.panelWin;
+ const gEditor = gDebugger.DebuggerView.editor;
+ const gSources = gDebugger.DebuggerView.Sources;
+
+ function testSourcesDisplay() {
+ let deferred = promise.defer();
+
+ is(gSources.itemCount, 2,
+ "Found the expected number of sources. (1)");
+
+ ok(getSourceActor(gSources, EXAMPLE_URL + gLabel1),
+ "First source url is incorrect. (1)");
+ ok(getSourceActor(gSources, EXAMPLE_URL + gLabel2 + gParams),
+ "Second source url is incorrect. (1)");
+
+ ok(gSources.getItemForAttachment(e => e.label == gLabel1),
+ "First source label is incorrect. (1)");
+ ok(gSources.getItemForAttachment(e => e.label == gLabel2),
+ "Second source label is incorrect. (1)");
+
+ ok(gSources.selectedItem,
+ "There should be a selected item in the sources pane. (1)");
+ is(getSelectedSourceURL(gSources), EXAMPLE_URL + gLabel2 + gParams,
+ "The selected value is the sources pane is incorrect. (1)");
+
+ is(gEditor.getText().search(/firstCall/), -1,
+ "The first source is not displayed. (1)");
+ is(gEditor.getText().search(/debugger/), 166,
+ "The second source is displayed. (1)");
+
+ ok(isCaretPos(gPanel, 6),
+ "Editor caret location is correct. (1)");
+ is(gEditor.getDebugLocation(), 5,
+ "Editor debugger location is correct. (1)");
+ ok(gEditor.hasLineClass(5, "debug-line"),
+ "The debugged line is highlighted appropriately. (1)");
+
+ waitForDebuggerEvents(gPanel, gDebugger.EVENTS.SOURCE_SHOWN).then(deferred.resolve);
+ gSources.selectedItem = e => e.attachment.label == gLabel1;
+
+ return deferred.promise;
+ }
+
+ function testSwitchPaused1() {
+ let deferred = promise.defer();
+
+ ok(gSources.selectedItem,
+ "There should be a selected item in the sources pane. (2)");
+ is(getSelectedSourceURL(gSources), EXAMPLE_URL + gLabel1,
+ "The selected value is the sources pane is incorrect. (2)");
+
+ is(gEditor.getText().search(/firstCall/), 118,
+ "The first source is displayed. (2)");
+ is(gEditor.getText().search(/debugger/), -1,
+ "The second source is not displayed. (2)");
+
+ // The editor's debug location takes a tick to update.
+ ok(isCaretPos(gPanel, 1),
+ "Editor caret location is correct. (2)");
+
+ is(gEditor.getDebugLocation(), null,
+ "Editor debugger location is correct. (2)");
+ ok(!gEditor.hasLineClass(5, "debug-line"),
+ "The debugged line highlight was removed. (2)");
+
+ waitForDebuggerEvents(gPanel, gDebugger.EVENTS.SOURCE_SHOWN).then(deferred.resolve);
+ gSources.selectedItem = e => e.attachment.label == gLabel2;
+
+ return deferred.promise;
+ }
+
+ function testSwitchPaused2() {
+ let deferred = promise.defer();
+
+ ok(gSources.selectedItem,
+ "There should be a selected item in the sources pane. (3)");
+ is(getSelectedSourceURL(gSources), EXAMPLE_URL + gLabel2 + gParams,
+ "The selected value is the sources pane is incorrect. (3)");
+
+ is(gEditor.getText().search(/firstCall/), -1,
+ "The first source is not displayed. (3)");
+ is(gEditor.getText().search(/debugger/), 166,
+ "The second source is displayed. (3)");
+
+ // The editor's debug location takes a tick to update.
+ ok(isCaretPos(gPanel, 6),
+ "Editor caret location is correct. (3)");
+ is(gEditor.getDebugLocation(), 5,
+ "Editor debugger location is correct. (3)");
+ ok(gEditor.hasLineClass(5, "debug-line"),
+ "The debugged line is highlighted appropriately. (3)");
+
+ // Step out three times.
+ waitForThreadEvents(gPanel, "paused").then(() => {
+ waitForDebuggerEvents(gPanel, gDebugger.EVENTS.SOURCE_SHOWN).then(deferred.resolve);
+ gDebugger.gThreadClient.stepOut();
+ });
+ gDebugger.gThreadClient.stepOut();
+
+ return deferred.promise;
+ }
+
+ function testSwitchRunning() {
+ let deferred = promise.defer();
+
+ ok(gSources.selectedItem,
+ "There should be a selected item in the sources pane. (4)");
+ is(getSelectedSourceURL(gSources), EXAMPLE_URL + gLabel1,
+ "The selected value is the sources pane is incorrect. (4)");
+
+ is(gEditor.getText().search(/firstCall/), 118,
+ "The first source is displayed. (4)");
+ is(gEditor.getText().search(/debugger/), -1,
+ "The second source is not displayed. (4)");
+
+ // The editor's debug location takes a tick to update.
+ ok(isCaretPos(gPanel, 6),
+ "Editor caret location is correct. (4)");
+ is(gEditor.getDebugLocation(), 5,
+ "Editor debugger location is correct. (4)");
+ ok(gEditor.hasLineClass(5, "debug-line"),
+ "The debugged line is highlighted appropriately. (4)");
+
+ deferred.resolve();
+
+ return deferred.promise;
+ }
+
+ Task.spawn(function* () {
+ yield waitForSourceAndCaretAndScopes(gPanel, "-02.js", 1);
+ yield testSourcesDisplay();
+ yield testSwitchPaused1();
+ yield testSwitchPaused2();
+ yield testSwitchRunning();
+ resumeDebuggerThenCloseAndFinish(gPanel);
+ });
+
+ callInTab(gTab, "firstCall");
+ });
+}
diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_scripts-switching-03.js b/devtools/client/debugger/test/mochitest/browser_dbg_scripts-switching-03.js
new file mode 100644
index 000000000..ab691b03c
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_scripts-switching-03.js
@@ -0,0 +1,63 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Make sure that the DebuggerView error loading source text is correct.
+ */
+
+const TAB_URL = EXAMPLE_URL + "doc_script-switching-01.html";
+
+function test() {
+ let options = {
+ source: EXAMPLE_URL + "code_script-switching-01.js",
+ line: 1
+ };
+ initDebugger(TAB_URL, options).then(([aTab,, aPanel]) => {
+ const gTab = aTab;
+ const gPanel = aPanel;
+ const gDebugger = gPanel.panelWin;
+ const gView = gDebugger.DebuggerView;
+ const gEditor = gDebugger.DebuggerView.editor;
+ const gL10N = gDebugger.L10N;
+ const require = gDebugger.require;
+ const actions = bindActionCreators(gPanel);
+ const constants = require("./content/constants");
+ const controller = gDebugger.DebuggerController;
+
+ function showBogusSource() {
+ const source = { actor: "fake.actor", url: "http://fake.url/" };
+ actions.newSource(source);
+
+ controller.dispatch({
+ type: constants.LOAD_SOURCE_TEXT,
+ source: source,
+ status: "start"
+ });
+
+ controller.dispatch({
+ type: constants.SELECT_SOURCE,
+ source: source
+ });
+
+ controller.dispatch({
+ type: constants.LOAD_SOURCE_TEXT,
+ source: source,
+ status: "error",
+ error: "bogus actor"
+ });
+ }
+
+ function testDebuggerLoadingError() {
+ ok(gEditor.getText().includes(gL10N.getFormatStr("errorLoadingText2", "")),
+ "The valid error loading message is displayed.");
+ }
+
+ Task.spawn(function* () {
+ showBogusSource();
+ testDebuggerLoadingError();
+ closeDebuggerAndFinish(gPanel);
+ });
+ });
+}
diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_search-autofill-identifier.js b/devtools/client/debugger/test/mochitest/browser_dbg_search-autofill-identifier.js
new file mode 100644
index 000000000..b86666ef5
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_search-autofill-identifier.js
@@ -0,0 +1,138 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Tests that Debugger Search uses the identifier under cursor if nothing is
+ * selected or manually passed and searching using certain operators.
+ */
+"use strict";
+
+function test() {
+ const TAB_URL = EXAMPLE_URL + "doc_function-search.html";
+
+ let options = {
+ source: EXAMPLE_URL + "code_function-search-01.js",
+ line: 1
+ };
+ initDebugger(TAB_URL, options).then(([aTab,, aPanel]) => {
+ let Debugger = aPanel.panelWin;
+ let Editor = Debugger.DebuggerView.editor;
+ let Filtering = Debugger.DebuggerView.Filtering;
+
+ function doSearch(aOperator) {
+ Editor.dropSelection();
+ Filtering._doSearch(aOperator);
+ }
+
+ info("Testing with cursor at the beginning of the file...");
+
+ doSearch();
+ is(Filtering._searchbox.value, "",
+ "The searchbox value should not be auto-filled when searching for files.");
+ is(Filtering._searchbox.selectionStart, Filtering._searchbox.selectionEnd,
+ "The searchbox contents should not be selected");
+ is(Editor.getSelection(), "",
+ "The selection in the editor should be empty.");
+
+ doSearch("!");
+ is(Filtering._searchbox.value, "!",
+ "The searchbox value should not be auto-filled when searching across all files.");
+ is(Filtering._searchbox.selectionStart, Filtering._searchbox.selectionEnd,
+ "The searchbox contents should not be selected");
+ is(Editor.getSelection(), "",
+ "The selection in the editor should be empty.");
+
+ doSearch("@");
+ is(Filtering._searchbox.value, "@",
+ "The searchbox value should not be auto-filled when searching for functions.");
+ is(Filtering._searchbox.selectionStart, Filtering._searchbox.selectionEnd,
+ "The searchbox contents should not be selected");
+ is(Editor.getSelection(), "",
+ "The selection in the editor should be empty.");
+
+ doSearch("#");
+ is(Filtering._searchbox.value, "#",
+ "The searchbox value should not be auto-filled when searching inside a file.");
+ is(Filtering._searchbox.selectionStart, Filtering._searchbox.selectionEnd,
+ "The searchbox contents should not be selected");
+ is(Editor.getSelection(), "",
+ "The selection in the editor should be empty.");
+
+ doSearch(":");
+ is(Filtering._searchbox.value, ":",
+ "The searchbox value should not be auto-filled when searching for a line.");
+ is(Filtering._searchbox.selectionStart, Filtering._searchbox.selectionEnd,
+ "The searchbox contents should not be selected");
+ is(Editor.getSelection(), "",
+ "The selection in the editor should be empty.");
+
+ doSearch("*");
+ is(Filtering._searchbox.value, "*",
+ "The searchbox value should not be auto-filled when searching for variables.");
+ is(Filtering._searchbox.selectionStart, Filtering._searchbox.selectionEnd,
+ "The searchbox contents should not be selected");
+ is(Editor.getSelection(), "",
+ "The selection in the editor should be empty.");
+
+ Editor.setCursor({ line: 7, ch: 0});
+ info("Testing with cursor at line 8 and char 1...");
+
+ doSearch();
+ is(Filtering._searchbox.value, "",
+ "The searchbox value should not be auto-filled when searching for files.");
+ is(Filtering._searchbox.selectionStart, Filtering._searchbox.selectionEnd,
+ "The searchbox contents should not be selected");
+ is(Editor.getSelection(), "",
+ "The selection in the editor should be empty.");
+
+ doSearch("!");
+ is(Filtering._searchbox.value, "!test",
+ "The searchbox value was incorrect when searching across all files.");
+ is(Filtering._searchbox.selectionStart, 1,
+ "The searchbox operator should not be selected");
+ is(Filtering._searchbox.selectionEnd, 5,
+ "The searchbox contents should be selected");
+ is(Editor.getSelection(), "",
+ "The selection in the editor should be empty.");
+
+ doSearch("@");
+ is(Filtering._searchbox.value, "@test",
+ "The searchbox value was incorrect when searching for functions.");
+ is(Filtering._searchbox.selectionStart, 1,
+ "The searchbox operator should not be selected");
+ is(Filtering._searchbox.selectionEnd, 5,
+ "The searchbox contents should be selected");
+ is(Editor.getSelection(), "",
+ "The selection in the editor should be empty.");
+
+ doSearch("#");
+ is(Filtering._searchbox.value, "#test",
+ "The searchbox value should be auto-filled when searching inside a file.");
+ is(Filtering._searchbox.selectionStart, 1,
+ "The searchbox operator should not be selected");
+ is(Filtering._searchbox.selectionEnd, 5,
+ "The searchbox contents should be selected");
+ is(Editor.getSelection(), "test",
+ "The selection in the editor should be 'test'.");
+
+ doSearch(":");
+ is(Filtering._searchbox.value, ":",
+ "The searchbox value should not be auto-filled when searching for a line.");
+ is(Filtering._searchbox.selectionStart, Filtering._searchbox.selectionEnd,
+ "The searchbox contents should not be selected");
+ is(Editor.getSelection(), "",
+ "The selection in the editor should be empty.");
+
+ doSearch("*");
+ is(Filtering._searchbox.value, "*",
+ "The searchbox value should not be auto-filled when searching for variables.");
+ is(Filtering._searchbox.selectionStart, Filtering._searchbox.selectionEnd,
+ "The searchbox contents should not be selected");
+ is(Editor.getSelection(), "",
+ "The selection in the editor should be empty.");
+
+ closeDebuggerAndFinish(aPanel);
+ });
+}
diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_search-basic-01.js b/devtools/client/debugger/test/mochitest/browser_dbg_search-basic-01.js
new file mode 100644
index 000000000..e2262d4e8
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_search-basic-01.js
@@ -0,0 +1,330 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Tests basic search functionality (find token and jump to line).
+ */
+
+const TAB_URL = EXAMPLE_URL + "doc_recursion-stack.html";
+
+var gTab, gPanel, gDebugger;
+var gEditor, gSources, gFiltering, gSearchBox;
+
+function test() {
+ let options = {
+ source: TAB_URL,
+ line: 1
+ };
+ initDebugger(TAB_URL, options).then(([aTab,, aPanel]) => {
+ gTab = aTab;
+ gPanel = aPanel;
+ gDebugger = gPanel.panelWin;
+ gEditor = gDebugger.DebuggerView.editor;
+ gSources = gDebugger.DebuggerView.Sources;
+ gFiltering = gDebugger.DebuggerView.Filtering;
+ gSearchBox = gDebugger.DebuggerView.Filtering._searchbox;
+
+ performTest();
+ });
+}
+
+function performTest() {
+ // Make sure that the search box becomes focused when pressing ctrl+f - Bug 1211038
+ gEditor.focus();
+ synthesizeKeyFromKeyTag(gDebugger.document.getElementById("tokenSearchKey"));
+ let focusedEl = Services.focus.focusedElement;
+ focusedEl = focusedEl.ownerDocument.getBindingParent(focusedEl) || focusedEl;
+ is(focusedEl, gDebugger.document.getElementById("searchbox"), "Searchbox is focused");
+
+ setText(gSearchBox, "#html");
+
+ EventUtils.synthesizeKey("VK_RETURN", { shiftKey: true }, gDebugger);
+ is(gFiltering.searchData.toSource(), '["#", ["", "html"]]',
+ "The searchbox data wasn't parsed correctly.");
+ ok(isCaretPos(gPanel, 35, 7),
+ "The editor didn't jump to the correct line.");
+
+ EventUtils.synthesizeKey("VK_RETURN", { shiftKey: true }, gDebugger);
+ is(gFiltering.searchData.toSource(), '["#", ["", "html"]]',
+ "The searchbox data wasn't parsed correctly.");
+ ok(isCaretPos(gPanel, 5, 6),
+ "The editor didn't jump to the correct line.");
+
+ EventUtils.synthesizeKey("VK_RETURN", { shiftKey: true }, gDebugger);
+ is(gFiltering.searchData.toSource(), '["#", ["", "html"]]',
+ "The searchbox data wasn't parsed correctly.");
+ ok(isCaretPos(gPanel, 3, 15),
+ "The editor didn't jump to the correct line.");
+
+ setText(gSearchBox, ":12");
+ is(gFiltering.searchData.toSource(), '[":", ["", 12]]',
+ "The searchbox data wasn't parsed correctly.");
+ ok(isCaretPos(gPanel, 12),
+ "The editor didn't jump to the correct line.");
+
+ EventUtils.synthesizeKey("g", { metaKey: true }, gDebugger);
+ is(gFiltering.searchData.toSource(), '[":", ["", 13]]',
+ "The searchbox data wasn't parsed correctly.");
+ ok(isCaretPos(gPanel, 13),
+ "The editor didn't jump to the correct line after Meta+G.");
+
+ EventUtils.synthesizeKey("n", { ctrlKey: true }, gDebugger);
+ is(gFiltering.searchData.toSource(), '[":", ["", 14]]',
+ "The searchbox data wasn't parsed correctly.");
+ ok(isCaretPos(gPanel, 14),
+ "The editor didn't jump to the correct line after Ctrl+N.");
+
+ EventUtils.synthesizeKey("G", { metaKey: true, shiftKey: true }, gDebugger);
+ is(gFiltering.searchData.toSource(), '[":", ["", 13]]',
+ "The searchbox data wasn't parsed correctly.");
+ ok(isCaretPos(gPanel, 13),
+ "The editor didn't jump to the correct line after Meta+Shift+G.");
+
+ EventUtils.synthesizeKey("p", { ctrlKey: true }, gDebugger);
+ is(gFiltering.searchData.toSource(), '[":", ["", 12]]',
+ "The searchbox data wasn't parsed correctly.");
+ ok(isCaretPos(gPanel, 12),
+ "The editor didn't jump to the correct line after Ctrl+P.");
+
+ for (let i = 0; i < 100; i++) {
+ EventUtils.sendKey("DOWN", gDebugger);
+ }
+ is(gFiltering.searchData.toSource(), '[":", ["", 36]]',
+ "The searchbox data wasn't parsed correctly.");
+ ok(isCaretPos(gPanel, 36),
+ "The editor didn't jump to the correct line after multiple DOWN keys.");
+
+ for (let i = 0; i < 100; i++) {
+ EventUtils.sendKey("UP", gDebugger);
+ }
+ is(gFiltering.searchData.toSource(), '[":", ["", 1]]',
+ "The searchbox data wasn't parsed correctly.");
+ ok(isCaretPos(gPanel, 1),
+ "The editor didn't jump to the correct line after multiple UP keys.");
+
+
+ let token = "debugger";
+ setText(gSearchBox, "#" + token);
+ is(gFiltering.searchData.toSource(), '["#", ["", "debugger"]]',
+ "The searchbox data wasn't parsed correctly.");
+ ok(isCaretPos(gPanel, 8, 12 + token.length),
+ "The editor didn't jump to the correct token (1).");
+
+ EventUtils.sendKey("DOWN", gDebugger);
+ is(gFiltering.searchData.toSource(), '["#", ["", "debugger"]]',
+ "The searchbox data wasn't parsed correctly.");
+ ok(isCaretPos(gPanel, 14, 9 + token.length),
+ "The editor didn't jump to the correct token (2).");
+
+ EventUtils.sendKey("DOWN", gDebugger);
+ is(gFiltering.searchData.toSource(), '["#", ["", "debugger"]]',
+ "The searchbox data wasn't parsed correctly.");
+ ok(isCaretPos(gPanel, 18, 15 + token.length),
+ "The editor didn't jump to the correct token (3).");
+
+ EventUtils.sendKey("RETURN", gDebugger);
+ is(gFiltering.searchData.toSource(), '["#", ["", "debugger"]]',
+ "The searchbox data wasn't parsed correctly.");
+ ok(isCaretPos(gPanel, 26, 11 + token.length),
+ "The editor didn't jump to the correct token (4).");
+
+ EventUtils.sendKey("RETURN", gDebugger);
+ is(gFiltering.searchData.toSource(), '["#", ["", "debugger"]]',
+ "The searchbox data wasn't parsed correctly.");
+ ok(isCaretPos(gPanel, 8, 12 + token.length),
+ "The editor didn't jump to the correct token (5).");
+
+ EventUtils.sendKey("UP", gDebugger);
+ is(gFiltering.searchData.toSource(), '["#", ["", "debugger"]]',
+ "The searchbox data wasn't parsed correctly.");
+ ok(isCaretPos(gPanel, 26, 11 + token.length),
+ "The editor didn't jump to the correct token (6).");
+
+ setText(gSearchBox, ":bogus#" + token + ";");
+ is(gFiltering.searchData.toSource(), '["#", [":bogus", "debugger;"]]',
+ "The searchbox data wasn't parsed correctly.");
+ ok(isCaretPos(gPanel, 14, 9 + token.length + 1),
+ "The editor didn't jump to the correct token (7).");
+
+ setText(gSearchBox, ":13#" + token + ";");
+ is(gFiltering.searchData.toSource(), '["#", [":13", "debugger;"]]',
+ "The searchbox data wasn't parsed correctly.");
+ ok(isCaretPos(gPanel, 14, 9 + token.length + 1),
+ "The editor didn't jump to the correct token (8).");
+
+ setText(gSearchBox, ":#" + token + ";");
+ is(gFiltering.searchData.toSource(), '["#", [":", "debugger;"]]',
+ "The searchbox data wasn't parsed correctly.");
+ ok(isCaretPos(gPanel, 14, 9 + token.length + 1),
+ "The editor didn't jump to the correct token (9).");
+
+ setText(gSearchBox, "::#" + token + ";");
+ is(gFiltering.searchData.toSource(), '["#", ["::", "debugger;"]]',
+ "The searchbox data wasn't parsed correctly.");
+ ok(isCaretPos(gPanel, 14, 9 + token.length + 1),
+ "The editor didn't jump to the correct token (10).");
+
+ setText(gSearchBox, ":::#" + token + ";");
+ is(gFiltering.searchData.toSource(), '["#", [":::", "debugger;"]]',
+ "The searchbox data wasn't parsed correctly.");
+ ok(isCaretPos(gPanel, 14, 9 + token.length + 1),
+ "The editor didn't jump to the correct token (11).");
+
+
+ setText(gSearchBox, "#" + token + ";" + ":bogus");
+ is(gFiltering.searchData.toSource(), '["#", ["", "debugger;:bogus"]]',
+ "The searchbox data wasn't parsed correctly.");
+ ok(isCaretPos(gPanel, 14, 9 + token.length + 1),
+ "The editor didn't jump to the correct token (12).");
+
+ setText(gSearchBox, "#" + token + ";" + ":13");
+ is(gFiltering.searchData.toSource(), '["#", ["", "debugger;:13"]]',
+ "The searchbox data wasn't parsed correctly.");
+ ok(isCaretPos(gPanel, 14, 9 + token.length + 1),
+ "The editor didn't jump to the correct token (13).");
+
+ setText(gSearchBox, "#" + token + ";" + ":");
+ is(gFiltering.searchData.toSource(), '["#", ["", "debugger;:"]]',
+ "The searchbox data wasn't parsed correctly.");
+ ok(isCaretPos(gPanel, 14, 9 + token.length + 1),
+ "The editor didn't jump to the correct token (14).");
+
+ setText(gSearchBox, "#" + token + ";" + "::");
+ is(gFiltering.searchData.toSource(), '["#", ["", "debugger;::"]]',
+ "The searchbox data wasn't parsed correctly.");
+ ok(isCaretPos(gPanel, 14, 9 + token.length + 1),
+ "The editor didn't jump to the correct token (15).");
+
+ setText(gSearchBox, "#" + token + ";" + ":::");
+ is(gFiltering.searchData.toSource(), '["#", ["", "debugger;:::"]]',
+ "The searchbox data wasn't parsed correctly.");
+ ok(isCaretPos(gPanel, 14, 9 + token.length + 1),
+ "The editor didn't jump to the correct token (16).");
+
+
+ setText(gSearchBox, ":i am not a number");
+ is(gFiltering.searchData.toSource(), '[":", ["", 0]]',
+ "The searchbox data wasn't parsed correctly.");
+ ok(isCaretPos(gPanel, 14, 9 + token.length + 1),
+ "The editor didn't remain at the correct token (17).");
+
+ setText(gSearchBox, "#__i do not exist__");
+ is(gFiltering.searchData.toSource(), '["#", ["", "__i do not exist__"]]',
+ "The searchbox data wasn't parsed correctly.");
+ ok(isCaretPos(gPanel, 14, 9 + token.length + 1),
+ "The editor didn't remain at the correct token (18).");
+
+
+ setText(gSearchBox, "#" + token);
+ is(gFiltering.searchData.toSource(), '["#", ["", "debugger"]]',
+ "The searchbox data wasn't parsed correctly.");
+ ok(isCaretPos(gPanel, 8, 12 + token.length),
+ "The editor didn't jump to the correct token (19).");
+
+
+ clearText(gSearchBox);
+ is(gFiltering.searchData.toSource(), '["", [""]]',
+ "The searchbox data wasn't parsed correctly.");
+
+ EventUtils.sendKey("RETURN", gDebugger);
+ is(gFiltering.searchData.toSource(), '["", [""]]',
+ "The searchbox data wasn't parsed correctly.");
+ ok(isCaretPos(gPanel, 8, 12 + token.length),
+ "The editor shouldn't jump to another token (20).");
+
+ EventUtils.sendKey("RETURN", gDebugger);
+ is(gFiltering.searchData.toSource(), '["", [""]]',
+ "The searchbox data wasn't parsed correctly.");
+ ok(isCaretPos(gPanel, 8, 12 + token.length),
+ "The editor shouldn't jump to another token (21).");
+
+
+ setText(gSearchBox, ":1:2:3:a:b:c:::12");
+ is(gFiltering.searchData.toSource(), '[":", [":1:2:3:a:b:c::", 12]]',
+ "The searchbox data wasn't parsed correctly.");
+ ok(isCaretPos(gPanel, 12),
+ "The editor didn't jump to the correct line (22).");
+
+ setText(gSearchBox, "#don't#find#me#instead#find#" + token);
+ is(gFiltering.searchData.toSource(), '["#", ["#don\'t#find#me#instead#find", "debugger"]]',
+ "The searchbox data wasn't parsed correctly.");
+ ok(isCaretPos(gPanel, 8, 12 + token.length),
+ "The editor didn't jump to the correct token (23).");
+
+ EventUtils.sendKey("DOWN", gDebugger);
+ is(gFiltering.searchData.toSource(), '["#", ["#don\'t#find#me#instead#find", "debugger"]]',
+ "The searchbox data wasn't parsed correctly.");
+ ok(isCaretPos(gPanel, 14, 9 + token.length),
+ "The editor didn't jump to the correct token (24).");
+
+ EventUtils.sendKey("DOWN", gDebugger);
+ is(gFiltering.searchData.toSource(), '["#", ["#don\'t#find#me#instead#find", "debugger"]]',
+ "The searchbox data wasn't parsed correctly.");
+ ok(isCaretPos(gPanel, 18, 15 + token.length),
+ "The editor didn't jump to the correct token (25).");
+
+ EventUtils.sendKey("RETURN", gDebugger);
+ is(gFiltering.searchData.toSource(), '["#", ["#don\'t#find#me#instead#find", "debugger"]]',
+ "The searchbox data wasn't parsed correctly.");
+ ok(isCaretPos(gPanel, 26, 11 + token.length),
+ "The editor didn't jump to the correct token (26).");
+
+ EventUtils.sendKey("RETURN", gDebugger);
+ is(gFiltering.searchData.toSource(), '["#", ["#don\'t#find#me#instead#find", "debugger"]]',
+ "The searchbox data wasn't parsed correctly.");
+ ok(isCaretPos(gPanel, 8, 12 + token.length),
+ "The editor didn't jump to the correct token (27).");
+
+ EventUtils.sendKey("UP", gDebugger);
+ is(gFiltering.searchData.toSource(), '["#", ["#don\'t#find#me#instead#find", "debugger"]]',
+ "The searchbox data wasn't parsed correctly.");
+ ok(isCaretPos(gPanel, 26, 11 + token.length),
+ "The editor didn't jump to the correct token (28).");
+
+
+ clearText(gSearchBox);
+ is(gFiltering.searchData.toSource(), '["", [""]]',
+ "The searchbox data wasn't parsed correctly.");
+ ok(isCaretPos(gPanel, 26, 11 + token.length),
+ "The editor didn't remain at the correct token (29).");
+ is(gSources.visibleItems.length, 1,
+ "Not all the sources are shown after the search (30).");
+
+
+ gEditor.focus();
+ gEditor.setSelection.apply(gEditor, gEditor.getPosition(1, 5));
+ ok(isCaretPos(gPanel, 1, 6),
+ "The editor caret position didn't update after selecting some text.");
+
+ EventUtils.synthesizeKey("F", { accelKey: true });
+ is(gFiltering.searchData.toSource(), '["#", ["", "!-- "]]',
+ "The searchbox data wasn't parsed correctly.");
+ is(gSearchBox.value, "#!-- ",
+ "The search field has the right initial value (1).");
+
+ gEditor.focus();
+ gEditor.setSelection.apply(gEditor, gEditor.getPosition(415, 418));
+ ok(isCaretPos(gPanel, 21, 30),
+ "The editor caret position didn't update after selecting some number.");
+
+ EventUtils.synthesizeKey("L", { accelKey: true });
+ is(gFiltering.searchData.toSource(), '[":", ["", 100]]',
+ "The searchbox data wasn't parsed correctly.");
+ is(gSearchBox.value, ":100",
+ "The search field has the right initial value (2).");
+
+
+ closeDebuggerAndFinish(gPanel);
+}
+
+registerCleanupFunction(function () {
+ gTab = null;
+ gPanel = null;
+ gDebugger = null;
+ gEditor = null;
+ gSources = null;
+ gFiltering = null;
+ gSearchBox = null;
+});
diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_search-basic-02.js b/devtools/client/debugger/test/mochitest/browser_dbg_search-basic-02.js
new file mode 100644
index 000000000..ef09e16da
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_search-basic-02.js
@@ -0,0 +1,129 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Tests basic file search functionality.
+ */
+
+const TAB_URL = EXAMPLE_URL + "doc_script-switching-01.html";
+
+var gTab, gPanel, gDebugger;
+var gSources, gSearchBox;
+
+function test() {
+ let options = {
+ source: EXAMPLE_URL + "code_script-switching-01.js",
+ line: 1,
+ };
+ initDebugger(TAB_URL, options).then(([aTab,, aPanel]) => {
+ gTab = aTab;
+ gPanel = aPanel;
+ gDebugger = gPanel.panelWin;
+ gSources = gDebugger.DebuggerView.Sources;
+ gSearchBox = gDebugger.DebuggerView.Filtering._searchbox;
+
+ // Calling `firstCall` is going to break into the other script
+ waitForSourceAndCaretAndScopes(gPanel, "-02.js", 6)
+ .then(performSimpleSearch)
+ .then(() => verifySourceAndCaret("-01.js", 1, 1, [1, 1]))
+ .then(combineWithLineSearch)
+ .then(() => verifySourceAndCaret("-01.js", 2, 1, [53, 53]))
+ .then(combineWithTokenSearch)
+ .then(() => verifySourceAndCaret("-01.js", 2, 48, [96, 100]))
+ .then(combineWithTokenColonSearch)
+ .then(() => verifySourceAndCaret("-01.js", 2, 11, [56, 63]))
+ .then(() => resumeDebuggerThenCloseAndFinish(gPanel))
+ .then(null, aError => {
+ ok(false, "Got an error: " + aError.message + "\n" + aError.stack);
+ });
+
+ callInTab(gTab, "firstCall");
+ });
+}
+
+function performSimpleSearch() {
+ let finished = promise.all([
+ ensureSourceIs(gPanel, "-02.js"),
+ ensureCaretAt(gPanel, 6),
+ waitForDebuggerEvents(gPanel, gDebugger.EVENTS.FILE_SEARCH_MATCH_FOUND),
+ waitForSourceShown(gPanel, "-01.js")
+ ]);
+
+ setText(gSearchBox, "1");
+
+ return finished.then(() => promise.all([
+ ensureSourceIs(gPanel, "-01.js"),
+ ensureCaretAt(gPanel, 1)
+ ]));
+}
+
+function combineWithLineSearch() {
+ let finished = promise.all([
+ ensureSourceIs(gPanel, "-01.js"),
+ ensureCaretAt(gPanel, 1),
+ waitForDebuggerEvents(gPanel, gDebugger.EVENTS.FILE_SEARCH_MATCH_FOUND),
+ waitForCaretUpdated(gPanel, 2)
+ ]);
+
+ typeText(gSearchBox, ":2");
+
+ return finished.then(() => promise.all([
+ ensureSourceIs(gPanel, "-01.js"),
+ ensureCaretAt(gPanel, 2)
+ ]));
+}
+
+function combineWithTokenSearch() {
+ let finished = promise.all([
+ ensureSourceIs(gPanel, "-01.js"),
+ ensureCaretAt(gPanel, 2),
+ waitForDebuggerEvents(gPanel, gDebugger.EVENTS.FILE_SEARCH_MATCH_FOUND),
+ waitForCaretUpdated(gPanel, 2, 48)
+ ]);
+
+ backspaceText(gSearchBox, 2);
+ typeText(gSearchBox, "#zero");
+
+ return finished.then(() => promise.all([
+ ensureSourceIs(gPanel, "-01.js"),
+ ensureCaretAt(gPanel, 2, 48)
+ ]));
+}
+
+function combineWithTokenColonSearch() {
+ let finished = promise.all([
+ ensureSourceIs(gPanel, "-01.js"),
+ ensureCaretAt(gPanel, 2, 48),
+ waitForDebuggerEvents(gPanel, gDebugger.EVENTS.FILE_SEARCH_MATCH_FOUND),
+ waitForCaretUpdated(gPanel, 2, 11)
+ ]);
+
+ backspaceText(gSearchBox, 4);
+ typeText(gSearchBox, "http://");
+
+ return finished.then(() => promise.all([
+ ensureSourceIs(gPanel, "-01.js"),
+ ensureCaretAt(gPanel, 2, 11)
+ ]));
+}
+
+function verifySourceAndCaret(aUrl, aLine, aColumn, aSelection) {
+ ok(gSources.selectedItem.attachment.label.includes(aUrl),
+ "The selected item's label appears to be correct.");
+ ok(gSources.selectedItem.attachment.source.url.includes(aUrl),
+ "The selected item's value appears to be correct.");
+ ok(isCaretPos(gPanel, aLine, aColumn),
+ "The current caret position appears to be correct.");
+ ok(isEditorSel(gPanel, aSelection),
+ "The current editor selection appears to be correct.");
+}
+
+registerCleanupFunction(function () {
+ gTab = null;
+ gPanel = null;
+ gDebugger = null;
+ gSources = null;
+ gSearchBox = null;
+});
diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_search-basic-03.js b/devtools/client/debugger/test/mochitest/browser_dbg_search-basic-03.js
new file mode 100644
index 000000000..0020776d6
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_search-basic-03.js
@@ -0,0 +1,123 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Tests that searches which cause a popup to be shown properly handle the
+ * ESCAPE key.
+ */
+
+const TAB_URL = EXAMPLE_URL + "doc_script-switching-01.html";
+
+var gTab, gPanel, gDebugger;
+var gSources, gSearchBox;
+
+function test() {
+ let options = {
+ source: EXAMPLE_URL + "code_script-switching-01.js",
+ line: 1,
+ };
+ initDebugger(TAB_URL, options).then(([aTab,, aPanel]) => {
+ gTab = aTab;
+ gPanel = aPanel;
+ gDebugger = gPanel.panelWin;
+ gSources = gDebugger.DebuggerView.Sources;
+ gSearchBox = gDebugger.DebuggerView.Filtering._searchbox;
+
+ // Calling `firstCall` is going to break into the other script
+ waitForSourceAndCaretAndScopes(gPanel, "-02.js", 6)
+ .then(performFileSearch)
+ .then(escapeAndHide)
+ .then(escapeAndClear)
+ .then(() => verifySourceAndCaret("-01.js", 1, 1))
+ .then(performFunctionSearch)
+ .then(escapeAndHide)
+ .then(escapeAndClear)
+ .then(() => verifySourceAndCaret("-01.js", 4, 10))
+ .then(performGlobalSearch)
+ .then(escapeAndClear)
+ .then(() => verifySourceAndCaret("-01.js", 4, 10))
+ .then(() => resumeDebuggerThenCloseAndFinish(gPanel))
+ .then(null, aError => {
+ ok(false, "Got an error: " + aError.message + "\n" + aError.stack);
+ });
+
+ callInTab(gTab, "firstCall");
+ });
+}
+
+function performFileSearch() {
+ let finished = promise.all([
+ ensureSourceIs(gPanel, "-02.js"),
+ ensureCaretAt(gPanel, 6),
+ once(gDebugger, "popupshown"),
+ waitForDebuggerEvents(gPanel, gDebugger.EVENTS.FILE_SEARCH_MATCH_FOUND),
+ waitForSourceShown(gPanel, "-01.js")
+ ]);
+
+ setText(gSearchBox, ".");
+
+ return finished.then(() => promise.all([
+ ensureSourceIs(gPanel, "-01.js"),
+ ensureCaretAt(gPanel, 1)
+ ]));
+}
+
+function performFunctionSearch() {
+ let finished = promise.all([
+ ensureSourceIs(gPanel, "-01.js"),
+ ensureCaretAt(gPanel, 1),
+ once(gDebugger, "popupshown"),
+ waitForDebuggerEvents(gPanel, gDebugger.EVENTS.FUNCTION_SEARCH_MATCH_FOUND)
+ ]);
+
+ setText(gSearchBox, "@");
+
+ return finished.then(() => promise.all([
+ ensureSourceIs(gPanel, "-01.js"),
+ ensureCaretAt(gPanel, 4, 10)
+ ]));
+}
+
+function performGlobalSearch() {
+ let finished = promise.all([
+ ensureSourceIs(gPanel, "-01.js"),
+ ensureCaretAt(gPanel, 4, 10),
+ waitForDebuggerEvents(gPanel, gDebugger.EVENTS.GLOBAL_SEARCH_MATCH_FOUND)
+ ]);
+
+ setText(gSearchBox, "!first");
+
+ return finished.then(() => promise.all([
+ ensureSourceIs(gPanel, "-01.js"),
+ ensureCaretAt(gPanel, 4, 10)
+ ]));
+}
+
+function escapeAndHide() {
+ let finished = once(gDebugger, "popuphidden", true);
+ EventUtils.sendKey("ESCAPE", gDebugger);
+ return finished;
+}
+
+function escapeAndClear() {
+ EventUtils.sendKey("ESCAPE", gDebugger);
+ is(gSearchBox.getAttribute("value"), "",
+ "The searchbox has properly emptied after pressing escape.");
+}
+
+function verifySourceAndCaret(aUrl, aLine, aColumn) {
+ ok(gSources.selectedItem.attachment.label.includes(aUrl),
+ "The selected item's label appears to be correct.");
+ ok(gSources.selectedItem.attachment.source.url.includes(aUrl),
+ "The selected item's value appears to be correct.");
+ ok(isCaretPos(gPanel, aLine, aColumn),
+ "The current caret position appears to be correct.");
+}
+
+registerCleanupFunction(function () {
+ gTab = null;
+ gPanel = null;
+ gDebugger = null;
+ gSources = null;
+ gSearchBox = null;
+});
diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_search-basic-04.js b/devtools/client/debugger/test/mochitest/browser_dbg_search-basic-04.js
new file mode 100644
index 000000000..4d708797d
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_search-basic-04.js
@@ -0,0 +1,132 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Tests that the selection is dropped for line and token searches, after
+ * pressing backspace enough times.
+ */
+
+const TAB_URL = EXAMPLE_URL + "doc_script-switching-01.html";
+
+var gTab, gPanel, gDebugger;
+var gEditor, gSources, gSearchBox;
+
+function test() {
+ let options = {
+ source: EXAMPLE_URL + "code_script-switching-01.js",
+ line: 1,
+ };
+ initDebugger(TAB_URL, options).then(([aTab,, aPanel]) => {
+ gTab = aTab;
+ gPanel = aPanel;
+ gDebugger = gPanel.panelWin;
+ gEditor = gDebugger.DebuggerView.editor;
+ gSources = gDebugger.DebuggerView.Sources;
+ gSearchBox = gDebugger.DebuggerView.Filtering._searchbox;
+
+ testLineSearch();
+ testTokenSearch();
+ closeDebuggerAndFinish(gPanel);
+ });
+}
+
+function testLineSearch() {
+ setText(gSearchBox, ":42");
+ ok(isCaretPos(gPanel, 7),
+ "The editor caret position appears to be correct (1.1).");
+ ok(isEditorSel(gPanel, [151, 151]),
+ "The editor selection appears to be correct (1.1).");
+ is(gEditor.getSelection(), "",
+ "The editor selected text appears to be correct (1.1).");
+
+ backspaceText(gSearchBox, 1);
+ ok(isCaretPos(gPanel, 4),
+ "The editor caret position appears to be correct (1.2).");
+ ok(isEditorSel(gPanel, [110, 110]),
+ "The editor selection appears to be correct (1.2).");
+ is(gEditor.getSelection(), "",
+ "The editor selected text appears to be correct (1.2).");
+
+ backspaceText(gSearchBox, 1);
+ ok(isCaretPos(gPanel, 4),
+ "The editor caret position appears to be correct (1.3).");
+ ok(isEditorSel(gPanel, [110, 110]),
+ "The editor selection appears to be correct (1.3).");
+ is(gEditor.getSelection(), "",
+ "The editor selected text appears to be correct (1.3).");
+
+ setText(gSearchBox, ":4");
+ ok(isCaretPos(gPanel, 4),
+ "The editor caret position appears to be correct (1.4).");
+ ok(isEditorSel(gPanel, [110, 110]),
+ "The editor selection appears to be correct (1.4).");
+ is(gEditor.getSelection(), "",
+ "The editor selected text appears to be correct (1.4).");
+
+ gSearchBox.select();
+ backspaceText(gSearchBox, 1);
+ ok(isCaretPos(gPanel, 4),
+ "The editor caret position appears to be correct (1.5).");
+ ok(isEditorSel(gPanel, [110, 110]),
+ "The editor selection appears to be correct (1.5).");
+ is(gEditor.getSelection(), "",
+ "The editor selected text appears to be correct (1.5).");
+ is(gSearchBox.value, "",
+ "The searchbox should have been cleared.");
+}
+
+function testTokenSearch() {
+ setText(gSearchBox, "#();");
+ ok(isCaretPos(gPanel, 5, 16),
+ "The editor caret position appears to be correct (2.1).");
+ ok(isEditorSel(gPanel, [145, 148]),
+ "The editor selection appears to be correct (2.1).");
+ is(gEditor.getSelection(), "();",
+ "The editor selected text appears to be correct (2.1).");
+
+ backspaceText(gSearchBox, 1);
+ ok(isCaretPos(gPanel, 4, 21),
+ "The editor caret position appears to be correct (2.2).");
+ ok(isEditorSel(gPanel, [128, 130]),
+ "The editor selection appears to be correct (2.2).");
+ is(gEditor.getSelection(), "()",
+ "The editor selected text appears to be correct (2.2).");
+
+ backspaceText(gSearchBox, 2);
+ ok(isCaretPos(gPanel, 4, 20),
+ "The editor caret position appears to be correct (2.3).");
+ ok(isEditorSel(gPanel, [129, 129]),
+ "The editor selection appears to be correct (2.3).");
+ is(gEditor.getSelection(), "",
+ "The editor selected text appears to be correct (2.3).");
+
+ setText(gSearchBox, "#;");
+ ok(isCaretPos(gPanel, 5, 16),
+ "The editor caret position appears to be correct (2.4).");
+ ok(isEditorSel(gPanel, [147, 148]),
+ "The editor selection appears to be correct (2.4).");
+ is(gEditor.getSelection(), ";",
+ "The editor selected text appears to be correct (2.4).");
+
+ gSearchBox.select();
+ backspaceText(gSearchBox, 1);
+ ok(isCaretPos(gPanel, 5, 16),
+ "The editor caret position appears to be correct (2.5).");
+ ok(isEditorSel(gPanel, [148, 148]),
+ "The editor selection appears to be correct (2.5).");
+ is(gEditor.getSelection(), "",
+ "The editor selected text appears to be correct (2.5).");
+ is(gSearchBox.value, "",
+ "The searchbox should have been cleared.");
+}
+
+registerCleanupFunction(function () {
+ gTab = null;
+ gPanel = null;
+ gDebugger = null;
+ gEditor = null;
+ gSources = null;
+ gSearchBox = null;
+});
diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_search-global-01.js b/devtools/client/debugger/test/mochitest/browser_dbg_search-global-01.js
new file mode 100644
index 000000000..c301dbbbc
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_search-global-01.js
@@ -0,0 +1,278 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Tests basic functionality of global search (lowercase + upper case, expected
+ * UI behavior, number of results found etc.)
+ */
+
+const TAB_URL = EXAMPLE_URL + "doc_script-switching-01.html";
+
+var gTab, gPanel, gDebugger;
+var gEditor, gSources, gSearchView, gSearchBox;
+
+function test() {
+ let options = {
+ source: EXAMPLE_URL + "code_script-switching-01.js",
+ line: 1
+ };
+ initDebugger(TAB_URL, options).then(([aTab,, aPanel]) => {
+ gTab = aTab;
+ gPanel = aPanel;
+ gDebugger = gPanel.panelWin;
+ gEditor = gDebugger.DebuggerView.editor;
+ gSources = gDebugger.DebuggerView.Sources;
+ gSearchView = gDebugger.DebuggerView.GlobalSearch;
+ gSearchBox = gDebugger.DebuggerView.Filtering._searchbox;
+
+ waitForSourceAndCaretAndScopes(gPanel, "-02.js", 1)
+ .then(firstSearch)
+ .then(secondSearch)
+ .then(clearSearch)
+ .then(() => resumeDebuggerThenCloseAndFinish(gPanel))
+ .then(null, aError => {
+ ok(false, "Got an error: " + aError.message + "\n" + aError.stack);
+ });
+
+ callInTab(gTab, "firstCall");
+ });
+}
+
+function firstSearch() {
+ let deferred = promise.defer();
+
+ is(gSearchView.itemCount, 0,
+ "The global search pane shouldn't have any entries yet.");
+ is(gSearchView.widget._parent.hidden, true,
+ "The global search pane shouldn't be visible yet.");
+ is(gSearchView._splitter.hidden, true,
+ "The global search pane splitter shouldn't be visible yet.");
+
+ gDebugger.once(gDebugger.EVENTS.GLOBAL_SEARCH_MATCH_FOUND, () => {
+ // Some operations are synchronously dispatched on the main thread,
+ // to avoid blocking UI, thus giving the impression of faster searching.
+ executeSoon(() => {
+ info("Current source url:\n" + getSelectedSourceURL(gSources));
+ info("Debugger editor text:\n" + gEditor.getText());
+
+ ok(isCaretPos(gPanel, 6),
+ "The editor shouldn't have jumped to a matching line yet.");
+ ok(getSelectedSourceURL(gSources).includes("-02.js"),
+ "The current source shouldn't have changed after a global search.");
+ is(gSources.visibleItems.length, 2,
+ "Not all the sources are shown after the global search.");
+
+ let sourceResults = gDebugger.document.querySelectorAll(".dbg-source-results");
+ is(sourceResults.length, 2,
+ "There should be matches found in two sources.");
+
+ let item0 = gDebugger.SourceResults.getItemForElement(sourceResults[0]);
+ let item1 = gDebugger.SourceResults.getItemForElement(sourceResults[1]);
+ is(item0.instance.expanded, true,
+ "The first source results should automatically be expanded.");
+ is(item1.instance.expanded, true,
+ "The second source results should automatically be expanded.");
+
+ let searchResult0 = sourceResults[0].querySelectorAll(".dbg-search-result");
+ let searchResult1 = sourceResults[1].querySelectorAll(".dbg-search-result");
+ is(searchResult0.length, 1,
+ "There should be one line result for the first url.");
+ is(searchResult1.length, 2,
+ "There should be two line results for the second url.");
+
+ let firstLine0 = searchResult0[0];
+ is(firstLine0.querySelector(".dbg-results-line-number").getAttribute("value"), "1",
+ "The first result for the first source doesn't have the correct line attached.");
+
+ is(firstLine0.querySelectorAll(".dbg-results-line-contents").length, 1,
+ "The first result for the first source doesn't have the correct number of nodes for a line.");
+ is(firstLine0.querySelectorAll(".dbg-results-line-contents-string").length, 3,
+ "The first result for the first source doesn't have the correct number of strings in a line.");
+
+ is(firstLine0.querySelectorAll(".dbg-results-line-contents-string[match=true]").length, 1,
+ "The first result for the first source doesn't have the correct number of matches in a line.");
+ is(firstLine0.querySelectorAll(".dbg-results-line-contents-string[match=true]")[0].getAttribute("value"), "de",
+ "The first result for the first source doesn't have the correct match in a line.");
+
+ is(firstLine0.querySelectorAll(".dbg-results-line-contents-string[match=false]").length, 2,
+ "The first result for the first source doesn't have the correct number of non-matches in a line.");
+ is(firstLine0.querySelectorAll(".dbg-results-line-contents-string[match=false]")[0].getAttribute("value"), "/* Any copyright is ",
+ "The first result for the first source doesn't have the correct non-matches in a line.");
+ is(firstLine0.querySelectorAll(".dbg-results-line-contents-string[match=false]")[1].getAttribute("value"), "dicated to the Public Domain.",
+ "The first result for the first source doesn't have the correct non-matches in a line.");
+
+ let firstLine1 = searchResult1[0];
+ is(firstLine1.querySelector(".dbg-results-line-number").getAttribute("value"), "1",
+ "The first result for the second source doesn't have the correct line attached.");
+
+ is(firstLine1.querySelectorAll(".dbg-results-line-contents").length, 1,
+ "The first result for the second source doesn't have the correct number of nodes for a line.");
+ is(firstLine1.querySelectorAll(".dbg-results-line-contents-string").length, 3,
+ "The first result for the second source doesn't have the correct number of strings in a line.");
+
+ is(firstLine1.querySelectorAll(".dbg-results-line-contents-string[match=true]").length, 1,
+ "The first result for the second source doesn't have the correct number of matches in a line.");
+ is(firstLine1.querySelectorAll(".dbg-results-line-contents-string[match=true]")[0].getAttribute("value"), "de",
+ "The first result for the second source doesn't have the correct match in a line.");
+
+ is(firstLine1.querySelectorAll(".dbg-results-line-contents-string[match=false]").length, 2,
+ "The first result for the second source doesn't have the correct number of non-matches in a line.");
+ is(firstLine1.querySelectorAll(".dbg-results-line-contents-string[match=false]")[0].getAttribute("value"), "/* Any copyright is ",
+ "The first result for the second source doesn't have the correct non-matches in a line.");
+ is(firstLine1.querySelectorAll(".dbg-results-line-contents-string[match=false]")[1].getAttribute("value"), "dicated to the Public Domain.",
+ "The first result for the second source doesn't have the correct non-matches in a line.");
+
+ let secondLine1 = searchResult1[1];
+ is(secondLine1.querySelector(".dbg-results-line-number").getAttribute("value"), "6",
+ "The second result for the second source doesn't have the correct line attached.");
+
+ is(secondLine1.querySelectorAll(".dbg-results-line-contents").length, 1,
+ "The second result for the second source doesn't have the correct number of nodes for a line.");
+ is(secondLine1.querySelectorAll(".dbg-results-line-contents-string").length, 3,
+ "The second result for the second source doesn't have the correct number of strings in a line.");
+
+ is(secondLine1.querySelectorAll(".dbg-results-line-contents-string[match=true]").length, 1,
+ "The second result for the second source doesn't have the correct number of matches in a line.");
+ is(secondLine1.querySelectorAll(".dbg-results-line-contents-string[match=true]")[0].getAttribute("value"), "de",
+ "The second result for the second source doesn't have the correct match in a line.");
+
+ is(secondLine1.querySelectorAll(".dbg-results-line-contents-string[match=false]").length, 2,
+ "The second result for the second source doesn't have the correct number of non-matches in a line.");
+ is(secondLine1.querySelectorAll(".dbg-results-line-contents-string[match=false]")[0].getAttribute("value"), " ",
+ "The second result for the second source doesn't have the correct non-matches in a line.");
+ is(secondLine1.querySelectorAll(".dbg-results-line-contents-string[match=false]")[1].getAttribute("value"), "bugger;",
+ "The second result for the second source doesn't have the correct non-matches in a line.");
+
+ deferred.resolve();
+ });
+ });
+
+ setText(gSearchBox, "!de");
+
+ return deferred.promise;
+}
+
+function secondSearch() {
+ let deferred = promise.defer();
+
+ is(gSearchView.itemCount, 2,
+ "The global search pane should have some child nodes from the previous search.");
+ is(gSearchView.widget._parent.hidden, false,
+ "The global search pane should be visible from the previous search.");
+ is(gSearchView._splitter.hidden, false,
+ "The global search pane splitter should be visible from the previous search.");
+
+ gDebugger.once(gDebugger.EVENTS.GLOBAL_SEARCH_MATCH_FOUND, () => {
+ // Some operations are synchronously dispatched on the main thread,
+ // to avoid blocking UI, thus giving the impression of faster searching.
+ executeSoon(() => {
+ info("Current source url:\n" + getSelectedSourceURL(gSources));
+ info("Debugger editor text:\n" + gEditor.getText());
+
+ ok(isCaretPos(gPanel, 6),
+ "The editor shouldn't have jumped to a matching line yet.");
+ ok(getSelectedSourceURL(gSources).includes("-02.js"),
+ "The current source shouldn't have changed after a global search.");
+ is(gSources.visibleItems.length, 2,
+ "Not all the sources are shown after the global search.");
+
+ let sourceResults = gDebugger.document.querySelectorAll(".dbg-source-results");
+ is(sourceResults.length, 2,
+ "There should be matches found in two sources.");
+
+ let item0 = gDebugger.SourceResults.getItemForElement(sourceResults[0]);
+ let item1 = gDebugger.SourceResults.getItemForElement(sourceResults[1]);
+ is(item0.instance.expanded, true,
+ "The first source results should automatically be expanded.");
+ is(item1.instance.expanded, true,
+ "The second source results should automatically be expanded.");
+
+ let searchResult0 = sourceResults[0].querySelectorAll(".dbg-search-result");
+ let searchResult1 = sourceResults[1].querySelectorAll(".dbg-search-result");
+ is(searchResult0.length, 1,
+ "There should be one line result for the first url.");
+ is(searchResult1.length, 1,
+ "There should be one line result for the second url.");
+
+ let firstLine0 = searchResult0[0];
+ is(firstLine0.querySelector(".dbg-results-line-number").getAttribute("value"), "1",
+ "The first result for the first source doesn't have the correct line attached.");
+
+ is(firstLine0.querySelectorAll(".dbg-results-line-contents").length, 1,
+ "The first result for the first source doesn't have the correct number of nodes for a line.");
+ is(firstLine0.querySelectorAll(".dbg-results-line-contents-string").length, 5,
+ "The first result for the first source doesn't have the correct number of strings in a line.");
+
+ is(firstLine0.querySelectorAll(".dbg-results-line-contents-string[match=true]").length, 2,
+ "The first result for the first source doesn't have the correct number of matches in a line.");
+ is(firstLine0.querySelectorAll(".dbg-results-line-contents-string[match=true]")[0].getAttribute("value"), "ed",
+ "The first result for the first source doesn't have the correct matches in a line.");
+ is(firstLine0.querySelectorAll(".dbg-results-line-contents-string[match=true]")[1].getAttribute("value"), "ed",
+ "The first result for the first source doesn't have the correct matches in a line.");
+
+ is(firstLine0.querySelectorAll(".dbg-results-line-contents-string[match=false]").length, 3,
+ "The first result for the first source doesn't have the correct number of non-matches in a line.");
+ is(firstLine0.querySelectorAll(".dbg-results-line-contents-string[match=false]")[0].getAttribute("value"), "/* Any copyright is d",
+ "The first result for the first source doesn't have the correct non-matches in a line.");
+ is(firstLine0.querySelectorAll(".dbg-results-line-contents-string[match=false]")[1].getAttribute("value"), "icat",
+ "The first result for the first source doesn't have the correct non-matches in a line.");
+ is(firstLine0.querySelectorAll(".dbg-results-line-contents-string[match=false]")[2].getAttribute("value"), " to the Public Domain.",
+ "The first result for the first source doesn't have the correct non-matches in a line.");
+
+ let firstLine1 = searchResult1[0];
+ is(firstLine1.querySelector(".dbg-results-line-number").getAttribute("value"), "1",
+ "The first result for the second source doesn't have the correct line attached.");
+
+ is(firstLine1.querySelectorAll(".dbg-results-line-contents").length, 1,
+ "The first result for the second source doesn't have the correct number of nodes for a line.");
+ is(firstLine1.querySelectorAll(".dbg-results-line-contents-string").length, 5,
+ "The first result for the second source doesn't have the correct number of strings in a line.");
+
+ is(firstLine1.querySelectorAll(".dbg-results-line-contents-string[match=true]").length, 2,
+ "The first result for the second source doesn't have the correct number of matches in a line.");
+ is(firstLine1.querySelectorAll(".dbg-results-line-contents-string[match=true]")[0].getAttribute("value"), "ed",
+ "The first result for the second source doesn't have the correct matches in a line.");
+ is(firstLine1.querySelectorAll(".dbg-results-line-contents-string[match=true]")[1].getAttribute("value"), "ed",
+ "The first result for the second source doesn't have the correct matches in a line.");
+
+ is(firstLine1.querySelectorAll(".dbg-results-line-contents-string[match=false]").length, 3,
+ "The first result for the second source doesn't have the correct number of non-matches in a line.");
+ is(firstLine1.querySelectorAll(".dbg-results-line-contents-string[match=false]")[0].getAttribute("value"), "/* Any copyright is d",
+ "The first result for the second source doesn't have the correct non-matches in a line.");
+ is(firstLine1.querySelectorAll(".dbg-results-line-contents-string[match=false]")[1].getAttribute("value"), "icat",
+ "The first result for the second source doesn't have the correct non-matches in a line.");
+ is(firstLine1.querySelectorAll(".dbg-results-line-contents-string[match=false]")[2].getAttribute("value"), " to the Public Domain.",
+ "The first result for the second source doesn't have the correct non-matches in a line.");
+
+ deferred.resolve();
+ });
+ });
+
+ backspaceText(gSearchBox, 2);
+ typeText(gSearchBox, "ED");
+
+ return deferred.promise;
+}
+
+function clearSearch() {
+ gSearchView.clearView();
+
+ is(gSearchView.itemCount, 0,
+ "The global search pane shouldn't have any child nodes after clearing.");
+ is(gSearchView.widget._parent.hidden, true,
+ "The global search pane shouldn't be visible after clearing.");
+ is(gSearchView._splitter.hidden, true,
+ "The global search pane splitter shouldn't be visible after clearing.");
+}
+
+registerCleanupFunction(function () {
+ gTab = null;
+ gPanel = null;
+ gDebugger = null;
+ gEditor = null;
+ gSources = null;
+ gSearchView = null;
+ gSearchBox = null;
+});
diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_search-global-02.js b/devtools/client/debugger/test/mochitest/browser_dbg_search-global-02.js
new file mode 100644
index 000000000..5713b3822
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_search-global-02.js
@@ -0,0 +1,203 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Tests if the global search results switch back and forth, and wrap around
+ * when switching between them.
+ */
+
+const TAB_URL = EXAMPLE_URL + "doc_script-switching-01.html";
+
+var gTab, gPanel, gDebugger;
+var gEditor, gSources, gSearchView, gSearchBox;
+
+function test() {
+ let options = {
+ source: EXAMPLE_URL + "code_script-switching-01.js",
+ line: 1
+ };
+ initDebugger(TAB_URL, options).then(([aTab,, aPanel]) => {
+ gTab = aTab;
+ gPanel = aPanel;
+ gDebugger = gPanel.panelWin;
+ gEditor = gDebugger.DebuggerView.editor;
+ gSources = gDebugger.DebuggerView.Sources;
+ gSearchView = gDebugger.DebuggerView.GlobalSearch;
+ gSearchBox = gDebugger.DebuggerView.Filtering._searchbox;
+
+ function firstSearch() {
+ let deferred = promise.defer();
+
+ is(gSearchView.itemCount, 0,
+ "The global search pane shouldn't have any entries yet.");
+ is(gSearchView.widget._parent.hidden, true,
+ "The global search pane shouldn't be visible yet.");
+ is(gSearchView._splitter.hidden, true,
+ "The global search pane splitter shouldn't be visible yet.");
+
+ gDebugger.once(gDebugger.EVENTS.GLOBAL_SEARCH_MATCH_FOUND, () => {
+ // Some operations are synchronously dispatched on the main thread,
+ // to avoid blocking UI, thus giving the impression of faster searching.
+ executeSoon(() => {
+ info("Current source url:\n" + getSelectedSourceURL(gSources));
+ info("Debugger editor text:\n" + gEditor.getText());
+
+ ok(isCaretPos(gPanel, 6),
+ "The editor shouldn't have jumped to a matching line yet.");
+ ok(getSelectedSourceURL(gSources).includes("-02.js"),
+ "The current source shouldn't have changed after a global search.");
+ is(gSources.visibleItems.length, 2,
+ "Not all the sources are shown after the global search.");
+
+ deferred.resolve();
+ });
+ });
+
+ setText(gSearchBox, "!function");
+
+ return deferred.promise;
+ }
+
+ function doFirstJump() {
+ let deferred = promise.defer();
+
+ waitForSourceAndCaret(gPanel, "-01.js", 4).then(() => {
+ info("Current source url:\n" + getSelectedSourceURL(gSources));
+ info("Debugger editor text:\n" + gEditor.getText());
+
+ ok(getSelectedSourceURL(gSources).includes("-01.js"),
+ "The currently shown source is incorrect (1).");
+ is(gSources.visibleItems.length, 2,
+ "Not all the sources are shown after the global search (1).");
+
+ // The editor's selected text takes a tick to update.
+ ok(isCaretPos(gPanel, 4, 9),
+ "The editor didn't jump to the correct line (1).");
+ is(gEditor.getSelection(), "function",
+ "The editor didn't select the correct text (1).");
+
+ deferred.resolve();
+ });
+
+ EventUtils.sendKey("DOWN", gDebugger);
+
+ return deferred.promise;
+ }
+
+ function doSecondJump() {
+ let deferred = promise.defer();
+
+ waitForSourceAndCaret(gPanel, "-02.js", 4).then(() => {
+ info("Current source url:\n" + getSelectedSourceURL(gSources));
+ info("Debugger editor text:\n" + gEditor.getText());
+
+ ok(getSelectedSourceURL(gSources).includes("-02.js"),
+ "The currently shown source is incorrect (2).");
+ is(gSources.visibleItems.length, 2,
+ "Not all the sources are shown after the global search (2).");
+
+ ok(isCaretPos(gPanel, 4, 9),
+ "The editor didn't jump to the correct line (2).");
+ is(gEditor.getSelection(), "function",
+ "The editor didn't select the correct text (2).");
+
+ deferred.resolve();
+ });
+
+ EventUtils.sendKey("DOWN", gDebugger);
+
+ return deferred.promise;
+ }
+
+ function doWrapAroundJump() {
+ let deferred = promise.defer();
+
+ waitForSourceAndCaret(gPanel, "-01.js", 4).then(() => {
+ info("Current source url:\n" + getSelectedSourceURL(gSources));
+ info("Debugger editor text:\n" + gEditor.getText());
+
+ ok(getSelectedSourceURL(gSources).includes("-01.js"),
+ "The currently shown source is incorrect (3).");
+ is(gSources.visibleItems.length, 2,
+ "Not all the sources are shown after the global search (3).");
+
+ // The editor's selected text takes a tick to update.
+ ok(isCaretPos(gPanel, 4, 9),
+ "The editor didn't jump to the correct line (3).");
+ is(gEditor.getSelection(), "function",
+ "The editor didn't select the correct text (3).");
+
+ deferred.resolve();
+ });
+
+ EventUtils.sendKey("DOWN", gDebugger);
+ EventUtils.sendKey("DOWN", gDebugger);
+
+ return deferred.promise;
+ }
+
+ function doBackwardsWrapAroundJump() {
+ let deferred = promise.defer();
+
+ waitForSourceAndCaret(gPanel, "-02.js", 7).then(() => {
+ info("Current source url:\n" + getSelectedSourceURL(gSources));
+ info("Debugger editor text:\n" + gEditor.getText());
+
+ ok(getSelectedSourceURL(gSources).includes("-02.js"),
+ "The currently shown source is incorrect (4).");
+ is(gSources.visibleItems.length, 2,
+ "Not all the sources are shown after the global search (4).");
+
+ // The editor's selected text takes a tick to update.
+ ok(isCaretPos(gPanel, 7, 11),
+ "The editor didn't jump to the correct line (4).");
+ is(gEditor.getSelection(), "function",
+ "The editor didn't select the correct text (4).");
+
+ deferred.resolve();
+ });
+
+ EventUtils.sendKey("UP", gDebugger);
+
+ return deferred.promise;
+ }
+
+ function testSearchTokenEmpty() {
+ backspaceText(gSearchBox, 4);
+
+ info("Current source url:\n" + getSelectedSourceURL(gSources));
+ info("Debugger editor text:\n" + gEditor.getText());
+
+ ok(getSelectedSourceURL(gSources).includes("-02.js"),
+ "The currently shown source is incorrect (4).");
+ is(gSources.visibleItems.length, 2,
+ "Not all the sources are shown after the global search (4).");
+ ok(isCaretPos(gPanel, 7, 11),
+ "The editor didn't remain at the correct line (4).");
+ is(gEditor.getSelection(), "",
+ "The editor shouldn't keep the previous text selected (4).");
+
+ is(gSearchView.itemCount, 0,
+ "The global search pane shouldn't have any child nodes after clearing.");
+ is(gSearchView.widget._parent.hidden, true,
+ "The global search pane shouldn't be visible after clearing.");
+ is(gSearchView._splitter.hidden, true,
+ "The global search pane splitter shouldn't be visible after clearing.");
+ }
+
+ Task.spawn(function* () {
+ yield waitForSourceAndCaretAndScopes(gPanel, "-02.js", 1);
+ yield firstSearch();
+ yield doFirstJump();
+ yield doSecondJump();
+ yield doWrapAroundJump();
+ yield doBackwardsWrapAroundJump();
+ yield testSearchTokenEmpty();
+ resumeDebuggerThenCloseAndFinish(gPanel);
+ });
+
+ callInTab(gTab, "firstCall");
+ });
+}
diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_search-global-03.js b/devtools/client/debugger/test/mochitest/browser_dbg_search-global-03.js
new file mode 100644
index 000000000..d36f42b59
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_search-global-03.js
@@ -0,0 +1,110 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Tests if the global search results are cleared on location changes, and
+ * the expected UI behaviors are triggered.
+ */
+
+const TAB_URL = EXAMPLE_URL + "doc_script-switching-01.html";
+
+var gTab, gPanel, gDebugger;
+var gEditor, gSources, gSearchView, gSearchBox;
+
+function test() {
+ let options = {
+ source: EXAMPLE_URL + "code_script-switching-01.js",
+ line: 1
+ };
+ initDebugger(TAB_URL, options).then(([aTab,, aPanel]) => {
+ gTab = aTab;
+ gPanel = aPanel;
+ gDebugger = gPanel.panelWin;
+ gEditor = gDebugger.DebuggerView.editor;
+ gSources = gDebugger.DebuggerView.Sources;
+ gSearchView = gDebugger.DebuggerView.GlobalSearch;
+ gSearchBox = gDebugger.DebuggerView.Filtering._searchbox;
+
+ waitForSourceAndCaretAndScopes(gPanel, "-02.js", 1)
+ .then(firstSearch)
+ .then(performTest)
+ .then(() => closeDebuggerAndFinish(gPanel))
+ .then(null, aError => {
+ ok(false, "Got an error: " + aError.message + "\n" + aError.stack);
+ });
+
+ callInTab(gTab, "firstCall");
+ });
+}
+
+function firstSearch() {
+ let deferred = promise.defer();
+
+ is(gSearchView.itemCount, 0,
+ "The global search pane shouldn't have any entries yet.");
+ is(gSearchView.widget._parent.hidden, true,
+ "The global search pane shouldn't be visible yet.");
+ is(gSearchView._splitter.hidden, true,
+ "The global search pane splitter shouldn't be visible yet.");
+
+ gDebugger.once(gDebugger.EVENTS.GLOBAL_SEARCH_MATCH_FOUND, () => {
+ // Some operations are synchronously dispatched on the main thread,
+ // to avoid blocking UI, thus giving the impression of faster searching.
+ executeSoon(() => {
+ info("Current source url:\n" + getSelectedSourceURL(gSources));
+ info("Debugger editor text:\n" + gEditor.getText());
+
+ ok(isCaretPos(gPanel, 6),
+ "The editor shouldn't have jumped to a matching line yet.");
+ ok(getSelectedSourceURL(gSources).includes("-02.js"),
+ "The current source shouldn't have changed after a global search.");
+ is(gSources.visibleItems.length, 2,
+ "Not all the sources are shown after the global search.");
+
+ deferred.resolve();
+ });
+ });
+
+ setText(gSearchBox, "!function");
+
+ return deferred.promise;
+}
+
+function performTest() {
+ let deferred = promise.defer();
+
+ is(gSearchView.itemCount, 2,
+ "The global search pane should have some entries from the previous search.");
+ is(gSearchView.widget._parent.hidden, false,
+ "The global search pane should be visible from the previous search.");
+ is(gSearchView._splitter.hidden, false,
+ "The global search pane splitter should be visible from the previous search.");
+
+ reloadActiveTab(gPanel, gDebugger.EVENTS.SOURCE_SHOWN).then(() => {
+ info("Current source url:\n" + getSelectedSourceURL(gSources));
+ info("Debugger editor text:\n" + gEditor.getText());
+
+ is(gSearchView.itemCount, 0,
+ "The global search pane shouldn't have any entries after a page navigation.");
+ is(gSearchView.widget._parent.hidden, true,
+ "The global search pane shouldn't be visible after a page navigation.");
+ is(gSearchView._splitter.hidden, true,
+ "The global search pane splitter shouldn't be visible after a page navigation.");
+
+ deferred.resolve();
+ });
+
+ return deferred.promise;
+}
+
+registerCleanupFunction(function () {
+ gTab = null;
+ gPanel = null;
+ gDebugger = null;
+ gEditor = null;
+ gSources = null;
+ gSearchView = null;
+ gSearchBox = null;
+});
diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_search-global-04.js b/devtools/client/debugger/test/mochitest/browser_dbg_search-global-04.js
new file mode 100644
index 000000000..3dde460e4
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_search-global-04.js
@@ -0,0 +1,98 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Tests if the global search results trigger MatchFound and NoMatchFound events
+ * properly, and triggers the expected UI behavior.
+ */
+
+const TAB_URL = EXAMPLE_URL + "doc_script-switching-01.html";
+
+var gTab, gPanel, gDebugger;
+var gEditor, gSources, gSearchView, gSearchBox;
+
+function test() {
+ let options = {
+ source: EXAMPLE_URL + "code_script-switching-01.js",
+ line: 1
+ };
+ initDebugger(TAB_URL, options).then(([aTab,, aPanel]) => {
+ gTab = aTab;
+ gPanel = aPanel;
+ gDebugger = gPanel.panelWin;
+ gEditor = gDebugger.DebuggerView.editor;
+ gSources = gDebugger.DebuggerView.Sources;
+ gSearchView = gDebugger.DebuggerView.GlobalSearch;
+ gSearchBox = gDebugger.DebuggerView.Filtering._searchbox;
+
+ waitForSourceAndCaretAndScopes(gPanel, "-02.js", 1)
+ .then(firstSearch)
+ .then(secondSearch)
+ .then(() => resumeDebuggerThenCloseAndFinish(gPanel))
+ .then(null, aError => {
+ ok(false, "Got an error: " + aError.message + "\n" + aError.stack);
+ });
+
+ callInTab(gTab, "firstCall");
+ });
+}
+
+function firstSearch() {
+ let deferred = promise.defer();
+
+ gDebugger.once(gDebugger.EVENTS.GLOBAL_SEARCH_MATCH_FOUND, () => {
+ // Some operations are synchronously dispatched on the main thread,
+ // to avoid blocking UI, thus giving the impression of faster searching.
+ executeSoon(() => {
+ info("Current source url:\n" + getSelectedSourceURL(gSources));
+ info("Debugger editor text:\n" + gEditor.getText());
+
+ ok(isCaretPos(gPanel, 6),
+ "The editor shouldn't have jumped to a matching line yet.");
+ ok(getSelectedSourceURL(gSources).includes("-02.js"),
+ "The current source shouldn't have changed after a global search.");
+ is(gSources.visibleItems.length, 2,
+ "Not all the sources are shown after the global search.");
+
+ deferred.resolve();
+ });
+ });
+
+ setText(gSearchBox, "!function");
+
+ return deferred.promise;
+}
+
+function secondSearch() {
+ let deferred = promise.defer();
+
+ gDebugger.once(gDebugger.EVENTS.GLOBAL_SEARCH_MATCH_NOT_FOUND, () => {
+ info("Current source url:\n" + getSelectedSourceURL(gSources));
+ info("Debugger editor text:\n" + gEditor.getText());
+
+ ok(isCaretPos(gPanel, 6),
+ "The editor shouldn't have jumped to a matching line yet.");
+ ok(getSelectedSourceURL(gSources).includes("-02.js"),
+ "The current source shouldn't have changed after a global search.");
+ is(gSources.visibleItems.length, 2,
+ "Not all the sources are shown after the global search.");
+
+ deferred.resolve();
+ });
+
+ typeText(gSearchBox, "/");
+
+ return deferred.promise;
+}
+
+registerCleanupFunction(function () {
+ gTab = null;
+ gPanel = null;
+ gDebugger = null;
+ gEditor = null;
+ gSources = null;
+ gSearchView = null;
+ gSearchBox = null;
+});
diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_search-global-05.js b/devtools/client/debugger/test/mochitest/browser_dbg_search-global-05.js
new file mode 100644
index 000000000..c441a88ac
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_search-global-05.js
@@ -0,0 +1,160 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Tests if the global search results are expanded/collapsed on click, and
+ * clicking matches makes the source editor shows the correct source and
+ * makes a selection based on the match.
+ */
+
+const TAB_URL = EXAMPLE_URL + "doc_script-switching-01.html";
+
+var gTab, gPanel, gDebugger;
+var gEditor, gSources, gSearchView, gSearchBox;
+
+function test() {
+ let options = {
+ source: EXAMPLE_URL + "code_script-switching-01.js",
+ line: 1
+ };
+ initDebugger(TAB_URL, options).then(([aTab,, aPanel]) => {
+ gTab = aTab;
+ gPanel = aPanel;
+ gDebugger = gPanel.panelWin;
+ gEditor = gDebugger.DebuggerView.editor;
+ gSources = gDebugger.DebuggerView.Sources;
+ gSearchView = gDebugger.DebuggerView.GlobalSearch;
+ gSearchBox = gDebugger.DebuggerView.Filtering._searchbox;
+
+ waitForSourceAndCaretAndScopes(gPanel, "-02.js", 1)
+ .then(doSearch)
+ .then(testExpandCollapse)
+ .then(testClickLineToJump)
+ .then(testClickMatchToJump)
+ .then(() => resumeDebuggerThenCloseAndFinish(gPanel))
+ .then(null, aError => {
+ ok(false, "Got an error: " + aError.message + "\n" + aError.stack);
+ });
+
+ callInTab(gTab, "firstCall");
+ });
+}
+
+function doSearch() {
+ let deferred = promise.defer();
+
+ gDebugger.once(gDebugger.EVENTS.GLOBAL_SEARCH_MATCH_FOUND, () => {
+ // Some operations are synchronously dispatched on the main thread,
+ // to avoid blocking UI, thus giving the impression of faster searching.
+ executeSoon(() => {
+ info("Current source url:\n" + getSelectedSourceURL(gSources));
+ info("Debugger editor text:\n" + gEditor.getText());
+
+ ok(isCaretPos(gPanel, 6),
+ "The editor shouldn't have jumped to a matching line yet. (1)");
+ ok(getSelectedSourceURL(gSources).includes("-02.js"),
+ "The current source shouldn't have changed after a global search. (2)");
+ is(gSources.visibleItems.length, 2,
+ "Not all the sources are shown after the global search. (3)");
+
+ deferred.resolve();
+ });
+ });
+
+ setText(gSearchBox, "!a");
+
+ return deferred.promise;
+}
+
+function testExpandCollapse() {
+ let sourceResults = gDebugger.document.querySelectorAll(".dbg-source-results");
+ let item0 = gDebugger.SourceResults.getItemForElement(sourceResults[0]);
+ let item1 = gDebugger.SourceResults.getItemForElement(sourceResults[1]);
+ let firstHeader = sourceResults[0].querySelector(".dbg-results-header");
+ let secondHeader = sourceResults[1].querySelector(".dbg-results-header");
+
+ EventUtils.sendMouseEvent({ type: "click" }, firstHeader);
+ EventUtils.sendMouseEvent({ type: "click" }, secondHeader);
+
+ is(item0.instance.expanded, false,
+ "The first source results should be collapsed on click. (2)");
+ is(item1.instance.expanded, false,
+ "The second source results should be collapsed on click. (2)");
+
+ EventUtils.sendMouseEvent({ type: "click" }, firstHeader);
+ EventUtils.sendMouseEvent({ type: "click" }, secondHeader);
+
+ is(item0.instance.expanded, true,
+ "The first source results should be expanded on an additional click. (3)");
+ is(item1.instance.expanded, true,
+ "The second source results should be expanded on an additional click. (3)");
+}
+
+function testClickLineToJump() {
+ let deferred = promise.defer();
+
+ let sourceResults = gDebugger.document.querySelectorAll(".dbg-source-results");
+ let firstHeader = sourceResults[0].querySelector(".dbg-results-header");
+ let firstLine = sourceResults[0].querySelector(".dbg-results-line-contents");
+
+ waitForSourceAndCaret(gPanel, "-01.js", 1, 1).then(() => {
+ info("Current source url:\n" + getSelectedSourceURL(gSources));
+ info("Debugger editor text:\n" + gEditor.getText());
+
+ ok(isCaretPos(gPanel, 1, 5),
+ "The editor didn't jump to the correct line (4).");
+ is(gEditor.getSelection(), "A",
+ "The editor didn't select the correct text (4).");
+ ok(getSelectedSourceURL(gSources).includes("-01.js"),
+ "The currently shown source is incorrect (4).");
+ is(gSources.visibleItems.length, 2,
+ "Not all the sources are shown after the global search (4).");
+
+ deferred.resolve();
+ });
+
+ EventUtils.sendMouseEvent({ type: "click" }, firstLine);
+
+ return deferred.promise;
+}
+
+function testClickMatchToJump() {
+ let deferred = promise.defer();
+
+ let sourceResults = gDebugger.document.querySelectorAll(".dbg-source-results");
+ let secondHeader = sourceResults[1].querySelector(".dbg-results-header");
+ let secondMatches = sourceResults[1].querySelectorAll(".dbg-results-line-contents-string[match=true]");
+ let lastMatch = Array.slice(secondMatches).pop();
+
+ waitForSourceAndCaret(gPanel, "-02.js", 13, 3).then(() => {
+ info("Current source url:\n" + getSelectedSourceURL(gSources));
+ info("Debugger editor text:\n" + gEditor.getText());
+
+ ok(isCaretPos(gPanel, 13, 3),
+ "The editor didn't jump to the correct line (5).");
+ is(gEditor.getSelection(), "a",
+ "The editor didn't select the correct text (5).");
+ ok(getSelectedSourceURL(gSources).includes("-02.js"),
+ "The currently shown source is incorrect (5).");
+ is(gSources.visibleItems.length, 2,
+ "Not all the sources are shown after the global search (5).");
+
+ deferred.resolve();
+ });
+
+ EventUtils.sendMouseEvent({ type: "click" }, lastMatch);
+
+ return deferred.promise;
+}
+
+registerCleanupFunction(function () {
+ gTab = null;
+ gPanel = null;
+ gDebugger = null;
+ gEditor = null;
+ gSources = null;
+ gSearchView = null;
+ gSearchBox = null;
+});
diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_search-global-06.js b/devtools/client/debugger/test/mochitest/browser_dbg_search-global-06.js
new file mode 100644
index 000000000..2de3ac558
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_search-global-06.js
@@ -0,0 +1,125 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Tests if the global search results are hidden when they're supposed to
+ * (after a focus lost, or when ESCAPE is pressed).
+ */
+
+const TAB_URL = EXAMPLE_URL + "doc_script-switching-01.html";
+
+var gTab, gPanel, gDebugger;
+var gEditor, gSources, gSearchView, gSearchBox;
+
+function test() {
+ let options = {
+ source: EXAMPLE_URL + "code_script-switching-01.js",
+ line: 1
+ };
+ initDebugger(TAB_URL, options).then(([aTab,, aPanel]) => {
+ gTab = aTab;
+ gPanel = aPanel;
+ gDebugger = gPanel.panelWin;
+ gEditor = gDebugger.DebuggerView.editor;
+ gSources = gDebugger.DebuggerView.Sources;
+ gSearchView = gDebugger.DebuggerView.GlobalSearch;
+ gSearchBox = gDebugger.DebuggerView.Filtering._searchbox;
+
+ waitForSourceAndCaretAndScopes(gPanel, "-02.js", 1)
+ .then(doSearch)
+ .then(testFocusLost)
+ .then(doSearch)
+ .then(testEscape)
+ .then(() => resumeDebuggerThenCloseAndFinish(gPanel))
+ .then(null, aError => {
+ ok(false, "Got an error: " + aError.message + "\n" + aError.stack);
+ });
+
+ callInTab(gTab, "firstCall");
+ });
+}
+
+function doSearch() {
+ let deferred = promise.defer();
+
+ is(gSearchView.itemCount, 0,
+ "The global search pane shouldn't have any entries yet.");
+ is(gSearchView.widget._parent.hidden, true,
+ "The global search pane shouldn't be visible yet.");
+ is(gSearchView._splitter.hidden, true,
+ "The global search pane splitter shouldn't be visible yet.");
+
+ gDebugger.once(gDebugger.EVENTS.GLOBAL_SEARCH_MATCH_FOUND, () => {
+ // Some operations are synchronously dispatched on the main thread,
+ // to avoid blocking UI, thus giving the impression of faster searching.
+ executeSoon(() => {
+ info("Current source url:\n" + getSelectedSourceURL(gSources));
+ info("Debugger editor text:\n" + gEditor.getText());
+
+ ok(isCaretPos(gPanel, 6),
+ "The editor shouldn't have jumped to a matching line yet.");
+ ok(getSelectedSourceURL(gSources).includes("-02.js"),
+ "The current source shouldn't have changed after a global search.");
+ is(gSources.visibleItems.length, 2,
+ "Not all the sources are shown after the global search.");
+
+ deferred.resolve();
+ });
+ });
+
+ setText(gSearchBox, "!a");
+
+ return deferred.promise;
+}
+
+function testFocusLost() {
+ is(gSearchView.itemCount, 2,
+ "The global search pane should have some entries from the previous search.");
+ is(gSearchView.widget._parent.hidden, false,
+ "The global search pane should be visible from the previous search.");
+ is(gSearchView._splitter.hidden, false,
+ "The global search pane splitter should be visible from the previous search.");
+
+ gDebugger.DebuggerView.editor.focus();
+
+ info("Current source url:\n" + getSelectedSourceURL(gSources));
+ info("Debugger editor text:\n" + gEditor.getText());
+
+ is(gSearchView.itemCount, 0,
+ "The global search pane shouldn't have any child nodes after clearing.");
+ is(gSearchView.widget._parent.hidden, true,
+ "The global search pane shouldn't be visible after clearing.");
+ is(gSearchView._splitter.hidden, true,
+ "The global search pane splitter shouldn't be visible after clearing.");
+}
+
+function testEscape() {
+ is(gSearchView.itemCount, 2,
+ "The global search pane should have some entries from the previous search.");
+ is(gSearchView.widget._parent.hidden, false,
+ "The global search pane should be visible from the previous search.");
+ is(gSearchView._splitter.hidden, false,
+ "The global search pane splitter should be visible from the previous search.");
+
+ gSearchBox.focus();
+ EventUtils.sendKey("ESCAPE", gDebugger);
+
+ is(gSearchView.itemCount, 0,
+ "The global search pane shouldn't have any child nodes after clearing.");
+ is(gSearchView.widget._parent.hidden, true,
+ "The global search pane shouldn't be visible after clearing.");
+ is(gSearchView._splitter.hidden, true,
+ "The global search pane splitter shouldn't be visible after clearing.");
+}
+
+registerCleanupFunction(function () {
+ gTab = null;
+ gPanel = null;
+ gDebugger = null;
+ gEditor = null;
+ gSources = null;
+ gSearchView = null;
+ gSearchBox = null;
+});
diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_search-popup-jank.js b/devtools/client/debugger/test/mochitest/browser_dbg_search-popup-jank.js
new file mode 100644
index 000000000..317dd6369
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_search-popup-jank.js
@@ -0,0 +1,128 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Tests that sources aren't selected by default when finding a match.
+ */
+
+const TAB_URL = EXAMPLE_URL + "doc_editor-mode.html";
+
+var gTab, gPanel, gDebugger;
+var gSearchBox;
+
+function test() {
+ let options = {
+ source: EXAMPLE_URL + "code_script-switching-01.js?a=b",
+ line: 1
+ };
+ initDebugger(TAB_URL, options).then(([aTab,, aPanel]) => {
+ gTab = aTab;
+ gPanel = aPanel;
+ gDebugger = gPanel.panelWin;
+ gSearchBox = gDebugger.DebuggerView.Filtering._searchbox;
+
+ gDebugger.DebuggerView.Filtering.FilteredSources._autoSelectFirstItem = false;
+ gDebugger.DebuggerView.Filtering.FilteredFunctions._autoSelectFirstItem = false;
+
+ superGenericFileSearch()
+ .then(() => ensureSourceIs(aPanel, "-01.js"))
+ .then(() => ensureCaretAt(aPanel, 1))
+
+ .then(superAccurateFileSearch)
+ .then(() => ensureSourceIs(aPanel, "-01.js"))
+ .then(() => ensureCaretAt(aPanel, 1))
+ .then(() => pressKeyToHide("RETURN"))
+ .then(() => ensureSourceIs(aPanel, "code_test-editor-mode", true))
+ .then(() => ensureCaretAt(aPanel, 1))
+
+ .then(superGenericFileSearch)
+ .then(() => ensureSourceIs(aPanel, "code_test-editor-mode"))
+ .then(() => ensureCaretAt(aPanel, 1))
+ .then(() => {
+ const shown = waitForSourceShown(aPanel, "doc_editor-mode");
+ pressKey("UP");
+ return shown;
+ })
+ .then(() => ensureCaretAt(aPanel, 1))
+ .then(() => pressKeyToHide("RETURN"))
+ .then(() => ensureSourceIs(aPanel, "doc_editor-mode"))
+ .then(() => ensureCaretAt(aPanel, 1))
+
+ .then(superAccurateFileSearch)
+ .then(() => ensureSourceIs(aPanel, "doc_editor-mode"))
+ .then(() => ensureCaretAt(aPanel, 1))
+ .then(() => {
+ const shown = waitForSourceShown(gPanel, "code_test-editor-mode");
+ typeText(gSearchBox, ":");
+ return shown;
+ })
+ .then(() => ensureSourceIs(aPanel, "code_test-editor-mode", true))
+ .then(() => ensureCaretAt(aPanel, 1))
+ .then(() => typeText(gSearchBox, "5"))
+ .then(() => ensureSourceIs(aPanel, "code_test-editor-mode"))
+ .then(() => ensureCaretAt(aPanel, 5))
+ .then(() => pressKey("DOWN"))
+ .then(() => ensureSourceIs(aPanel, "code_test-editor-mode"))
+ .then(() => ensureCaretAt(aPanel, 6))
+
+ .then(superGenericFunctionSearch)
+ .then(() => ensureSourceIs(aPanel, "code_test-editor-mode"))
+ .then(() => ensureCaretAt(aPanel, 6))
+ .then(() => pressKey("RETURN"))
+ .then(() => ensureSourceIs(aPanel, "code_test-editor-mode"))
+ .then(() => ensureCaretAt(aPanel, 4, 10))
+
+ .then(() => closeDebuggerAndFinish(gPanel))
+ .then(null, aError => {
+ ok(false, "Got an error: " + aError.message + "\n" + aError.stack);
+ });
+ });
+}
+
+function waitForMatchFoundAndResultsShown(aName) {
+ return promise.all([
+ once(gDebugger, "popupshown"),
+ waitForDebuggerEvents(gPanel, gDebugger.EVENTS[aName])
+ ]);
+}
+
+function waitForResultsHidden() {
+ return once(gDebugger, "popuphidden");
+}
+
+function superGenericFunctionSearch() {
+ let finished = waitForMatchFoundAndResultsShown("FUNCTION_SEARCH_MATCH_FOUND");
+ setText(gSearchBox, "@");
+ return finished;
+}
+
+function superGenericFileSearch() {
+ let finished = waitForMatchFoundAndResultsShown("FILE_SEARCH_MATCH_FOUND");
+ setText(gSearchBox, ".");
+ return finished;
+}
+
+function superAccurateFileSearch() {
+ let finished = waitForMatchFoundAndResultsShown("FILE_SEARCH_MATCH_FOUND");
+ setText(gSearchBox, "editor");
+ return finished;
+}
+
+function pressKey(aKey) {
+ EventUtils.sendKey(aKey, gDebugger);
+}
+
+function pressKeyToHide(aKey) {
+ let finished = waitForResultsHidden();
+ EventUtils.sendKey(aKey, gDebugger);
+ return finished;
+}
+
+registerCleanupFunction(function () {
+ gTab = null;
+ gPanel = null;
+ gDebugger = null;
+ gSearchBox = null;
+});
diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_search-sources-01.js b/devtools/client/debugger/test/mochitest/browser_dbg_search-sources-01.js
new file mode 100644
index 000000000..671f931a7
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_search-sources-01.js
@@ -0,0 +1,232 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Tests basic functionality of sources filtering (file search).
+ */
+
+const TAB_URL = EXAMPLE_URL + "doc_script-switching-01.html";
+
+function test() {
+ // Debug test slaves are a bit slow at this test.
+ requestLongerTimeout(3);
+
+ let options = {
+ source: EXAMPLE_URL + "code_script-switching-01.js",
+ line: 1
+ };
+ initDebugger(TAB_URL, options).then(([aTab,, aPanel]) => {
+ const gTab = aTab;
+ const gPanel = aPanel;
+ const gDebugger = gPanel.panelWin;
+ const gSources = gDebugger.DebuggerView.Sources;
+ const gSearchView = gDebugger.DebuggerView.Filtering.FilteredSources;
+ const gSearchBox = gDebugger.DebuggerView.Filtering._searchbox;
+
+ Task.spawn(function* () {
+ // move searches to yields
+ // not sure what to do with the error...
+ yield bogusSearch();
+ yield firstSearch();
+ yield secondSearch();
+ yield thirdSearch();
+ yield fourthSearch();
+ yield fifthSearch();
+ yield sixthSearch();
+ yield seventhSearch();
+
+ return closeDebuggerAndFinish(gPanel)
+ .then(null, aError => {
+ ok(false, "Got an error: " + aError.message + "\n" + aError.stack);
+ });
+ });
+
+ function bogusSearch() {
+ let finished = promise.all([
+ ensureSourceIs(gPanel, "-01.js"),
+ ensureCaretAt(gPanel, 1),
+ waitForDebuggerEvents(gPanel, gDebugger.EVENTS.FILE_SEARCH_MATCH_NOT_FOUND)
+ ]);
+
+ setText(gSearchBox, "BOGUS");
+
+ return finished.then(() => promise.all([
+ ensureSourceIs(gPanel, "-01.js"),
+ ensureCaretAt(gPanel, 1),
+ verifyContents({ itemCount: 0, hidden: true })
+ ]));
+ }
+
+ function firstSearch() {
+ let finished = promise.all([
+ ensureSourceIs(gPanel, "-01.js"),
+ ensureCaretAt(gPanel, 1),
+ once(gDebugger, "popupshown"),
+ waitForDebuggerEvents(gPanel, gDebugger.EVENTS.FILE_SEARCH_MATCH_FOUND),
+ waitForSourceShown(gPanel, "-02.js")
+ ]);
+
+ setText(gSearchBox, "-02.js");
+
+ return finished.then(() => promise.all([
+ ensureSourceIs(gPanel, "-02.js"),
+ ensureCaretAt(gPanel, 1),
+ verifyContents({ itemCount: 1, hidden: false })
+ ]));
+ }
+
+ function secondSearch() {
+ let finished = promise.all([
+ once(gDebugger, "popupshown"),
+ waitForDebuggerEvents(gPanel, gDebugger.EVENTS.FILE_SEARCH_MATCH_FOUND),
+ waitForSourceShown(gPanel, "-01.js")
+ ])
+ .then(() => {
+ let finished = promise.all([
+ once(gDebugger, "popupshown"),
+ waitForDebuggerEvents(gPanel, gDebugger.EVENTS.FILE_SEARCH_MATCH_FOUND),
+ waitForCaretUpdated(gPanel, 5)
+ ])
+ .then(() => promise.all([
+ ensureSourceIs(gPanel, "-01.js"),
+ ensureCaretAt(gPanel, 5),
+ verifyContents({ itemCount: 1, hidden: false })
+ ]));
+
+ typeText(gSearchBox, ":5");
+ return finished;
+ });
+
+ setText(gSearchBox, ".*-01\.js");
+ return finished;
+ }
+
+ function thirdSearch() {
+ let finished = promise.all([
+ once(gDebugger, "popupshown"),
+ waitForDebuggerEvents(gPanel, gDebugger.EVENTS.FILE_SEARCH_MATCH_FOUND),
+ waitForSourceShown(gPanel, "-02.js")
+ ])
+ .then(() => {
+ let finished = promise.all([
+ once(gDebugger, "popupshown"),
+ waitForDebuggerEvents(gPanel, gDebugger.EVENTS.FILE_SEARCH_MATCH_FOUND),
+ waitForCaretUpdated(gPanel, 6, 6)
+ ])
+ .then(() => promise.all([
+ ensureSourceIs(gPanel, "-02.js"),
+ ensureCaretAt(gPanel, 6, 6),
+ verifyContents({ itemCount: 1, hidden: false })
+ ]));
+
+ typeText(gSearchBox, "#deb");
+ return finished;
+ });
+
+ setText(gSearchBox, ".*-02\.js");
+ return finished;
+ }
+
+ function fourthSearch() {
+ let finished = promise.all([
+ once(gDebugger, "popupshown"),
+ waitForDebuggerEvents(gPanel, gDebugger.EVENTS.FILE_SEARCH_MATCH_FOUND),
+ waitForSourceShown(gPanel, "-01.js")
+ ])
+ .then(() => {
+ let finished = promise.all([
+ once(gDebugger, "popupshown"),
+ waitForDebuggerEvents(gPanel, gDebugger.EVENTS.FILE_SEARCH_MATCH_FOUND),
+ waitForCaretUpdated(gPanel, 2, 9),
+ ])
+ .then(() => promise.all([
+ ensureSourceIs(gPanel, "-01.js"),
+ ensureCaretAt(gPanel, 2, 9),
+ verifyContents({ itemCount: 1, hidden: false })
+ // ...because we simply searched for ":" in the current file.
+ ]));
+
+ typeText(gSearchBox, "#:"); // # has precedence.
+ return finished;
+ });
+
+ setText(gSearchBox, ".*-01\.js");
+ return finished;
+ }
+
+ function fifthSearch() {
+ let finished = promise.all([
+ once(gDebugger, "popupshown"),
+ waitForDebuggerEvents(gPanel, gDebugger.EVENTS.FILE_SEARCH_MATCH_FOUND),
+ waitForSourceShown(gPanel, "-02.js")
+ ])
+ .then(() => {
+ let finished = promise.all([
+ once(gDebugger, "popuphidden"),
+ waitForDebuggerEvents(gPanel, gDebugger.EVENTS.FILE_SEARCH_MATCH_NOT_FOUND),
+ waitForCaretUpdated(gPanel, 1, 3)
+ ])
+ .then(() => promise.all([
+ ensureSourceIs(gPanel, "-02.js"),
+ ensureCaretAt(gPanel, 1, 3),
+ verifyContents({ itemCount: 0, hidden: true })
+ // ...because the searched label includes ":5", so nothing is found.
+ ]));
+
+ typeText(gSearchBox, ":5#*"); // # has precedence.
+ return finished;
+ });
+
+ setText(gSearchBox, ".*-02\.js");
+ return finished;
+ }
+
+ function sixthSearch() {
+ let finished = promise.all([
+ ensureSourceIs(gPanel, "-02.js"),
+ ensureCaretAt(gPanel, 1, 3),
+ once(gDebugger, "popupshown"),
+ waitForDebuggerEvents(gPanel, gDebugger.EVENTS.FILE_SEARCH_MATCH_FOUND),
+ waitForCaretUpdated(gPanel, 5)
+ ]);
+
+ backspaceText(gSearchBox, 2);
+
+ return finished.then(() => promise.all([
+ ensureSourceIs(gPanel, "-02.js"),
+ ensureCaretAt(gPanel, 5),
+ verifyContents({ itemCount: 1, hidden: false })
+ ]));
+ }
+
+ function seventhSearch() {
+ let finished = promise.all([
+ ensureSourceIs(gPanel, "-02.js"),
+ ensureCaretAt(gPanel, 5),
+ once(gDebugger, "popupshown"),
+ waitForDebuggerEvents(gPanel, gDebugger.EVENTS.FILE_SEARCH_MATCH_FOUND),
+ waitForSourceShown(gPanel, "-01.js"),
+ ]);
+
+ backspaceText(gSearchBox, 6);
+
+ return finished.then(() => promise.all([
+ ensureSourceIs(gPanel, "-01.js"),
+ ensureCaretAt(gPanel, 2, 9),
+ verifyContents({ itemCount: 2, hidden: false })
+ ]));
+ }
+
+ function verifyContents(aArgs) {
+ is(gSources.visibleItems.length, 2,
+ "The unmatched sources in the widget should not be hidden.");
+ is(gSearchView.itemCount, aArgs.itemCount,
+ "No sources should be displayed in the sources container after a bogus search.");
+ is(gSearchView.hidden, aArgs.hidden,
+ "No sources should be displayed in the sources container after a bogus search.");
+ }
+
+ });
+}
diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_search-sources-02.js b/devtools/client/debugger/test/mochitest/browser_dbg_search-sources-02.js
new file mode 100644
index 000000000..3e06a1bdf
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_search-sources-02.js
@@ -0,0 +1,281 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Tests more complex functionality of sources filtering (file search).
+ */
+
+const TAB_URL = EXAMPLE_URL + "doc_editor-mode.html";
+
+var gTab, gPanel, gDebugger;
+var gSources, gSourceUtils, gSearchView, gSearchBox;
+
+function test() {
+ // Debug test slaves are a bit slow at this test.
+ requestLongerTimeout(3);
+
+ let options = {
+ source: EXAMPLE_URL + "code_script-switching-01.js?a=b",
+ line: 1
+ };
+ initDebugger(TAB_URL, options).then(([aTab,, aPanel]) => {
+ gTab = aTab;
+ gPanel = aPanel;
+ gDebugger = gPanel.panelWin;
+ gSources = gDebugger.DebuggerView.Sources;
+ gSourceUtils = gDebugger.SourceUtils;
+ gSearchView = gDebugger.DebuggerView.Filtering.FilteredSources;
+ gSearchBox = gDebugger.DebuggerView.Filtering._searchbox;
+
+ firstSearch()
+ .then(secondSearch)
+ .then(thirdSearch)
+ .then(fourthSearch)
+ .then(fifthSearch)
+ .then(goDown)
+ .then(goDownAndWrap)
+ .then(goUpAndWrap)
+ .then(goUp)
+ .then(returnAndSwitch)
+ .then(firstSearch)
+ .then(clickAndSwitch)
+ .then(() => closeDebuggerAndFinish(gPanel))
+ .then(null, aError => {
+ ok(false, "Got an error: " + aError.message + "\n" + aError.stack);
+ });
+ });
+}
+
+function firstSearch() {
+ let finished = promise.all([
+ ensureSourceIs(gPanel, "-01.js"),
+ ensureCaretAt(gPanel, 1),
+ once(gDebugger, "popupshown"),
+ waitForDebuggerEvents(gPanel, gDebugger.EVENTS.FILE_SEARCH_MATCH_FOUND)
+ ]);
+
+ setText(gSearchBox, ".");
+
+ return finished.then(() => promise.all([
+ ensureSourceIs(gPanel, "-01.js"),
+ ensureCaretAt(gPanel, 1),
+ verifyContents([
+ "code_script-switching-01.js?a=b",
+ "code_test-editor-mode?c=d",
+ "doc_editor-mode.html"
+ ])
+ ]));
+}
+
+function secondSearch() {
+ let finished = promise.all([
+ ensureSourceIs(gPanel, "-01.js"),
+ ensureCaretAt(gPanel, 1),
+ once(gDebugger, "popupshown"),
+ waitForDebuggerEvents(gPanel, gDebugger.EVENTS.FILE_SEARCH_MATCH_FOUND)
+ ]);
+
+ typeText(gSearchBox, "-0");
+
+ return finished.then(() => promise.all([
+ ensureSourceIs(gPanel, "-01.js"),
+ ensureCaretAt(gPanel, 1),
+ verifyContents(["code_script-switching-01.js?a=b"])
+ ]));
+}
+
+function thirdSearch() {
+ let finished = promise.all([
+ ensureSourceIs(gPanel, "-01.js"),
+ ensureCaretAt(gPanel, 1),
+ once(gDebugger, "popupshown"),
+ waitForDebuggerEvents(gPanel, gDebugger.EVENTS.FILE_SEARCH_MATCH_FOUND)
+ ]);
+
+ backspaceText(gSearchBox, 1);
+
+ return finished.then(() => promise.all([
+ ensureSourceIs(gPanel, "-01.js"),
+ ensureCaretAt(gPanel, 1),
+ verifyContents([
+ "code_script-switching-01.js?a=b",
+ "code_test-editor-mode?c=d",
+ "doc_editor-mode.html"
+ ])
+ ]));
+}
+
+function fourthSearch() {
+ let finished = promise.all([
+ ensureSourceIs(gPanel, "-01.js"),
+ ensureCaretAt(gPanel, 1),
+ once(gDebugger, "popupshown"),
+ waitForDebuggerEvents(gPanel, gDebugger.EVENTS.FILE_SEARCH_MATCH_FOUND),
+ waitForSourceShown(gPanel, "test-editor-mode")
+ ]);
+
+ setText(gSearchBox, "code_test");
+
+ return finished.then(() => promise.all([
+ ensureSourceIs(gPanel, "test-editor-mode"),
+ ensureCaretAt(gPanel, 1),
+ verifyContents(["code_test-editor-mode?c=d"])
+ ]));
+}
+
+function fifthSearch() {
+ let finished = promise.all([
+ ensureSourceIs(gPanel, "test-editor-mode"),
+ ensureCaretAt(gPanel, 1),
+ once(gDebugger, "popupshown"),
+ waitForDebuggerEvents(gPanel, gDebugger.EVENTS.FILE_SEARCH_MATCH_FOUND),
+ waitForSourceShown(gPanel, "-01.js")
+ ]);
+
+ backspaceText(gSearchBox, 4);
+
+ return finished.then(() => promise.all([
+ ensureSourceIs(gPanel, "-01.js"),
+ ensureCaretAt(gPanel, 1),
+ verifyContents([
+ "code_script-switching-01.js?a=b",
+ "code_test-editor-mode?c=d"
+ ])
+ ]));
+}
+
+function goDown() {
+ let finished = promise.all([
+ ensureSourceIs(gPanel, "-01.js"),
+ ensureCaretAt(gPanel, 1),
+ waitForSourceShown(gPanel, "test-editor-mode"),
+ ]);
+
+ EventUtils.sendKey("DOWN", gDebugger);
+
+ return finished.then(() => promise.all([
+ ensureSourceIs(gPanel, "test-editor-mode"),
+ ensureCaretAt(gPanel, 1),
+ verifyContents([
+ "code_script-switching-01.js?a=b",
+ "code_test-editor-mode?c=d"
+ ])
+ ]));
+}
+
+function goDownAndWrap() {
+ let finished = promise.all([
+ ensureSourceIs(gPanel, "test-editor-mode"),
+ ensureCaretAt(gPanel, 1),
+ waitForSourceShown(gPanel, "-01.js")
+ ]);
+
+ EventUtils.synthesizeKey("g", { metaKey: true }, gDebugger);
+
+ return finished.then(() => promise.all([
+ ensureSourceIs(gPanel, "-01.js"),
+ ensureCaretAt(gPanel, 1),
+ verifyContents([
+ "code_script-switching-01.js?a=b",
+ "code_test-editor-mode?c=d"
+ ])
+ ]));
+}
+
+function goUpAndWrap() {
+ let finished = promise.all([
+ ensureSourceIs(gPanel, "-01.js"),
+ ensureCaretAt(gPanel, 1),
+ waitForSourceShown(gPanel, "test-editor-mode")
+ ]);
+
+ EventUtils.synthesizeKey("G", { metaKey: true }, gDebugger);
+
+ return finished.then(() => promise.all([
+ ensureSourceIs(gPanel, "test-editor-mode"),
+ ensureCaretAt(gPanel, 1),
+ verifyContents([
+ "code_script-switching-01.js?a=b",
+ "code_test-editor-mode?c=d"
+ ])
+ ]));
+}
+
+function goUp() {
+ let finished = promise.all([
+ ensureSourceIs(gPanel, "test-editor-mode"),
+ ensureCaretAt(gPanel, 1),
+ waitForSourceShown(gPanel, "-01.js"),
+ ]);
+
+ EventUtils.sendKey("UP", gDebugger);
+
+ return finished.then(() => promise.all([
+ ensureSourceIs(gPanel, "-01.js"),
+ ensureCaretAt(gPanel, 1),
+ verifyContents([
+ "code_script-switching-01.js?a=b",
+ "code_test-editor-mode?c=d"
+ ])
+ ]));
+}
+
+function returnAndSwitch() {
+ let finished = promise.all([
+ ensureSourceIs(gPanel, "-01.js"),
+ ensureCaretAt(gPanel, 1),
+ once(gDebugger, "popuphidden")
+ ]);
+
+ EventUtils.sendKey("RETURN", gDebugger);
+
+ return finished.then(() => promise.all([
+ ensureSourceIs(gPanel, "-01.js"),
+ ensureCaretAt(gPanel, 1)
+ ]));
+}
+
+function clickAndSwitch() {
+ let finished = promise.all([
+ ensureSourceIs(gPanel, "-01.js"),
+ ensureCaretAt(gPanel, 1),
+ once(gDebugger, "popuphidden"),
+ waitForSourceShown(gPanel, "test-editor-mode")
+ ]);
+
+ EventUtils.sendMouseEvent({ type: "click" }, gSearchView.items[1].target, gDebugger);
+
+ return finished.then(() => promise.all([
+ ensureSourceIs(gPanel, "test-editor-mode"),
+ ensureCaretAt(gPanel, 1)
+ ]));
+}
+
+function verifyContents(aMatches) {
+ is(gSources.visibleItems.length, 3,
+ "The unmatched sources in the widget should not be hidden.");
+ is(gSearchView.itemCount, aMatches.length,
+ "The filtered sources view should have the right items available.");
+
+ for (let i = 0; i < gSearchView.itemCount; i++) {
+ let trimmedLabel = gSourceUtils.trimUrlLength(gSourceUtils.trimUrlQuery(aMatches[i]));
+ let trimmedLocation = gSourceUtils.trimUrlLength(EXAMPLE_URL + aMatches[i], 0, "start");
+
+ ok(gSearchView.widget._parent.querySelector(".results-panel-item-label[value=\"" + trimmedLabel + "\"]"),
+ "The filtered sources view should have the correct source labels.");
+ ok(gSearchView.widget._parent.querySelector(".results-panel-item-label-below[value=\"" + trimmedLocation + "\"]"),
+ "The filtered sources view should have the correct source locations.");
+ }
+}
+
+registerCleanupFunction(function () {
+ gTab = null;
+ gPanel = null;
+ gDebugger = null;
+ gSources = null;
+ gSourceUtils = null;
+ gSearchView = null;
+ gSearchBox = null;
+});
diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_search-sources-03.js b/devtools/client/debugger/test/mochitest/browser_dbg_search-sources-03.js
new file mode 100644
index 000000000..904a51f76
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_search-sources-03.js
@@ -0,0 +1,103 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Tests that while searching for files, the sources list remains unchanged.
+ */
+
+const TAB_URL = EXAMPLE_URL + "doc_editor-mode.html";
+
+var gTab, gPanel, gDebugger;
+var gSources, gSearchBox;
+
+function test() {
+ let options = {
+ source: EXAMPLE_URL + "code_script-switching-01.js?a=b",
+ line: 1
+ };
+ initDebugger(TAB_URL, options).then(([aTab,, aPanel]) => {
+ gTab = aTab;
+ gPanel = aPanel;
+ gDebugger = gPanel.panelWin;
+ gSources = gDebugger.DebuggerView.Sources;
+ gSearchBox = gDebugger.DebuggerView.Filtering._searchbox;
+
+ superGenericSearch()
+ .then(verifySourcesPane)
+ .then(kindaInterpretableSearch)
+ .then(verifySourcesPane)
+ .then(incrediblySpecificSearch)
+ .then(verifySourcesPane)
+ .then(returnAndHide)
+ .then(verifySourcesPane)
+ .then(() => closeDebuggerAndFinish(gPanel))
+ .then(null, aError => {
+ ok(false, "Got an error: " + aError.message + "\n" + aError.stack);
+ });
+ });
+}
+
+function waitForMatchFoundAndResultsShown() {
+ return promise.all([
+ once(gDebugger, "popupshown"),
+ waitForDebuggerEvents(gPanel, gDebugger.EVENTS.FILE_SEARCH_MATCH_FOUND)
+ ]).then(() => promise.all([
+ ensureSourceIs(gPanel, "-01.js"),
+ ensureCaretAt(gPanel, 1)
+ ]));
+}
+
+function waitForResultsHidden() {
+ return once(gDebugger, "popuphidden").then(() => promise.all([
+ ensureSourceIs(gPanel, "-01.js"),
+ ensureCaretAt(gPanel, 1)
+ ]));
+}
+
+function superGenericSearch() {
+ let finished = waitForMatchFoundAndResultsShown();
+ setText(gSearchBox, ".");
+ return finished;
+}
+
+function kindaInterpretableSearch() {
+ let finished = waitForMatchFoundAndResultsShown();
+ typeText(gSearchBox, "-0");
+ return finished;
+}
+
+function incrediblySpecificSearch() {
+ let finished = waitForMatchFoundAndResultsShown();
+ typeText(gSearchBox, "1.js");
+ return finished;
+}
+
+function returnAndHide() {
+ let finished = waitForResultsHidden();
+ EventUtils.sendKey("RETURN", gDebugger);
+ return finished;
+}
+
+function verifySourcesPane() {
+ is(gSources.itemCount, 3,
+ "There should be 3 items present in the sources container.");
+ is(gSources.visibleItems.length, 3,
+ "There should be no hidden items in the sources container.");
+
+ ok(gSources.getItemForAttachment(e => e.label == "code_script-switching-01.js"),
+ "The first source's label should be correct.");
+ ok(gSources.getItemForAttachment(e => e.label == "code_test-editor-mode"),
+ "The second source's label should be correct.");
+ ok(gSources.getItemForAttachment(e => e.label == "doc_editor-mode.html"),
+ "The third source's label should be correct.");
+}
+
+registerCleanupFunction(function () {
+ gTab = null;
+ gPanel = null;
+ gDebugger = null;
+ gSources = null;
+ gSearchBox = null;
+});
diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_search-symbols.js b/devtools/client/debugger/test/mochitest/browser_dbg_search-symbols.js
new file mode 100644
index 000000000..5998acf8e
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_search-symbols.js
@@ -0,0 +1,472 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Tests if the function searching works properly.
+ */
+
+const TAB_URL = EXAMPLE_URL + "doc_function-search.html";
+
+var gTab, gPanel, gDebugger;
+var gEditor, gSources, gSearchBox, gFilteredFunctions;
+
+function test() {
+ let options = {
+ source: EXAMPLE_URL + "code_function-search-01.js",
+ line: 1
+ };
+ initDebugger(TAB_URL, options).then(([aTab,, aPanel]) => {
+ gTab = aTab;
+ gPanel = aPanel;
+ gDebugger = gPanel.panelWin;
+ gEditor = gDebugger.DebuggerView.editor;
+ gSources = gDebugger.DebuggerView.Sources;
+ gSearchBox = gDebugger.DebuggerView.Filtering._searchbox;
+ gFilteredFunctions = gDebugger.DebuggerView.Filtering.FilteredFunctions;
+
+ showSource("doc_function-search.html")
+ .then(htmlSearch)
+ .then(() => showSource("code_function-search-01.js"))
+ .then(firstJsSearch)
+ .then(() => showSource("code_function-search-02.js"))
+ .then(secondJsSearch)
+ .then(() => showSource("code_function-search-03.js"))
+ .then(thirdJsSearch)
+ .then(saveSearch)
+ .then(filterSearch)
+ .then(bogusSearch)
+ .then(incrementalSearch)
+ .then(emptySearch)
+ .then(() => closeDebuggerAndFinish(gPanel))
+ .then(null, aError => {
+ ok(false, "Got an error: " + aError.message + "\n" + aError.stack);
+ });
+ });
+}
+
+function htmlSearch() {
+ let deferred = promise.defer();
+
+ once(gDebugger, "popupshown").then(() => {
+ writeInfo();
+
+ is(gFilteredFunctions.selectedIndex, 0,
+ "An item should be selected in the filtered functions view (1).");
+ ok(gFilteredFunctions.selectedItem,
+ "An item should be selected in the filtered functions view (2).");
+
+ if (gSources.selectedItem.attachment.source.url.indexOf(".html") != -1) {
+ let expectedResults = [
+ ["inline", ".html", "", 19, 16],
+ ["arrow", ".html", "", 20, 11],
+ ["foo", ".html", "", 22, 11],
+ ["foo2", ".html", "", 23, 11],
+ ["bar2", ".html", "", 23, 18]
+ ];
+
+ for (let [label, value, description, line, column] of expectedResults) {
+ let target = gFilteredFunctions.selectedItem.target;
+
+ if (label) {
+ is(target.querySelector(".results-panel-item-label").getAttribute("value"),
+ gDebugger.SourceUtils.trimUrlLength(label + "()"),
+ "The correct label (" + label + ") is currently selected.");
+ } else {
+ ok(!target.querySelector(".results-panel-item-label"),
+ "Shouldn't create empty label nodes.");
+ }
+ if (value) {
+ ok(target.querySelector(".results-panel-item-label-below").getAttribute("value").includes(value),
+ "The correct value (" + value + ") is attached.");
+ } else {
+ ok(!target.querySelector(".results-panel-item-label-below"),
+ "Shouldn't create empty label nodes.");
+ }
+ if (description) {
+ is(target.querySelector(".results-panel-item-label-before").getAttribute("value"), description,
+ "The correct description (" + description + ") is currently shown.");
+ } else {
+ ok(!target.querySelector(".results-panel-item-label-before"),
+ "Shouldn't create empty label nodes.");
+ }
+
+ ok(isCaretPos(gPanel, line, column),
+ "The editor didn't jump to the correct line.");
+
+ EventUtils.sendKey("DOWN", gDebugger);
+ }
+
+ ok(isCaretPos(gPanel, expectedResults[0][3], expectedResults[0][4]),
+ "The editor didn't jump to the correct line again.");
+
+ deferred.resolve();
+ } else {
+ ok(false, "How did you get here? Go away, you.");
+ }
+ });
+
+ setText(gSearchBox, "@");
+ return deferred.promise;
+}
+
+function firstJsSearch() {
+ let deferred = promise.defer();
+
+ once(gDebugger, "popupshown").then(() => {
+ writeInfo();
+
+ is(gFilteredFunctions.selectedIndex, 0,
+ "An item should be selected in the filtered functions view (1).");
+ ok(gFilteredFunctions.selectedItem,
+ "An item should be selected in the filtered functions view (2).");
+
+ if (gSources.selectedItem.attachment.source.url.indexOf("-01.js") != -1) {
+ let s = " " + gDebugger.L10N.getStr("functionSearchSeparatorLabel") + " ";
+ let expectedResults = [
+ ["test", "-01.js", "", 4, 10],
+ ["anonymousExpression", "-01.js", "test.prototype", 9, 3],
+ ["namedExpression" + s + "NAME", "-01.js", "test.prototype", 11, 3],
+ ["a_test", "-01.js", "foo", 22, 3],
+ ["n_test" + s + "x", "-01.js", "foo", 24, 3],
+ ["a_test", "-01.js", "foo.sub", 27, 5],
+ ["n_test" + s + "y", "-01.js", "foo.sub", 29, 5],
+ ["a_test", "-01.js", "foo.sub.sub", 32, 7],
+ ["n_test" + s + "z", "-01.js", "foo.sub.sub", 34, 7],
+ ["test_SAME_NAME", "-01.js", "foo.sub.sub.sub", 37, 9]
+ ];
+
+ for (let [label, value, description, line, column] of expectedResults) {
+ let target = gFilteredFunctions.selectedItem.target;
+
+ if (label) {
+ is(target.querySelector(".results-panel-item-label").getAttribute("value"),
+ gDebugger.SourceUtils.trimUrlLength(label + "()"),
+ "The correct label (" + label + ") is currently selected.");
+ } else {
+ ok(!target.querySelector(".results-panel-item-label"),
+ "Shouldn't create empty label nodes.");
+ }
+ if (value) {
+ ok(target.querySelector(".results-panel-item-label-below").getAttribute("value").includes(value),
+ "The correct value (" + value + ") is attached.");
+ } else {
+ ok(!target.querySelector(".results-panel-item-label-below"),
+ "Shouldn't create empty label nodes.");
+ }
+ if (description) {
+ is(target.querySelector(".results-panel-item-label-before").getAttribute("value"), description,
+ "The correct description (" + description + ") is currently shown.");
+ } else {
+ ok(!target.querySelector(".results-panel-item-label-before"),
+ "Shouldn't create empty label nodes.");
+ }
+
+ ok(isCaretPos(gPanel, line, column),
+ "The editor didn't jump to the correct line.");
+
+ EventUtils.sendKey("DOWN", gDebugger);
+ }
+
+ ok(isCaretPos(gPanel, expectedResults[0][3], expectedResults[0][4]),
+ "The editor didn't jump to the correct line again.");
+
+ deferred.resolve();
+ } else {
+ ok(false, "How did you get here? Go away, you.");
+ }
+ });
+
+ setText(gSearchBox, "@");
+ return deferred.promise;
+}
+
+function secondJsSearch() {
+ let deferred = promise.defer();
+
+ once(gDebugger, "popupshown").then(() => {
+ writeInfo();
+
+ is(gFilteredFunctions.selectedIndex, 0,
+ "An item should be selected in the filtered functions view (1).");
+ ok(gFilteredFunctions.selectedItem,
+ "An item should be selected in the filtered functions view (2).");
+
+ if (gSources.selectedItem.attachment.source.url.indexOf("-02.js") != -1) {
+ let s = " " + gDebugger.L10N.getStr("functionSearchSeparatorLabel") + " ";
+ let expectedResults = [
+ ["test2", "-02.js", "", 4, 5],
+ ["test3" + s + "test3_NAME", "-02.js", "", 8, 5],
+ ["test4_SAME_NAME", "-02.js", "", 11, 5],
+ ["x" + s + "X", "-02.js", "test.prototype", 14, 1],
+ ["y" + s + "Y", "-02.js", "test.prototype.sub", 16, 1],
+ ["z" + s + "Z", "-02.js", "test.prototype.sub.sub", 18, 1],
+ ["t", "-02.js", "test.prototype.sub.sub.sub", 20, 1],
+ ["x", "-02.js", "this", 20, 32],
+ ["y", "-02.js", "this", 20, 41],
+ ["z", "-02.js", "this", 20, 50]
+ ];
+
+ for (let [label, value, description, line, column] of expectedResults) {
+ let target = gFilteredFunctions.selectedItem.target;
+
+ if (label) {
+ is(target.querySelector(".results-panel-item-label").getAttribute("value"),
+ gDebugger.SourceUtils.trimUrlLength(label + "()"),
+ "The correct label (" + label + ") is currently selected.");
+ } else {
+ ok(!target.querySelector(".results-panel-item-label"),
+ "Shouldn't create empty label nodes.");
+ }
+ if (value) {
+ ok(target.querySelector(".results-panel-item-label-below").getAttribute("value").includes(value),
+ "The correct value (" + value + ") is attached.");
+ } else {
+ ok(!target.querySelector(".results-panel-item-label-below"),
+ "Shouldn't create empty label nodes.");
+ }
+ if (description) {
+ is(target.querySelector(".results-panel-item-label-before").getAttribute("value"), description,
+ "The correct description (" + description + ") is currently shown.");
+ } else {
+ ok(!target.querySelector(".results-panel-item-label-before"),
+ "Shouldn't create empty label nodes.");
+ }
+
+ ok(isCaretPos(gPanel, line, column),
+ "The editor didn't jump to the correct line.");
+
+ EventUtils.sendKey("DOWN", gDebugger);
+ }
+
+ ok(isCaretPos(gPanel, expectedResults[0][3], expectedResults[0][4]),
+ "The editor didn't jump to the correct line again.");
+
+ deferred.resolve();
+ } else {
+ ok(false, "How did you get here? Go away, you.");
+ }
+ });
+
+ setText(gSearchBox, "@");
+ return deferred.promise;
+}
+
+function thirdJsSearch() {
+ let deferred = promise.defer();
+
+ once(gDebugger, "popupshown").then(() => {
+ writeInfo();
+
+ is(gFilteredFunctions.selectedIndex, 0,
+ "An item should be selected in the filtered functions view (1).");
+ ok(gFilteredFunctions.selectedItem,
+ "An item should be selected in the filtered functions view (2).");
+
+ if (gSources.selectedItem.attachment.source.url.indexOf("-03.js") != -1) {
+ let s = " " + gDebugger.L10N.getStr("functionSearchSeparatorLabel") + " ";
+ let expectedResults = [
+ ["namedEventListener", "-03.js", "", 4, 43],
+ ["a" + s + "A", "-03.js", "bar", 10, 5],
+ ["b" + s + "B", "-03.js", "bar.alpha", 15, 5],
+ ["c" + s + "C", "-03.js", "bar.alpha.beta", 20, 5],
+ ["d" + s + "D", "-03.js", "this.theta", 25, 5],
+ ["fun", "-03.js", "", 29, 7],
+ ["foo", "-03.js", "", 29, 13],
+ ["bar", "-03.js", "", 29, 19],
+ ["t_foo", "-03.js", "this", 29, 25],
+ ["w_bar" + s + "baz", "-03.js", "window", 29, 38]
+ ];
+
+ for (let [label, value, description, line, column] of expectedResults) {
+ let target = gFilteredFunctions.selectedItem.target;
+
+ if (label) {
+ is(target.querySelector(".results-panel-item-label").getAttribute("value"),
+ gDebugger.SourceUtils.trimUrlLength(label + "()"),
+ "The correct label (" + label + ") is currently selected.");
+ } else {
+ ok(!target.querySelector(".results-panel-item-label"),
+ "Shouldn't create empty label nodes.");
+ }
+ if (value) {
+ ok(target.querySelector(".results-panel-item-label-below").getAttribute("value").includes(value),
+ "The correct value (" + value + ") is attached.");
+ } else {
+ ok(!target.querySelector(".results-panel-item-label-below"),
+ "Shouldn't create empty label nodes.");
+ }
+ if (description) {
+ is(target.querySelector(".results-panel-item-label-before").getAttribute("value"), description,
+ "The correct description (" + description + ") is currently shown.");
+ } else {
+ ok(!target.querySelector(".results-panel-item-label-before"),
+ "Shouldn't create empty label nodes.");
+ }
+
+ ok(isCaretPos(gPanel, line, column),
+ "The editor didn't jump to the correct line.");
+
+ EventUtils.sendKey("DOWN", gDebugger);
+ }
+
+ ok(isCaretPos(gPanel, expectedResults[0][3], expectedResults[0][4]),
+ "The editor didn't jump to the correct line again.");
+
+ deferred.resolve();
+ } else {
+ ok(false, "How did you get here? Go away, you.");
+ }
+ });
+
+ setText(gSearchBox, "@");
+ return deferred.promise;
+}
+
+function filterSearch() {
+ let deferred = promise.defer();
+
+ once(gDebugger, "popupshown").then(() => {
+ writeInfo();
+
+ is(gFilteredFunctions.selectedIndex, 0,
+ "An item should be selected in the filtered functions view (1).");
+ ok(gFilteredFunctions.selectedItem,
+ "An item should be selected in the filtered functions view (2).");
+
+ if (gSources.selectedItem.attachment.source.url.indexOf("-03.js") != -1) {
+ let s = " " + gDebugger.L10N.getStr("functionSearchSeparatorLabel") + " ";
+ let expectedResults = [
+ ["namedEventListener", "-03.js", "", 4, 43],
+ ["bar", "-03.js", "", 29, 19],
+ ["w_bar" + s + "baz", "-03.js", "window", 29, 38],
+ ["anonymousExpression", "-01.js", "test.prototype", 9, 3],
+ ["namedExpression" + s + "NAME", "-01.js", "test.prototype", 11, 3],
+ ["arrow", ".html", "", 20, 11],
+ ["bar2", ".html", "", 23, 18]
+ ];
+
+ for (let [label, value, description, line, column] of expectedResults) {
+ let target = gFilteredFunctions.selectedItem.target;
+
+ if (label) {
+ is(target.querySelector(".results-panel-item-label").getAttribute("value"),
+ gDebugger.SourceUtils.trimUrlLength(label + "()"),
+ "The correct label (" + label + ") is currently selected.");
+ } else {
+ ok(!target.querySelector(".results-panel-item-label"),
+ "Shouldn't create empty label nodes.");
+ }
+ if (value) {
+ ok(target.querySelector(".results-panel-item-label-below").getAttribute("value").includes(value),
+ "The correct value (" + value + ") is attached.");
+ } else {
+ ok(!target.querySelector(".results-panel-item-label-below"),
+ "Shouldn't create empty label nodes.");
+ }
+ if (description) {
+ is(target.querySelector(".results-panel-item-label-before").getAttribute("value"), description,
+ "The correct description (" + description + ") is currently shown.");
+ } else {
+ ok(!target.querySelector(".results-panel-item-label-before"),
+ "Shouldn't create empty label nodes.");
+ }
+
+ ok(isCaretPos(gPanel, line, column),
+ "The editor didn't jump to the correct line.");
+
+ EventUtils.sendKey("DOWN", gDebugger);
+ }
+
+ ok(isCaretPos(gPanel, expectedResults[0][3], expectedResults[0][4]),
+ "The editor didn't jump to the correct line again.");
+
+ deferred.resolve();
+ } else {
+ ok(false, "How did you get here? Go away, you.");
+ }
+ });
+
+ setText(gSearchBox, "@r");
+ return deferred.promise;
+}
+
+function bogusSearch() {
+ let deferred = promise.defer();
+
+ once(gDebugger, "popuphidden").then(() => {
+ ok(true, "Popup was successfully hidden after no matches were found.");
+ deferred.resolve();
+ });
+
+ setText(gSearchBox, "@bogus");
+ return deferred.promise;
+}
+
+function incrementalSearch() {
+ let deferred = promise.defer();
+
+ once(gDebugger, "popupshown").then(() => {
+ ok(true, "Popup was successfully shown after some matches were found.");
+ deferred.resolve();
+ });
+
+ setText(gSearchBox, "@NAME");
+ return deferred.promise;
+}
+
+function emptySearch() {
+ let deferred = promise.defer();
+
+ once(gDebugger, "popuphidden").then(() => {
+ ok(true, "Popup was successfully hidden when nothing was searched.");
+ deferred.resolve();
+ });
+
+ clearText(gSearchBox);
+ return deferred.promise;
+}
+
+function showSource(aLabel) {
+ let deferred = promise.defer();
+
+ waitForSourceShown(gPanel, aLabel).then(deferred.resolve);
+ gSources.selectedItem = e => e.attachment.label == aLabel;
+
+ return deferred.promise;
+}
+
+function saveSearch() {
+ let finished = once(gDebugger, "popuphidden");
+
+ // Either by pressing RETURN or clicking on an item in the popup,
+ // the popup should hide and the item should become selected.
+ let random = Math.random();
+ if (random >= 0.33) {
+ EventUtils.sendKey("RETURN", gDebugger);
+ } else if (random >= 0.66) {
+ EventUtils.sendKey("RETURN", gDebugger);
+ } else {
+ EventUtils.sendMouseEvent({ type: "click" },
+ gFilteredFunctions.selectedItem.target,
+ gDebugger);
+ }
+
+ return finished;
+}
+
+function writeInfo() {
+ info("Current source url:\n" + getSelectedSourceURL(gSources));
+ info("Debugger editor text:\n" + gEditor.getText());
+}
+
+registerCleanupFunction(function () {
+ gTab = null;
+ gPanel = null;
+ gDebugger = null;
+ gEditor = null;
+ gSources = null;
+ gSearchBox = null;
+ gFilteredFunctions = null;
+});
diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_searchbox-help-popup-01.js b/devtools/client/debugger/test/mochitest/browser_dbg_searchbox-help-popup-01.js
new file mode 100644
index 000000000..6276567c2
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_searchbox-help-popup-01.js
@@ -0,0 +1,64 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Make sure that the searchbox popup is displayed when focusing the searchbox,
+ * and hidden when the user starts typing.
+ */
+
+const TAB_URL = EXAMPLE_URL + "doc_script-switching-01.html";
+
+var gTab, gPanel, gDebugger;
+var gSearchBox, gSearchBoxPanel;
+
+function test() {
+ let options = {
+ source: EXAMPLE_URL + "code_script-switching-01.js",
+ line: 1
+ };
+ initDebugger(TAB_URL, options).then(([aTab,, aPanel]) => {
+ gTab = aTab;
+ gPanel = aPanel;
+ gDebugger = gPanel.panelWin;
+ gSearchBox = gDebugger.DebuggerView.Filtering._searchbox;
+ gSearchBoxPanel = gDebugger.DebuggerView.Filtering._searchboxHelpPanel;
+
+ waitForSourceAndCaretAndScopes(gPanel, "-02.js", 1)
+ .then(showPopup)
+ .then(hidePopup)
+ .then(() => resumeDebuggerThenCloseAndFinish(gPanel))
+ .then(null, aError => {
+ ok(false, "Got an error: " + aError.message + "\n" + aError.stack);
+ });
+
+ callInTab(gTab, "firstCall");
+ });
+}
+
+function showPopup() {
+ is(gSearchBoxPanel.state, "closed",
+ "The search box panel shouldn't be visible yet.");
+
+ let finished = once(gSearchBoxPanel, "popupshown");
+ EventUtils.sendMouseEvent({ type: "click" }, gSearchBox, gDebugger);
+ return finished;
+}
+
+function hidePopup() {
+ is(gSearchBoxPanel.state, "open",
+ "The search box panel should be visible after searching started.");
+
+ let finished = once(gSearchBoxPanel, "popuphidden");
+ setText(gSearchBox, "#");
+ return finished;
+}
+
+registerCleanupFunction(function () {
+ gTab = null;
+ gPanel = null;
+ gDebugger = null;
+ gSearchBox = null;
+ gSearchBoxPanel = null;
+});
diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_searchbox-help-popup-02.js b/devtools/client/debugger/test/mochitest/browser_dbg_searchbox-help-popup-02.js
new file mode 100644
index 000000000..277dd0bc4
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_searchbox-help-popup-02.js
@@ -0,0 +1,90 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Make sure that the searchbox popup isn't displayed when there's some text
+ * already present.
+ */
+
+const TAB_URL = EXAMPLE_URL + "doc_script-switching-01.html";
+
+var gTab, gPanel, gDebugger;
+var gEditor, gSearchBox, gSearchBoxPanel;
+
+function test() {
+ let options = {
+ source: EXAMPLE_URL + "code_script-switching-01.js",
+ line: 1
+ };
+ initDebugger(TAB_URL, options).then(([aTab,, aPanel]) => {
+ gTab = aTab;
+ gPanel = aPanel;
+ gDebugger = gPanel.panelWin;
+ gEditor = gDebugger.DebuggerView.editor;
+ gSearchBox = gDebugger.DebuggerView.Filtering._searchbox;
+ gSearchBoxPanel = gDebugger.DebuggerView.Filtering._searchboxHelpPanel;
+
+ once(gSearchBoxPanel, "popupshown").then(() => {
+ ok(false, "Damn it, this shouldn't have happened.");
+ });
+
+ waitForSourceAndCaretAndScopes(gPanel, "-02.js", 1)
+ .then(tryShowPopup)
+ .then(focusEditor)
+ .then(testFocusLost)
+ .then(() => resumeDebuggerThenCloseAndFinish(gPanel))
+ .then(null, aError => {
+ ok(false, "Got an error: " + aError.message + "\n" + aError.stack);
+ });
+
+ callInTab(gTab, "firstCall");
+ });
+}
+
+function tryShowPopup() {
+ setText(gSearchBox, "#call()");
+ ok(isCaretPos(gPanel, 4, 22),
+ "The editor caret position appears to be correct.");
+ ok(isEditorSel(gPanel, [125, 131]),
+ "The editor selection appears to be correct.");
+ is(gEditor.getSelection(), "Call()",
+ "The editor selected text appears to be correct.");
+
+ is(gSearchBoxPanel.state, "closed",
+ "The search box panel shouldn't be visible yet.");
+
+ EventUtils.sendMouseEvent({ type: "click" }, gSearchBox, gDebugger);
+}
+
+function focusEditor() {
+ let deferred = promise.defer();
+
+ // Focusing the editor takes a tick to update the caret and selection.
+ gEditor.focus();
+ executeSoon(deferred.resolve);
+
+ return deferred.promise;
+}
+
+function testFocusLost() {
+ ok(isCaretPos(gPanel, 4, 22),
+ "The editor caret position appears to be correct after gaining focus.");
+ ok(isEditorSel(gPanel, [125, 131]),
+ "The editor selection appears to be correct after gaining focus.");
+ is(gEditor.getSelection(), "Call()",
+ "The editor selected text appears to be correct after gaining focus.");
+
+ is(gSearchBoxPanel.state, "closed",
+ "The search box panel should still not be visible.");
+}
+
+registerCleanupFunction(function () {
+ gTab = null;
+ gPanel = null;
+ gDebugger = null;
+ gEditor = null;
+ gSearchBox = null;
+ gSearchBoxPanel = null;
+});
diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_searchbox-parse.js b/devtools/client/debugger/test/mochitest/browser_dbg_searchbox-parse.js
new file mode 100644
index 000000000..2bb8e4150
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_searchbox-parse.js
@@ -0,0 +1,126 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Tests that text entered in the debugger's searchbox is properly parsed.
+ */
+
+function test() {
+ initDebugger().then(([aTab,, aPanel]) => {
+ let filterView = aPanel.panelWin.DebuggerView.Filtering;
+ let searchbox = aPanel.panelWin.DebuggerView.Filtering._searchbox;
+
+ setText(searchbox, "");
+ is(filterView.searchData.toSource(), '["", [""]]',
+ "The searchbox data wasn't parsed correctly (1).");
+
+ setText(searchbox, "#token");
+ is(filterView.searchData.toSource(), '["#", ["", "token"]]',
+ "The searchbox data wasn't parsed correctly (2).");
+
+ setText(searchbox, ":42");
+ is(filterView.searchData.toSource(), '[":", ["", 42]]',
+ "The searchbox data wasn't parsed correctly (3).");
+
+ setText(searchbox, "#token:42");
+ is(filterView.searchData.toSource(), '["#", ["", "token:42"]]',
+ "The searchbox data wasn't parsed correctly (4).");
+
+ setText(searchbox, ":42#token");
+ is(filterView.searchData.toSource(), '["#", [":42", "token"]]',
+ "The searchbox data wasn't parsed correctly (5).");
+
+ setText(searchbox, "#token:42#token:42");
+ is(filterView.searchData.toSource(), '["#", ["#token:42", "token:42"]]',
+ "The searchbox data wasn't parsed correctly (6).");
+
+ setText(searchbox, ":42#token:42#token");
+ is(filterView.searchData.toSource(), '["#", [":42#token:42", "token"]]',
+ "The searchbox data wasn't parsed correctly (7).");
+
+
+ setText(searchbox, "file");
+ is(filterView.searchData.toSource(), '["", ["file"]]',
+ "The searchbox data wasn't parsed correctly (8).");
+
+ setText(searchbox, "file#token");
+ is(filterView.searchData.toSource(), '["#", ["file", "token"]]',
+ "The searchbox data wasn't parsed correctly (9).");
+
+ setText(searchbox, "file:42");
+ is(filterView.searchData.toSource(), '[":", ["file", 42]]',
+ "The searchbox data wasn't parsed correctly (10).");
+
+ setText(searchbox, "file#token:42");
+ is(filterView.searchData.toSource(), '["#", ["file", "token:42"]]',
+ "The searchbox data wasn't parsed correctly (11).");
+
+ setText(searchbox, "file:42#token");
+ is(filterView.searchData.toSource(), '["#", ["file:42", "token"]]',
+ "The searchbox data wasn't parsed correctly (12).");
+
+ setText(searchbox, "file#token:42#token:42");
+ is(filterView.searchData.toSource(), '["#", ["file#token:42", "token:42"]]',
+ "The searchbox data wasn't parsed correctly (13).");
+
+ setText(searchbox, "file:42#token:42#token");
+ is(filterView.searchData.toSource(), '["#", ["file:42#token:42", "token"]]',
+ "The searchbox data wasn't parsed correctly (14).");
+
+
+ setText(searchbox, "!token");
+ is(filterView.searchData.toSource(), '["!", ["token"]]',
+ "The searchbox data wasn't parsed correctly (15).");
+
+ setText(searchbox, "!token#global");
+ is(filterView.searchData.toSource(), '["!", ["token#global"]]',
+ "The searchbox data wasn't parsed correctly (16).");
+
+ setText(searchbox, "!token#global:42");
+ is(filterView.searchData.toSource(), '["!", ["token#global:42"]]',
+ "The searchbox data wasn't parsed correctly (17).");
+
+ setText(searchbox, "!token:42#global");
+ is(filterView.searchData.toSource(), '["!", ["token:42#global"]]',
+ "The searchbox data wasn't parsed correctly (18).");
+
+
+ setText(searchbox, "@token");
+ is(filterView.searchData.toSource(), '["@", ["token"]]',
+ "The searchbox data wasn't parsed correctly (19).");
+
+ setText(searchbox, "@token#global");
+ is(filterView.searchData.toSource(), '["@", ["token#global"]]',
+ "The searchbox data wasn't parsed correctly (20).");
+
+ setText(searchbox, "@token#global:42");
+ is(filterView.searchData.toSource(), '["@", ["token#global:42"]]',
+ "The searchbox data wasn't parsed correctly (21).");
+
+ setText(searchbox, "@token:42#global");
+ is(filterView.searchData.toSource(), '["@", ["token:42#global"]]',
+ "The searchbox data wasn't parsed correctly (22).");
+
+
+ setText(searchbox, "*token");
+ is(filterView.searchData.toSource(), '["*", ["token"]]',
+ "The searchbox data wasn't parsed correctly (23).");
+
+ setText(searchbox, "*token#global");
+ is(filterView.searchData.toSource(), '["*", ["token#global"]]',
+ "The searchbox data wasn't parsed correctly (24).");
+
+ setText(searchbox, "*token#global:42");
+ is(filterView.searchData.toSource(), '["*", ["token#global:42"]]',
+ "The searchbox data wasn't parsed correctly (25).");
+
+ setText(searchbox, "*token:42#global");
+ is(filterView.searchData.toSource(), '["*", ["token:42#global"]]',
+ "The searchbox data wasn't parsed correctly (26).");
+
+
+ closeDebuggerAndFinish(aPanel);
+ });
+}
diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_server-conditional-bp-01.js b/devtools/client/debugger/test/mochitest/browser_dbg_server-conditional-bp-01.js
new file mode 100644
index 000000000..b318d8798
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_server-conditional-bp-01.js
@@ -0,0 +1,218 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Test adding conditional breakpoints (with server-side support)
+ */
+
+const TAB_URL = EXAMPLE_URL + "doc_conditional-breakpoints.html";
+
+function test() {
+ // Linux debug test slaves are a bit slow at this test sometimes.
+ requestLongerTimeout(2);
+
+ let options = {
+ source: TAB_URL,
+ line: 1
+ };
+ initDebugger(TAB_URL, options).then(([aTab,, aPanel]) => {
+ const gTab = aTab;
+ const gPanel = aPanel;
+ const gDebugger = gPanel.panelWin;
+ const gEditor = gDebugger.DebuggerView.editor;
+ const gSources = gDebugger.DebuggerView.Sources;
+ const queries = gDebugger.require("./content/queries");
+ const constants = gDebugger.require("./content/constants");
+ const actions = bindActionCreators(gPanel);
+ const getState = gDebugger.DebuggerController.getState;
+
+ const addBreakpoints = Task.async(function* () {
+ yield actions.addBreakpoint({ actor: gSources.selectedValue, line: 18 },
+ "undefined");
+ yield actions.addBreakpoint({ actor: gSources.selectedValue, line: 19 },
+ "null");
+ yield actions.addBreakpoint({ actor: gSources.selectedValue, line: 20 },
+ "42");
+ yield actions.addBreakpoint({ actor: gSources.selectedValue, line: 21 },
+ "true");
+ yield actions.addBreakpoint({ actor: gSources.selectedValue, line: 22 },
+ "'nasu'");
+ yield actions.addBreakpoint({ actor: gSources.selectedValue, line: 23 },
+ "/regexp/");
+ yield actions.addBreakpoint({ actor: gSources.selectedValue, line: 24 },
+ "({})");
+ yield actions.addBreakpoint({ actor: gSources.selectedValue, line: 25 },
+ "(function() {})");
+ yield actions.addBreakpoint({ actor: gSources.selectedValue, line: 26 },
+ "(function() { return false; })()");
+ yield actions.addBreakpoint({ actor: gSources.selectedValue, line: 27 },
+ "a");
+ yield actions.addBreakpoint({ actor: gSources.selectedValue, line: 28 },
+ "a !== undefined");
+ yield actions.addBreakpoint({ actor: gSources.selectedValue, line: 29 },
+ "b");
+ yield actions.addBreakpoint({ actor: gSources.selectedValue, line: 30 },
+ "a !== null");
+ });
+
+ function resumeAndTestBreakpoint(line) {
+ let finished = waitForCaretUpdated(gPanel, line).then(() => testBreakpoint(line));
+
+ EventUtils.sendMouseEvent({ type: "mousedown" },
+ gDebugger.document.getElementById("resume"),
+ gDebugger);
+
+ return finished;
+ }
+
+ function resumeAndTestNoBreakpoint() {
+ let finished = waitForDebuggerEvents(gPanel, gDebugger.EVENTS.AFTER_FRAMES_CLEARED).then(() => {
+ is(gSources.itemCount, 1,
+ "Found the expected number of sources.");
+ is(gEditor.getText().indexOf("ermahgerd"), 253,
+ "The correct source was loaded initially.");
+ is(gSources.selectedValue, gSources.values[0],
+ "The correct source is selected.");
+
+ ok(gSources.selectedItem,
+ "There should be a selected source in the sources pane.");
+ ok(!gSources._selectedBreakpoint,
+ "There should be no selected breakpoint in the sources pane.");
+ is(gSources._conditionalPopupVisible, false,
+ "The breakpoint conditional expression popup should not be shown.");
+
+ is(gDebugger.document.querySelectorAll(".dbg-stackframe").length, 0,
+ "There should be no visible stackframes.");
+ is(gDebugger.document.querySelectorAll(".dbg-breakpoint").length, 13,
+ "There should be thirteen visible breakpoints.");
+ });
+
+ gDebugger.gThreadClient.resume();
+
+ return finished;
+ }
+
+ function testBreakpoint(line, highlightBreakpoint) {
+ // Highlight the breakpoint only if required.
+ if (highlightBreakpoint) {
+ let finished = waitForCaretUpdated(gPanel, line).then(() => testBreakpoint(line));
+ gSources.highlightBreakpoint({ actor: gSources.selectedValue, line: line });
+ return finished;
+ }
+
+ let selectedActor = gSources.selectedValue;
+ let selectedBreakpoint = gSources._selectedBreakpoint;
+ let selectedBreakpointItem = gSources._getBreakpoint(selectedBreakpoint);
+
+ ok(selectedActor,
+ "There should be a selected item in the sources pane.");
+ ok(selectedBreakpoint,
+ "There should be a selected breakpoint in the sources pane.");
+
+ let source = gSources.selectedItem.attachment.source;
+ let bp = queries.getBreakpoint(getState(), selectedBreakpoint.location);
+
+ ok(bp, "The selected breakpoint exists");
+ is(bp.location.actor, source.actor,
+ "The breakpoint on line " + line + " wasn't added on the correct source.");
+ is(bp.location.line, line,
+ "The breakpoint on line " + line + " wasn't found.");
+ is(!!bp.disabled, false,
+ "The breakpoint on line " + line + " should be enabled.");
+ is(!!selectedBreakpointItem.attachment.openPopup, false,
+ "The breakpoint on line " + line + " should not have opened a popup.");
+ is(gSources._conditionalPopupVisible, false,
+ "The breakpoint conditional expression popup should not have been shown.");
+ isnot(bp.condition, undefined,
+ "The breakpoint on line " + line + " should have a conditional expression.");
+ ok(isCaretPos(gPanel, line),
+ "The editor caret position is not properly set.");
+ }
+
+ const testAfterReload = Task.async(function* () {
+ let selectedActor = gSources.selectedValue;
+ let selectedBreakpoint = gSources._selectedBreakpoint;
+
+ ok(selectedActor,
+ "There should be a selected item in the sources pane after reload.");
+ ok(!selectedBreakpoint,
+ "There should be no selected breakpoint in the sources pane after reload.");
+
+ yield testBreakpoint(18, true);
+ yield testBreakpoint(19, true);
+ yield testBreakpoint(20, true);
+ yield testBreakpoint(21, true);
+ yield testBreakpoint(22, true);
+ yield testBreakpoint(23, true);
+ yield testBreakpoint(24, true);
+ yield testBreakpoint(25, true);
+ yield testBreakpoint(26, true);
+ yield testBreakpoint(27, true);
+ yield testBreakpoint(28, true);
+ yield testBreakpoint(29, true);
+ yield testBreakpoint(30, true);
+
+ is(gSources.itemCount, 1,
+ "Found the expected number of sources.");
+ is(gEditor.getText().indexOf("ermahgerd"), 253,
+ "The correct source was loaded again.");
+ is(gSources.selectedValue, gSources.values[0],
+ "The correct source is selected.");
+
+ ok(gSources.selectedItem,
+ "There should be a selected source in the sources pane.");
+ ok(gSources._selectedBreakpoint,
+ "There should be a selected breakpoint in the sources pane.");
+ is(gSources._conditionalPopupVisible, false,
+ "The breakpoint conditional expression popup should not be shown.");
+ });
+
+ Task.spawn(function* () {
+ let onCaretUpdated = waitForCaretAndScopes(gPanel, 17);
+ callInTab(gTab, "ermahgerd");
+ yield onCaretUpdated;
+
+ yield addBreakpoints();
+
+ is(gDebugger.gThreadClient.state, "paused",
+ "Should only be getting stack frames while paused.");
+ is(queries.getSourceCount(getState()), 1,
+ "Found the expected number of sources.");
+ is(gEditor.getText().indexOf("ermahgerd"), 253,
+ "The correct source was loaded initially.");
+ is(gSources.selectedValue, gSources.values[0],
+ "The correct source is selected.");
+ is(queries.getBreakpoints(getState()).length, 13,
+ "13 breakpoints currently added.");
+
+ yield resumeAndTestBreakpoint(20);
+ yield resumeAndTestBreakpoint(21);
+ yield resumeAndTestBreakpoint(22);
+ yield resumeAndTestBreakpoint(23);
+ yield resumeAndTestBreakpoint(24);
+ yield resumeAndTestBreakpoint(25);
+ yield resumeAndTestBreakpoint(27);
+ yield resumeAndTestBreakpoint(28);
+ yield resumeAndTestBreakpoint(29);
+ yield resumeAndTestBreakpoint(30);
+ yield resumeAndTestNoBreakpoint();
+
+ let sourceShown = waitForSourceShown(gPanel, ".html");
+ reload(gPanel),
+ yield sourceShown;
+ testAfterReload();
+
+ // When a breakpoint is highlighted, the conditional expression
+ // popup opens, and then closes, and when it closes it sends the
+ // expression to the server which pauses the server. Make sure
+ // we wait if there is a pending request.
+ if (gDebugger.gThreadClient.state === "paused") {
+ yield waitForThreadEvents(gPanel, "resumed");
+ }
+
+ closeDebuggerAndFinish(gPanel);
+ });
+ });
+}
diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_server-conditional-bp-02.js b/devtools/client/debugger/test/mochitest/browser_dbg_server-conditional-bp-02.js
new file mode 100644
index 000000000..31f2af36c
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_server-conditional-bp-02.js
@@ -0,0 +1,214 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Test adding and modifying conditional breakpoints (with server-side support)
+ */
+
+const TAB_URL = EXAMPLE_URL + "doc_conditional-breakpoints.html";
+
+function test() {
+ let options = {
+ source: TAB_URL,
+ line: 1
+ };
+ initDebugger(TAB_URL, options).then(([aTab,, aPanel]) => {
+ const gTab = aTab;
+ const gPanel = aPanel;
+ const gDebugger = gPanel.panelWin;
+ const gEditor = gDebugger.DebuggerView.editor;
+ const gSources = gDebugger.DebuggerView.Sources;
+ const queries = gDebugger.require("./content/queries");
+ const constants = gDebugger.require("./content/constants");
+ const actions = bindActionCreators(gPanel);
+ const getState = gDebugger.DebuggerController.getState;
+ const CONDITIONAL_POPUP_SHOWN = gDebugger.EVENTS.CONDITIONAL_BREAKPOINT_POPUP_SHOWN;
+
+ function addBreakpoint1() {
+ return actions.addBreakpoint({ actor: gSources.selectedValue, line: 18 });
+ }
+
+ function addBreakpoint2() {
+ let finished = waitForDispatch(gPanel, constants.ADD_BREAKPOINT);
+ setCaretPosition(19);
+ gSources._onCmdAddBreakpoint();
+ return finished;
+ }
+
+ function modBreakpoint2() {
+ setCaretPosition(19);
+
+ let popupShown = waitForDebuggerEvents(gPanel, CONDITIONAL_POPUP_SHOWN);
+ gSources._onCmdAddConditionalBreakpoint();
+ return popupShown;
+ }
+
+ function* addBreakpoint3() {
+ let finished = waitForDispatch(gPanel, constants.ADD_BREAKPOINT);
+ let popupShown = waitForDebuggerEvents(gPanel, CONDITIONAL_POPUP_SHOWN);
+
+ setCaretPosition(20);
+ gSources._onCmdAddConditionalBreakpoint();
+ yield finished;
+ yield popupShown;
+ }
+
+ function* modBreakpoint3() {
+ setCaretPosition(20);
+
+ let popupShown = waitForDebuggerEvents(gPanel, CONDITIONAL_POPUP_SHOWN);
+ gSources._onCmdAddConditionalBreakpoint();
+ yield popupShown;
+
+ typeText(gSources._cbTextbox, "bamboocha");
+
+ let finished = waitForDispatch(gPanel, constants.SET_BREAKPOINT_CONDITION);
+ EventUtils.sendKey("RETURN", gDebugger);
+ yield finished;
+ }
+
+ function addBreakpoint4() {
+ let finished = waitForDispatch(gPanel, constants.ADD_BREAKPOINT);
+ setCaretPosition(21);
+ gSources._onCmdAddBreakpoint();
+ return finished;
+ }
+
+ function delBreakpoint4() {
+ let finished = waitForDispatch(gPanel, constants.REMOVE_BREAKPOINT);
+ setCaretPosition(21);
+ gSources._onCmdAddBreakpoint();
+ return finished;
+ }
+
+ function testBreakpoint(aLine, aPopupVisible, aConditionalExpression) {
+ const source = queries.getSelectedSource(getState());
+ ok(source,
+ "There should be a selected item in the sources pane.");
+
+ const bp = queries.getBreakpoint(getState(), {
+ actor: source.actor,
+ line: aLine
+ });
+ const bpItem = gSources._getBreakpoint(bp);
+ ok(bp, "There should be a breakpoint.");
+ ok(bpItem, "There should be a breakpoint in the sources pane.");
+
+ is(bp.location.actor, source.actor,
+ "The breakpoint on line " + aLine + " wasn't added on the correct source.");
+ is(bp.location.line, aLine,
+ "The breakpoint on line " + aLine + " wasn't found.");
+ is(!!bp.disabled, false,
+ "The breakpoint on line " + aLine + " should be enabled.");
+ is(gSources._conditionalPopupVisible, aPopupVisible,
+ "The breakpoint on line " + aLine + " should have a correct popup state (2).");
+ is(bp.condition, aConditionalExpression,
+ "The breakpoint on line " + aLine + " should have a correct conditional expression.");
+ }
+
+ function testNoBreakpoint(aLine) {
+ let selectedActor = gSources.selectedValue;
+ let selectedBreakpoint = gSources._selectedBreakpoint;
+
+ ok(selectedActor,
+ "There should be a selected item in the sources pane for line " + aLine + ".");
+ ok(!selectedBreakpoint,
+ "There should be no selected brekapoint in the sources pane for line " + aLine + ".");
+
+ ok(isCaretPos(gPanel, aLine),
+ "The editor caret position is not properly set.");
+ }
+
+ function setCaretPosition(aLine) {
+ gEditor.setCursor({ line: aLine - 1, ch: 0 });
+ }
+
+ function clickOnBreakpoint(aIndex) {
+ EventUtils.sendMouseEvent({ type: "click" },
+ gDebugger.document.querySelectorAll(".dbg-breakpoint")[aIndex],
+ gDebugger);
+ }
+
+ function waitForConditionUpdate() {
+ // This will close the popup and send another request to update
+ // the condition
+ gSources._hideConditionalPopup();
+ return waitForDispatch(gPanel, constants.SET_BREAKPOINT_CONDITION);
+ }
+
+ Task.spawn(function* () {
+ let onCaretUpdated = waitForCaretAndScopes(gPanel, 17);
+ callInTab(gTab, "ermahgerd");
+ yield onCaretUpdated;
+
+ is(gDebugger.gThreadClient.state, "paused",
+ "Should only be getting stack frames while paused.");
+ is(queries.getSourceCount(getState()), 1,
+ "Found the expected number of sources.");
+ is(gEditor.getText().indexOf("ermahgerd"), 253,
+ "The correct source was loaded initially.");
+ is(gSources.selectedValue, gSources.values[0],
+ "The correct source is selected.");
+
+ is(queries.getBreakpoints(getState()).length, 0,
+ "No breakpoints currently added.");
+
+ yield addBreakpoint1();
+ testBreakpoint(18, false, undefined);
+
+ yield addBreakpoint2();
+ testBreakpoint(19, false, undefined);
+ yield modBreakpoint2();
+ testBreakpoint(19, true, undefined);
+ yield waitForConditionUpdate();
+ yield addBreakpoint3();
+ testBreakpoint(20, true, "");
+ yield waitForConditionUpdate();
+ yield modBreakpoint3();
+ testBreakpoint(20, false, "bamboocha");
+ yield addBreakpoint4();
+ testBreakpoint(21, false, undefined);
+ yield delBreakpoint4();
+
+ setCaretPosition(18);
+ is(gSources._selectedBreakpoint.location.line, 18,
+ "The selected breakpoint is line 18");
+ yield testBreakpoint(18, false, undefined);
+
+ setCaretPosition(19);
+ is(gSources._selectedBreakpoint.location.line, 19,
+ "The selected breakpoint is line 19");
+ yield testBreakpoint(19, false, "");
+
+ setCaretPosition(20);
+ is(gSources._selectedBreakpoint.location.line, 20,
+ "The selected breakpoint is line 20");
+ yield testBreakpoint(20, false, "bamboocha");
+
+ setCaretPosition(17);
+ yield testNoBreakpoint(17);
+
+ setCaretPosition(21);
+ yield testNoBreakpoint(21);
+
+ clickOnBreakpoint(0);
+ is(gSources._selectedBreakpoint.location.line, 18,
+ "The selected breakpoint is line 18");
+ yield testBreakpoint(18, false, undefined);
+
+ clickOnBreakpoint(1);
+ is(gSources._selectedBreakpoint.location.line, 19,
+ "The selected breakpoint is line 19");
+ yield testBreakpoint(19, false, "");
+
+ clickOnBreakpoint(2);
+ is(gSources._selectedBreakpoint.location.line, 20,
+ "The selected breakpoint is line 20");
+ testBreakpoint(20, true, "bamboocha");
+
+ resumeDebuggerThenCloseAndFinish(gPanel);
+ });
+ });
+}
diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_server-conditional-bp-03.js b/devtools/client/debugger/test/mochitest/browser_dbg_server-conditional-bp-03.js
new file mode 100644
index 000000000..b83c96e39
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_server-conditional-bp-03.js
@@ -0,0 +1,73 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Test that conditional breakpoints survive disabled breakpoints
+ * (with server-side support)
+ */
+
+const TAB_URL = EXAMPLE_URL + "doc_conditional-breakpoints.html";
+
+function test() {
+ let options = {
+ source: TAB_URL,
+ line: 1
+ };
+ initDebugger(TAB_URL, options).then(([aTab,, aPanel]) => {
+ const gTab = aTab;
+ const gPanel = aPanel;
+ const gDebugger = gPanel.panelWin;
+ const gSources = gDebugger.DebuggerView.Sources;
+ const queries = gDebugger.require("./content/queries");
+ const constants = gDebugger.require("./content/constants");
+ const actions = bindActionCreators(gPanel);
+ const getState = gDebugger.DebuggerController.getState;
+
+ function waitForConditionUpdate() {
+ // This will close the popup and send another request to update
+ // the condition
+ gSources._hideConditionalPopup();
+ return waitForDispatch(gPanel, constants.SET_BREAKPOINT_CONDITION);
+ }
+
+ Task.spawn(function* () {
+ let onCaretUpdated = waitForCaretAndScopes(gPanel, 17);
+ callInTab(gTab, "ermahgerd");
+ yield onCaretUpdated;
+
+ const location = { actor: gSources.selectedValue, line: 18 };
+
+ yield actions.addBreakpoint(location, "hello");
+ yield actions.disableBreakpoint(location);
+ yield actions.addBreakpoint(location);
+
+ const bp = queries.getBreakpoint(getState(), location);
+ is(bp.condition, "hello", "The conditional expression is correct.");
+
+ let finished = waitForDebuggerEvents(gPanel, gDebugger.EVENTS.CONDITIONAL_BREAKPOINT_POPUP_SHOWN);
+ EventUtils.sendMouseEvent({ type: "click" },
+ gDebugger.document.querySelector(".dbg-breakpoint"),
+ gDebugger);
+ yield finished;
+
+ const textbox = gDebugger.document.getElementById("conditional-breakpoint-panel-textbox");
+ is(textbox.value, "hello", "The expression is correct (2).");
+
+ yield waitForConditionUpdate();
+ yield actions.disableBreakpoint(location);
+ yield actions.setBreakpointCondition(location, "foo");
+ yield actions.addBreakpoint(location);
+
+ finished = waitForDebuggerEvents(gPanel, gDebugger.EVENTS.CONDITIONAL_BREAKPOINT_POPUP_SHOWN);
+ EventUtils.sendMouseEvent({ type: "click" },
+ gDebugger.document.querySelector(".dbg-breakpoint"),
+ gDebugger);
+ yield finished;
+ is(textbox.value, "foo", "The expression is correct (3).");
+
+ yield resumeDebuggerThenCloseAndFinish(gPanel);
+ });
+ });
+}
diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_server-conditional-bp-04.js b/devtools/client/debugger/test/mochitest/browser_dbg_server-conditional-bp-04.js
new file mode 100644
index 000000000..2f35c4d60
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_server-conditional-bp-04.js
@@ -0,0 +1,46 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Make sure that conditional breakpoints with undefined expressions
+ * maintain their conditions when re-enabling them (with
+ * server-side support)
+ */
+
+const TAB_URL = EXAMPLE_URL + "doc_conditional-breakpoints.html";
+
+function test() {
+ let options = {
+ source: TAB_URL,
+ line: 1
+ };
+ initDebugger(TAB_URL, options).then(([aTab,, aPanel]) => {
+ const gTab = aTab;
+ const gPanel = aPanel;
+ const gDebugger = gPanel.panelWin;
+ const gSources = gDebugger.DebuggerView.Sources;
+ const queries = gDebugger.require("./content/queries");
+ const constants = gDebugger.require("./content/constants");
+ const actions = bindActionCreators(gPanel);
+ const getState = gDebugger.DebuggerController.getState;
+
+ Task.spawn(function* () {
+ let onCaretUpdated = waitForCaretAndScopes(gPanel, 17);
+ callInTab(gTab, "ermahgerd");
+ yield onCaretUpdated;
+
+ const location = { actor: gSources.selectedValue, line: 18 };
+
+ yield actions.addBreakpoint(location, "");
+ yield actions.disableBreakpoint(location);
+ yield actions.addBreakpoint(location);
+
+ const bp = queries.getBreakpoint(getState(), location);
+ is(bp.condition, "", "The conditional expression is correct.");
+
+ resumeDebuggerThenCloseAndFinish(gPanel);
+ });
+ });
+}
diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_server-conditional-bp-05.js b/devtools/client/debugger/test/mochitest/browser_dbg_server-conditional-bp-05.js
new file mode 100644
index 000000000..21607d8fd
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_server-conditional-bp-05.js
@@ -0,0 +1,134 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Test conditional breakpoints throwing exceptions
+ * with server support
+ */
+
+const TAB_URL = EXAMPLE_URL + "doc_conditional-breakpoints.html";
+
+function test() {
+ const options = {
+ source: TAB_URL,
+ line: 1
+ };
+ initDebugger(TAB_URL, options).then(([aTab,, aPanel]) => {
+ const gTab = aTab;
+ const gPanel = aPanel;
+ const gDebugger = gPanel.panelWin;
+ const gEditor = gDebugger.DebuggerView.editor;
+ const gSources = gDebugger.DebuggerView.Sources;
+ const queries = gDebugger.require("./content/queries");
+ const constants = gDebugger.require("./content/constants");
+ const actions = bindActionCreators(gPanel);
+ const getState = gDebugger.DebuggerController.getState;
+
+ function resumeAndTestBreakpoint(line) {
+ let finished = waitForCaretUpdated(gPanel, line).then(() => testBreakpoint(line));
+ EventUtils.sendMouseEvent({ type: "mousedown" },
+ gDebugger.document.getElementById("resume"),
+ gDebugger);
+
+ return finished;
+ }
+
+ function resumeAndTestNoBreakpoint() {
+ let finished = waitForDebuggerEvents(gPanel, gDebugger.EVENTS.AFTER_FRAMES_CLEARED).then(() => {
+ is(gSources.itemCount, 1,
+ "Found the expected number of sources.");
+ is(gEditor.getText().indexOf("ermahgerd"), 253,
+ "The correct source was loaded initially.");
+ is(gSources.selectedValue, gSources.values[0],
+ "The correct source is selected.");
+
+ ok(gSources.selectedItem,
+ "There should be a selected source in the sources pane.");
+ ok(!gSources._selectedBreakpoint,
+ "There should be no selected breakpoint in the sources pane.");
+ is(gSources._conditionalPopupVisible, false,
+ "The breakpoint conditional expression popup should not be shown.");
+
+ is(gDebugger.document.querySelectorAll(".dbg-stackframe").length, 0,
+ "There should be no visible stackframes.");
+ is(gDebugger.document.querySelectorAll(".dbg-breakpoint").length, 6,
+ "There should be thirteen visible breakpoints.");
+ });
+
+ gDebugger.gThreadClient.resume();
+
+ return finished;
+ }
+
+ function testBreakpoint(line, highlightBreakpoint) {
+ // Highlight the breakpoint only if required.
+ if (highlightBreakpoint) {
+ let finished = waitForCaretUpdated(gPanel, line).then(() => testBreakpoint(line));
+ gSources.highlightBreakpoint({ actor: gSources.selectedValue, line: line });
+ return finished;
+ }
+
+ let selectedActor = gSources.selectedValue;
+ let selectedBreakpoint = gSources._selectedBreakpoint;
+ let selectedBreakpointItem = gSources._getBreakpoint(selectedBreakpoint);
+ let source = queries.getSource(getState(), selectedActor);
+
+ ok(selectedActor,
+ "There should be a selected item in the sources pane.");
+ ok(selectedBreakpoint,
+ "There should be a selected breakpoint.");
+ ok(selectedBreakpointItem,
+ "There should be a selected breakpoint item in the sources pane.");
+
+ is(selectedBreakpoint.location.actor, source.actor,
+ "The breakpoint on line " + line + " wasn't added on the correct source.");
+ is(selectedBreakpoint.location.line, line,
+ "The breakpoint on line " + line + " wasn't found.");
+ is(!!selectedBreakpoint.location.disabled, false,
+ "The breakpoint on line " + line + " should be enabled.");
+ is(gSources._conditionalPopupVisible, false,
+ "The breakpoint conditional expression popup should not have been shown.");
+
+ isnot(selectedBreakpoint.condition, undefined,
+ "The breakpoint on line " + line + " should have a conditional expression.");
+
+ ok(isCaretPos(gPanel, line),
+ "The editor caret position is not properly set.");
+ }
+
+ Task.spawn(function* () {
+ let onCaretUpdated = waitForCaretAndScopes(gPanel, 17);
+ callInTab(gTab, "ermahgerd");
+ yield onCaretUpdated;
+
+ yield actions.addBreakpoint(
+ { actor: gSources.selectedValue, line: 18 }, " 1a"
+ );
+ yield actions.addBreakpoint(
+ { actor: gSources.selectedValue, line: 19 }, "new Error()"
+ );
+ yield actions.addBreakpoint(
+ { actor: gSources.selectedValue, line: 20 }, "true"
+ );
+ yield actions.addBreakpoint(
+ { actor: gSources.selectedValue, line: 21 }, "false"
+ );
+ yield actions.addBreakpoint(
+ { actor: gSources.selectedValue, line: 22 }, "0"
+ );
+ yield actions.addBreakpoint(
+ { actor: gSources.selectedValue, line: 23 }, "randomVar"
+ );
+
+ yield resumeAndTestBreakpoint(18);
+ yield resumeAndTestBreakpoint(19);
+ yield resumeAndTestBreakpoint(20);
+ yield resumeAndTestBreakpoint(23);
+ yield resumeAndTestNoBreakpoint();
+
+ closeDebuggerAndFinish(gPanel);
+ });
+ });
+}
diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_source-maps-01.js b/devtools/client/debugger/test/mochitest/browser_dbg_source-maps-01.js
new file mode 100644
index 000000000..c0681fec6
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_source-maps-01.js
@@ -0,0 +1,170 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Test that we can set breakpoints and step through source mapped
+ * coffee script.
+ */
+
+const TAB_URL = EXAMPLE_URL + "doc_binary_search.html";
+const COFFEE_URL = EXAMPLE_URL + "code_binary_search.coffee";
+
+var gTab, gPanel, gDebugger;
+var gEditor, gSources;
+
+function test() {
+ let options = {
+ source: COFFEE_URL,
+ line: 1
+ };
+ initDebugger(TAB_URL, options).then(([aTab,, aPanel]) => {
+ gTab = aTab;
+ gPanel = aPanel;
+ gDebugger = gPanel.panelWin;
+ gEditor = gDebugger.DebuggerView.editor;
+ gSources = gDebugger.DebuggerView.Sources;
+
+ checkSourceMapsEnabled();
+
+ checkInitialSource();
+ testSetBreakpoint()
+ .then(testSetBreakpointBlankLine)
+ .then(testHitBreakpoint)
+ .then(testStepping)
+ .then(() => resumeDebuggerThenCloseAndFinish(gPanel))
+ .then(null, aError => {
+ ok(false, "Got an error: " + aError.message + "\n" + aError.stack);
+ });
+ });
+}
+
+function checkSourceMapsEnabled() {
+ is(Services.prefs.getBoolPref("devtools.debugger.source-maps-enabled"), true,
+ "The source maps functionality should be enabled by default.");
+ is(gDebugger.Prefs.sourceMapsEnabled, true,
+ "The source maps pref should be true from startup.");
+ is(gDebugger.DebuggerView.Options._showOriginalSourceItem.getAttribute("checked"), "true",
+ "Source maps should be enabled from startup.");
+}
+
+function checkInitialSource() {
+ isnot(gSources.selectedItem.attachment.source.url.indexOf(".coffee"), -1,
+ "The debugger should show the source mapped coffee source file.");
+ is(gSources.selectedValue.indexOf(".js"), -1,
+ "The debugger should not show the generated js source file.");
+ is(gEditor.getText().indexOf("isnt"), 218,
+ "The debugger's editor should have the coffee source source displayed.");
+ is(gEditor.getText().indexOf("function"), -1,
+ "The debugger's editor should not have the JS source displayed.");
+}
+
+function testSetBreakpoint() {
+ let deferred = promise.defer();
+ let sourceForm = getSourceForm(gSources, COFFEE_URL);
+
+ gDebugger.gThreadClient.interrupt(aResponse => {
+ let source = gDebugger.gThreadClient.source(sourceForm);
+ source.setBreakpoint({ line: 5 }, aResponse => {
+ ok(!aResponse.error,
+ "Should be able to set a breakpoint in a coffee source file.");
+ ok(!aResponse.actualLocation,
+ "Should be able to set a breakpoint on line 5.");
+
+ deferred.resolve();
+ });
+ });
+
+ return deferred.promise;
+}
+
+function testSetBreakpointBlankLine() {
+ let deferred = promise.defer();
+ let sourceForm = getSourceForm(gSources, COFFEE_URL);
+
+ let source = gDebugger.gThreadClient.source(sourceForm);
+ source.setBreakpoint({ line: 8 }, aResponse => {
+ ok(!aResponse.error,
+ "Should be able to set a breakpoint in a coffee source file on a blank line.");
+ ok(!aResponse.isPending,
+ "Should not be a pending breakpoint.");
+ ok(!aResponse.actualLocation,
+ "Should not be a moved breakpoint.");
+
+ deferred.resolve();
+ });
+
+ return deferred.promise;
+}
+
+function testHitBreakpoint() {
+ let deferred = promise.defer();
+
+ gDebugger.gThreadClient.resume(aResponse => {
+ ok(!aResponse.error, "Shouldn't get an error resuming.");
+ is(aResponse.type, "resumed", "Type should be 'resumed'.");
+
+ gDebugger.gThreadClient.addOneTimeListener("paused", (aEvent, aPacket) => {
+ is(aPacket.type, "paused",
+ "We should now be paused again.");
+ is(aPacket.why.type, "breakpoint",
+ "and the reason we should be paused is because we hit a breakpoint.");
+
+ // Check that we stopped at the right place, by making sure that the
+ // environment is in the state that we expect.
+ is(aPacket.frame.environment.bindings.variables.start.value, 0,
+ "'start' is 0.");
+ is(aPacket.frame.environment.bindings.variables.stop.value.type, "undefined",
+ "'stop' hasn't been assigned to yet.");
+ is(aPacket.frame.environment.bindings.variables.pivot.value.type, "undefined",
+ "'pivot' hasn't been assigned to yet.");
+
+ waitForCaretUpdated(gPanel, 5).then(deferred.resolve);
+ });
+
+ // This will cause the breakpoint to be hit, and put us back in the
+ // paused state.
+ callInTab(gTab, "binary_search", [0, 2, 3, 5, 7, 10], 5);
+ });
+
+ return deferred.promise;
+}
+
+function testStepping() {
+ let deferred = promise.defer();
+
+ gDebugger.gThreadClient.stepIn(aResponse => {
+ ok(!aResponse.error, "Shouldn't get an error resuming.");
+ is(aResponse.type, "resumed", "Type should be 'resumed'.");
+
+ // After stepping, we will pause again, so listen for that.
+ gDebugger.gThreadClient.addOneTimeListener("paused", (aEvent, aPacket) => {
+ is(aPacket.type, "paused",
+ "We should now be paused again.");
+ is(aPacket.why.type, "resumeLimit",
+ "and the reason we should be paused is because we hit the resume limit.");
+
+ // Check that we stopped at the right place, by making sure that the
+ // environment is in the state that we expect.
+ is(aPacket.frame.environment.bindings.variables.start.value, 0,
+ "'start' is 0.");
+ is(aPacket.frame.environment.bindings.variables.stop.value, 5,
+ "'stop' is 5.");
+ is(aPacket.frame.environment.bindings.variables.pivot.value.type, "undefined",
+ "'pivot' hasn't been assigned to yet.");
+
+ waitForCaretUpdated(gPanel, 6).then(deferred.resolve);
+ });
+ });
+
+ return deferred.promise;
+}
+
+registerCleanupFunction(function () {
+ gTab = null;
+ gPanel = null;
+ gDebugger = null;
+ gEditor = null;
+ gSources = null;
+});
diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_source-maps-02.js b/devtools/client/debugger/test/mochitest/browser_dbg_source-maps-02.js
new file mode 100644
index 000000000..e406c9ce4
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_source-maps-02.js
@@ -0,0 +1,153 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Test that we can toggle between the original and generated sources.
+ */
+
+const TAB_URL = EXAMPLE_URL + "doc_binary_search.html";
+const JS_URL = EXAMPLE_URL + "code_binary_search.js";
+
+var gTab, gPanel, gDebugger, gEditor;
+var gSources, gFrames, gPrefs, gOptions;
+
+function test() {
+ let options = {
+ source: EXAMPLE_URL + "code_binary_search.coffee",
+ line: 1
+ };
+ initDebugger(TAB_URL, options).then(([aTab,, aPanel]) => {
+ gTab = aTab;
+ gPanel = aPanel;
+ gDebugger = gPanel.panelWin;
+ gEditor = gDebugger.DebuggerView.editor;
+ gSources = gDebugger.DebuggerView.Sources;
+ gFrames = gDebugger.DebuggerView.StackFrames;
+ gPrefs = gDebugger.Prefs;
+ gOptions = gDebugger.DebuggerView.Options;
+
+ testToggleGeneratedSource()
+ .then(testSetBreakpoint)
+ .then(testToggleOnPause)
+ .then(testResume)
+ .then(() => closeDebuggerAndFinish(gPanel))
+ .then(null, aError => {
+ ok(false, "Got an error: " + aError.message + "\n" + aError.stack);
+ });
+ });
+}
+
+function testToggleGeneratedSource() {
+ let finished = waitForSourceShown(gPanel, ".js").then(() => {
+ is(gPrefs.sourceMapsEnabled, false,
+ "The source maps pref should have been set to false.");
+ is(gOptions._showOriginalSourceItem.getAttribute("checked"), "false",
+ "Source maps should now be disabled.");
+
+ is(gSources.selectedItem.attachment.source.url.indexOf(".coffee"), -1,
+ "The debugger should not show the source mapped coffee source file.");
+ isnot(gSources.selectedItem.attachment.source.url.indexOf(".js"), -1,
+ "The debugger should show the generated js source file.");
+
+ is(gEditor.getText().indexOf("isnt"), -1,
+ "The debugger's editor should not have the coffee source source displayed.");
+ is(gEditor.getText().indexOf("function"), 36,
+ "The debugger's editor should have the JS source displayed.");
+ });
+
+ gOptions._showOriginalSourceItem.setAttribute("checked", "false");
+ gOptions._toggleShowOriginalSource();
+ gOptions._onPopupHidden();
+
+ return finished;
+}
+
+function testSetBreakpoint() {
+ let deferred = promise.defer();
+ let sourceForm = getSourceForm(gSources, JS_URL);
+ let source = gDebugger.gThreadClient.source(sourceForm);
+
+ source.setBreakpoint({ line: 7 }, aResponse => {
+ ok(!aResponse.error,
+ "Should be able to set a breakpoint in a js file.");
+
+ gDebugger.gClient.addOneTimeListener("resumed", () => {
+ waitForCaretAndScopes(gPanel, 7).then(() => {
+ // Make sure that we have JavaScript stack frames.
+ is(gFrames.itemCount, 1,
+ "Should have only one frame.");
+ is(gFrames.getItemAtIndex(0).attachment.url.indexOf(".coffee"), -1,
+ "First frame should not be a coffee source frame.");
+ isnot(gFrames.getItemAtIndex(0).attachment.url.indexOf(".js"), -1,
+ "First frame should be a JS frame.");
+
+ deferred.resolve();
+ });
+
+ // This will cause the breakpoint to be hit, and put us back in the
+ // paused state.
+ callInTab(gTab, "binary_search", [0, 2, 3, 5, 7, 10], 5);
+ });
+ });
+
+ return deferred.promise;
+}
+
+function testToggleOnPause() {
+ let finished = waitForSourceAndCaretAndScopes(gPanel, ".coffee", 5).then(() => {
+ is(gPrefs.sourceMapsEnabled, true,
+ "The source maps pref should have been set to true.");
+ is(gOptions._showOriginalSourceItem.getAttribute("checked"), "true",
+ "Source maps should now be enabled.");
+
+ isnot(gSources.selectedItem.attachment.source.url.indexOf(".coffee"), -1,
+ "The debugger should show the source mapped coffee source file.");
+ is(gSources.selectedItem.attachment.source.url.indexOf(".js"), -1,
+ "The debugger should not show the generated js source file.");
+
+ is(gEditor.getText().indexOf("isnt"), 218,
+ "The debugger's editor should have the coffee source source displayed.");
+ is(gEditor.getText().indexOf("function"), -1,
+ "The debugger's editor should not have the JS source displayed.");
+
+ // Make sure that we have coffee source stack frames.
+ is(gFrames.itemCount, 1,
+ "Should have only one frame.");
+ is(gFrames.getItemAtIndex(0).attachment.url.indexOf(".js"), -1,
+ "First frame should not be a JS frame.");
+ isnot(gFrames.getItemAtIndex(0).attachment.url.indexOf(".coffee"), -1,
+ "First frame should be a coffee source frame.");
+ });
+
+ gOptions._showOriginalSourceItem.setAttribute("checked", "true");
+ gOptions._toggleShowOriginalSource();
+ gOptions._onPopupHidden();
+
+ return finished;
+}
+
+function testResume() {
+ let deferred = promise.defer();
+
+ gDebugger.gThreadClient.resume(aResponse => {
+ ok(!aResponse.error, "Shouldn't get an error resuming.");
+ is(aResponse.type, "resumed", "Type should be 'resumed'.");
+
+ deferred.resolve();
+ });
+
+ return deferred.promise;
+}
+
+registerCleanupFunction(function () {
+ gTab = null;
+ gPanel = null;
+ gDebugger = null;
+ gEditor = null;
+ gSources = null;
+ gFrames = null;
+ gPrefs = null;
+ gOptions = null;
+});
diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_source-maps-03.js b/devtools/client/debugger/test/mochitest/browser_dbg_source-maps-03.js
new file mode 100644
index 000000000..b729be49f
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_source-maps-03.js
@@ -0,0 +1,88 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Test that we can debug minified javascript with source maps.
+ */
+
+const TAB_URL = EXAMPLE_URL + "doc_minified.html";
+const JS_URL = EXAMPLE_URL + "code_math.js";
+
+var gTab, gPanel, gDebugger;
+var gEditor, gSources, gFrames;
+
+function test() {
+ let options = {
+ source: JS_URL,
+ line: 1
+ };
+ initDebugger(TAB_URL, options).then(([aTab,, aPanel]) => {
+ gTab = aTab;
+ gPanel = aPanel;
+ gDebugger = gPanel.panelWin;
+ gEditor = gDebugger.DebuggerView.editor;
+ gSources = gDebugger.DebuggerView.Sources;
+ gFrames = gDebugger.DebuggerView.StackFrames;
+
+ checkInitialSource()
+ testSetBreakpoint()
+ .then(() => resumeDebuggerThenCloseAndFinish(gPanel))
+ .then(null, aError => {
+ ok(false, "Got an error: " + aError.message + "\n" + aError.stack);
+ });
+ });
+}
+
+function checkInitialSource() {
+ isnot(gSources.selectedItem.attachment.source.url.indexOf(".js"), -1,
+ "The debugger should not show the minified js file.");
+ is(gSources.selectedItem.attachment.source.url.indexOf(".min.js"), -1,
+ "The debugger should show the original js file.");
+ is(gEditor.getText().split("\n").length, 46,
+ "The debugger's editor should have the original source displayed, " +
+ "not the whitespace stripped minified version.");
+}
+
+function testSetBreakpoint() {
+ let deferred = promise.defer();
+ let sourceForm = getSourceForm(gSources, JS_URL);
+ let source = gDebugger.gThreadClient.source(sourceForm);
+
+ source.setBreakpoint({ line: 30 }, aResponse => {
+ ok(!aResponse.error,
+ "Should be able to set a breakpoint in a js file.");
+ ok(!aResponse.actualLocation,
+ "Should be able to set a breakpoint on line 30.");
+
+ gDebugger.gClient.addOneTimeListener("resumed", () => {
+ waitForCaretAndScopes(gPanel, 30).then(() => {
+ // Make sure that we have the right stack frames.
+ is(gFrames.itemCount, 9,
+ "Should have nine frames.");
+ is(gFrames.getItemAtIndex(0).attachment.url.indexOf(".min.js"), -1,
+ "First frame should not be a minified JS frame.");
+ isnot(gFrames.getItemAtIndex(0).attachment.url.indexOf(".js"), -1,
+ "First frame should be a JS frame.");
+
+ deferred.resolve();
+ });
+
+ // This will cause the breakpoint to be hit, and put us back in the
+ // paused state.
+ callInTab(gTab, "arithmetic");
+ });
+ });
+
+ return deferred.promise;
+}
+
+registerCleanupFunction(function () {
+ gTab = null;
+ gPanel = null;
+ gDebugger = null;
+ gEditor = null;
+ gSources = null;
+ gFrames = null;
+});
diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_source-maps-04.js b/devtools/client/debugger/test/mochitest/browser_dbg_source-maps-04.js
new file mode 100644
index 000000000..f3c4e89a8
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_source-maps-04.js
@@ -0,0 +1,187 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Test that bogus source maps don't break debugging.
+ */
+
+const TAB_URL = EXAMPLE_URL + "doc_minified_bogus_map.html";
+const JS_URL = EXAMPLE_URL + "code_math_bogus_map.js";
+
+// This test causes an error to be logged in the console, which appears in TBPL
+// logs, so we are disabling that here.
+DevToolsUtils.reportingDisabled = true;
+
+var gPanel, gDebugger, gFrames, gSources, gPrefs, gOptions;
+
+function test() {
+ let options = {
+ source: JS_URL,
+ line: 1
+ };
+ initDebugger(TAB_URL, options).then(([aTab,, aPanel]) => {
+ gPanel = aPanel;
+ gDebugger = gPanel.panelWin;
+ gFrames = gDebugger.DebuggerView.StackFrames;
+ gSources = gDebugger.DebuggerView.Sources;
+ gPrefs = gDebugger.Prefs;
+ gOptions = gDebugger.DebuggerView.Options;
+
+ is(gPrefs.pauseOnExceptions, false,
+ "The pause-on-exceptions pref should be disabled by default.");
+ isnot(gOptions._pauseOnExceptionsItem.getAttribute("checked"), "true",
+ "The pause-on-exceptions menu item should not be checked.");
+
+ checkInitialSource();
+ enablePauseOnExceptions()
+ .then(disableIgnoreCaughtExceptions)
+ .then(testSetBreakpoint)
+ .then(reloadPage)
+ .then(testHitBreakpoint)
+ .then(enableIgnoreCaughtExceptions)
+ .then(disablePauseOnExceptions)
+ .then(() => closeDebuggerAndFinish(gPanel))
+ .then(null, aError => {
+ ok(false, "Got an error: " + aError.message + "\n" + aError.stack);
+ });
+ });
+}
+
+function checkInitialSource() {
+ isnot(gSources.selectedItem.attachment.source.url.indexOf("code_math_bogus_map.js"), -1,
+ "The debugger should show the minified js file.");
+}
+
+function enablePauseOnExceptions() {
+ let deferred = promise.defer();
+
+ gDebugger.gThreadClient.addOneTimeListener("resumed", () => {
+ is(gPrefs.pauseOnExceptions, true,
+ "The pause-on-exceptions pref should now be enabled.");
+
+ ok(true, "Pausing on exceptions was enabled.");
+ deferred.resolve();
+ });
+
+ gOptions._pauseOnExceptionsItem.setAttribute("checked", "true");
+ gOptions._togglePauseOnExceptions();
+
+ return deferred.promise;
+}
+
+function disableIgnoreCaughtExceptions() {
+ let deferred = promise.defer();
+
+ gDebugger.gThreadClient.addOneTimeListener("resumed", () => {
+ is(gPrefs.ignoreCaughtExceptions, false,
+ "The ignore-caught-exceptions pref should now be disabled.");
+
+ ok(true, "Ignore caught exceptions was disabled.");
+ deferred.resolve();
+ });
+
+ gOptions._ignoreCaughtExceptionsItem.setAttribute("checked", "false");
+ gOptions._toggleIgnoreCaughtExceptions();
+
+ return deferred.promise;
+}
+
+function testSetBreakpoint() {
+ let deferred = promise.defer();
+ let sourceForm = getSourceForm(gSources, JS_URL);
+ let source = gDebugger.gThreadClient.source(sourceForm);
+
+ source.setBreakpoint({ line: 3, column: 18 }, aResponse => {
+ ok(!aResponse.error,
+ "Should be able to set a breakpoint in a js file.");
+ ok(!aResponse.actualLocation,
+ "Should be able to set a breakpoint on line 3 and column 18.");
+
+ deferred.resolve();
+ });
+
+ return deferred.promise;
+}
+
+function reloadPage() {
+ let loaded = waitForSourceAndCaret(gPanel, ".js", 3);
+ gDebugger.DebuggerController._target.activeTab.reload();
+ return loaded.then(() => ok(true, "Page was reloaded and execution resumed."));
+}
+
+function testHitBreakpoint() {
+ let deferred = promise.defer();
+
+ gDebugger.gThreadClient.resume(aResponse => {
+ ok(!aResponse.error, "Shouldn't get an error resuming.");
+ is(aResponse.type, "resumed", "Type should be 'resumed'.");
+
+ waitForDebuggerEvents(gPanel, gDebugger.EVENTS.FETCHED_SCOPES).then(() => {
+ is(gFrames.itemCount, 2, "Should have two frames.");
+
+ // This is weird, but we need to let the debugger a chance to
+ // update first
+ executeSoon(() => {
+ gDebugger.gThreadClient.resume(() => {
+ gDebugger.gThreadClient.addOneTimeListener("paused", () => {
+ gDebugger.gThreadClient.resume(() => {
+ // We also need to make sure the next step doesn't add a
+ // "resumed" handler until this is completely finished
+ executeSoon(() => {
+ deferred.resolve();
+ });
+ });
+ });
+ });
+ });
+ });
+ });
+
+ return deferred.promise;
+}
+
+function enableIgnoreCaughtExceptions() {
+ let deferred = promise.defer();
+
+ gDebugger.gThreadClient.addOneTimeListener("resumed", () => {
+ is(gPrefs.ignoreCaughtExceptions, true,
+ "The ignore-caught-exceptions pref should now be enabled.");
+
+ ok(true, "Ignore caught exceptions was enabled.");
+ deferred.resolve();
+ });
+
+ gOptions._ignoreCaughtExceptionsItem.setAttribute("checked", "true");
+ gOptions._toggleIgnoreCaughtExceptions();
+
+ return deferred.promise;
+}
+
+function disablePauseOnExceptions() {
+ let deferred = promise.defer();
+
+ gDebugger.gThreadClient.addOneTimeListener("resumed", () => {
+ is(gPrefs.pauseOnExceptions, false,
+ "The pause-on-exceptions pref should now be disabled.");
+
+ ok(true, "Pausing on exceptions was disabled.");
+ deferred.resolve();
+ });
+
+ gOptions._pauseOnExceptionsItem.setAttribute("checked", "false");
+ gOptions._togglePauseOnExceptions();
+
+ return deferred.promise;
+}
+
+registerCleanupFunction(function () {
+ gPanel = null;
+ gDebugger = null;
+ gFrames = null;
+ gSources = null;
+ gPrefs = null;
+ gOptions = null;
+ DevToolsUtils.reportingDisabled = false;
+});
diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_sources-bookmarklet.js b/devtools/client/debugger/test/mochitest/browser_dbg_sources-bookmarklet.js
new file mode 100644
index 000000000..506e24006
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_sources-bookmarklet.js
@@ -0,0 +1,53 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Make sure javascript bookmarklet scripts appear and load correctly in the source list
+ */
+
+const TAB_URL = EXAMPLE_URL + "doc_script-bookmarklet.html";
+
+const BOOKMARKLET_SCRIPT_CODE = "console.log('bookmarklet executed');";
+
+function test() {
+ let options = {
+ source: TAB_URL,
+ line: 1
+ };
+ initDebugger(TAB_URL, options).then(([aTab,, aPanel]) => {
+ const gTab = aTab;
+ const gPanel = aPanel;
+ const gDebugger = gPanel.panelWin;
+ const gSources = gDebugger.DebuggerView.Sources;
+ const gBreakpoints = gDebugger.DebuggerController.Breakpoints;
+ const getState = gDebugger.DebuggerController.getState;
+ const constants = gDebugger.require("./content/constants");
+ const queries = gDebugger.require("./content/queries");
+ const actions = bindActionCreators(gPanel);
+
+ return Task.spawn(function* () {
+ const added = waitForNextDispatch(gDebugger.DebuggerController, constants.ADD_SOURCE);
+ // NOTE: devtools debugger panel needs to be already open,
+ // or the bookmarklet script will not be shown in the sources panel
+ callInTab(gTab, "injectBookmarklet", BOOKMARKLET_SCRIPT_CODE);
+ yield added;
+
+ is(queries.getSourceCount(getState()), 2, "Should have 2 sources");
+
+ const sources = queries.getSources(getState());
+ const sourceActor = Object.keys(sources).filter(k => {
+ return sources[k].url.indexOf("javascript:") === 0;
+ })[0];
+ const source = sources[sourceActor];
+ ok(source, "Source exists.");
+
+ let res = yield actions.loadSourceText(source);
+ is(res.text, BOOKMARKLET_SCRIPT_CODE, "source is correct");
+ is(res.contentType, "text/javascript", "contentType is correct");
+
+ yield closeDebuggerAndFinish(gPanel);
+ });
+ });
+}
diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_sources-cache.js b/devtools/client/debugger/test/mochitest/browser_dbg_sources-cache.js
new file mode 100644
index 000000000..2c5d9d0e2
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_sources-cache.js
@@ -0,0 +1,147 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Tests if the sources cache knows how to cache sources when prompted.
+ */
+
+const TAB_URL = EXAMPLE_URL + "doc_function-search.html";
+const TOTAL_SOURCES = 4;
+
+function test() {
+ let options = {
+ source: EXAMPLE_URL + "code_function-search-01.js",
+ line: 1
+ };
+ initDebugger(TAB_URL, options).then(([aTab, aDebuggee, aPanel]) => {
+ const gTab = aTab;
+ const gDebuggee = aDebuggee;
+ const gPanel = aPanel;
+ const gDebugger = gPanel.panelWin;
+ const gEditor = gDebugger.DebuggerView.editor;
+ const gSources = gDebugger.DebuggerView.Sources;
+ const gPrevLabelsCache = gDebugger.SourceUtils._labelsCache;
+ const gPrevGroupsCache = gDebugger.SourceUtils._groupsCache;
+ const getState = gDebugger.DebuggerController.getState;
+ const queries = gDebugger.require("./content/queries");
+ const actions = bindActionCreators(gPanel);
+
+ function initialChecks() {
+ ok(gEditor.getText().includes("First source!"),
+ "Editor text contents appears to be correct.");
+ is(gSources.selectedItem.attachment.label, "code_function-search-01.js",
+ "The currently selected label in the sources container is correct.");
+ ok(getSelectedSourceURL(gSources).includes("code_function-search-01.js"),
+ "The currently selected value in the sources container appears to be correct.");
+
+ is(gSources.itemCount, TOTAL_SOURCES,
+ "There should be " + TOTAL_SOURCES + " sources present in the sources list.");
+ is(gSources.visibleItems.length, TOTAL_SOURCES,
+ "There should be " + TOTAL_SOURCES + " sources visible in the sources list.");
+ is(gSources.attachments.length, TOTAL_SOURCES,
+ "There should be " + TOTAL_SOURCES + " attachments stored in the sources container model.");
+ is(gSources.values.length, TOTAL_SOURCES,
+ "There should be " + TOTAL_SOURCES + " values stored in the sources container model.");
+
+ info("Source labels: " + gSources.attachments.toSource());
+ info("Source values: " + gSources.values.toSource());
+
+ is(gSources.attachments[0].label, "code_function-search-01.js",
+ "The first source label is correct.");
+ ok(gSources.attachments[0].source.url.includes("code_function-search-01.js"),
+ "The first source value appears to be correct.");
+
+ is(gSources.attachments[1].label, "code_function-search-02.js",
+ "The second source label is correct.");
+ ok(gSources.attachments[1].source.url.includes("code_function-search-02.js"),
+ "The second source value appears to be correct.");
+
+ is(gSources.attachments[2].label, "code_function-search-03.js",
+ "The third source label is correct.");
+ ok(gSources.attachments[2].source.url.includes("code_function-search-03.js"),
+ "The third source value appears to be correct.");
+
+ is(gSources.attachments[3].label, "doc_function-search.html",
+ "The third source label is correct.");
+ ok(gSources.attachments[3].source.url.includes("doc_function-search.html"),
+ "The third source value appears to be correct.");
+
+ is(gDebugger.SourceUtils._labelsCache.size, TOTAL_SOURCES,
+ "There should be " + TOTAL_SOURCES + " labels cached.");
+ is(gDebugger.SourceUtils._groupsCache.size, TOTAL_SOURCES,
+ "There should be " + TOTAL_SOURCES + " groups cached.");
+ }
+
+ function performReloadAndTestState() {
+ gDebugger.gTarget.once("will-navigate", testStateBeforeReload);
+ gDebugger.gTarget.once("navigate", testStateAfterReload);
+ return reloadActiveTab(gPanel, gDebugger.EVENTS.SOURCE_SHOWN);
+ }
+
+ function testCacheIntegrity(cachedSources) {
+ const contents = {
+ [EXAMPLE_URL + "code_function-search-01.js"]: "First source!",
+ [EXAMPLE_URL + "code_function-search-02.js"]: "Second source!",
+ [EXAMPLE_URL + "code_function-search-03.js"]: "Third source!",
+ [EXAMPLE_URL + "doc_function-search.html"]: "Peanut butter jelly time!"
+ };
+
+ const sourcesText = getState().sources.sourcesText;
+ is(Object.keys(sourcesText).length, cachedSources.length,
+ "The right number of sources is cached");
+
+ cachedSources.forEach(sourceUrl => {
+ const source = queries.getSourceByURL(getState(), EXAMPLE_URL + sourceUrl);
+ const content = queries.getSourceText(getState(), source.actor);
+ ok(content, "Source text is cached");
+ ok(content.text.includes(contents[source.url]), "Source text is correct");
+ });
+ }
+
+ function fetchAllSources() {
+ const sources = queries.getSources(getState());
+ return Promise.all(Object.keys(sources).map(k => {
+ const source = sources[k];
+ return actions.loadSourceText(source);
+ }));
+ }
+
+ function testStateBeforeReload() {
+ is(gSources.itemCount, 0,
+ "There should be no sources present in the sources list during reload.");
+ is(gDebugger.SourceUtils._labelsCache, gPrevLabelsCache,
+ "The labels cache has been refreshed during reload and no new objects were created.");
+ is(gDebugger.SourceUtils._groupsCache, gPrevGroupsCache,
+ "The groups cache has been refreshed during reload and no new objects were created.");
+ is(gDebugger.SourceUtils._labelsCache.size, 0,
+ "There should be no labels cached during reload");
+ is(gDebugger.SourceUtils._groupsCache.size, 0,
+ "There should be no groups cached during reload");
+ }
+
+ function testStateAfterReload() {
+ is(gSources.itemCount, TOTAL_SOURCES,
+ "There should be " + TOTAL_SOURCES + " sources present in the sources list.");
+ is(gDebugger.SourceUtils._labelsCache.size, TOTAL_SOURCES,
+ "There should be " + TOTAL_SOURCES + " labels cached after reload.");
+ is(gDebugger.SourceUtils._groupsCache.size, TOTAL_SOURCES,
+ "There should be " + TOTAL_SOURCES + " groups cached after reload.");
+ }
+
+ Task.spawn(function* () {
+ yield initialChecks();
+ yield testCacheIntegrity(["code_function-search-01.js"]);
+ yield fetchAllSources();
+ yield testCacheIntegrity([
+ "code_function-search-01.js",
+ "code_function-search-02.js",
+ "code_function-search-03.js",
+ "doc_function-search.html"
+ ]);
+ yield performReloadAndTestState();
+ closeDebuggerAndFinish(gPanel);
+ });
+ });
+}
diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_sources-contextmenu-01.js b/devtools/client/debugger/test/mochitest/browser_dbg_sources-contextmenu-01.js
new file mode 100644
index 000000000..967c98cff
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_sources-contextmenu-01.js
@@ -0,0 +1,57 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Tests the "Copy URL" functionality of the sources panel context menu
+ */
+
+const TAB_URL = EXAMPLE_URL + "doc_function-search.html";
+const SCRIPT_URI = EXAMPLE_URL + "code_function-search-01.js";
+
+function test() {
+ let gTab, gPanel, gDebugger, gSources;
+
+ let options = {
+ source: SCRIPT_URI,
+ line: 1
+ };
+ initDebugger(TAB_URL, options).then(([aTab,, aPanel]) => {
+ gTab = aTab;
+ gPanel = aPanel;
+ gDebugger = gPanel.panelWin;
+ gSources = gDebugger.DebuggerView.Sources;
+
+ openContextMenu()
+ .then(testCopyMenuItem)
+ .then(() => closeDebuggerAndFinish(gPanel))
+ .then(null, aError => {
+ ok(false, "Got an error: " + aError.message + "\n" + aError.stack);
+ });
+ });
+
+ function clickCopyURL() {
+ return new Promise((resolve, reject) => {
+ let copyURLMenuItem = gDebugger.document.getElementById("debugger-sources-context-copyurl");
+ if (!copyURLMenuItem) {
+ reject(new Error("The Copy URL context menu item is not available."));
+ }
+
+ ok(copyURLMenuItem, "The Copy URL context menu item is available.");
+ EventUtils.synthesizeMouseAtCenter(copyURLMenuItem, {}, gDebugger);
+ resolve();
+ });
+ }
+
+ function testCopyMenuItem() {
+ return waitForClipboardPromise(clickCopyURL, SCRIPT_URI);
+ }
+
+ function openContextMenu() {
+ let contextMenu = gDebugger.document.getElementById("debuggerSourcesContextMenu");
+ let contextMenuShown = once(contextMenu, "popupshown");
+ EventUtils.synthesizeMouseAtCenter(gSources.selectedItem.prebuiltNode, {type: "contextmenu"}, gDebugger);
+ return contextMenuShown;
+ }
+}
diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_sources-contextmenu-02.js b/devtools/client/debugger/test/mochitest/browser_dbg_sources-contextmenu-02.js
new file mode 100644
index 000000000..da6668f51
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_sources-contextmenu-02.js
@@ -0,0 +1,75 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Tests the "Open in New Tab" functionality of the sources panel context menu
+ */
+
+const TAB_URL = EXAMPLE_URL + "doc_function-search.html";
+const SCRIPT_URI = EXAMPLE_URL + "code_function-search-01.js";
+
+function test() {
+ let gTab, gPanel, gDebugger;
+ let gSources;
+
+ let options = {
+ source: SCRIPT_URI,
+ line: 1
+ };
+ initDebugger(TAB_URL, options).then(([aTab,, aPanel]) => {
+ gTab = aTab;
+ gPanel = aPanel;
+ gDebugger = gPanel.panelWin;
+ gSources = gDebugger.DebuggerView.Sources;
+
+ openContextMenu()
+ .then(testNewTabMenuItem)
+ .then(testNewTabURI)
+ .then(() => closeDebuggerAndFinish(gPanel))
+ .then(null, aError => {
+ ok(false, "Got an error: " + aError.message + "\n" + aError.stack);
+ });
+ });
+
+ function testNewTabURI(tabUri) {
+ is(tabUri, SCRIPT_URI, "The tab contains the right script.");
+ gBrowser.removeCurrentTab();
+ }
+
+ function waitForTabOpen() {
+ return new Promise(resolve => {
+ gBrowser.tabContainer.addEventListener("TabOpen", function onOpen(e) {
+ gBrowser.tabContainer.removeEventListener("TabOpen", onOpen, false);
+ ok(true, "A new tab loaded");
+
+ gBrowser.addEventListener("DOMContentLoaded", function onTabLoad(e) {
+ gBrowser.removeEventListener("DOMContentLoaded", onTabLoad, false);
+ // Pass along the new tab's URI.
+ resolve(gBrowser.currentURI.spec);
+ }, false);
+ }, false);
+ });
+ }
+
+ function testNewTabMenuItem() {
+ return new Promise((resolve, reject) => {
+ let newTabMenuItem = gDebugger.document.getElementById("debugger-sources-context-newtab");
+ if (!newTabMenuItem) {
+ reject(new Error("The Open in New Tab context menu item is not available."));
+ }
+
+ ok(newTabMenuItem, "The Open in New Tab context menu item is available.");
+ waitForTabOpen().then(resolve);
+ EventUtils.synthesizeMouseAtCenter(newTabMenuItem, {}, gDebugger);
+ });
+ }
+
+ function openContextMenu() {
+ let contextMenu = gDebugger.document.getElementById("debuggerSourcesContextMenu");
+ let contextMenuShown = once(contextMenu, "popupshown");
+ EventUtils.synthesizeMouseAtCenter(gSources.selectedItem.prebuiltNode, {type: "contextmenu"}, gDebugger);
+ return contextMenuShown;
+ }
+}
diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_sources-eval-01.js b/devtools/client/debugger/test/mochitest/browser_dbg_sources-eval-01.js
new file mode 100644
index 000000000..0e794d06c
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_sources-eval-01.js
@@ -0,0 +1,44 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Make sure eval scripts appear in the source list
+ */
+
+const TAB_URL = EXAMPLE_URL + "doc_script-eval.html";
+
+function test() {
+ let gTab, gPanel, gDebugger;
+ let gSources, gBreakpoints;
+
+ let options = {
+ source: EXAMPLE_URL + "code_script-eval.js",
+ line: 1
+ };
+ initDebugger(TAB_URL, options).then(([aTab,, aPanel]) => {
+ gTab = aTab;
+ gPanel = aPanel;
+ gDebugger = gPanel.panelWin;
+ gSources = gDebugger.DebuggerView.Sources;
+ gBreakpoints = gDebugger.DebuggerController.Breakpoints;
+
+ return Task.spawn(function* () {
+ is(gSources.values.length, 1, "Should have 1 source");
+
+ let newSource = waitForDebuggerEvents(gPanel, gDebugger.EVENTS.NEW_SOURCE);
+ callInTab(gTab, "evalSource");
+ yield newSource;
+
+ is(gSources.values.length, 2, "Should have 2 sources");
+
+ let item = gSources.getItemForAttachment(e => e.label.indexOf("> eval") !== -1);
+ ok(item, "Source label is incorrect.");
+ is(item.attachment.group, gDebugger.L10N.getStr("evalGroupLabel"),
+ "Source group is incorrect");
+
+ yield closeDebuggerAndFinish(gPanel);
+ });
+ });
+}
diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_sources-eval-02.js b/devtools/client/debugger/test/mochitest/browser_dbg_sources-eval-02.js
new file mode 100644
index 000000000..b932df143
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_sources-eval-02.js
@@ -0,0 +1,55 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Make sure eval scripts with the sourceURL pragma are correctly
+ * displayed
+ */
+
+const TAB_URL = EXAMPLE_URL + "doc_script-eval.html";
+
+function test() {
+ let gTab, gPanel, gDebugger;
+ let gSources, gBreakpoints, gEditor;
+
+ let options = {
+ source: EXAMPLE_URL + "code_script-eval.js",
+ line: 1
+ };
+ initDebugger(TAB_URL, options).then(([aTab,, aPanel]) => {
+ gTab = aTab;
+ gPanel = aPanel;
+ gDebugger = gPanel.panelWin;
+ gSources = gDebugger.DebuggerView.Sources;
+ gBreakpoints = gDebugger.DebuggerController.Breakpoints;
+ gEditor = gDebugger.DebuggerView.editor;
+ const constants = gDebugger.require("./content/constants");
+ const queries = gDebugger.require("./content/queries");
+ const actions = bindActionCreators(gPanel);
+ const getState = gDebugger.DebuggerController.getState;
+
+ return Task.spawn(function* () {
+ is(queries.getSourceCount(getState()), 1, "Should have 1 source");
+
+ const newSource = waitForDispatch(gPanel, constants.ADD_SOURCE);
+ callInTab(gTab, "evalSourceWithSourceURL");
+ yield newSource;
+
+ is(queries.getSourceCount(getState()), 2, "Should have 2 sources");
+
+ const source = queries.getSourceByURL(getState(), EXAMPLE_URL + "bar.js");
+ ok(source, "Source exists.");
+
+ let shown = waitForDebuggerEvents(gPanel, gDebugger.EVENTS.SOURCE_SHOWN);
+ actions.selectSource(source);
+ yield shown;
+
+ ok(gEditor.getText().indexOf("bar = function() {") === 0,
+ "Correct source is shown");
+
+ yield closeDebuggerAndFinish(gPanel);
+ });
+ });
+}
diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_sources-iframe-reload.js b/devtools/client/debugger/test/mochitest/browser_dbg_sources-iframe-reload.js
new file mode 100644
index 000000000..63c53fba5
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_sources-iframe-reload.js
@@ -0,0 +1,35 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Make sure iframe scripts don't disappear after few reloads (bug 1259743)
+ */
+
+"use strict";
+
+const IFRAME_URL = "data:text/html;charset=utf-8," +
+ "<script>function fn() { console.log('hello'); }</script>" +
+ "<div onclick='fn()'>hello</div>";
+const TAB_URL = `data:text/html;charset=utf-8,<iframe src="${IFRAME_URL}"/>`;
+
+add_task(function* () {
+ let [,, panel] = yield initDebugger();
+ let dbg = panel.panelWin;
+ let newSource;
+
+ newSource = waitForDebuggerEvents(panel, dbg.EVENTS.NEW_SOURCE);
+ reload(panel, TAB_URL);
+ yield newSource;
+ ok(true, "Source event fired on initial load");
+
+ for (let i = 0; i < 5; i++) {
+ newSource = waitForDebuggerEvents(panel, dbg.EVENTS.NEW_SOURCE);
+ reload(panel);
+ yield newSource;
+ ok(true, `Source event fired after ${i + 1} reloads`);
+ }
+
+ yield closeDebuggerAndFinish(panel);
+});
diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_sources-keybindings.js b/devtools/client/debugger/test/mochitest/browser_dbg_sources-keybindings.js
new file mode 100644
index 000000000..80f043637
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_sources-keybindings.js
@@ -0,0 +1,40 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Tests related to source panel keyboard shortcut bindings
+ */
+
+const TAB_URL = EXAMPLE_URL + "doc_function-search.html";
+const SCRIPT_URI = EXAMPLE_URL + "code_function-search-01.js";
+
+function test() {
+ let gTab, gPanel, gDebugger, gSources;
+
+ let options = {
+ source: SCRIPT_URI,
+ line: 1
+ };
+ initDebugger(TAB_URL, options).then(([aTab,, aPanel]) => {
+ gTab = aTab;
+ gPanel = aPanel;
+ gDebugger = gPanel.panelWin;
+ gSources = gDebugger.DebuggerView.Sources;
+
+ testCopyURLShortcut()
+ .then(() => closeDebuggerAndFinish(gPanel))
+ .then(null, aError => {
+ ok(false, "Got an error: " + aError.message + "\n" + aError.stack);
+ });
+ });
+
+ function testCopyURLShortcut() {
+ return waitForClipboardPromise(sendCopyShortcut, SCRIPT_URI);
+ }
+
+ function sendCopyShortcut() {
+ EventUtils.synthesizeKey("C", { accelKey: true }, gDebugger);
+ }
+}
diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_sources-labels.js b/devtools/client/debugger/test/mochitest/browser_dbg_sources-labels.js
new file mode 100644
index 000000000..1c4cfd6da
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_sources-labels.js
@@ -0,0 +1,172 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Tests that urls are correctly shortened to unique labels.
+ */
+
+const TAB_URL = EXAMPLE_URL + "doc_recursion-stack.html";
+const { ELLIPSIS } = require("devtools/shared/l10n");
+
+function test() {
+ let gTab, gPanel, gDebugger;
+ let gSources, gUtils;
+
+ let options = {
+ source: TAB_URL,
+ line: 1
+ };
+ initDebugger(TAB_URL, options).then(Task.async(function* ([aTab,, aPanel]) {
+ gTab = aTab;
+ gPanel = aPanel;
+ gDebugger = gPanel.panelWin;
+ gSources = gDebugger.DebuggerView.Sources;
+ gUtils = gDebugger.SourceUtils;
+
+ let nananana = new Array(20).join(NaN);
+
+ // Test trimming url queries.
+
+ let someUrl = "a/b/c.d?test=1&random=4#reference";
+ let shortenedUrl = "a/b/c.d";
+ is(gUtils.trimUrlQuery(someUrl), shortenedUrl,
+ "Trimming the url query isn't done properly.");
+
+ // Test trimming long urls with an ellipsis.
+
+ let largeLabel = new Array(100).join("Beer can in Jamaican sounds like Bacon!");
+ let trimmedLargeLabel = gUtils.trimUrlLength(largeLabel, 1234);
+ is(trimmedLargeLabel.length, 1235,
+ "Trimming large labels isn't done properly.");
+ ok(trimmedLargeLabel.endsWith(ELLIPSIS),
+ "Trimming large labels should add an ellipsis at the end : " + ELLIPSIS);
+
+ // Test the sources list behaviour with certain urls.
+
+ let urls = [
+ { href: "http://some.address.com/random/", leaf: "subrandom/" },
+ { href: "http://some.address.com/random/", leaf: "suprandom/?a=1" },
+ { href: "http://some.address.com/random/", leaf: "?a=1" },
+ { href: "https://another.address.org/random/subrandom/", leaf: "page.html" },
+
+ { href: "ftp://interesting.address.org/random/", leaf: "script.js" },
+ { href: "ftp://interesting.address.com/random/", leaf: "script.js" },
+ { href: "ftp://interesting.address.com/random/", leaf: "x/script.js" },
+ { href: "ftp://interesting.address.com/random/", leaf: "x/y/script.js?a=1" },
+ { href: "ftp://interesting.address.com/random/x/", leaf: "y/script.js?a=1&b=2" },
+ { href: "ftp://interesting.address.com/random/x/y/", leaf: "script.js?a=1&b=2&c=3" },
+ { href: "ftp://interesting.address.com/random/", leaf: "x/y/script.js?a=2" },
+ { href: "ftp://interesting.address.com/random/x/", leaf: "y/script.js?a=2&b=3" },
+ { href: "ftp://interesting.address.com/random/x/y/", leaf: "script.js?a=2&b=3&c=4" },
+
+ { href: "file://random/", leaf: "script_t1.js&a=1&b=2&c=3" },
+ { href: "file://random/", leaf: "script_t2_1.js#id" },
+ { href: "file://random/", leaf: "script_t2_2.js?a" },
+ { href: "file://random/", leaf: "script_t2_3.js&b" },
+ { href: "resource://random/", leaf: "script_t3_1.js#id?a=1&b=2" },
+ { href: "resource://random/", leaf: "script_t3_2.js?a=1&b=2#id" },
+ { href: "resource://random/", leaf: "script_t3_3.js&a=1&b=2#id" },
+
+ { href: nananana, leaf: "Batman!" + "{trim me, now and forevermore}" }
+ ];
+
+ is(gSources.itemCount, 1,
+ "Should contain the original source label in the sources widget.");
+ is(gSources.selectedIndex, 0,
+ "The first item in the sources widget should be selected (1).");
+ is(gSources.selectedItem.attachment.label, "doc_recursion-stack.html",
+ "The first item in the sources widget should be selected (2).");
+ is(getSelectedSourceURL(gSources), TAB_URL,
+ "The first item in the sources widget should be selected (3).");
+
+ let id = 0;
+ for (let { href, leaf } of urls) {
+ let url = href + leaf;
+ let actor = "actor" + id++;
+ let label = gUtils.trimUrlLength(gUtils.getSourceLabel(url));
+ let group = gUtils.getSourceGroup(url);
+ let dummy = document.createElement("label");
+ dummy.setAttribute("value", label);
+
+ gSources.push([dummy, actor], {
+ attachment: {
+ source: { actor: actor, url: url },
+ label: label,
+ group: group
+ }
+ });
+ }
+
+ info("Source locations:");
+ info(gSources.values.toSource());
+
+ info("Source attachments:");
+ info(gSources.attachments.toSource());
+
+ for (let { href, leaf, dupe } of urls) {
+ let url = href + leaf;
+ if (dupe) {
+ ok(!gSources.containsValue(getSourceActor(gSources, url)), "Shouldn't contain source: " + url);
+ } else {
+ ok(gSources.containsValue(getSourceActor(gSources, url)), "Should contain source: " + url);
+ }
+ }
+
+ ok(gSources.getItemForAttachment(e => e.label == "random/subrandom/"),
+ "Source (0) label is incorrect.");
+ ok(gSources.getItemForAttachment(e => e.label == "random/suprandom/?a=1"),
+ "Source (1) label is incorrect.");
+ ok(gSources.getItemForAttachment(e => e.label == "random/?a=1"),
+ "Source (2) label is incorrect.");
+ ok(gSources.getItemForAttachment(e => e.label == "page.html"),
+ "Source (3) label is incorrect.");
+
+ ok(gSources.getItemForAttachment(e => e.label == "script.js"),
+ "Source (4) label is incorrect.");
+ ok(gSources.getItemForAttachment(e => e.label == "random/script.js"),
+ "Source (5) label is incorrect.");
+ ok(gSources.getItemForAttachment(e => e.label == "random/x/script.js"),
+ "Source (6) label is incorrect.");
+ ok(gSources.getItemForAttachment(e => e.label == "script.js?a=1"),
+ "Source (7) label is incorrect.");
+
+ ok(gSources.getItemForAttachment(e => e.label == "script_t1.js"),
+ "Source (8) label is incorrect.");
+ ok(gSources.getItemForAttachment(e => e.label == "script_t2_1.js"),
+ "Source (9) label is incorrect.");
+ ok(gSources.getItemForAttachment(e => e.label == "script_t2_2.js"),
+ "Source (10) label is incorrect.");
+ ok(gSources.getItemForAttachment(e => e.label == "script_t2_3.js"),
+ "Source (11) label is incorrect.");
+ ok(gSources.getItemForAttachment(e => e.label == "script_t3_1.js"),
+ "Source (12) label is incorrect.");
+ ok(gSources.getItemForAttachment(e => e.label == "script_t3_2.js"),
+ "Source (13) label is incorrect.");
+ ok(gSources.getItemForAttachment(e => e.label == "script_t3_3.js"),
+ "Source (14) label is incorrect.");
+
+ ok(gSources.getItemForAttachment(e => e.label == nananana + "Batman!" + ELLIPSIS),
+ "Source (15) label is incorrect.");
+
+ is(gSources.itemCount, urls.filter(({ dupe }) => !dupe).length + 1,
+ "Didn't get the correct number of sources in the list.");
+
+ is(gSources.getItemByValue(getSourceActor(gSources, "http://some.address.com/random/subrandom/")).attachment.label,
+ "random/subrandom/",
+ "gSources.getItemByValue isn't functioning properly (0).");
+ is(gSources.getItemByValue(getSourceActor(gSources, "http://some.address.com/random/suprandom/?a=1")).attachment.label,
+ "random/suprandom/?a=1",
+ "gSources.getItemByValue isn't functioning properly (1).");
+
+ is(gSources.getItemForAttachment(e => e.label == "random/subrandom/").attachment.source.url,
+ "http://some.address.com/random/subrandom/",
+ "gSources.getItemForAttachment isn't functioning properly (0).");
+ is(gSources.getItemForAttachment(e => e.label == "random/suprandom/?a=1").attachment.source.url,
+ "http://some.address.com/random/suprandom/?a=1",
+ "gSources.getItemForAttachment isn't functioning properly (1).");
+
+ closeDebuggerAndFinish(gPanel);
+ }));
+}
diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_sources-large.js b/devtools/client/debugger/test/mochitest/browser_dbg_sources-large.js
new file mode 100644
index 000000000..31b64c2fd
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_sources-large.js
@@ -0,0 +1,80 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Tests that large files are treated differently in the debugger:
+ * 1) No parsing to determine current symbol is attempted when
+ * starting a search
+ */
+
+const TAB_URL = EXAMPLE_URL + "doc_function-search.html";
+
+function test() {
+ let options = {
+ source: EXAMPLE_URL + "code_function-search-01.js",
+ line: 1
+ };
+ initDebugger(TAB_URL, options).then(([aTab, aDebuggee, aPanel]) => {
+ const gTab = aTab;
+ const gDebuggee = aDebuggee;
+ const gPanel = aPanel;
+ const gDebugger = gPanel.panelWin;
+
+ const gEditor = gDebugger.DebuggerView.editor;
+ const gSources = gDebugger.DebuggerView.Sources;
+ const Filtering = gDebugger.DebuggerView.Filtering;
+
+ // Setting max size so that code_function-search-01.js will be
+ // considered a large file on first load
+ gDebugger.DebuggerView.LARGE_FILE_SIZE = 1;
+
+ function testLargeFile() {
+ ok(gEditor.getText().length > gDebugger.DebuggerView.LARGE_FILE_SIZE,
+ "First source is considered a large file.");
+ is(gEditor.getMode().name, "javascript",
+ "Editor is syntax highlighting.");
+ ok(gEditor.getText().includes("First source!"),
+ "Editor text contents appears to be correct.");
+
+ // Press ctrl+f with the cursor in a token
+ gEditor.focus();
+ gEditor.setCursor({ line: 3, ch: 10});
+ synthesizeKeyFromKeyTag(gDebugger.document.getElementById("tokenSearchKey"));
+ is(Filtering._searchbox.value, "#",
+ "Search box is NOT prefilled with current token");
+ }
+
+ function testSmallFile() {
+ ok(gEditor.getText().length < gDebugger.DebuggerView.LARGE_FILE_SIZE,
+ "Second source is considered a small file.");
+ is(gEditor.getMode().name, "javascript",
+ "Editor is syntax highlighting.");
+ ok(gEditor.getText().includes("First source!"),
+ "Editor text contents appears to be correct.");
+
+ // Press ctrl+f with the cursor in a token
+ gEditor.focus();
+ gEditor.setCursor({ line: 3, ch: 10});
+ synthesizeKeyFromKeyTag(gDebugger.document.getElementById("tokenSearchKey"));
+ is(Filtering._searchbox.value, "#test",
+ "Search box is prefilled with current token");
+ }
+
+ Task.spawn(function* () {
+ yield testLargeFile();
+
+ info("Making it appear as a small file and then reselecting 01.js");
+ gDebugger.DebuggerView.LARGE_FILE_SIZE = 1000;
+ gSources.selectedIndex = 1;
+ yield waitForSourceShown(gPanel, "-02.js");
+ gSources.selectedIndex = 0;
+ yield waitForSourceShown(gPanel, "-01.js");
+
+ yield testSmallFile();
+
+ closeDebuggerAndFinish(gPanel);
+ });
+ });
+}
diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_sources-sorting.js b/devtools/client/debugger/test/mochitest/browser_dbg_sources-sorting.js
new file mode 100644
index 000000000..2a600893b
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_sources-sorting.js
@@ -0,0 +1,141 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Tests that urls are correctly sorted when added to the sources widget.
+ */
+
+const TAB_URL = EXAMPLE_URL + "doc_recursion-stack.html";
+
+var gTab, gPanel, gDebugger;
+var gSources, gUtils;
+
+function test() {
+ let options = {
+ source: TAB_URL,
+ line: 1
+ };
+ initDebugger(TAB_URL, options).then(([aTab,, aPanel]) => {
+ gTab = aTab;
+ gPanel = aPanel;
+ gDebugger = gPanel.panelWin;
+ gSources = gDebugger.DebuggerView.Sources;
+ gUtils = gDebugger.SourceUtils;
+
+ addSourceAndCheckOrder(1);
+ addSourceAndCheckOrder(2);
+ addSourceAndCheckOrder(3);
+ closeDebuggerAndFinish(gPanel);
+ });
+}
+
+function addSourceAndCheckOrder(aMethod) {
+ gSources.empty();
+ gSources.suppressSelectionEvents = true;
+
+ let urls = [
+ { href: "ici://some.address.com/random/", leaf: "subrandom/" },
+ { href: "ni://another.address.org/random/subrandom/", leaf: "page.html" },
+ { href: "san://interesting.address.gro/random/", leaf: "script.js" },
+ { href: "si://interesting.address.moc/random/", leaf: "script.js" },
+ { href: "si://interesting.address.moc/random/", leaf: "x/script.js" },
+ { href: "si://interesting.address.moc/random/", leaf: "x/y/script.js?a=1" },
+ { href: "si://interesting.address.moc/random/x/", leaf: "y/script.js?a=1&b=2" },
+ { href: "si://interesting.address.moc/random/x/y/", leaf: "script.js?a=1&b=2&c=3" }
+ ];
+
+ urls.sort(function (a, b) {
+ return Math.random() - 0.5;
+ });
+
+ let id = 0;
+
+ switch (aMethod) {
+ case 1:
+ for (let { href, leaf } of urls) {
+ let url = href + leaf;
+ let actor = "actor" + id++;
+ let label = gUtils.getSourceLabel(url);
+ let dummy = document.createElement("label");
+ gSources.push([dummy, actor], {
+ staged: true,
+ attachment: {
+ label: label
+ }
+ });
+ }
+ gSources.commit({ sorted: true });
+ break;
+
+ case 2:
+ for (let { href, leaf } of urls) {
+ let url = href + leaf;
+ let actor = "actor" + id++;
+ let label = gUtils.getSourceLabel(url);
+ let dummy = document.createElement("label");
+ gSources.push([dummy, actor], {
+ staged: false,
+ attachment: {
+ label: label
+ }
+ });
+ }
+ break;
+
+ case 3:
+ let i = 0;
+ for (; i < urls.length / 2; i++) {
+ let { href, leaf } = urls[i];
+ let url = href + leaf;
+ let actor = "actor" + id++;
+ let label = gUtils.getSourceLabel(url);
+ let dummy = document.createElement("label");
+ gSources.push([dummy, actor], {
+ staged: true,
+ attachment: {
+ label: label
+ }
+ });
+ }
+ gSources.commit({ sorted: true });
+
+ for (; i < urls.length; i++) {
+ let { href, leaf } = urls[i];
+ let url = href + leaf;
+ let actor = "actor" + id++;
+ let label = gUtils.getSourceLabel(url);
+ let dummy = document.createElement("label");
+ gSources.push([dummy, actor], {
+ staged: false,
+ attachment: {
+ label: label
+ }
+ });
+ }
+ break;
+ }
+
+ checkSourcesOrder(aMethod);
+}
+
+function checkSourcesOrder(aMethod) {
+ let attachments = gSources.attachments;
+
+ for (let i = 0; i < attachments.length - 1; i++) {
+ let first = attachments[i].label;
+ let second = attachments[i + 1].label;
+ ok(first < second,
+ "Using method " + aMethod + ", " +
+ "the sources weren't in the correct order: " + first + " vs. " + second);
+ }
+}
+
+registerCleanupFunction(function () {
+ gTab = null;
+ gPanel = null;
+ gDebugger = null;
+ gSources = null;
+ gUtils = null;
+});
diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_sources-webext-contentscript.js b/devtools/client/debugger/test/mochitest/browser_dbg_sources-webext-contentscript.js
new file mode 100644
index 000000000..2fd8067f9
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_sources-webext-contentscript.js
@@ -0,0 +1,63 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Make sure eval scripts appear in the source list
+ */
+
+const ADDON_PATH = "addon-webext-contentscript.xpi";
+const TAB_URL = EXAMPLE_URL + "doc_script_webext_contentscript.html";
+
+let {getExtensionUUID} = Cu.import("resource://gre/modules/Extension.jsm", {});
+
+function test() {
+ let gPanel, gDebugger;
+ let gSources, gAddon;
+
+ let cleanup = function* (e) {
+ if (gAddon) {
+ // Remove the addon, if any.
+ yield removeAddon(gAddon);
+ }
+ if (gPanel) {
+ // Close the debugger panel, if any.
+ yield closeDebuggerAndFinish(gPanel);
+ } else {
+ // If no debugger panel was opened, call finish directly.
+ finish();
+ }
+ };
+
+ return Task.spawn(function* () {
+ gAddon = yield addTemporaryAddon(ADDON_PATH);
+ let uuid = getExtensionUUID(gAddon.id);
+
+ let options = {
+ source: `moz-extension://${uuid}/webext-content-script.js`,
+ line: 1
+ };
+ [,, gPanel] = yield initDebugger(TAB_URL, options);
+ gDebugger = gPanel.panelWin;
+ gSources = gDebugger.DebuggerView.Sources;
+
+ is(gSources.values.length, 2, "Should have 2 sources");
+
+ let item = gSources.getItemForAttachment(attachment => {
+ return attachment.source.url.includes("moz-extension");
+ });
+
+ ok(item, "Got the expected WebExtensions ContentScript source");
+ ok(item && item.attachment.source.url.includes(item.attachment.group),
+ "The source is in the expected source group");
+ is(item && item.attachment.label, "webext-content-script.js",
+ "Got the expected filename in the label");
+
+ yield cleanup();
+ }).catch((e) => {
+ ok(false, `Got an unexpected exception: ${e}`);
+ // Cleanup in case of failures in the above task.
+ return Task.spawn(cleanup);
+ });
+}
diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_split-console-keypress.js b/devtools/client/debugger/test/mochitest/browser_dbg_split-console-keypress.js
new file mode 100644
index 000000000..5bfe0a61e
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_split-console-keypress.js
@@ -0,0 +1,108 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * When the split console is focused and the debugger is open,
+ * debugger shortcut keys like F11 should work
+ */
+const TAB_URL = EXAMPLE_URL + "doc_step-many-statements.html";
+
+function test() {
+ // This does the same assertions over a series of sub-tests, and it
+ // can timeout in linux e10s. No sense in breaking it up into multiple
+ // tests, so request extra time.
+ requestLongerTimeout(2);
+
+ let gDebugger, gToolbox, gThreadClient, gTab, gPanel;
+ let options = {
+ source: TAB_URL,
+ line: 1
+ };
+ initDebugger(TAB_URL, options).then(([aTab, debuggeeWin, aPanel]) => {
+ gPanel = aPanel;
+ gDebugger = aPanel.panelWin;
+ gToolbox = gDevTools.getToolbox(aPanel.target);
+ gTab = aTab;
+ gThreadClient = gDebugger.DebuggerController.activeThread;
+ testConsole();
+ });
+ let testConsole = Task.async(function* () {
+ // We need to open the split console (with an ESC keypress),
+ // then get the script into a paused state by pressing a button in the page,
+ // ensure focus is in the split console,
+ // synthesize a few keys - important ones we share listener for are
+ // "resumeKey", "stepOverKey", "stepInKey", "stepOutKey"
+ // then check that
+ // * The input cursor remains in the console's input box
+ // * The paused state is as expected
+ // * the debugger cursor is where we want it
+ let jsterm = yield getSplitConsole(gToolbox, gDebugger);
+ // The console is now open (if not make the test fail already)
+ ok(gToolbox.splitConsole, "Split console is shown.");
+
+ // Information for sub-tests. When 'key' is synthesized 'keyRepeat' times,
+ // cursor should be at 'caretLine' of this test..
+ let stepTests = [
+ {key: "VK_F11", keyRepeat: 1, caretLine: 16},
+ {key: "VK_F11", keyRepeat: 2, caretLine: 18},
+ {key: "VK_F11", keyRepeat: 2, caretLine: 27},
+ {key: "VK_F10", keyRepeat: 1, caretLine: 27},
+ {key: "VK_F11", keyRepeat: 1, caretLine: 18},
+ {key: "VK_F11", keyRepeat: 5, caretLine: 32},
+ {key: "VK_F11", modifier:"Shift", keyRepeat: 1, caretLine: 29},
+ {key: "VK_F11", modifier:"Shift", keyRepeat: 2, caretLine: 34},
+ {key: "VK_F11", modifier:"Shift", keyRepeat: 2, caretLine: 34}
+ ];
+ // Trigger script that stops at debugger statement
+ executeSoon(() => generateMouseClickInTab(gTab,
+ "content.document.getElementById('start')"));
+ yield waitForPause(gThreadClient);
+
+ // Focus the console and add event listener to track whether it loses focus
+ // (Must happen after generateMouseClickInTab() call)
+ let consoleLostFocus = false;
+ jsterm.focus();
+ jsterm.inputNode.addEventListener("blur", () => {consoleLostFocus = true;});
+
+ is(gThreadClient.paused, true,
+ "Should be paused at debugger statement.");
+ // As long as we have test work to do..
+ for (let i = 0, thisTest; thisTest = stepTests[i]; i++) {
+ // First we send another key event if required by the test
+ while (thisTest.keyRepeat > 0) {
+ thisTest.keyRepeat --;
+ let keyMods = thisTest.modifier === "Shift" ? {shiftKey:true} : {};
+ executeSoon(() => {EventUtils.synthesizeKey(thisTest.key, keyMods);});
+ yield waitForPause(gThreadClient);
+ }
+
+ // We've sent the required number of keys
+ // Here are the conditions we're interested in: paused state,
+ // cursor still in console (tested later), caret correct in editor
+ is(gThreadClient.paused, true,
+ "Should still be paused");
+ // ok(isCaretPos(gPanel, thisTest.caretLine),
+ // "Test " + i + ": CaretPos at line " + thisTest.caretLine);
+ ok(isDebugPos(gPanel, thisTest.caretLine),
+ "Test " + i + ": DebugPos at line " + thisTest.caretLine);
+ }
+ // Did focus go missing while we were stepping?
+ is(consoleLostFocus, false, "Console input should not lose focus");
+ // We're done with the tests in the stepTests array
+ // Last key we test is "resume"
+ executeSoon(() => EventUtils.synthesizeKey("VK_F8", {}));
+
+ // We reset the variable tracking loss of focus to test the resume case
+ consoleLostFocus = false;
+
+ gPanel.target.on("thread-resumed", () => {
+ is(gThreadClient.paused, false,
+ "Should not be paused after resume");
+ // Final test: did we preserve console inputNode focus during resume?
+ is(consoleLostFocus, false, "Resume - console should keep focus");
+ closeDebuggerAndFinish(gPanel);
+ });
+ });
+}
diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_split-console-paused-reload.js b/devtools/client/debugger/test/mochitest/browser_dbg_split-console-paused-reload.js
new file mode 100644
index 000000000..e9daaa4bc
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_split-console-paused-reload.js
@@ -0,0 +1,67 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Hitting ESC to open the split console when paused on reload should not stop
+ * the pending navigation.
+ */
+
+function test() {
+ Task.spawn(runTests);
+}
+
+function* runTests() {
+ let TAB_URL = EXAMPLE_URL + "doc_split-console-paused-reload.html";
+ let options = {
+ source: TAB_URL,
+ line: 1
+ };
+ let [,, panel] = yield initDebugger(TAB_URL, options);
+ let dbgWin = panel.panelWin;
+ let sources = dbgWin.DebuggerView.Sources;
+ let frames = dbgWin.DebuggerView.StackFrames;
+ let toolbox = gDevTools.getToolbox(panel.target);
+
+ yield panel.addBreakpoint({ actor: getSourceActor(sources, TAB_URL), line: 16 });
+ info("Breakpoint was set.");
+ dbgWin.DebuggerController._target.activeTab.reload();
+ info("Page reloaded.");
+ yield waitForSourceAndCaretAndScopes(panel, ".html", 16);
+ yield ensureThreadClientState(panel, "paused");
+ info("Breakpoint was hit.");
+ EventUtils.sendMouseEvent({ type: "mousedown" },
+ frames.selectedItem.target,
+ dbgWin);
+ info("The breadcrumb received focus.");
+
+ // This is the meat of the test.
+ let jsterm = yield getSplitConsole(toolbox);
+
+ is(dbgWin.gThreadClient.state, "paused", "Execution is still paused.");
+
+ let dbgFrameConsoleEvalResult = yield jsterm.execute("privateVar");
+
+ is(
+ dbgFrameConsoleEvalResult.querySelector(".console-string").textContent,
+ '"privateVarValue"',
+ "Got the expected split console result on paused debugger"
+ );
+
+ yield dbgWin.gThreadClient.resume();
+
+ is(dbgWin.gThreadClient.state, "attached", "Execution is resumed.");
+
+ // Get the last evaluation result adopted by the new debugger.
+ let mainTargetConsoleEvalResult = yield jsterm.execute("$_");
+
+ is(
+ mainTargetConsoleEvalResult.querySelector(".console-string").textContent,
+ '"privateVarValue"',
+ "Got the expected split console log on $_ executed on resumed debugger"
+ );
+
+ Services.prefs.clearUserPref("devtools.toolbox.splitconsoleEnabled");
+ yield closeDebuggerAndFinish(panel);
+}
diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_stack-01.js b/devtools/client/debugger/test/mochitest/browser_dbg_stack-01.js
new file mode 100644
index 000000000..513823ba9
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_stack-01.js
@@ -0,0 +1,49 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Test that stackframes are added when debugger is paused.
+ */
+
+const TAB_URL = EXAMPLE_URL + "doc_recursion-stack.html";
+
+var gTab, gPanel, gDebugger;
+var gFrames, gClassicFrames;
+
+function test() {
+ let options = {
+ source: TAB_URL,
+ line: 1
+ };
+ initDebugger(TAB_URL, options).then(([aTab,, aPanel]) => {
+ gTab = aTab;
+ gPanel = aPanel;
+ gDebugger = gPanel.panelWin;
+ gFrames = gDebugger.DebuggerView.StackFrames;
+ gClassicFrames = gDebugger.DebuggerView.StackFramesClassicList;
+
+ waitForCaretAndScopes(gPanel, 14).then(performTest);
+ callInTab(gTab, "simpleCall");
+ });
+}
+
+function performTest() {
+ is(gDebugger.gThreadClient.state, "paused",
+ "Should only be getting stack frames while paused.");
+ is(gFrames.itemCount, 1,
+ "Should have only one frame.");
+ is(gClassicFrames.itemCount, 1,
+ "Should also have only one frame in the mirrored view.");
+
+ resumeDebuggerThenCloseAndFinish(gPanel);
+}
+
+registerCleanupFunction(function () {
+ gTab = null;
+ gPanel = null;
+ gDebugger = null;
+ gFrames = null;
+ gClassicFrames = null;
+});
diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_stack-02.js b/devtools/client/debugger/test/mochitest/browser_dbg_stack-02.js
new file mode 100644
index 000000000..5c972f4ee
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_stack-02.js
@@ -0,0 +1,115 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Test that stackframes are added when debugger is paused in eval calls.
+ */
+
+const TAB_URL = EXAMPLE_URL + "doc_recursion-stack.html";
+
+function test() {
+ let options = {
+ source: TAB_URL,
+ line: 1
+ };
+ initDebugger(TAB_URL, options).then(([aTab,, aPanel]) => {
+ const gTab = aTab;
+ const gPanel = aPanel;
+ const gDebugger = gPanel.panelWin;
+ const gFrames = gDebugger.DebuggerView.StackFrames;
+ const gClassicFrames = gDebugger.DebuggerView.StackFramesClassicList;
+
+ const performTest = Task.async(function* () {
+ is(gDebugger.gThreadClient.state, "paused",
+ "Should only be getting stack frames while paused.");
+ is(gFrames.itemCount, 2,
+ "Should have two frames.");
+ is(gClassicFrames.itemCount, 2,
+ "Should also have only two in the mirrored view.");
+
+ is(gFrames.getItemAtIndex(0).attachment.title,
+ "evalCall", "Oldest frame name should be correct.");
+ is(gFrames.getItemAtIndex(0).attachment.url,
+ TAB_URL, "Oldest frame url should be correct.");
+ is(gClassicFrames.getItemAtIndex(0).attachment.depth,
+ 0, "Oldest frame name is mirrored correctly.");
+
+ is(gFrames.getItemAtIndex(1).attachment.title,
+ "(eval)", "Newest frame name should be correct.");
+ is(gFrames.getItemAtIndex(1).attachment.url,
+ "SCRIPT0", "Newest frame url should be correct.");
+ is(gClassicFrames.getItemAtIndex(1).attachment.depth,
+ 1, "Newest frame name is mirrored correctly.");
+
+ is(gFrames.selectedIndex, 1,
+ "Newest frame should be selected by default.");
+ is(gClassicFrames.selectedIndex, 0,
+ "Newest frame should be selected by default in the mirrored view.");
+
+ isnot(gFrames.selectedIndex, 0,
+ "Oldest frame should not be selected.");
+ isnot(gClassicFrames.selectedIndex, 1,
+ "Oldest frame should not be selected in the mirrored view.");
+
+ EventUtils.sendMouseEvent({ type: "mousedown" },
+ gFrames.getItemAtIndex(0).target,
+ gDebugger);
+
+ isnot(gFrames.selectedIndex, 1,
+ "Newest frame should not be selected after click.");
+ isnot(gClassicFrames.selectedIndex, 0,
+ "Newest frame in the mirrored view should not be selected.");
+
+ is(gFrames.selectedIndex, 0,
+ "Oldest frame should be selected after click.");
+ is(gClassicFrames.selectedIndex, 1,
+ "Oldest frame in the mirrored view should be selected.");
+
+ EventUtils.sendMouseEvent({ type: "mousedown" },
+ gFrames.getItemAtIndex(1).target.querySelector(".dbg-stackframe-title"),
+ gDebugger);
+ // Give the UI some time to update. For some reason if we don't
+ // do this there is global window leakage. We are continually
+ // cleaning up our tests so this will be refactored out at some
+ // point.
+ yield waitForTime(1);
+
+ is(gFrames.selectedIndex, 1,
+ "Newest frame should be selected after click inside the newest frame.");
+ is(gClassicFrames.selectedIndex, 0,
+ "Newest frame in the mirrored view should be selected.");
+
+ isnot(gFrames.selectedIndex, 0,
+ "Oldest frame should not be selected after click inside the newest frame.");
+ isnot(gClassicFrames.selectedIndex, 1,
+ "Oldest frame in the mirrored view should not be selected.");
+
+ EventUtils.sendMouseEvent({ type: "mousedown" },
+ gFrames.getItemAtIndex(0).target.querySelector(".dbg-stackframe-details"),
+ gDebugger);
+ // See comment above on the same statement.
+ yield waitForTime(1);
+
+ isnot(gFrames.selectedIndex, 1,
+ "Newest frame should not be selected after click inside the oldest frame.");
+ isnot(gClassicFrames.selectedIndex, 0,
+ "Newest frame in the mirrored view should not be selected.");
+
+ is(gFrames.selectedIndex, 0,
+ "Oldest frame should be selected after click inside the oldest frame.");
+ is(gClassicFrames.selectedIndex, 1,
+ "Oldest frame in the mirrored view should be selected.");
+
+ resumeDebuggerThenCloseAndFinish(gPanel);
+ });
+
+ Task.spawn(function* () {
+ yield waitForCaretAndScopes(gPanel, 1);
+ performTest();
+ });
+
+ callInTab(gTab, "evalCall");
+ });
+}
diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_stack-03.js b/devtools/client/debugger/test/mochitest/browser_dbg_stack-03.js
new file mode 100644
index 000000000..28993bfd5
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_stack-03.js
@@ -0,0 +1,64 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Test that stackframes are scrollable.
+ */
+
+const TAB_URL = EXAMPLE_URL + "doc_recursion-stack.html";
+let framesScrollingInterval;
+
+function test() {
+ let options = {
+ source: TAB_URL,
+ line: 1
+ };
+ initDebugger(TAB_URL, options).then(([aTab, aDebuggee, aPanel]) => {
+ const tab = aTab;
+ const debuggee = aDebuggee;
+ const panel = aPanel;
+ const gDebugger = panel.panelWin;
+ const frames = gDebugger.DebuggerView.StackFrames;
+ const classicFrames = gDebugger.DebuggerView.StackFramesClassicList;
+
+ Task.spawn(function* () {
+ framesScrollingInterval = window.setInterval(() => {
+ frames.widget._list.scrollByIndex(-1);
+ }, 100);
+
+ yield waitForDebuggerEvents(panel, gDebugger.EVENTS.AFTER_FRAMES_REFILLED);
+
+ is(gDebugger.gThreadClient.state, "paused",
+ "Should only be getting stack frames while paused.");
+ is(frames.itemCount, gDebugger.gCallStackPageSize,
+ "Should have only the max limit of frames.");
+ is(classicFrames.itemCount, gDebugger.gCallStackPageSize,
+ "Should have only the max limit of frames in the mirrored view as well.");
+
+ yield waitForDebuggerEvents(panel, gDebugger.EVENTS.AFTER_FRAMES_REFILLED);
+
+ is(frames.itemCount, gDebugger.gCallStackPageSize * 2,
+ "Should now have twice the max limit of frames.");
+ is(classicFrames.itemCount, gDebugger.gCallStackPageSize * 2,
+ "Should now have twice the max limit of frames in the mirrored view as well.");
+
+ yield waitForDebuggerEvents(panel, gDebugger.EVENTS.AFTER_FRAMES_REFILLED);
+
+ is(frames.itemCount, debuggee.gRecurseLimit,
+ "Should have reached the recurse limit.");
+ is(classicFrames.itemCount, debuggee.gRecurseLimit,
+ "Should have reached the recurse limit in the mirrored view as well.");
+
+
+ // Call stack frame scrolling should stop before
+ // we resume the gDebugger as it could be a source of race conditions.
+ window.clearInterval(framesScrollingInterval);
+ resumeDebuggerThenCloseAndFinish(panel);
+ });
+
+ debuggee.gRecurseLimit = (gDebugger.gCallStackPageSize * 2) + 1;
+ debuggee.recurse();
+ });
+}
diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_stack-04.js b/devtools/client/debugger/test/mochitest/browser_dbg_stack-04.js
new file mode 100644
index 000000000..808ec634e
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_stack-04.js
@@ -0,0 +1,58 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Test that stackframes are cleared after resume.
+ */
+
+const TAB_URL = EXAMPLE_URL + "doc_recursion-stack.html";
+
+var gTab, gPanel, gDebugger;
+var gFrames, gClassicFrames;
+
+function test() {
+ let options = {
+ source: TAB_URL,
+ line: 1
+ };
+ initDebugger(TAB_URL, options).then(([aTab,, aPanel]) => {
+ gTab = aTab;
+ gPanel = aPanel;
+ gDebugger = gPanel.panelWin;
+ gFrames = gDebugger.DebuggerView.StackFrames;
+ gClassicFrames = gDebugger.DebuggerView.StackFramesClassicList;
+
+ waitForCaretAndScopes(gPanel, 1).then(performTest);
+ callInTab(gTab, "evalCall");
+ });
+}
+
+function performTest() {
+ is(gDebugger.gThreadClient.state, "paused",
+ "Should only be getting stack frames while paused.");
+ is(gFrames.itemCount, 2,
+ "Should have two frames.");
+ is(gClassicFrames.itemCount, 2,
+ "Should also have two frames in the mirrored view.");
+
+ gDebugger.once(gDebugger.EVENTS.AFTER_FRAMES_CLEARED, () => {
+ is(gFrames.itemCount, 0,
+ "Should have no frames after resume.");
+ is(gClassicFrames.itemCount, 0,
+ "Should also have no frames in the mirrored view after resume.");
+
+ closeDebuggerAndFinish(gPanel);
+ }, true);
+
+ gDebugger.gThreadClient.resume();
+}
+
+registerCleanupFunction(function () {
+ gTab = null;
+ gPanel = null;
+ gDebugger = null;
+ gFrames = null;
+ gClassicFrames = null;
+});
diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_stack-05.js b/devtools/client/debugger/test/mochitest/browser_dbg_stack-05.js
new file mode 100644
index 000000000..2e5648922
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_stack-05.js
@@ -0,0 +1,102 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Test that switching between stack frames properly sets the current debugger
+ * location in the source editor.
+ */
+
+const TAB_URL = EXAMPLE_URL + "doc_script-switching-01.html";
+
+function test() {
+ let options = {
+ source: EXAMPLE_URL + "code_script-switching-01.js",
+ line: 1
+ };
+ initDebugger(TAB_URL, options).then(([aTab,, aPanel]) => {
+ const gTab = aTab;
+ const gPanel = aPanel;
+ const gDebugger = gPanel.panelWin;
+ const gEditor = gDebugger.DebuggerView.editor;
+ const gSources = gDebugger.DebuggerView.Sources;
+ const gFrames = gDebugger.DebuggerView.StackFrames;
+ const gClassicFrames = gDebugger.DebuggerView.StackFramesClassicList;
+
+ function initialChecks() {
+ is(gDebugger.gThreadClient.state, "paused",
+ "Should only be getting stack frames while paused.");
+ is(gFrames.itemCount, 2,
+ "Should have four frames.");
+ is(gClassicFrames.itemCount, 2,
+ "Should also have four frames in the mirrored view.");
+ }
+
+ function testNewestFrame() {
+ is(gFrames.selectedIndex, 1,
+ "Newest frame should be selected by default.");
+ is(gClassicFrames.selectedIndex, 0,
+ "Newest frame should be selected in the mirrored view as well.");
+ is(gSources.selectedIndex, 1,
+ "The second source is selected in the widget.");
+ ok(isCaretPos(gPanel, 6),
+ "Editor caret location is correct.");
+ is(gEditor.getDebugLocation(), 5,
+ "Editor debug location is correct.");
+ }
+
+ function testOldestFrame() {
+ const shown = waitForSourceAndCaret(gPanel, "-01.js", 5).then(() => {
+ is(gFrames.selectedIndex, 0,
+ "Second frame should be selected after click.");
+ is(gClassicFrames.selectedIndex, 1,
+ "Second frame should be selected in the mirrored view as well.");
+ is(gSources.selectedIndex, 0,
+ "The first source is now selected in the widget.");
+ ok(isCaretPos(gPanel, 5),
+ "Editor caret location is correct (3).");
+ is(gEditor.getDebugLocation(), 4,
+ "Editor debug location is correct.");
+ });
+
+ EventUtils.sendMouseEvent({ type: "mousedown" },
+ gDebugger.document.querySelector("#stackframe-1"),
+ gDebugger);
+
+ return shown;
+ }
+
+ function testAfterResume() {
+ let deferred = promise.defer();
+
+ gDebugger.once(gDebugger.EVENTS.AFTER_FRAMES_CLEARED, () => {
+ is(gFrames.itemCount, 0,
+ "Should have no frames after resume.");
+ is(gClassicFrames.itemCount, 0,
+ "Should have no frames in the mirrored view as well.");
+ ok(isCaretPos(gPanel, 5),
+ "Editor caret location is correct after resume.");
+ is(gEditor.getDebugLocation(), null,
+ "Editor debug location is correct after resume.");
+
+ deferred.resolve();
+ }, true);
+
+ gDebugger.gThreadClient.resume();
+
+ return deferred.promise;
+ }
+
+ Task.spawn(function* () {
+ yield waitForSourceAndCaretAndScopes(gPanel, "-02.js", 6);
+ yield initialChecks();
+ yield testNewestFrame();
+ yield testOldestFrame();
+ yield testAfterResume();
+ closeDebuggerAndFinish(gPanel);
+ });
+
+ callInTab(gTab, "firstCall");
+ });
+}
diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_stack-06.js b/devtools/client/debugger/test/mochitest/browser_dbg_stack-06.js
new file mode 100644
index 000000000..11f3b9534
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_stack-06.js
@@ -0,0 +1,92 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Make sure that selecting a stack frame loads the right source in the editor
+ * pane and highlights the proper line.
+ */
+
+const TAB_URL = EXAMPLE_URL + "doc_script-switching-01.html";
+
+var gTab, gPanel, gDebugger;
+var gEditor, gSources, gFrames, gClassicFrames;
+
+function test() {
+ let options = {
+ source: EXAMPLE_URL + "code_script-switching-01.js",
+ line: 1
+ };
+ initDebugger(TAB_URL, options).then(([aTab,, aPanel]) => {
+ gTab = aTab;
+ gPanel = aPanel;
+ gDebugger = gPanel.panelWin;
+ gEditor = gDebugger.DebuggerView.editor;
+ gSources = gDebugger.DebuggerView.Sources;
+ gFrames = gDebugger.DebuggerView.StackFrames;
+ gClassicFrames = gDebugger.DebuggerView.StackFramesClassicList;
+
+ waitForSourceAndCaretAndScopes(gPanel, "-02.js", 1).then(performTest);
+ callInTab(gTab, "firstCall");
+ });
+}
+
+function performTest() {
+ is(gFrames.selectedIndex, 1,
+ "Newest frame should be selected by default.");
+ is(gClassicFrames.selectedIndex, 0,
+ "Newest frame should also be selected in the mirrored view.");
+ is(gSources.selectedIndex, 1,
+ "The second source is selected in the widget.");
+ is(gEditor.getText().search(/firstCall/), -1,
+ "The first source is not displayed.");
+ is(gEditor.getText().search(/debugger/), 166,
+ "The second source is displayed.");
+
+ waitForSourceAndCaret(gPanel, "-01.js", 5).then(waitForTick).then(() => {
+ is(gFrames.selectedIndex, 0,
+ "Oldest frame should be selected after click.");
+ is(gClassicFrames.selectedIndex, 1,
+ "Oldest frame should also be selected in the mirrored view.");
+ is(gSources.selectedIndex, 0,
+ "The first source is now selected in the widget.");
+ is(gEditor.getText().search(/firstCall/), 118,
+ "The first source is displayed.");
+ is(gEditor.getText().search(/debugger/), -1,
+ "The second source is not displayed.");
+
+ waitForSourceAndCaret(gPanel, "-02.js", 6).then(waitForTick).then(() => {
+ is(gFrames.selectedIndex, 1,
+ "Newest frame should be selected again after click.");
+ is(gClassicFrames.selectedIndex, 0,
+ "Newest frame should also be selected again in the mirrored view.");
+ is(gSources.selectedIndex, 1,
+ "The second source is selected in the widget.");
+ is(gEditor.getText().search(/firstCall/), -1,
+ "The first source is not displayed.");
+ is(gEditor.getText().search(/debugger/), 166,
+ "The second source is displayed.");
+
+ resumeDebuggerThenCloseAndFinish(gPanel);
+ });
+
+ EventUtils.sendMouseEvent({ type: "mousedown" },
+ gDebugger.document.querySelector("#classic-stackframe-0"),
+ gDebugger);
+ });
+
+ EventUtils.sendMouseEvent({ type: "mousedown" },
+ gDebugger.document.querySelector("#stackframe-1"),
+ gDebugger);
+}
+
+registerCleanupFunction(function () {
+ gTab = null;
+ gPanel = null;
+ gDebugger = null;
+ gEditor = null;
+ gSources = null;
+ gFrames = null;
+ gClassicFrames = null;
+});
diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_stack-07.js b/devtools/client/debugger/test/mochitest/browser_dbg_stack-07.js
new file mode 100644
index 000000000..a5bbc5a10
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_stack-07.js
@@ -0,0 +1,113 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Make sure that after selecting a different stack frame, resuming reselects
+ * the topmost stackframe, loads the right source in the editor pane and
+ * highlights the proper line.
+ */
+
+const TAB_URL = EXAMPLE_URL + "doc_script-switching-01.html";
+
+var gTab, gPanel, gDebugger;
+var gEditor, gSources, gFrames, gClassicFrames, gToolbar;
+
+function test() {
+ let options = {
+ source: EXAMPLE_URL + "code_script-switching-01.js",
+ line: 1
+ };
+ initDebugger(TAB_URL, options).then(([aTab,, aPanel]) => {
+ gTab = aTab;
+ gPanel = aPanel;
+ gDebugger = gPanel.panelWin;
+ gEditor = gDebugger.DebuggerView.editor;
+ gSources = gDebugger.DebuggerView.Sources;
+ gFrames = gDebugger.DebuggerView.StackFrames;
+ gClassicFrames = gDebugger.DebuggerView.StackFramesClassicList;
+ gToolbar = gDebugger.DebuggerView.Toolbar;
+
+ waitForSourceAndCaretAndScopes(gPanel, "-02.js", 1).then(performTest);
+ callInTab(gTab, "firstCall");
+ });
+}
+
+function performTest() {
+ return Task.spawn(function* () {
+ yield selectBottomFrame();
+ testBottomFrame(4);
+
+ yield performStep("StepOver");
+ testTopFrame(1);
+
+ yield selectBottomFrame();
+ testBottomFrame(4);
+
+ yield performStep("StepIn");
+ testTopFrame(1);
+
+ yield selectBottomFrame();
+ testBottomFrame(4);
+
+ yield performStep("StepOut");
+ testTopFrame(1);
+
+ yield resumeDebuggerThenCloseAndFinish(gPanel);
+ });
+
+ function selectBottomFrame() {
+ let shown = waitForSourceShown(gPanel, "-01.js");
+ gClassicFrames.selectedIndex = gClassicFrames.itemCount - 1;
+ return shown;
+ }
+
+ function testBottomFrame(debugLocation) {
+ is(gFrames.selectedIndex, 0,
+ "Oldest frame should be selected after click.");
+ is(gClassicFrames.selectedIndex, gFrames.itemCount - 1,
+ "Oldest frame should also be selected in the mirrored view.");
+ is(gSources.selectedIndex, 0,
+ "The first source is now selected in the widget.");
+ is(gEditor.getText().search(/firstCall/), 118,
+ "The first source is displayed.");
+ is(gEditor.getText().search(/debugger/), -1,
+ "The second source is not displayed.");
+
+ is(gEditor.getDebugLocation(), debugLocation,
+ "Editor debugger location is correct.");
+ ok(gEditor.hasLineClass(debugLocation, "debug-line"),
+ "The debugged line is highlighted appropriately.");
+ }
+
+ function performStep(type) {
+ let updated = waitForDebuggerEvents(gPanel, gDebugger.EVENTS.FETCHED_SCOPES);
+ gToolbar["_on" + type + "Pressed"]();
+ return updated.then(waitForTick);
+ }
+
+ function testTopFrame(frameIndex) {
+ is(gFrames.selectedIndex, frameIndex,
+ "Topmost frame should be selected after click.");
+ is(gClassicFrames.selectedIndex, gFrames.itemCount - frameIndex - 1,
+ "Topmost frame should also be selected in the mirrored view.");
+ is(gSources.selectedIndex, 1,
+ "The second source is now selected in the widget.");
+ is(gEditor.getText().search(/firstCall/), -1,
+ "The second source is displayed.");
+ is(gEditor.getText().search(/debugger/), 166,
+ "The first source is not displayed.");
+ }
+}
+
+registerCleanupFunction(function () {
+ gTab = null;
+ gPanel = null;
+ gDebugger = null;
+ gEditor = null;
+ gSources = null;
+ gFrames = null;
+ gClassicFrames = null;
+ gToolbar = null;
+});
diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_stack-contextmenu-01.js b/devtools/client/debugger/test/mochitest/browser_dbg_stack-contextmenu-01.js
new file mode 100644
index 000000000..61d964b91
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_stack-contextmenu-01.js
@@ -0,0 +1,58 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Test that the copy contextmenu has been added to the stack frames view.
+ */
+
+ const TAB_URL = EXAMPLE_URL + "doc_recursion-stack.html";
+ let gTab, gPanel, gDebugger;
+ let gFrames, gContextMenu;
+
+ function test() {
+ let options = {
+ source: TAB_URL,
+ line: 1
+ };
+ initDebugger(TAB_URL, options).then(([aTab,, aPanel]) => {
+ gTab = aTab;
+ gPanel = aPanel;
+ gDebugger = gPanel.panelWin;
+ gFrames = gDebugger.DebuggerView.StackFrames;
+
+ waitForDebuggerEvents(gPanel, gDebugger.EVENTS.AFTER_FRAMES_REFILLED)
+ .then(performTest);
+ callInTab(gTab, "simpleCall");
+ });
+ }
+
+ function performTest() {
+ gContextMenu = gDebugger.document.getElementById("stackFramesContextMenu");
+ is(gDebugger.gThreadClient.state, "paused",
+ "Should only be getting stack frames while paused.");
+ is(gFrames.itemCount, 1,
+ "Should have only one frame.");
+ ok(gContextMenu, "The stack frame's context menupopup is available.");
+
+ once(gContextMenu, "popupshown").then(testContextMenu);
+ EventUtils.synthesizeMouseAtCenter(gFrames.getItemAtIndex(0).prebuiltNode, {type: "contextmenu", button: 2}, gDebugger);
+ }
+
+ function testContextMenu() {
+ let document = gDebugger.document;
+ ok(document.getElementById("copyStackMenuItem"),
+ "#copyStackMenuItem found.");
+
+ gContextMenu.hidePopup();
+ resumeDebuggerThenCloseAndFinish(gPanel);
+ }
+
+ registerCleanupFunction(function () {
+ gTab = null;
+ gPanel = null;
+ gDebugger = null;
+ gFrames = null;
+ gContextMenu = null;
+ });
diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_stack-contextmenu-02.js b/devtools/client/debugger/test/mochitest/browser_dbg_stack-contextmenu-02.js
new file mode 100644
index 000000000..828bce6c8
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_stack-contextmenu-02.js
@@ -0,0 +1,58 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Test that the copy contextmenu copys the stack frames to the clipboard.
+ */
+
+const TAB_URL = EXAMPLE_URL + "doc_recursion-stack.html";
+const STACK_STRING = "simpleCall@" + EXAMPLE_URL + "doc_recursion-stack.html:14:8";
+
+function test() {
+ let gTab, gPanel, gDebugger, gFrames;
+ let options = {
+ source: TAB_URL,
+ line: 1
+ };
+ initDebugger(TAB_URL, options).then(([aTab,, aPanel]) => {
+ gTab = aTab;
+ gPanel = aPanel;
+ gDebugger = gPanel.panelWin;
+ gFrames = gDebugger.DebuggerView.StackFrames;
+
+ waitForDebuggerEvents(gPanel, gDebugger.EVENTS.AFTER_FRAMES_REFILLED)
+ .then(openContextMenu)
+ .then(testCopyStackMenuItem)
+ .then(() => resumeDebuggerThenCloseAndFinish(gPanel))
+ .then(null, aError => {
+ ok(false, "Got an error: " + aError.message + "\n" + aError.stack);
+ });
+ callInTab(gTab, "simpleCall");
+ });
+
+ function clickCopyStack() {
+ return new Promise((resolve, reject) => {
+ let copyStackMenuItem = gDebugger.document.getElementById("copyStackMenuItem");
+ if (!copyStackMenuItem) {
+ reject(new Error("The Copy stack context menu item is not available."));
+ }
+
+ ok(copyStackMenuItem, "The Copy stack context menu item is available.");
+ EventUtils.synthesizeMouseAtCenter(copyStackMenuItem, {}, gDebugger);
+ resolve();
+ });
+ }
+
+ function testCopyStackMenuItem() {
+ return waitForClipboardPromise(clickCopyStack, STACK_STRING);
+ }
+
+ function openContextMenu() {
+ let contextMenu = gDebugger.document.getElementById("stackFramesContextMenu");
+ let contextMenuShown = once(contextMenu, "popupshown");
+ EventUtils.synthesizeMouseAtCenter(gFrames.getItemAtIndex(0).prebuiltNode, {type: "contextmenu", button: 2}, gDebugger);
+ return contextMenuShown;
+ }
+}
diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_step-out.js b/devtools/client/debugger/test/mochitest/browser_dbg_step-out.js
new file mode 100644
index 000000000..ae1099a92
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_step-out.js
@@ -0,0 +1,91 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Make sure that stepping out of a function displays the right return value.
+ */
+
+const TAB_URL = EXAMPLE_URL + "doc_step-out.html";
+
+var gTab, gPanel, gDebugger;
+var gVars;
+
+function test() {
+ let options = {
+ source: TAB_URL,
+ line: 1
+ };
+ initDebugger(TAB_URL, options).then(([aTab,, aPanel]) => {
+ gTab = aTab;
+ gPanel = aPanel;
+ gDebugger = gPanel.panelWin;
+ gVars = gDebugger.DebuggerView.Variables;
+
+ testNormalReturn();
+ });
+}
+
+function testNormalReturn() {
+ waitForCaretAndScopes(gPanel, 17).then(() => {
+ waitForCaretAndScopes(gPanel, 20).then(() => {
+ let innerScope = gVars.getScopeAtIndex(0);
+ let returnVar = innerScope.get("<return>");
+
+ is(returnVar.name, "<return>",
+ "Should have the right property name for the returned value.");
+ is(returnVar.value, 10,
+ "Should have the right property value for the returned value.");
+ ok(returnVar._internalItem, "Should be an internal item");
+ ok(returnVar._target.hasAttribute("pseudo-item"),
+ "Element should be marked as a pseudo-item");
+
+ resumeDebuggee().then(() => testReturnWithException());
+ });
+
+ EventUtils.sendMouseEvent({ type: "mousedown" },
+ gDebugger.document.getElementById("step-out"),
+ gDebugger);
+ });
+
+ generateMouseClickInTab(gTab, "content.document.getElementById('return')");
+}
+
+function testReturnWithException() {
+ waitForCaretAndScopes(gPanel, 24).then(() => {
+ waitForCaretAndScopes(gPanel, 26).then(() => {
+ let innerScope = gVars.getScopeAtIndex(0);
+ let exceptionVar = innerScope.get("<exception>");
+
+ is(exceptionVar.name, "<exception>",
+ "Should have the right property name for the returned value.");
+ is(exceptionVar.value, "boom",
+ "Should have the right property value for the returned value.");
+ ok(exceptionVar._internalItem, "Should be an internal item");
+ ok(exceptionVar._target.hasAttribute("pseudo-item"),
+ "Element should be marked as a pseudo-item");
+
+ resumeDebuggee().then(() => closeDebuggerAndFinish(gPanel));
+ });
+
+ EventUtils.sendMouseEvent({ type: "mousedown" },
+ gDebugger.document.getElementById("step-out"),
+ gDebugger);
+ });
+
+ generateMouseClickInTab(gTab, "content.document.getElementById('throw')");
+}
+
+function resumeDebuggee() {
+ let deferred = promise.defer();
+ gDebugger.gThreadClient.resume(deferred.resolve);
+ return deferred.promise;
+}
+
+registerCleanupFunction(function () {
+ gTab = null;
+ gPanel = null;
+ gDebugger = null;
+ gVars = null;
+});
diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_tabactor-01.js b/devtools/client/debugger/test/mochitest/browser_dbg_tabactor-01.js
new file mode 100644
index 000000000..dfb073617
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_tabactor-01.js
@@ -0,0 +1,65 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Check extension-added tab actor lifetimes.
+ */
+
+const ACTORS_URL = CHROME_URL + "testactors.js";
+const TAB_URL = EXAMPLE_URL + "doc_empty-tab-01.html";
+
+var gClient;
+
+function test() {
+ if (!DebuggerServer.initialized) {
+ DebuggerServer.init();
+ DebuggerServer.addBrowserActors();
+ }
+
+ DebuggerServer.addActors(ACTORS_URL);
+
+ let transport = DebuggerServer.connectPipe();
+ gClient = new DebuggerClient(transport);
+ gClient.connect().then(([aType, aTraits]) => {
+ is(aType, "browser",
+ "Root actor should identify itself as a browser.");
+
+ addTab(TAB_URL)
+ .then(() => attachTabActorForUrl(gClient, TAB_URL))
+ .then(testTabActor)
+ .then(closeTab)
+ .then(() => gClient.close())
+ .then(finish)
+ .then(null, aError => {
+ ok(false, "Got an error: " + aError.message + "\n" + aError.stack);
+ });
+ });
+}
+
+function testTabActor([aGrip, aResponse]) {
+ let deferred = promise.defer();
+
+ ok(aGrip.testTabActor1,
+ "Found the test tab actor.");
+ ok(aGrip.testTabActor1.includes("test_one"),
+ "testTabActor1's actorPrefix should be used.");
+
+ gClient.request({ to: aGrip.testTabActor1, type: "ping" }, aResponse => {
+ is(aResponse.pong, "pong",
+ "Actor should respond to requests.");
+
+ deferred.resolve();
+ });
+
+ return deferred.promise;
+}
+
+function closeTab() {
+ return removeTab(gBrowser.selectedTab);
+}
+
+registerCleanupFunction(function () {
+ gClient = null;
+});
diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_tabactor-02.js b/devtools/client/debugger/test/mochitest/browser_dbg_tabactor-02.js
new file mode 100644
index 000000000..c9f506db2
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_tabactor-02.js
@@ -0,0 +1,79 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Check extension-added tab actor lifetimes.
+ */
+
+const ACTORS_URL = CHROME_URL + "testactors.js";
+const TAB_URL = EXAMPLE_URL + "doc_empty-tab-01.html";
+
+var gClient;
+
+function test() {
+ if (!DebuggerServer.initialized) {
+ DebuggerServer.init();
+ DebuggerServer.addBrowserActors();
+ }
+
+ DebuggerServer.addActors(ACTORS_URL);
+
+ let transport = DebuggerServer.connectPipe();
+ gClient = new DebuggerClient(transport);
+ gClient.connect().then(([aType, aTraits]) => {
+ is(aType, "browser",
+ "Root actor should identify itself as a browser.");
+
+ addTab(TAB_URL)
+ .then(() => attachTabActorForUrl(gClient, TAB_URL))
+ .then(testTabActor)
+ .then(closeTab)
+ .then(() => gClient.close())
+ .then(finish)
+ .then(null, aError => {
+ ok(false, "Got an error: " + aError.message + "\n" + aError.stack);
+ });
+ });
+}
+
+function testTabActor([aGrip, aResponse]) {
+ let deferred = promise.defer();
+
+ ok(aGrip.testTabActor1,
+ "Found the test tab actor.");
+ ok(aGrip.testTabActor1.includes("test_one"),
+ "testTabActor1's actorPrefix should be used.");
+
+ gClient.request({ to: aGrip.testTabActor1, type: "ping" }, aResponse => {
+ is(aResponse.pong, "pong",
+ "Actor should respond to requests.");
+
+ deferred.resolve(aResponse.actor);
+ });
+
+ return deferred.promise;
+}
+
+function closeTab(aTestActor) {
+ return removeTab(gBrowser.selectedTab).then(() => {
+ let deferred = promise.defer();
+
+ try {
+ gClient.request({ to: aTestActor, type: "ping" }, aResponse => {
+ ok(false, "testTabActor1 didn't go away with the tab.");
+ deferred.reject(aResponse);
+ });
+ } catch (e) {
+ is(e.message, "'ping' request packet has no destination.", "testTabActor1 went away.");
+ deferred.resolve();
+ }
+
+ return deferred.promise;
+ });
+}
+
+registerCleanupFunction(function () {
+ gClient = null;
+});
diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_terminate-on-tab-close.js b/devtools/client/debugger/test/mochitest/browser_dbg_terminate-on-tab-close.js
new file mode 100644
index 000000000..42a1e6c70
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_terminate-on-tab-close.js
@@ -0,0 +1,34 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Whitelisting this test.
+// As part of bug 1077403, the leaking uncaught rejections should be fixed.
+thisTestLeaksUncaughtRejectionsAndShouldBeFixed("[object Object]");
+
+/**
+ * Tests that debuggee scripts are terminated on tab closure.
+ */
+
+const TAB_URL = EXAMPLE_URL + "doc_terminate-on-tab-close.html";
+
+function test() {
+ let options = {
+ source: TAB_URL,
+ line: 1
+ };
+ initDebugger(TAB_URL, options).then(([aTab,, aPanel]) => {
+ const gTab = aTab;
+ const gPanel = aPanel;
+ const gDebugger = gPanel.panelWin;
+
+ gDebugger.gThreadClient.addOneTimeListener("paused", () => {
+ resumeDebuggerThenCloseAndFinish(gPanel).then(function () {
+ ok(true, "should not throw after this point");
+ });
+ });
+
+ callInTab(gTab, "debuggerThenThrow");
+ });
+}
diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-01.js b/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-01.js
new file mode 100644
index 000000000..c5c846978
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-01.js
@@ -0,0 +1,132 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Tests that creating, collpasing and expanding scopes in the
+ * variables view works as expected.
+ */
+
+const TAB_URL = EXAMPLE_URL + "doc_recursion-stack.html";
+
+function test() {
+ let options = {
+ source: TAB_URL,
+ line: 1
+ };
+ initDebugger(TAB_URL, options).then(([aTab,, aPanel]) => {
+ let variables = aPanel.panelWin.DebuggerView.Variables;
+ let testScope = variables.addScope("test");
+
+ ok(testScope,
+ "Should have created a scope.");
+ ok(testScope.id.includes("test"),
+ "The newly created scope should have the default id set.");
+ is(testScope.name, "test",
+ "The newly created scope should have the desired name set.");
+
+ ok(!testScope.displayValue,
+ "The newly created scope should not have a displayed value (1).");
+ ok(!testScope.displayValueClassName,
+ "The newly created scope should not have a displayed value (2).");
+
+ ok(testScope.target,
+ "The newly created scope should point to a target node.");
+ ok(testScope.target.id.includes("test"),
+ "Should have the correct scope id on the element.");
+
+ is(testScope.target.querySelector(".name").getAttribute("value"), "test",
+ "Any new scope should have the designated name.");
+ is(testScope.target.querySelector(".variables-view-element-details.enum").childNodes.length, 0,
+ "Any new scope should have a container with no enumerable child nodes.");
+ is(testScope.target.querySelector(".variables-view-element-details.nonenum").childNodes.length, 0,
+ "Any new scope should have a container with no non-enumerable child nodes.");
+
+ ok(!testScope.expanded,
+ "Any new created scope should be initially collapsed.");
+ ok(testScope.visible,
+ "Any new created scope should be initially visible.");
+
+ let expandCallbackArg = null;
+ let collapseCallbackArg = null;
+ let toggleCallbackArg = null;
+ let hideCallbackArg = null;
+ let showCallbackArg = null;
+
+ testScope.onexpand = aScope => expandCallbackArg = aScope;
+ testScope.oncollapse = aScope => collapseCallbackArg = aScope;
+ testScope.ontoggle = aScope => toggleCallbackArg = aScope;
+ testScope.onhide = aScope => hideCallbackArg = aScope;
+ testScope.onshow = aScope => showCallbackArg = aScope;
+
+ testScope.expand();
+ ok(testScope.expanded,
+ "The testScope shouldn't be collapsed anymore.");
+ is(expandCallbackArg, testScope,
+ "The expandCallback wasn't called as it should.");
+
+ testScope.collapse();
+ ok(!testScope.expanded,
+ "The testScope should be collapsed again.");
+ is(collapseCallbackArg, testScope,
+ "The collapseCallback wasn't called as it should.");
+
+ testScope.expanded = true;
+ ok(testScope.expanded,
+ "The testScope shouldn't be collapsed anymore.");
+
+ testScope.toggle();
+ ok(!testScope.expanded,
+ "The testScope should be collapsed again.");
+ is(toggleCallbackArg, testScope,
+ "The toggleCallback wasn't called as it should.");
+
+ testScope.hide();
+ ok(!testScope.visible,
+ "The testScope should be invisible after hiding.");
+ is(hideCallbackArg, testScope,
+ "The hideCallback wasn't called as it should.");
+
+ testScope.show();
+ ok(testScope.visible,
+ "The testScope should be visible again.");
+ is(showCallbackArg, testScope,
+ "The showCallback wasn't called as it should.");
+
+ testScope.visible = false;
+ ok(!testScope.visible,
+ "The testScope should be invisible after hiding.");
+ ok(!testScope.expanded,
+ "The testScope should remember it is collapsed even if it is hidden.");
+
+ testScope.visible = true;
+ ok(testScope.visible,
+ "The testScope should be visible after reshowing.");
+ ok(!testScope.expanded,
+ "The testScope should remember it is collapsed after it is reshown.");
+
+ EventUtils.sendMouseEvent({ type: "mousedown", button: 1 },
+ testScope.target.querySelector(".title"),
+ aPanel.panelWin);
+
+ ok(!testScope.expanded,
+ "Clicking the testScope title with the right mouse button should't expand it.");
+
+ EventUtils.sendMouseEvent({ type: "mousedown" },
+ testScope.target.querySelector(".title"),
+ aPanel.panelWin);
+
+ ok(testScope.expanded,
+ "Clicking the testScope title should expand it.");
+
+ EventUtils.sendMouseEvent({ type: "mousedown" },
+ testScope.target.querySelector(".title"),
+ aPanel.panelWin);
+
+ ok(!testScope.expanded,
+ "Clicking again the testScope title should collapse it.");
+
+ closeDebuggerAndFinish(aPanel);
+ });
+}
diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-02.js b/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-02.js
new file mode 100644
index 000000000..f97353dba
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-02.js
@@ -0,0 +1,227 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Tests that creating, collapsing and expanding variables in the
+ * variables view works as expected.
+ */
+
+const TAB_URL = EXAMPLE_URL + "doc_recursion-stack.html";
+
+function test() {
+ let options = {
+ source: TAB_URL,
+ line: 1
+ };
+ initDebugger(TAB_URL, options).then(([aTab,, aPanel]) => {
+ let variables = aPanel.panelWin.DebuggerView.Variables;
+ let testScope = variables.addScope("test");
+ let testVar = testScope.addItem("something");
+ let duplVar = testScope.addItem("something");
+
+ info("Scope id: " + testScope.id);
+ info("Scope name: " + testScope.name);
+ info("Variable id: " + testVar.id);
+ info("Variable name: " + testVar.name);
+
+ ok(testScope,
+ "Should have created a scope.");
+ is(duplVar, testVar,
+ "Shouldn't be able to duplicate variables in the same scope.");
+
+ ok(testVar,
+ "Should have created a variable.");
+ ok(testVar.id.includes("something"),
+ "The newly created variable should have the default id set.");
+ is(testVar.name, "something",
+ "The newly created variable should have the desired name set.");
+
+ ok(!testVar.displayValue,
+ "The newly created variable should not have a displayed value yet (1).");
+ ok(!testVar.displayValueClassName,
+ "The newly created variable should not have a displayed value yet (2).");
+
+ ok(testVar.target,
+ "The newly created scope should point to a target node.");
+ ok(testVar.target.id.includes("something"),
+ "Should have the correct variable id on the element.");
+
+ is(testVar.target.querySelector(".name").getAttribute("value"), "something",
+ "Any new variable should have the designated name.");
+ is(testVar.target.querySelector(".variables-view-element-details.enum").childNodes.length, 0,
+ "Any new variable should have a container with no enumerable child nodes.");
+ is(testVar.target.querySelector(".variables-view-element-details.nonenum").childNodes.length, 0,
+ "Any new variable should have a container with no non-enumerable child nodes.");
+
+ ok(!testVar.expanded,
+ "Any new created scope should be initially collapsed.");
+ ok(testVar.visible,
+ "Any new created scope should be initially visible.");
+
+ let expandCallbackArg = null;
+ let collapseCallbackArg = null;
+ let toggleCallbackArg = null;
+ let hideCallbackArg = null;
+ let showCallbackArg = null;
+
+ testVar.onexpand = aScope => expandCallbackArg = aScope;
+ testVar.oncollapse = aScope => collapseCallbackArg = aScope;
+ testVar.ontoggle = aScope => toggleCallbackArg = aScope;
+ testVar.onhide = aScope => hideCallbackArg = aScope;
+ testVar.onshow = aScope => showCallbackArg = aScope;
+
+ testVar.expand();
+ ok(testVar.expanded,
+ "The testVar shouldn't be collapsed anymore.");
+ is(expandCallbackArg, testVar,
+ "The expandCallback wasn't called as it should.");
+
+ testVar.collapse();
+ ok(!testVar.expanded,
+ "The testVar should be collapsed again.");
+ is(collapseCallbackArg, testVar,
+ "The collapseCallback wasn't called as it should.");
+
+ testVar.expanded = true;
+ ok(testVar.expanded,
+ "The testVar shouldn't be collapsed anymore.");
+
+ testVar.toggle();
+ ok(!testVar.expanded,
+ "The testVar should be collapsed again.");
+ is(toggleCallbackArg, testVar,
+ "The toggleCallback wasn't called as it should.");
+
+ testVar.hide();
+ ok(!testVar.visible,
+ "The testVar should be invisible after hiding.");
+ is(hideCallbackArg, testVar,
+ "The hideCallback wasn't called as it should.");
+
+ testVar.show();
+ ok(testVar.visible,
+ "The testVar should be visible again.");
+ is(showCallbackArg, testVar,
+ "The showCallback wasn't called as it should.");
+
+ testVar.visible = false;
+ ok(!testVar.visible,
+ "The testVar should be invisible after hiding.");
+ ok(!testVar.expanded,
+ "The testVar should remember it is collapsed even if it is hidden.");
+
+ testVar.visible = true;
+ ok(testVar.visible,
+ "The testVar should be visible after reshowing.");
+ ok(!testVar.expanded,
+ "The testVar should remember it is collapsed after it is reshown.");
+
+ EventUtils.sendMouseEvent({ type: "mousedown" },
+ testVar.target.querySelector(".name"),
+ aPanel.panelWin);
+
+ ok(testVar.expanded,
+ "Clicking the testVar name should expand it.");
+
+ EventUtils.sendMouseEvent({ type: "mousedown" },
+ testVar.target.querySelector(".name"),
+ aPanel.panelWin);
+
+ ok(!testVar.expanded,
+ "Clicking again the testVar name should collapse it.");
+
+ EventUtils.sendMouseEvent({ type: "mousedown" },
+ testVar.target.querySelector(".arrow"),
+ aPanel.panelWin);
+
+ ok(testVar.expanded,
+ "Clicking the testVar arrow should expand it.");
+
+ EventUtils.sendMouseEvent({ type: "mousedown" },
+ testVar.target.querySelector(".arrow"),
+ aPanel.panelWin);
+
+ ok(!testVar.expanded,
+ "Clicking again the testVar arrow should collapse it.");
+
+ EventUtils.sendMouseEvent({ type: "mousedown" },
+ testVar.target.querySelector(".title"),
+ aPanel.panelWin);
+
+ ok(testVar.expanded,
+ "Clicking the testVar title should expand it again.");
+
+ testVar.addItem("child", {
+ value: {
+ type: "object",
+ class: "Object"
+ }
+ });
+
+ let testChild = testVar.get("child");
+ ok(testChild,
+ "Should have created a child property.");
+ ok(testChild.id.includes("child"),
+ "The newly created property should have the default id set.");
+ is(testChild.name, "child",
+ "The newly created property should have the desired name set.");
+
+ is(testChild.displayValue, "Object",
+ "The newly created property should not have a displayed value yet (1).");
+ is(testChild.displayValueClassName, "token-other",
+ "The newly created property should not have a displayed value yet (2).");
+
+ ok(testChild.target,
+ "The newly created scope should point to a target node.");
+ ok(testChild.target.id.includes("child"),
+ "Should have the correct property id on the element.");
+
+ is(testChild.target.querySelector(".name").getAttribute("value"), "child",
+ "Any new property should have the designated name.");
+ is(testChild.target.querySelector(".variables-view-element-details.enum").childNodes.length, 0,
+ "Any new property should have a container with no enumerable child nodes.");
+ is(testChild.target.querySelector(".variables-view-element-details.nonenum").childNodes.length, 0,
+ "Any new property should have a container with no non-enumerable child nodes.");
+
+ ok(!testChild.expanded,
+ "Any new created scope should be initially collapsed.");
+ ok(testChild.visible,
+ "Any new created scope should be initially visible.");
+
+ EventUtils.sendMouseEvent({ type: "mousedown" },
+ testChild.target.querySelector(".name"),
+ aPanel.panelWin);
+
+ ok(testChild.expanded,
+ "Clicking the testChild name should expand it.");
+
+ EventUtils.sendMouseEvent({ type: "mousedown" },
+ testChild.target.querySelector(".name"),
+ aPanel.panelWin);
+
+ ok(!testChild.expanded,
+ "Clicking again the testChild name should collapse it.");
+
+ EventUtils.sendMouseEvent({ type: "mousedown" },
+ testChild.target.querySelector(".arrow"),
+ aPanel.panelWin);
+
+ ok(testChild.expanded,
+ "Clicking the testChild arrow should expand it.");
+
+ EventUtils.sendMouseEvent({ type: "mousedown" },
+ testChild.target.querySelector(".arrow"),
+ aPanel.panelWin);
+
+ ok(!testChild.expanded,
+ "Clicking again the testChild arrow should collapse it.");
+
+ EventUtils.sendMouseEvent({ type: "mousedown" },
+ testChild.target.querySelector(".title"),
+ aPanel.panelWin);
+
+ closeDebuggerAndFinish(aPanel);
+ });
+}
diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-03.js b/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-03.js
new file mode 100644
index 000000000..64e4d45a2
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-03.js
@@ -0,0 +1,157 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Tests that recursively creating properties in the variables view works
+ * as expected.
+ */
+
+const TAB_URL = EXAMPLE_URL + "doc_recursion-stack.html";
+
+function test() {
+ let options = {
+ source: TAB_URL,
+ line: 1
+ };
+ initDebugger(TAB_URL, options).then(([aTab,, aPanel]) => {
+ let variables = aPanel.panelWin.DebuggerView.Variables;
+ let testScope = variables.addScope("test");
+
+ is(testScope.target.querySelectorAll(".variables-view-element-details.enum").length, 1,
+ "One enumerable container should be present in the scope.");
+ is(testScope.target.querySelectorAll(".variables-view-element-details.nonenum").length, 1,
+ "One non-enumerable container should be present in the scope.");
+ is(testScope.target.querySelector(".variables-view-element-details.enum").childNodes.length, 0,
+ "No enumerable variables should be present in the scope.");
+ is(testScope.target.querySelector(".variables-view-element-details.nonenum").childNodes.length, 0,
+ "No non-enumerable variables should be present in the scope.");
+
+ testScope.addItem("something", {
+ value: {
+ type: "object",
+ class: "Object"
+ },
+ enumerable: true
+ });
+
+ is(testScope.target.querySelectorAll(".variables-view-element-details.enum").length, 2,
+ "Two enumerable containers should be present in the tree.");
+ is(testScope.target.querySelectorAll(".variables-view-element-details.nonenum").length, 2,
+ "Two non-enumerable containers should be present in the tree.");
+
+ is(testScope.target.querySelector(".variables-view-element-details.enum").childNodes.length, 1,
+ "A new enumerable variable should have been added in the scope.");
+ is(testScope.target.querySelector(".variables-view-element-details.nonenum").childNodes.length, 0,
+ "No new non-enumerable variables should have been added in the scope.");
+
+ let testVar = testScope.get("something");
+ ok(testVar,
+ "The added variable should be accessible from the scope.");
+
+ is(testVar.target.querySelectorAll(".variables-view-element-details.enum").length, 1,
+ "One enumerable container should be present in the variable.");
+ is(testVar.target.querySelectorAll(".variables-view-element-details.nonenum").length, 1,
+ "One non-enumerable container should be present in the variable.");
+ is(testVar.target.querySelector(".variables-view-element-details.enum").childNodes.length, 0,
+ "No enumerable properties should be present in the variable.");
+ is(testVar.target.querySelector(".variables-view-element-details.nonenum").childNodes.length, 0,
+ "No non-enumerable properties should be present in the variable.");
+
+ testVar.addItem("child", {
+ value: {
+ type: "object",
+ class: "Object"
+ },
+ enumerable: true
+ });
+
+ is(testScope.target.querySelectorAll(".variables-view-element-details.enum").length, 3,
+ "Three enumerable containers should be present in the tree.");
+ is(testScope.target.querySelectorAll(".variables-view-element-details.nonenum").length, 3,
+ "Three non-enumerable containers should be present in the tree.");
+
+ is(testVar.target.querySelector(".variables-view-element-details.enum").childNodes.length, 1,
+ "A new enumerable property should have been added in the variable.");
+ is(testVar.target.querySelector(".variables-view-element-details.nonenum").childNodes.length, 0,
+ "No new non-enumerable properties should have been added in the variable.");
+
+ let testChild = testVar.get("child");
+ ok(testChild,
+ "The added property should be accessible from the variable.");
+
+ is(testChild.target.querySelectorAll(".variables-view-element-details.enum").length, 1,
+ "One enumerable container should be present in the property.");
+ is(testChild.target.querySelectorAll(".variables-view-element-details.nonenum").length, 1,
+ "One non-enumerable container should be present in the property.");
+ is(testChild.target.querySelector(".variables-view-element-details.enum").childNodes.length, 0,
+ "No enumerable sub-properties should be present in the property.");
+ is(testChild.target.querySelector(".variables-view-element-details.nonenum").childNodes.length, 0,
+ "No non-enumerable sub-properties should be present in the property.");
+
+ testChild.addItem("grandChild", {
+ value: {
+ type: "object",
+ class: "Object"
+ },
+ enumerable: true
+ });
+
+ is(testScope.target.querySelectorAll(".variables-view-element-details.enum").length, 4,
+ "Four enumerable containers should be present in the tree.");
+ is(testScope.target.querySelectorAll(".variables-view-element-details.nonenum").length, 4,
+ "Four non-enumerable containers should be present in the tree.");
+
+ is(testChild.target.querySelector(".variables-view-element-details.enum").childNodes.length, 1,
+ "A new enumerable sub-property should have been added in the property.");
+ is(testChild.target.querySelector(".variables-view-element-details.nonenum").childNodes.length, 0,
+ "No new non-enumerable sub-properties should have been added in the property.");
+
+ let testGrandChild = testChild.get("grandChild");
+ ok(testGrandChild,
+ "The added sub-property should be accessible from the property.");
+
+ is(testGrandChild.target.querySelectorAll(".variables-view-element-details.enum").length, 1,
+ "One enumerable container should be present in the property.");
+ is(testGrandChild.target.querySelectorAll(".variables-view-element-details.nonenum").length, 1,
+ "One non-enumerable container should be present in the property.");
+ is(testGrandChild.target.querySelector(".variables-view-element-details.enum").childNodes.length, 0,
+ "No enumerable sub-properties should be present in the property.");
+ is(testGrandChild.target.querySelector(".variables-view-element-details.nonenum").childNodes.length, 0,
+ "No non-enumerable sub-properties should be present in the property.");
+
+ testGrandChild.addItem("granderChild", {
+ value: {
+ type: "object",
+ class: "Object"
+ },
+ enumerable: true
+ });
+
+ is(testScope.target.querySelectorAll(".variables-view-element-details.enum").length, 5,
+ "Five enumerable containers should be present in the tree.");
+ is(testScope.target.querySelectorAll(".variables-view-element-details.nonenum").length, 5,
+ "Five non-enumerable containers should be present in the tree.");
+
+ is(testGrandChild.target.querySelector(".variables-view-element-details.enum").childNodes.length, 1,
+ "A new enumerable variable should have been added in the variable.");
+ is(testGrandChild.target.querySelector(".variables-view-element-details.nonenum").childNodes.length, 0,
+ "No new non-enumerable variables should have been added in the variable.");
+
+ let testGranderChild = testGrandChild.get("granderChild");
+ ok(testGranderChild,
+ "The added sub-property should be accessible from the property.");
+
+ is(testGranderChild.target.querySelectorAll(".variables-view-element-details.enum").length, 1,
+ "One enumerable container should be present in the property.");
+ is(testGranderChild.target.querySelectorAll(".variables-view-element-details.nonenum").length, 1,
+ "One non-enumerable container should be present in the property.");
+ is(testGranderChild.target.querySelector(".variables-view-element-details.enum").childNodes.length, 0,
+ "No enumerable sub-properties should be present in the property.");
+ is(testGranderChild.target.querySelector(".variables-view-element-details.nonenum").childNodes.length, 0,
+ "No non-enumerable sub-properties should be present in the property.");
+
+ closeDebuggerAndFinish(aPanel);
+ });
+}
diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-04.js b/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-04.js
new file mode 100644
index 000000000..9db8b9cb8
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-04.js
@@ -0,0 +1,156 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Tests that grips are correctly applied to variables.
+ */
+
+const TAB_URL = EXAMPLE_URL + "doc_recursion-stack.html";
+
+function test() {
+ let options = {
+ source: TAB_URL,
+ line: 1
+ };
+ initDebugger(TAB_URL, options).then(([aTab,, aPanel]) => {
+ let variables = aPanel.panelWin.DebuggerView.Variables;
+ let testScope = variables.addScope("test");
+ let testVar = testScope.addItem("something");
+
+ testVar.setGrip(1.618);
+
+ is(testVar.target.querySelector(".value").getAttribute("value"), "1.618",
+ "The grip information for the variable wasn't set correctly (1).");
+ is(testVar.target.querySelector(".variables-view-element-details.enum").childNodes.length, 0,
+ "Setting the grip alone shouldn't add any new tree nodes (1).");
+ is(testVar.target.querySelector(".variables-view-element-details.nonenum").childNodes.length, 0,
+ "Setting the grip alone shouldn't add any new tree nodes (2).");
+
+ testVar.setGrip({
+ type: "object",
+ class: "Window"
+ });
+
+ is(testVar.target.querySelector(".value").getAttribute("value"), "Window",
+ "The grip information for the variable wasn't set correctly (2).");
+ is(testVar.target.querySelector(".variables-view-element-details.enum").childNodes.length, 0,
+ "Setting the grip alone shouldn't add any new tree nodes (3).");
+ is(testVar.target.querySelector(".variables-view-element-details.nonenum").childNodes.length, 0,
+ "Setting the grip alone shouldn't add any new tree nodes (4).");
+
+ testVar.addItems({
+ helloWorld: {
+ value: "hello world",
+ enumerable: true
+ }
+ });
+
+ is(testVar.target.querySelector(".variables-view-element-details").childNodes.length, 1,
+ "A new detail node should have been added in the variable tree.");
+ is(testVar.get("helloWorld").target.querySelector(".value").getAttribute("value"), "\"hello world\"",
+ "The grip information for the variable wasn't set correctly (3).");
+
+ testVar.addItems({
+ helloWorld: {
+ value: "hello jupiter",
+ enumerable: true
+ }
+ });
+
+ is(testVar.target.querySelector(".variables-view-element-details").childNodes.length, 1,
+ "Shouldn't be able to duplicate nodes added in the variable tree.");
+ is(testVar.get("helloWorld").target.querySelector(".value").getAttribute("value"), "\"hello world\"",
+ "The grip information for the variable wasn't preserved correctly (4).");
+
+ testVar.addItems({
+ someProp0: {
+ value: "random string",
+ enumerable: true
+ },
+ someProp1: {
+ value: "another string",
+ enumerable: true
+ }
+ });
+
+ is(testVar.target.querySelector(".variables-view-element-details").childNodes.length, 3,
+ "Two new detail nodes should have been added in the variable tree.");
+ is(testVar.get("someProp0").target.querySelector(".value").getAttribute("value"), "\"random string\"",
+ "The grip information for the variable wasn't set correctly (5).");
+ is(testVar.get("someProp1").target.querySelector(".value").getAttribute("value"), "\"another string\"",
+ "The grip information for the variable wasn't set correctly (6).");
+
+ testVar.addItems({
+ someProp2: {
+ value: {
+ type: "null"
+ },
+ enumerable: true
+ },
+ someProp3: {
+ value: {
+ type: "undefined"
+ },
+ enumerable: true
+ },
+ someProp4: {
+ value: {
+ type: "object",
+ class: "Object"
+ },
+ enumerable: true
+ }
+ });
+
+ is(testVar.target.querySelector(".variables-view-element-details").childNodes.length, 6,
+ "Three new detail nodes should have been added in the variable tree.");
+ is(testVar.get("someProp2").target.querySelector(".value").getAttribute("value"), "null",
+ "The grip information for the variable wasn't set correctly (7).");
+ is(testVar.get("someProp3").target.querySelector(".value").getAttribute("value"), "undefined",
+ "The grip information for the variable wasn't set correctly (8).");
+ is(testVar.get("someProp4").target.querySelector(".value").getAttribute("value"), "Object",
+ "The grip information for the variable wasn't set correctly (9).");
+
+ let parent = testVar.get("someProp2");
+ let child = parent.addItem("child", {
+ value: {
+ type: "null"
+ }
+ });
+
+ is(variables.getItemForNode(parent.target), parent,
+ "VariablesView should have a record of the parent.");
+ is(variables.getItemForNode(child.target), child,
+ "VariablesView should have a record of the child.");
+ is([...parent].length, 1,
+ "Parent should have one child.");
+
+ parent.remove();
+
+ is(variables.getItemForNode(parent.target), undefined,
+ "VariablesView should not have a record of the parent anymore.");
+ is(parent.target.parentNode, null,
+ "Parent element should not have a parent.");
+ is(variables.getItemForNode(child.target), undefined,
+ "VariablesView should not have a record of the child anymore.");
+ is(child.target.parentNode, null,
+ "Child element should not have a parent.");
+ is([...parent].length, 0,
+ "Parent should have zero children.");
+
+ testScope.remove();
+
+ is([...variables].length, 0,
+ "VariablesView should have been emptied.");
+ is(ThreadSafeChromeUtils.nondeterministicGetWeakMapKeys(variables._itemsByElement).length,
+ 0, "VariablesView _itemsByElement map has been emptied.");
+ is(variables._currHierarchy.size, 0,
+ "VariablesView _currHierarchy map has been emptied.");
+ is(variables._list.children.length, 0,
+ "VariablesView element should have no children.");
+
+ closeDebuggerAndFinish(aPanel);
+ });
+}
diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-05.js b/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-05.js
new file mode 100644
index 000000000..ebad7c4e2
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-05.js
@@ -0,0 +1,234 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Tests that grips are correctly applied to variables and properties.
+ */
+
+const TAB_URL = EXAMPLE_URL + "doc_recursion-stack.html";
+
+function test() {
+ let options = {
+ source: TAB_URL,
+ line: 1
+ };
+ initDebugger(TAB_URL, options).then(([aTab,, aPanel]) => {
+ let variables = aPanel.panelWin.DebuggerView.Variables;
+
+ let globalScope = variables.addScope("Test-Global");
+ let localScope = variables.addScope("Test-Local");
+
+ ok(globalScope, "The globalScope hasn't been created correctly.");
+ ok(localScope, "The localScope hasn't been created correctly.");
+
+ is(globalScope.target.querySelector(".separator"), null,
+ "No separator string should be created for scopes (1).");
+ is(localScope.target.querySelector(".separator"), null,
+ "No separator string should be created for scopes (2).");
+
+ let windowVar = globalScope.addItem("window");
+ let documentVar = globalScope.addItem("document");
+
+ ok(windowVar, "The windowVar hasn't been created correctly.");
+ ok(documentVar, "The documentVar hasn't been created correctly.");
+
+ ok(windowVar.target.querySelector(".separator").hidden,
+ "No separator string should be shown for variables without a grip (1).");
+ ok(documentVar.target.querySelector(".separator").hidden,
+ "No separator string should be shown for variables without a grip (2).");
+
+ windowVar.setGrip({ type: "object", class: "Window" });
+ documentVar.setGrip({ type: "object", class: "HTMLDocument" });
+
+ is(windowVar.target.querySelector(".separator").hidden, false,
+ "A separator string should now be shown after setting the grip (1).");
+ is(documentVar.target.querySelector(".separator").hidden, false,
+ "A separator string should now be shown after setting the grip (2).");
+
+ is(windowVar.target.querySelector(".separator").getAttribute("value"), ": ",
+ "The separator string label is correct (1).");
+ is(documentVar.target.querySelector(".separator").getAttribute("value"), ": ",
+ "The separator string label is correct (2).");
+
+ let localVar0 = localScope.addItem("localVar0");
+ let localVar1 = localScope.addItem("localVar1");
+ let localVar2 = localScope.addItem("localVar2");
+ let localVar3 = localScope.addItem("localVar3");
+ let localVar4 = localScope.addItem("localVar4");
+ let localVar5 = localScope.addItem("localVar5");
+
+ let localVar6 = localScope.addItem("localVar6");
+ let localVar7 = localScope.addItem("localVar7");
+ let localVar8 = localScope.addItem("localVar8");
+ let localVar9 = localScope.addItem("localVar9");
+
+ ok(localVar0, "The localVar0 hasn't been created correctly.");
+ ok(localVar1, "The localVar1 hasn't been created correctly.");
+ ok(localVar2, "The localVar2 hasn't been created correctly.");
+ ok(localVar3, "The localVar3 hasn't been created correctly.");
+ ok(localVar4, "The localVar4 hasn't been created correctly.");
+ ok(localVar5, "The localVar5 hasn't been created correctly.");
+ ok(localVar6, "The localVar6 hasn't been created correctly.");
+ ok(localVar7, "The localVar7 hasn't been created correctly.");
+ ok(localVar8, "The localVar8 hasn't been created correctly.");
+ ok(localVar9, "The localVar9 hasn't been created correctly.");
+
+ localVar0.setGrip(42);
+ localVar1.setGrip(true);
+ localVar2.setGrip("nasu");
+
+ localVar3.setGrip({ type: "undefined" });
+ localVar4.setGrip({ type: "null" });
+ localVar5.setGrip({ type: "object", class: "Object" });
+ localVar6.setGrip({ type: "Infinity" });
+ localVar7.setGrip({ type: "-Infinity" });
+ localVar8.setGrip({ type: "NaN" });
+ localVar9.setGrip({ type: "-0" });
+
+ localVar5.addItems({
+ someProp0: { value: 42, enumerable: true },
+ someProp1: { value: true, enumerable: true },
+ someProp2: { value: "nasu", enumerable: true },
+ someProp3: { value: { type: "undefined" }, enumerable: true },
+ someProp4: { value: { type: "null" }, enumerable: true },
+ someProp5: { value: { type: "object", class: "Object" }, enumerable: true },
+ someProp6: { value: { type: "Infinity" }, enumerable: true },
+ someProp7: { value: { type: "-Infinity" }, enumerable: true },
+ someProp8: { value: { type: "NaN" }, enumerable: true },
+ someProp9: { value: { type: "-0" }, enumerable: true },
+ someUndefined: {
+ get: { type: "undefined" },
+ set: { type: "undefined" },
+ enumerable: true
+ },
+ someAccessor: {
+ get: { type: "object", class: "Function" },
+ set: { type: "undefined" },
+ enumerable: true
+ }
+ });
+
+ localVar5.get("someProp5").addItems({
+ someProp0: { value: 42, enumerable: true },
+ someProp1: { value: true, enumerable: true },
+ someProp2: { value: "nasu", enumerable: true },
+ someProp3: { value: { type: "undefined" }, enumerable: true },
+ someProp4: { value: { type: "null" }, enumerable: true },
+ someProp5: { value: { type: "object", class: "Object" }, enumerable: true },
+ someProp6: { value: { type: "Infinity" }, enumerable: true },
+ someProp7: { value: { type: "-Infinity" }, enumerable: true },
+ someProp8: { value: { type: "NaN" }, enumerable: true },
+ someProp9: { value: { type: "-0" }, enumerable: true },
+ someUndefined: {
+ get: { type: "undefined" },
+ set: { type: "undefined" },
+ enumerable: true
+ },
+ someAccessor: {
+ get: { type: "object", class: "Function" },
+ set: { type: "undefined" },
+ enumerable: true
+ }
+ });
+
+ is(globalScope.target.querySelector(".enum").childNodes.length, 0,
+ "The globalScope doesn't contain all the created enumerable variable elements.");
+ is(globalScope.target.querySelector(".nonenum").childNodes.length, 2,
+ "The globalScope doesn't contain all the created non-enumerable variable elements.");
+
+ is(localScope.target.querySelector(".enum").childNodes.length, 0,
+ "The localScope doesn't contain all the created enumerable variable elements.");
+ is(localScope.target.querySelector(".nonenum").childNodes.length, 10,
+ "The localScope doesn't contain all the created non-enumerable variable elements.");
+
+ is(localVar5.target.querySelector(".enum").childNodes.length, 12,
+ "The localVar5 doesn't contain all the created enumerable properties.");
+ is(localVar5.target.querySelector(".nonenum").childNodes.length, 0,
+ "The localVar5 doesn't contain all the created non-enumerable properties.");
+
+ is(localVar5.get("someProp5").target.querySelector(".enum").childNodes.length, 12,
+ "The localVar5.someProp5 doesn't contain all the created enumerable properties.");
+ is(localVar5.get("someProp5").target.querySelector(".nonenum").childNodes.length, 0,
+ "The localVar5.someProp5 doesn't contain all the created non-enumerable properties.");
+
+ is(windowVar.target.querySelector(".value").getAttribute("value"), "Window",
+ "The grip information for the windowVar wasn't set correctly.");
+ is(documentVar.target.querySelector(".value").getAttribute("value"), "HTMLDocument",
+ "The grip information for the documentVar wasn't set correctly.");
+
+ is(localVar0.target.querySelector(".value").getAttribute("value"), "42",
+ "The grip information for the localVar0 wasn't set correctly.");
+ is(localVar1.target.querySelector(".value").getAttribute("value"), "true",
+ "The grip information for the localVar1 wasn't set correctly.");
+ is(localVar2.target.querySelector(".value").getAttribute("value"), "\"nasu\"",
+ "The grip information for the localVar2 wasn't set correctly.");
+ is(localVar3.target.querySelector(".value").getAttribute("value"), "undefined",
+ "The grip information for the localVar3 wasn't set correctly.");
+ is(localVar4.target.querySelector(".value").getAttribute("value"), "null",
+ "The grip information for the localVar4 wasn't set correctly.");
+ is(localVar5.target.querySelector(".value").getAttribute("value"), "Object",
+ "The grip information for the localVar5 wasn't set correctly.");
+ is(localVar6.target.querySelector(".value").getAttribute("value"), "Infinity",
+ "The grip information for the localVar6 wasn't set correctly.");
+ is(localVar7.target.querySelector(".value").getAttribute("value"), "-Infinity",
+ "The grip information for the localVar7 wasn't set correctly.");
+ is(localVar8.target.querySelector(".value").getAttribute("value"), "NaN",
+ "The grip information for the localVar8 wasn't set correctly.");
+ is(localVar9.target.querySelector(".value").getAttribute("value"), "-0",
+ "The grip information for the localVar9 wasn't set correctly.");
+
+ is(localVar5.get("someProp0").target.querySelector(".value").getAttribute("value"), "42",
+ "The grip information for the someProp0 wasn't set correctly.");
+ is(localVar5.get("someProp1").target.querySelector(".value").getAttribute("value"), "true",
+ "The grip information for the someProp1 wasn't set correctly.");
+ is(localVar5.get("someProp2").target.querySelector(".value").getAttribute("value"), "\"nasu\"",
+ "The grip information for the someProp2 wasn't set correctly.");
+ is(localVar5.get("someProp3").target.querySelector(".value").getAttribute("value"), "undefined",
+ "The grip information for the someProp3 wasn't set correctly.");
+ is(localVar5.get("someProp4").target.querySelector(".value").getAttribute("value"), "null",
+ "The grip information for the someProp4 wasn't set correctly.");
+ is(localVar5.get("someProp5").target.querySelector(".value").getAttribute("value"), "Object",
+ "The grip information for the someProp5 wasn't set correctly.");
+ is(localVar5.get("someProp6").target.querySelector(".value").getAttribute("value"), "Infinity",
+ "The grip information for the someProp6 wasn't set correctly.");
+ is(localVar5.get("someProp7").target.querySelector(".value").getAttribute("value"), "-Infinity",
+ "The grip information for the someProp7 wasn't set correctly.");
+ is(localVar5.get("someProp8").target.querySelector(".value").getAttribute("value"), "NaN",
+ "The grip information for the someProp8 wasn't set correctly.");
+ is(localVar5.get("someProp9").target.querySelector(".value").getAttribute("value"), "-0",
+ "The grip information for the someProp9 wasn't set correctly.");
+ is(localVar5.get("someUndefined").target.querySelector(".value").getAttribute("value"), "",
+ "The grip information for the someUndefined wasn't set correctly.");
+ is(localVar5.get("someAccessor").target.querySelector(".value").getAttribute("value"), "",
+ "The grip information for the someAccessor wasn't set correctly.");
+
+ is(localVar5.get("someProp5").get("someProp0").target.querySelector(".value").getAttribute("value"), "42",
+ "The grip information for the sub-someProp0 wasn't set correctly.");
+ is(localVar5.get("someProp5").get("someProp1").target.querySelector(".value").getAttribute("value"), "true",
+ "The grip information for the sub-someProp1 wasn't set correctly.");
+ is(localVar5.get("someProp5").get("someProp2").target.querySelector(".value").getAttribute("value"), "\"nasu\"",
+ "The grip information for the sub-someProp2 wasn't set correctly.");
+ is(localVar5.get("someProp5").get("someProp3").target.querySelector(".value").getAttribute("value"), "undefined",
+ "The grip information for the sub-someProp3 wasn't set correctly.");
+ is(localVar5.get("someProp5").get("someProp4").target.querySelector(".value").getAttribute("value"), "null",
+ "The grip information for the sub-someProp4 wasn't set correctly.");
+ is(localVar5.get("someProp5").get("someProp5").target.querySelector(".value").getAttribute("value"), "Object",
+ "The grip information for the sub-someProp5 wasn't set correctly.");
+ is(localVar5.get("someProp5").get("someProp6").target.querySelector(".value").getAttribute("value"), "Infinity",
+ "The grip information for the sub-someProp6 wasn't set correctly.");
+ is(localVar5.get("someProp5").get("someProp7").target.querySelector(".value").getAttribute("value"), "-Infinity",
+ "The grip information for the sub-someProp7 wasn't set correctly.");
+ is(localVar5.get("someProp5").get("someProp8").target.querySelector(".value").getAttribute("value"), "NaN",
+ "The grip information for the sub-someProp8 wasn't set correctly.");
+ is(localVar5.get("someProp5").get("someProp9").target.querySelector(".value").getAttribute("value"), "-0",
+ "The grip information for the sub-someProp9 wasn't set correctly.");
+ is(localVar5.get("someProp5").get("someUndefined").target.querySelector(".value").getAttribute("value"), "",
+ "The grip information for the sub-someUndefined wasn't set correctly.");
+ is(localVar5.get("someProp5").get("someAccessor").target.querySelector(".value").getAttribute("value"), "",
+ "The grip information for the sub-someAccessor wasn't set correctly.");
+
+ closeDebuggerAndFinish(aPanel);
+ });
+}
diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-06.js b/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-06.js
new file mode 100644
index 000000000..6d923eb02
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-06.js
@@ -0,0 +1,125 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Test that Promises get their internal state added as psuedo properties.
+ */
+
+const TAB_URL = EXAMPLE_URL + "doc_promise.html";
+
+var test = Task.async(function* () {
+ let options = {
+ source: TAB_URL,
+ line: 1
+ };
+ const [tab,, panel] = yield initDebugger(TAB_URL, options);
+
+ const scopes = waitForCaretAndScopes(panel, 21);
+ callInTab(tab, "doPause");
+ yield scopes;
+
+ const variables = panel.panelWin.DebuggerView.Variables;
+ ok(variables, "Should get the variables view.");
+
+ const scope = [...variables][0];
+ ok(scope, "Should get the current function's scope.");
+
+ const promiseVariables = [...scope].filter(([name]) =>
+ ["p", "f", "r"].indexOf(name) !== -1);
+
+ is(promiseVariables.length, 3,
+ "Should have our 3 promise variables: p, f, r");
+
+ for (let [name, item] of promiseVariables) {
+ info("Expanding variable '" + name + "'");
+ let expanded = once(variables, "fetched");
+ item.expand();
+ yield expanded;
+
+ let foundState = false;
+ switch (name) {
+ case "p":
+ for (let [property, { value }] of item) {
+ if (property !== "<state>") {
+ isnot(property, "<value>",
+ "A pending promise shouldn't have a value");
+ isnot(property, "<reason>",
+ "A pending promise shouldn't have a reason");
+ continue;
+ }
+
+ foundState = true;
+ is(value, "pending", "The state should be pending.");
+ }
+ ok(foundState, "We should have found the <state> property.");
+ break;
+
+ case "f":
+ let foundValue = false;
+ for (let [property, value] of item) {
+ if (property === "<state>") {
+ foundState = true;
+ is(value.value, "fulfilled", "The state should be fulfilled.");
+ } else if (property === "<value>") {
+ foundValue = true;
+
+ let expanded = once(variables, "fetched");
+ value.expand();
+ yield expanded;
+
+ let expectedProps = new Map([["a", 1], ["b", 2], ["c", 3]]);
+ for (let [prop, val] of value) {
+ if (prop === "__proto__") {
+ continue;
+ }
+ ok(expectedProps.has(prop), "The property should be expected.");
+ is(val.value, expectedProps.get(prop), "The property value should be correct.");
+ expectedProps.delete(prop);
+ }
+ is(Object.keys(expectedProps).length, 0,
+ "Should have found all of the expected properties.");
+ } else {
+ isnot(property, "<reason>",
+ "A fulfilled promise shouldn't have a reason");
+ }
+ }
+ ok(foundState, "We should have found the <state> property.");
+ ok(foundValue, "We should have found the <value> property.");
+ break;
+
+ case "r":
+ let foundReason = false;
+ for (let [property, value] of item) {
+ if (property === "<state>") {
+ foundState = true;
+ is(value.value, "rejected", "The state should be rejected.");
+ } else if (property === "<reason>") {
+ foundReason = true;
+
+ let expanded = once(variables, "fetched");
+ value.expand();
+ yield expanded;
+
+ let foundMessage = false;
+ for (let [prop, val] of value) {
+ if (prop !== "message") {
+ continue;
+ }
+ foundMessage = true;
+ is(val.value, "uh oh", "Should have the correct error message.");
+ }
+ ok(foundMessage, "Should have found the error's message");
+ } else {
+ isnot(property, "<value>",
+ "A rejected promise shouldn't have a value");
+ }
+ }
+ ok(foundState, "We should have found the <state> property.");
+ break;
+ }
+ }
+
+ resumeDebuggerThenCloseAndFinish(panel);
+});
diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-07.js b/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-07.js
new file mode 100644
index 000000000..a05f33e7f
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-07.js
@@ -0,0 +1,69 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Test that proxy objects get their internal state added as pseudo properties.
+ */
+
+const TAB_URL = EXAMPLE_URL + "doc_proxy.html";
+
+var test = Task.async(function* () {
+ let options = {
+ source: TAB_URL,
+ line: 1
+ };
+ var dbg = initDebugger(TAB_URL, options);
+ const [tab,, panel] = yield dbg;
+ const debuggerLineNumber = 34;
+ const scopes = waitForCaretAndScopes(panel, debuggerLineNumber);
+ callInTab(tab, "doPause");
+ yield scopes;
+
+ const variables = panel.panelWin.DebuggerView.Variables;
+ ok(variables, "Should get the variables view.");
+
+ const scope = [...variables][0];
+ ok(scope, "Should get the current function's scope.");
+
+ let proxy;
+ for (let [name, value] of scope) {
+ if (name === "proxy") {
+ proxy = value;
+ }
+ }
+ ok(proxy, "Should have found the proxy variable");
+
+ info("Expanding variable 'proxy'");
+ let expanded = once(variables, "fetched");
+ proxy.expand();
+ yield expanded;
+
+ let foundTarget = false;
+ let foundHandler = false;
+ for (let [property, data] of proxy) {
+ info("Expanding property '" + property + "'");
+ let expanded = once(variables, "fetched");
+ data.expand();
+ yield expanded;
+ if (property === "<target>") {
+ for(let [subprop, subdata] of data) if(subprop === "name") {
+ is(subdata.value, "target", "The value of '<target>' should be the [[ProxyTarget]]");
+ foundTarget = true;
+ }
+ } else {
+ is(property, "<handler>", "There shouldn't be properties other than <target> and <handler>");
+ for (let [subprop, subdata] of data) {
+ if(subprop === "name") {
+ is(subdata.value, "handler", "The value of '<handler>' should be the [[ProxyHandler]]");
+ foundHandler = true;
+ }
+ }
+ }
+ }
+ ok(foundTarget, "Should have found the '<target>' property containing the [[ProxyTarget]]");
+ ok(foundHandler, "Should have found the '<handler>' property containing the [[ProxyHandler]]");
+
+ resumeDebuggerThenCloseAndFinish(panel);
+});
diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-08.js b/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-08.js
new file mode 100644
index 000000000..83083eef3
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-08.js
@@ -0,0 +1,61 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Test that property values are not missing when the property names only contain whitespace.
+ */
+
+const TAB_URL = EXAMPLE_URL + "doc_whitespace-property-names.html";
+
+var test = Task.async(function* () {
+ let options = {
+ source: TAB_URL,
+ line: 1
+ };
+ var dbg = initDebugger(TAB_URL, options);
+ const [tab,, panel] = yield dbg;
+ const debuggerLineNumber = 24;
+ const scopes = waitForCaretAndScopes(panel, debuggerLineNumber);
+ callInTab(tab, "doPause");
+ yield scopes;
+
+ const variables = panel.panelWin.DebuggerView.Variables;
+ ok(variables, "Should get the variables view.");
+
+ const scope = [...variables][0];
+ ok(scope, "Should get the current function's scope.");
+
+ let obj;
+ for (let [name, value] of scope) {
+ if (name === "obj") {
+ obj = value;
+ }
+ }
+ ok(obj, "Should have found the 'obj' variable");
+
+ info("Expanding variable 'obj'");
+ let expanded = once(variables, "fetched");
+ obj.expand();
+ yield expanded;
+
+ let values = ["", " ", "\r", "\n", "\t", "\f", "\uFEFF", "\xA0"];
+ let count = values.length;
+
+ for (let [property, value] of obj) {
+ let index = values.indexOf(property);
+ if (index >= 0) {
+ --count;
+ is(value._nameString, property,
+ "The _nameString is different than the property name");
+ is(value._valueString, index + "",
+ "The _valueString is different than the stringified value");
+ is(value._valueLabel.getAttribute("value"), index + "",
+ "The _valueLabel value is different than the stringified value");
+ }
+ }
+ is(count, 0, "There are " + count + " missing properties");
+
+ resumeDebuggerThenCloseAndFinish(panel);
+});
diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-accessibility.js b/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-accessibility.js
new file mode 100644
index 000000000..6acec5583
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-accessibility.js
@@ -0,0 +1,557 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Make sure that the variables view is keyboard accessible.
+ */
+
+var gTab, gPanel, gDebugger;
+var gVariablesView;
+
+function test() {
+ initDebugger().then(([aTab,, aPanel]) => {
+ gTab = aTab;
+ gPanel = aPanel;
+ gDebugger = gPanel.panelWin;
+ gVariablesView = gDebugger.DebuggerView.Variables;
+
+ performTest().then(null, aError => {
+ ok(false, "Got an error: " + aError.message + "\n" + aError.stack);
+ });
+ });
+}
+
+function performTest() {
+ let arr = [
+ 42,
+ true,
+ "nasu",
+ undefined,
+ null,
+ [0, 1, 2],
+ { prop1: 9, prop2: 8 }
+ ];
+
+ let obj = {
+ p0: 42,
+ p1: true,
+ p2: "nasu",
+ p3: undefined,
+ p4: null,
+ p5: [3, 4, 5],
+ p6: { prop1: 7, prop2: 6 },
+ get p7() { return arr; },
+ set p8(value) { arr[0] = value; }
+ };
+
+ let test = {
+ someProp0: 42,
+ someProp1: true,
+ someProp2: "nasu",
+ someProp3: undefined,
+ someProp4: null,
+ someProp5: arr,
+ someProp6: obj,
+ get someProp7() { return arr; },
+ set someProp7(value) { arr[0] = value; }
+ };
+
+ gVariablesView.eval = function () {};
+ gVariablesView.switch = function () {};
+ gVariablesView.delete = function () {};
+ gVariablesView.rawObject = test;
+ gVariablesView.scrollPageSize = 5;
+
+ return Task.spawn(function* () {
+ yield waitForTick();
+
+ // Part 0: Test generic focus methods on the variables view.
+
+ gVariablesView.focusFirstVisibleItem();
+ is(gVariablesView.getFocusedItem().name, "someProp0",
+ "The 'someProp0' item should be focused.");
+
+ gVariablesView.focusNextItem();
+ is(gVariablesView.getFocusedItem().name, "someProp1",
+ "The 'someProp1' item should be focused.");
+
+ gVariablesView.focusPrevItem();
+ is(gVariablesView.getFocusedItem().name, "someProp0",
+ "The 'someProp0' item should be focused.");
+
+ // Part 1: Make sure that UP/DOWN keys don't scroll the variables view.
+
+ yield synthesizeKeyAndWaitForTick("VK_DOWN", {});
+ is(gVariablesView._parent.scrollTop, 0,
+ "The 'variables' view shouldn't scroll when pressing the DOWN key.");
+
+ yield synthesizeKeyAndWaitForTick("VK_UP", {});
+ is(gVariablesView._parent.scrollTop, 0,
+ "The 'variables' view shouldn't scroll when pressing the UP key.");
+
+ // Part 2: Make sure that RETURN/ESCAPE toggle input elements.
+
+ yield synthesizeKeyAndWaitForElement("VK_RETURN", {}, ".element-value-input", true);
+ yield synthesizeKeyAndWaitForElement("VK_ESCAPE", {}, ".element-value-input", false);
+ yield synthesizeKeyAndWaitForElement("VK_RETURN", { shiftKey: true }, ".element-name-input", true);
+ yield synthesizeKeyAndWaitForElement("VK_ESCAPE", {}, ".element-name-input", false);
+
+ // Part 3: Test simple navigation.
+
+ EventUtils.sendKey("DOWN", gDebugger);
+ is(gVariablesView.getFocusedItem().name, "someProp1",
+ "The 'someProp1' item should be focused.");
+
+ EventUtils.sendKey("UP", gDebugger);
+ is(gVariablesView.getFocusedItem().name, "someProp0",
+ "The 'someProp0' item should be focused.");
+
+ EventUtils.sendKey("PAGE_DOWN", gDebugger);
+ is(gVariablesView.getFocusedItem().name, "someProp5",
+ "The 'someProp5' item should be focused.");
+
+ EventUtils.sendKey("PAGE_UP", gDebugger);
+ is(gVariablesView.getFocusedItem().name, "someProp0",
+ "The 'someProp0' item should be focused.");
+
+ EventUtils.sendKey("END", gDebugger);
+ is(gVariablesView.getFocusedItem().name, "__proto__",
+ "The '__proto__' item should be focused.");
+
+ EventUtils.sendKey("HOME", gDebugger);
+ is(gVariablesView.getFocusedItem().name, "someProp0",
+ "The 'someProp0' item should be focused.");
+
+ // Part 4: Test if pressing the same navigation key twice works as expected.
+
+ EventUtils.sendKey("DOWN", gDebugger);
+ is(gVariablesView.getFocusedItem().name, "someProp1",
+ "The 'someProp1' item should be focused.");
+
+ EventUtils.sendKey("DOWN", gDebugger);
+ is(gVariablesView.getFocusedItem().name, "someProp2",
+ "The 'someProp2' item should be focused.");
+
+ EventUtils.sendKey("UP", gDebugger);
+ is(gVariablesView.getFocusedItem().name, "someProp1",
+ "The 'someProp1' item should be focused.");
+
+ EventUtils.sendKey("UP", gDebugger);
+ is(gVariablesView.getFocusedItem().name, "someProp0",
+ "The 'someProp0' item should be focused.");
+
+ EventUtils.sendKey("PAGE_DOWN", gDebugger);
+ is(gVariablesView.getFocusedItem().name, "someProp5",
+ "The 'someProp5' item should be focused.");
+
+ EventUtils.sendKey("PAGE_DOWN", gDebugger);
+ is(gVariablesView.getFocusedItem().name, "__proto__",
+ "The '__proto__' item should be focused.");
+
+ EventUtils.sendKey("PAGE_UP", gDebugger);
+ is(gVariablesView.getFocusedItem().name, "someProp5",
+ "The 'someProp5' item should be focused.");
+
+ EventUtils.sendKey("PAGE_UP", gDebugger);
+ is(gVariablesView.getFocusedItem().name, "someProp0",
+ "The 'someProp0' item should be focused.");
+
+ // Part 5: Test that HOME/PAGE_UP/PAGE_DOWN are symmetrical.
+
+ EventUtils.sendKey("HOME", gDebugger);
+ is(gVariablesView.getFocusedItem().name, "someProp0",
+ "The 'someProp0' item should be focused.");
+
+ EventUtils.sendKey("HOME", gDebugger);
+ is(gVariablesView.getFocusedItem().name, "someProp0",
+ "The 'someProp0' item should be focused.");
+
+ EventUtils.sendKey("PAGE_UP", gDebugger);
+ is(gVariablesView.getFocusedItem().name, "someProp0",
+ "The 'someProp0' item should be focused.");
+
+ EventUtils.sendKey("HOME", gDebugger);
+ is(gVariablesView.getFocusedItem().name, "someProp0",
+ "The 'someProp0' item should be focused.");
+
+ EventUtils.sendKey("END", gDebugger);
+ is(gVariablesView.getFocusedItem().name, "__proto__",
+ "The '__proto__' item should be focused.");
+
+ EventUtils.sendKey("END", gDebugger);
+ is(gVariablesView.getFocusedItem().name, "__proto__",
+ "The '__proto__' item should be focused.");
+
+ EventUtils.sendKey("PAGE_DOWN", gDebugger);
+ is(gVariablesView.getFocusedItem().name, "__proto__",
+ "The '__proto__' item should be focused.");
+
+ EventUtils.sendKey("END", gDebugger);
+ is(gVariablesView.getFocusedItem().name, "__proto__",
+ "The '__proto__' item should be focused.");
+
+ // Part 6: Test that focus doesn't leave the variables view.
+
+ EventUtils.sendKey("PAGE_UP", gDebugger);
+ is(gVariablesView.getFocusedItem().name, "someProp5",
+ "The 'someProp5' item should be focused.");
+
+ EventUtils.sendKey("PAGE_UP", gDebugger);
+ is(gVariablesView.getFocusedItem().name, "someProp0",
+ "The 'someProp0' item should be focused.");
+
+ EventUtils.sendKey("UP", gDebugger);
+ is(gVariablesView.getFocusedItem().name, "someProp0",
+ "The 'someProp0' item should be focused.");
+
+ EventUtils.sendKey("UP", gDebugger);
+ is(gVariablesView.getFocusedItem().name, "someProp0",
+ "The 'someProp0' item should be focused.");
+
+ EventUtils.sendKey("PAGE_DOWN", gDebugger);
+ is(gVariablesView.getFocusedItem().name, "someProp5",
+ "The 'someProp5' item should be focused.");
+
+ EventUtils.sendKey("PAGE_DOWN", gDebugger);
+ is(gVariablesView.getFocusedItem().name, "__proto__",
+ "The '__proto__' item should be focused.");
+
+ EventUtils.sendKey("PAGE_DOWN", gDebugger);
+ is(gVariablesView.getFocusedItem().name, "__proto__",
+ "The '__proto__' item should be focused.");
+
+ EventUtils.sendKey("PAGE_DOWN", gDebugger);
+ is(gVariablesView.getFocusedItem().name, "__proto__",
+ "The '__proto__' item should be focused.");
+
+ // Part 7: Test that random offsets don't occur in tandem with HOME/END.
+
+ EventUtils.sendKey("HOME", gDebugger);
+ is(gVariablesView.getFocusedItem().name, "someProp0",
+ "The 'someProp0' item should be focused.");
+
+ EventUtils.sendKey("DOWN", gDebugger);
+ is(gVariablesView.getFocusedItem().name, "someProp1",
+ "The 'someProp1' item should be focused.");
+
+ EventUtils.sendKey("END", gDebugger);
+ is(gVariablesView.getFocusedItem().name, "__proto__",
+ "The '__proto__' item should be focused.");
+
+ EventUtils.sendKey("DOWN", gDebugger);
+ is(gVariablesView.getFocusedItem().name, "__proto__",
+ "The '__proto__' item should be focused.");
+
+ // Part 8: Test that the RIGHT key expands elements as intended.
+
+ EventUtils.sendKey("PAGE_UP", gDebugger);
+ is(gVariablesView.getFocusedItem().name, "someProp5",
+ "The 'someProp5' item should be focused.");
+ is(gVariablesView.getFocusedItem().expanded, false,
+ "The 'someProp5' item should not be expanded yet.");
+
+ yield synthesizeKeyAndWaitForTick("VK_RIGHT", {});
+ is(gVariablesView.getFocusedItem().name, "someProp5",
+ "The 'someProp5' item should be focused.");
+ is(gVariablesView.getFocusedItem().expanded, true,
+ "The 'someProp5' item should now be expanded.");
+ is(gVariablesView.getFocusedItem()._store.size, 9,
+ "There should be 9 properties in the selected variable.");
+ is(gVariablesView.getFocusedItem()._enumItems.length, 7,
+ "There should be 7 enumerable properties in the selected variable.");
+ is(gVariablesView.getFocusedItem()._nonEnumItems.length, 2,
+ "There should be 2 non-enumerable properties in the selected variable.");
+
+ yield waitForChildNodes(gVariablesView.getFocusedItem()._enum, 7);
+ yield waitForChildNodes(gVariablesView.getFocusedItem()._nonenum, 2);
+
+ EventUtils.sendKey("RIGHT", gDebugger);
+ is(gVariablesView.getFocusedItem().name, "0",
+ "The '0' item should be focused.");
+
+ EventUtils.sendKey("RIGHT", gDebugger);
+ is(gVariablesView.getFocusedItem().name, "0",
+ "The '0' item should still be focused.");
+
+ EventUtils.sendKey("PAGE_DOWN", gDebugger);
+ is(gVariablesView.getFocusedItem().name, "5",
+ "The '5' item should be focused.");
+ is(gVariablesView.getFocusedItem().expanded, false,
+ "The '5' item should not be expanded yet.");
+
+ yield synthesizeKeyAndWaitForTick("VK_RIGHT", {});
+ is(gVariablesView.getFocusedItem().name, "5",
+ "The '5' item should be focused.");
+ is(gVariablesView.getFocusedItem().expanded, true,
+ "The '5' item should now be expanded.");
+ is(gVariablesView.getFocusedItem()._store.size, 5,
+ "There should be 5 properties in the selected variable.");
+ is(gVariablesView.getFocusedItem()._enumItems.length, 3,
+ "There should be 3 enumerable properties in the selected variable.");
+ is(gVariablesView.getFocusedItem()._nonEnumItems.length, 2,
+ "There should be 2 non-enumerable properties in the selected variable.");
+
+ yield waitForChildNodes(gVariablesView.getFocusedItem()._enum, 3);
+ yield waitForChildNodes(gVariablesView.getFocusedItem()._nonenum, 2);
+
+ EventUtils.sendKey("RIGHT", gDebugger);
+ is(gVariablesView.getFocusedItem().name, "0",
+ "The '0' item should be focused.");
+
+ EventUtils.sendKey("RIGHT", gDebugger);
+ is(gVariablesView.getFocusedItem().name, "0",
+ "The '0' item should still be focused.");
+
+ EventUtils.sendKey("PAGE_DOWN", gDebugger);
+ is(gVariablesView.getFocusedItem().name, "6",
+ "The '6' item should be focused.");
+ is(gVariablesView.getFocusedItem().expanded, false,
+ "The '6' item should not be expanded yet.");
+
+ yield synthesizeKeyAndWaitForTick("VK_RIGHT", {});
+ is(gVariablesView.getFocusedItem().name, "6",
+ "The '6' item should be focused.");
+ is(gVariablesView.getFocusedItem().expanded, true,
+ "The '6' item should now be expanded.");
+ is(gVariablesView.getFocusedItem()._store.size, 3,
+ "There should be 3 properties in the selected variable.");
+ is(gVariablesView.getFocusedItem()._enumItems.length, 2,
+ "There should be 2 enumerable properties in the selected variable.");
+ is(gVariablesView.getFocusedItem()._nonEnumItems.length, 1,
+ "There should be 1 non-enumerable properties in the selected variable.");
+
+ yield waitForChildNodes(gVariablesView.getFocusedItem()._enum, 2);
+ yield waitForChildNodes(gVariablesView.getFocusedItem()._nonenum, 1);
+
+ EventUtils.sendKey("RIGHT", gDebugger);
+ is(gVariablesView.getFocusedItem().name, "prop1",
+ "The 'prop1' item should be focused.");
+
+ EventUtils.sendKey("RIGHT", gDebugger);
+ is(gVariablesView.getFocusedItem().name, "prop1",
+ "The 'prop1' item should still be focused.");
+
+ EventUtils.sendKey("PAGE_DOWN", gDebugger);
+ is(gVariablesView.getFocusedItem().name, "someProp6",
+ "The 'someProp6' item should be focused.");
+ is(gVariablesView.getFocusedItem().expanded, false,
+ "The 'someProp6' item should not be expanded yet.");
+
+ // Part 9: Test that the RIGHT key collapses elements as intended.
+
+ EventUtils.sendKey("LEFT", gDebugger);
+ is(gVariablesView.getFocusedItem().name, "someProp6",
+ "The 'someProp6' item should be focused.");
+
+ EventUtils.sendKey("UP", gDebugger);
+ is(gVariablesView.getFocusedItem().name, "__proto__",
+ "The '__proto__' item should be focused.");
+
+ EventUtils.sendKey("LEFT", gDebugger);
+ is(gVariablesView.getFocusedItem().name, "someProp5",
+ "The 'someProp5' item should be focused.");
+ is(gVariablesView.getFocusedItem().expanded, true,
+ "The '6' item should still be expanded.");
+
+ EventUtils.sendKey("LEFT", gDebugger);
+ is(gVariablesView.getFocusedItem().name, "someProp5",
+ "The 'someProp5' item should still be focused.");
+ is(gVariablesView.getFocusedItem().expanded, false,
+ "The '6' item should still not be expanded anymore.");
+
+ EventUtils.sendKey("LEFT", gDebugger);
+ is(gVariablesView.getFocusedItem().name, "someProp5",
+ "The 'someProp5' item should still be focused.");
+
+ // Part 9: Test continuous navigation.
+
+ EventUtils.sendKey("UP", gDebugger);
+ is(gVariablesView.getFocusedItem().name, "someProp4",
+ "The 'someProp4' item should be focused.");
+
+ EventUtils.sendKey("UP", gDebugger);
+ is(gVariablesView.getFocusedItem().name, "someProp3",
+ "The 'someProp3' item should be focused.");
+
+ EventUtils.sendKey("UP", gDebugger);
+ is(gVariablesView.getFocusedItem().name, "someProp2",
+ "The 'someProp2' item should be focused.");
+
+ EventUtils.sendKey("UP", gDebugger);
+ is(gVariablesView.getFocusedItem().name, "someProp1",
+ "The 'someProp1' item should be focused.");
+
+ EventUtils.sendKey("UP", gDebugger);
+ is(gVariablesView.getFocusedItem().name, "someProp0",
+ "The 'someProp0' item should be focused.");
+
+ EventUtils.sendKey("PAGE_UP", gDebugger);
+ is(gVariablesView.getFocusedItem().name, "someProp0",
+ "The 'someProp0' item should be focused.");
+
+ EventUtils.sendKey("PAGE_DOWN", gDebugger);
+ is(gVariablesView.getFocusedItem().name, "someProp5",
+ "The 'someProp5' item should be focused.");
+
+ EventUtils.sendKey("DOWN", gDebugger);
+ is(gVariablesView.getFocusedItem().name, "someProp6",
+ "The 'someProp6' item should be focused.");
+
+ EventUtils.sendKey("DOWN", gDebugger);
+ is(gVariablesView.getFocusedItem().name, "someProp7",
+ "The 'someProp7' item should be focused.");
+
+ EventUtils.sendKey("DOWN", gDebugger);
+ is(gVariablesView.getFocusedItem().name, "get",
+ "The 'get' item should be focused.");
+
+ EventUtils.sendKey("DOWN", gDebugger);
+ is(gVariablesView.getFocusedItem().name, "set",
+ "The 'set' item should be focused.");
+
+ EventUtils.sendKey("DOWN", gDebugger);
+ is(gVariablesView.getFocusedItem().name, "__proto__",
+ "The '__proto__' item should be focused.");
+
+ // Part 10: Test that BACKSPACE deletes items in the variables view.
+
+ EventUtils.sendKey("BACK_SPACE", gDebugger);
+ is(gVariablesView.getFocusedItem().name, "__proto__",
+ "The '__proto__' variable should still be focused.");
+ is(gVariablesView.getFocusedItem().value, "[object Object]",
+ "The '__proto__' variable should not have an empty value.");
+ is(gVariablesView.getFocusedItem().visible, false,
+ "The '__proto__' variable should be hidden.");
+
+ EventUtils.sendKey("UP", gDebugger);
+ is(gVariablesView.getFocusedItem().name, "set",
+ "The 'set' item should be focused.");
+ is(gVariablesView.getFocusedItem().value, "[object Object]",
+ "The 'set' item should not have an empty value.");
+ is(gVariablesView.getFocusedItem().visible, true,
+ "The 'set' item should be visible.");
+
+ EventUtils.sendKey("BACK_SPACE", gDebugger);
+ is(gVariablesView.getFocusedItem().name, "set",
+ "The 'set' item should still be focused.");
+ is(gVariablesView.getFocusedItem().value, "[object Object]",
+ "The 'set' item should not have an empty value.");
+ is(gVariablesView.getFocusedItem().visible, true,
+ "The 'set' item should be visible.");
+ is(gVariablesView.getFocusedItem().twisty, false,
+ "The 'set' item should be disabled and have a hidden twisty.");
+
+ EventUtils.sendKey("UP", gDebugger);
+ is(gVariablesView.getFocusedItem().name, "get",
+ "The 'get' item should be focused.");
+ is(gVariablesView.getFocusedItem().value, "[object Object]",
+ "The 'get' item should not have an empty value.");
+ is(gVariablesView.getFocusedItem().visible, true,
+ "The 'get' item should be visible.");
+
+ EventUtils.sendKey("BACK_SPACE", gDebugger);
+ is(gVariablesView.getFocusedItem().name, "get",
+ "The 'get' item should still be focused.");
+ is(gVariablesView.getFocusedItem().value, "[object Object]",
+ "The 'get' item should not have an empty value.");
+ is(gVariablesView.getFocusedItem().visible, true,
+ "The 'get' item should be visible.");
+ is(gVariablesView.getFocusedItem().twisty, false,
+ "The 'get' item should be disabled and have a hidden twisty.");
+
+ EventUtils.sendKey("UP", gDebugger);
+ is(gVariablesView.getFocusedItem().name, "someProp7",
+ "The 'someProp7' item should be focused.");
+ is(gVariablesView.getFocusedItem().value, undefined,
+ "The 'someProp7' variable should have an empty value.");
+ is(gVariablesView.getFocusedItem().visible, true,
+ "The 'someProp7' variable should be visible.");
+
+ EventUtils.sendKey("BACK_SPACE", gDebugger);
+ is(gVariablesView.getFocusedItem().name, "someProp7",
+ "The 'someProp7' variable should still be focused.");
+ is(gVariablesView.getFocusedItem().value, undefined,
+ "The 'someProp7' variable should have an empty value.");
+ is(gVariablesView.getFocusedItem().visible, false,
+ "The 'someProp7' variable should be hidden.");
+
+ // Part 11: Test that Ctrl-C copies the current item to the system clipboard
+
+ gVariablesView.focusFirstVisibleItem();
+ let copied = promise.defer();
+ let expectedValue = gVariablesView.getFocusedItem().name
+ + gVariablesView.getFocusedItem().separatorStr
+ + gVariablesView.getFocusedItem().value;
+
+ waitForClipboard(expectedValue, function setup() {
+ EventUtils.synthesizeKey("C", { metaKey: true }, gDebugger);
+ }, copied.resolve, copied.reject
+ );
+
+ try {
+ yield copied.promise;
+ ok(true,
+ "Ctrl-C copied the selected item to the clipboard.");
+ } catch (e) {
+ ok(false,
+ "Ctrl-C didn't copy the selected item to the clipboard.");
+ }
+
+ yield closeDebuggerAndFinish(gPanel);
+ });
+}
+
+registerCleanupFunction(function () {
+ gTab = null;
+ gPanel = null;
+ gDebugger = null;
+ gVariablesView = null;
+});
+
+function synthesizeKeyAndWaitForElement(aKey, aModifiers, aSelector, aExistence) {
+ EventUtils.synthesizeKey(aKey, aModifiers, gDebugger);
+ return waitForElement(aSelector, aExistence);
+}
+
+function synthesizeKeyAndWaitForTick(aKey, aModifiers) {
+ EventUtils.synthesizeKey(aKey, aModifiers, gDebugger);
+ return waitForTick();
+}
+
+function waitForElement(aSelector, aExistence) {
+ return waitForPredicate(() => {
+ return !!gVariablesView._list.querySelector(aSelector) == aExistence;
+ });
+}
+
+function waitForChildNodes(aTarget, aCount) {
+ return waitForPredicate(() => {
+ return aTarget.childNodes.length == aCount;
+ });
+}
+
+function waitForPredicate(aPredicate, aInterval = 10) {
+ let deferred = promise.defer();
+
+ // Poll every few milliseconds until the element is retrieved.
+ let count = 0;
+ let intervalID = window.setInterval(() => {
+ // Make sure we don't wait for too long.
+ if (++count > 1000) {
+ deferred.reject("Timed out while polling for the element.");
+ window.clearInterval(intervalID);
+ return;
+ }
+ // Check if the predicate condition is fulfilled.
+ if (!aPredicate()) {
+ return;
+ }
+ // We got the element, it's safe to callback.
+ window.clearInterval(intervalID);
+ deferred.resolve();
+ }, aInterval);
+
+ return deferred.promise;
+}
diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-data.js b/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-data.js
new file mode 100644
index 000000000..02679e073
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-data.js
@@ -0,0 +1,611 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Make sure that the variables view correctly populates itself
+ * when given some raw data.
+ */
+
+var gTab, gPanel, gDebugger;
+var gVariablesView, gScope, gVariable;
+
+function test() {
+ initDebugger().then(([aTab,, aPanel]) => {
+ gTab = aTab;
+ gPanel = aPanel;
+ gDebugger = gPanel.panelWin;
+ gVariablesView = gDebugger.DebuggerView.Variables;
+
+ performTest();
+ });
+}
+
+function performTest() {
+ let arr = [
+ 42,
+ true,
+ "nasu",
+ undefined,
+ null,
+ [0, 1, 2],
+ { prop1: 9, prop2: 8 }
+ ];
+
+ let obj = {
+ p0: 42,
+ p1: true,
+ p2: "nasu",
+ p3: undefined,
+ p4: null,
+ p5: [3, 4, 5],
+ p6: { prop1: 7, prop2: 6 },
+ get p7() { return arr; },
+ set p8(value) { arr[0] = value; }
+ };
+
+ let test = {
+ someProp0: 42,
+ someProp1: true,
+ someProp2: "nasu",
+ someProp3: undefined,
+ someProp4: null,
+ someProp5: arr,
+ someProp6: obj,
+ get someProp7() { return arr; },
+ set someProp7(value) { arr[0] = value; }
+ };
+
+ gVariablesView.eval = function () {};
+ gVariablesView.switch = function () {};
+ gVariablesView.delete = function () {};
+ gVariablesView.new = function () {};
+ gVariablesView.rawObject = test;
+
+ testHierarchy();
+ testHeader();
+ testFirstLevelContents();
+ testSecondLevelContents();
+ testThirdLevelContents();
+ testOriginalRawDataIntegrity(arr, obj);
+
+ let fooScope = gVariablesView.addScope("foo");
+ let anonymousVar = fooScope.addItem();
+
+ let anonymousScope = gVariablesView.addScope();
+ let barVar = anonymousScope.addItem("bar");
+ let bazProperty = barVar.addItem("baz");
+
+ testAnonymousHeaders(fooScope, anonymousVar, anonymousScope, barVar, bazProperty);
+ testPropertyInheritance(fooScope, anonymousVar, anonymousScope, barVar, bazProperty);
+
+ testClearHierarchy();
+ closeDebuggerAndFinish(gPanel);
+}
+
+function testHierarchy() {
+ is(gVariablesView._currHierarchy.size, 13,
+ "There should be 1 scope, 1 var, 1 proto, 8 props, 1 getter and 1 setter.");
+
+ gScope = gVariablesView._currHierarchy.get("");
+ gVariable = gVariablesView._currHierarchy.get("[]");
+
+ is(gVariablesView._store.length, 1,
+ "There should be only one scope in the view.");
+ is(gScope._store.size, 1,
+ "There should be only one variable in the scope.");
+ is(gVariable._store.size, 9,
+ "There should be 1 __proto__ and 8 properties in the variable.");
+}
+
+function testHeader() {
+ is(gScope.header, false,
+ "The scope title header should be hidden.");
+ is(gVariable.header, false,
+ "The variable title header should be hidden.");
+
+ gScope.showHeader();
+ gVariable.showHeader();
+
+ is(gScope.header, false,
+ "The scope title header should still not be visible.");
+ is(gVariable.header, false,
+ "The variable title header should still not be visible.");
+
+ gScope.hideHeader();
+ gVariable.hideHeader();
+
+ is(gScope.header, false,
+ "The scope title header should now still be hidden.");
+ is(gVariable.header, false,
+ "The variable title header should now still be hidden.");
+}
+
+function testFirstLevelContents() {
+ let someProp0 = gVariable.get("someProp0");
+ let someProp1 = gVariable.get("someProp1");
+ let someProp2 = gVariable.get("someProp2");
+ let someProp3 = gVariable.get("someProp3");
+ let someProp4 = gVariable.get("someProp4");
+ let someProp5 = gVariable.get("someProp5");
+ let someProp6 = gVariable.get("someProp6");
+ let someProp7 = gVariable.get("someProp7");
+ let __proto__ = gVariable.get("__proto__");
+
+ is(someProp0.visible, true, "The first property visible state is correct.");
+ is(someProp1.visible, true, "The second property visible state is correct.");
+ is(someProp2.visible, true, "The third property visible state is correct.");
+ is(someProp3.visible, true, "The fourth property visible state is correct.");
+ is(someProp4.visible, true, "The fifth property visible state is correct.");
+ is(someProp5.visible, true, "The sixth property visible state is correct.");
+ is(someProp6.visible, true, "The seventh property visible state is correct.");
+ is(someProp7.visible, true, "The eight property visible state is correct.");
+ is(__proto__.visible, true, "The __proto__ property visible state is correct.");
+
+ is(someProp0.expanded, false, "The first property expanded state is correct.");
+ is(someProp1.expanded, false, "The second property expanded state is correct.");
+ is(someProp2.expanded, false, "The third property expanded state is correct.");
+ is(someProp3.expanded, false, "The fourth property expanded state is correct.");
+ is(someProp4.expanded, false, "The fifth property expanded state is correct.");
+ is(someProp5.expanded, false, "The sixth property expanded state is correct.");
+ is(someProp6.expanded, false, "The seventh property expanded state is correct.");
+ is(someProp7.expanded, true, "The eight property expanded state is correct.");
+ is(__proto__.expanded, false, "The __proto__ property expanded state is correct.");
+
+ is(someProp0.header, true, "The first property header state is correct.");
+ is(someProp1.header, true, "The second property header state is correct.");
+ is(someProp2.header, true, "The third property header state is correct.");
+ is(someProp3.header, true, "The fourth property header state is correct.");
+ is(someProp4.header, true, "The fifth property header state is correct.");
+ is(someProp5.header, true, "The sixth property header state is correct.");
+ is(someProp6.header, true, "The seventh property header state is correct.");
+ is(someProp7.header, true, "The eight property header state is correct.");
+ is(__proto__.header, true, "The __proto__ property header state is correct.");
+
+ is(someProp0.twisty, false, "The first property twisty state is correct.");
+ is(someProp1.twisty, false, "The second property twisty state is correct.");
+ is(someProp2.twisty, false, "The third property twisty state is correct.");
+ is(someProp3.twisty, false, "The fourth property twisty state is correct.");
+ is(someProp4.twisty, false, "The fifth property twisty state is correct.");
+ is(someProp5.twisty, true, "The sixth property twisty state is correct.");
+ is(someProp6.twisty, true, "The seventh property twisty state is correct.");
+ is(someProp7.twisty, true, "The eight property twisty state is correct.");
+ is(__proto__.twisty, true, "The __proto__ property twisty state is correct.");
+
+ is(someProp0.name, "someProp0", "The first property name is correct.");
+ is(someProp1.name, "someProp1", "The second property name is correct.");
+ is(someProp2.name, "someProp2", "The third property name is correct.");
+ is(someProp3.name, "someProp3", "The fourth property name is correct.");
+ is(someProp4.name, "someProp4", "The fifth property name is correct.");
+ is(someProp5.name, "someProp5", "The sixth property name is correct.");
+ is(someProp6.name, "someProp6", "The seventh property name is correct.");
+ is(someProp7.name, "someProp7", "The eight property name is correct.");
+ is(__proto__.name, "__proto__", "The __proto__ property name is correct.");
+
+ is(someProp0.value, 42, "The first property value is correct.");
+ is(someProp1.value, true, "The second property value is correct.");
+ is(someProp2.value, "nasu", "The third property value is correct.");
+ is(someProp3.value.type, "undefined", "The fourth property value is correct.");
+ is(someProp4.value.type, "null", "The fifth property value is correct.");
+ is(someProp5.value.type, "object", "The sixth property value type is correct.");
+ is(someProp5.value.class, "Array", "The sixth property value class is correct.");
+ is(someProp6.value.type, "object", "The seventh property value type is correct.");
+ is(someProp6.value.class, "Object", "The seventh property value class is correct.");
+ is(someProp7.value, null, "The eight property value is correct.");
+ isnot(someProp7.getter, null, "The eight property getter is correct.");
+ isnot(someProp7.setter, null, "The eight property setter is correct.");
+ is(someProp7.getter.type, "object", "The eight property getter type is correct.");
+ is(someProp7.getter.class, "Function", "The eight property getter class is correct.");
+ is(someProp7.setter.type, "object", "The eight property setter type is correct.");
+ is(someProp7.setter.class, "Function", "The eight property setter class is correct.");
+ is(__proto__.value.type, "object", "The __proto__ property value type is correct.");
+ is(__proto__.value.class, "Object", "The __proto__ property value class is correct.");
+
+ someProp0.expand();
+ someProp1.expand();
+ someProp2.expand();
+ someProp3.expand();
+ someProp4.expand();
+ someProp7.expand();
+
+ ok(!someProp0.get("__proto__"), "Number primitives should not have a prototype");
+ ok(!someProp1.get("__proto__"), "Boolean primitives should not have a prototype");
+ ok(!someProp2.get("__proto__"), "String literals should not have a prototype");
+ ok(!someProp3.get("__proto__"), "Undefined values should not have a prototype");
+ ok(!someProp4.get("__proto__"), "Null values should not have a prototype");
+ ok(!someProp7.get("__proto__"), "Getter properties should not have a prototype");
+}
+
+function testSecondLevelContents() {
+ let someProp5 = gVariable.get("someProp5");
+ let someProp6 = gVariable.get("someProp6");
+
+ is(someProp5._store.size, 0, "No properties should be in someProp5 before expanding");
+ someProp5.expand();
+ is(someProp5._store.size, 9, "Some properties should be in someProp5 before expanding");
+
+ let arrayItem0 = someProp5.get("0");
+ let arrayItem1 = someProp5.get("1");
+ let arrayItem2 = someProp5.get("2");
+ let arrayItem3 = someProp5.get("3");
+ let arrayItem4 = someProp5.get("4");
+ let arrayItem5 = someProp5.get("5");
+ let arrayItem6 = someProp5.get("6");
+ let __proto__ = someProp5.get("__proto__");
+
+ is(arrayItem0.visible, true, "The first array item visible state is correct.");
+ is(arrayItem1.visible, true, "The second array item visible state is correct.");
+ is(arrayItem2.visible, true, "The third array item visible state is correct.");
+ is(arrayItem3.visible, true, "The fourth array item visible state is correct.");
+ is(arrayItem4.visible, true, "The fifth array item visible state is correct.");
+ is(arrayItem5.visible, true, "The sixth array item visible state is correct.");
+ is(arrayItem6.visible, true, "The seventh array item visible state is correct.");
+ is(__proto__.visible, true, "The __proto__ property visible state is correct.");
+
+ is(arrayItem0.expanded, false, "The first array item expanded state is correct.");
+ is(arrayItem1.expanded, false, "The second array item expanded state is correct.");
+ is(arrayItem2.expanded, false, "The third array item expanded state is correct.");
+ is(arrayItem3.expanded, false, "The fourth array item expanded state is correct.");
+ is(arrayItem4.expanded, false, "The fifth array item expanded state is correct.");
+ is(arrayItem5.expanded, false, "The sixth array item expanded state is correct.");
+ is(arrayItem6.expanded, false, "The seventh array item expanded state is correct.");
+ is(__proto__.expanded, false, "The __proto__ property expanded state is correct.");
+
+ is(arrayItem0.header, true, "The first array item header state is correct.");
+ is(arrayItem1.header, true, "The second array item header state is correct.");
+ is(arrayItem2.header, true, "The third array item header state is correct.");
+ is(arrayItem3.header, true, "The fourth array item header state is correct.");
+ is(arrayItem4.header, true, "The fifth array item header state is correct.");
+ is(arrayItem5.header, true, "The sixth array item header state is correct.");
+ is(arrayItem6.header, true, "The seventh array item header state is correct.");
+ is(__proto__.header, true, "The __proto__ property header state is correct.");
+
+ is(arrayItem0.twisty, false, "The first array item twisty state is correct.");
+ is(arrayItem1.twisty, false, "The second array item twisty state is correct.");
+ is(arrayItem2.twisty, false, "The third array item twisty state is correct.");
+ is(arrayItem3.twisty, false, "The fourth array item twisty state is correct.");
+ is(arrayItem4.twisty, false, "The fifth array item twisty state is correct.");
+ is(arrayItem5.twisty, true, "The sixth array item twisty state is correct.");
+ is(arrayItem6.twisty, true, "The seventh array item twisty state is correct.");
+ is(__proto__.twisty, true, "The __proto__ property twisty state is correct.");
+
+ is(arrayItem0.name, "0", "The first array item name is correct.");
+ is(arrayItem1.name, "1", "The second array item name is correct.");
+ is(arrayItem2.name, "2", "The third array item name is correct.");
+ is(arrayItem3.name, "3", "The fourth array item name is correct.");
+ is(arrayItem4.name, "4", "The fifth array item name is correct.");
+ is(arrayItem5.name, "5", "The sixth array item name is correct.");
+ is(arrayItem6.name, "6", "The seventh array item name is correct.");
+ is(__proto__.name, "__proto__", "The __proto__ property name is correct.");
+
+ is(arrayItem0.value, 42, "The first array item value is correct.");
+ is(arrayItem1.value, true, "The second array item value is correct.");
+ is(arrayItem2.value, "nasu", "The third array item value is correct.");
+ is(arrayItem3.value.type, "undefined", "The fourth array item value is correct.");
+ is(arrayItem4.value.type, "null", "The fifth array item value is correct.");
+ is(arrayItem5.value.type, "object", "The sixth array item value type is correct.");
+ is(arrayItem5.value.class, "Array", "The sixth array item value class is correct.");
+ is(arrayItem6.value.type, "object", "The seventh array item value type is correct.");
+ is(arrayItem6.value.class, "Object", "The seventh array item value class is correct.");
+ is(__proto__.value.type, "object", "The __proto__ property value type is correct.");
+ is(__proto__.value.class, "Array", "The __proto__ property value class is correct.");
+
+ is(someProp6._store.size, 0, "No properties should be in someProp6 before expanding");
+ someProp6.expand();
+ is(someProp6._store.size, 10, "Some properties should be in someProp6 before expanding");
+
+ let objectItem0 = someProp6.get("p0");
+ let objectItem1 = someProp6.get("p1");
+ let objectItem2 = someProp6.get("p2");
+ let objectItem3 = someProp6.get("p3");
+ let objectItem4 = someProp6.get("p4");
+ let objectItem5 = someProp6.get("p5");
+ let objectItem6 = someProp6.get("p6");
+ let objectItem7 = someProp6.get("p7");
+ let objectItem8 = someProp6.get("p8");
+ __proto__ = someProp6.get("__proto__");
+
+ is(objectItem0.visible, true, "The first object item visible state is correct.");
+ is(objectItem1.visible, true, "The second object item visible state is correct.");
+ is(objectItem2.visible, true, "The third object item visible state is correct.");
+ is(objectItem3.visible, true, "The fourth object item visible state is correct.");
+ is(objectItem4.visible, true, "The fifth object item visible state is correct.");
+ is(objectItem5.visible, true, "The sixth object item visible state is correct.");
+ is(objectItem6.visible, true, "The seventh object item visible state is correct.");
+ is(objectItem7.visible, true, "The eight object item visible state is correct.");
+ is(objectItem8.visible, true, "The ninth object item visible state is correct.");
+ is(__proto__.visible, true, "The __proto__ property visible state is correct.");
+
+ is(objectItem0.expanded, false, "The first object item expanded state is correct.");
+ is(objectItem1.expanded, false, "The second object item expanded state is correct.");
+ is(objectItem2.expanded, false, "The third object item expanded state is correct.");
+ is(objectItem3.expanded, false, "The fourth object item expanded state is correct.");
+ is(objectItem4.expanded, false, "The fifth object item expanded state is correct.");
+ is(objectItem5.expanded, false, "The sixth object item expanded state is correct.");
+ is(objectItem6.expanded, false, "The seventh object item expanded state is correct.");
+ is(objectItem7.expanded, true, "The eight object item expanded state is correct.");
+ is(objectItem8.expanded, true, "The ninth object item expanded state is correct.");
+ is(__proto__.expanded, false, "The __proto__ property expanded state is correct.");
+
+ is(objectItem0.header, true, "The first object item header state is correct.");
+ is(objectItem1.header, true, "The second object item header state is correct.");
+ is(objectItem2.header, true, "The third object item header state is correct.");
+ is(objectItem3.header, true, "The fourth object item header state is correct.");
+ is(objectItem4.header, true, "The fifth object item header state is correct.");
+ is(objectItem5.header, true, "The sixth object item header state is correct.");
+ is(objectItem6.header, true, "The seventh object item header state is correct.");
+ is(objectItem7.header, true, "The eight object item header state is correct.");
+ is(objectItem8.header, true, "The ninth object item header state is correct.");
+ is(__proto__.header, true, "The __proto__ property header state is correct.");
+
+ is(objectItem0.twisty, false, "The first object item twisty state is correct.");
+ is(objectItem1.twisty, false, "The second object item twisty state is correct.");
+ is(objectItem2.twisty, false, "The third object item twisty state is correct.");
+ is(objectItem3.twisty, false, "The fourth object item twisty state is correct.");
+ is(objectItem4.twisty, false, "The fifth object item twisty state is correct.");
+ is(objectItem5.twisty, true, "The sixth object item twisty state is correct.");
+ is(objectItem6.twisty, true, "The seventh object item twisty state is correct.");
+ is(objectItem7.twisty, true, "The eight object item twisty state is correct.");
+ is(objectItem8.twisty, true, "The ninth object item twisty state is correct.");
+ is(__proto__.twisty, true, "The __proto__ property twisty state is correct.");
+
+ is(objectItem0.name, "p0", "The first object item name is correct.");
+ is(objectItem1.name, "p1", "The second object item name is correct.");
+ is(objectItem2.name, "p2", "The third object item name is correct.");
+ is(objectItem3.name, "p3", "The fourth object item name is correct.");
+ is(objectItem4.name, "p4", "The fifth object item name is correct.");
+ is(objectItem5.name, "p5", "The sixth object item name is correct.");
+ is(objectItem6.name, "p6", "The seventh object item name is correct.");
+ is(objectItem7.name, "p7", "The eight seventh object item name is correct.");
+ is(objectItem8.name, "p8", "The ninth seventh object item name is correct.");
+ is(__proto__.name, "__proto__", "The __proto__ property name is correct.");
+
+ is(objectItem0.value, 42, "The first object item value is correct.");
+ is(objectItem1.value, true, "The second object item value is correct.");
+ is(objectItem2.value, "nasu", "The third object item value is correct.");
+ is(objectItem3.value.type, "undefined", "The fourth object item value is correct.");
+ is(objectItem4.value.type, "null", "The fifth object item value is correct.");
+ is(objectItem5.value.type, "object", "The sixth object item value type is correct.");
+ is(objectItem5.value.class, "Array", "The sixth object item value class is correct.");
+ is(objectItem6.value.type, "object", "The seventh object item value type is correct.");
+ is(objectItem6.value.class, "Object", "The seventh object item value class is correct.");
+ is(objectItem7.value, null, "The eight object item value is correct.");
+ isnot(objectItem7.getter, null, "The eight object item getter is correct.");
+ isnot(objectItem7.setter, null, "The eight object item setter is correct.");
+ is(objectItem7.setter.type, "undefined", "The eight object item setter type is correct.");
+ is(objectItem7.getter.type, "object", "The eight object item getter type is correct.");
+ is(objectItem7.getter.class, "Function", "The eight object item getter class is correct.");
+ is(objectItem8.value, null, "The ninth object item value is correct.");
+ isnot(objectItem8.getter, null, "The ninth object item getter is correct.");
+ isnot(objectItem8.setter, null, "The ninth object item setter is correct.");
+ is(objectItem8.getter.type, "undefined", "The eight object item getter type is correct.");
+ is(objectItem8.setter.type, "object", "The ninth object item setter type is correct.");
+ is(objectItem8.setter.class, "Function", "The ninth object item setter class is correct.");
+ is(__proto__.value.type, "object", "The __proto__ property value type is correct.");
+ is(__proto__.value.class, "Object", "The __proto__ property value class is correct.");
+}
+
+function testThirdLevelContents() {
+ (function () {
+ let someProp5 = gVariable.get("someProp5");
+ let arrayItem5 = someProp5.get("5");
+ let arrayItem6 = someProp5.get("6");
+
+ is(arrayItem5._store.size, 0, "No properties should be in arrayItem5 before expanding");
+ arrayItem5.expand();
+ is(arrayItem5._store.size, 5, "Some properties should be in arrayItem5 before expanding");
+
+ is(arrayItem6._store.size, 0, "No properties should be in arrayItem6 before expanding");
+ arrayItem6.expand();
+ is(arrayItem6._store.size, 3, "Some properties should be in arrayItem6 before expanding");
+
+ let arraySubItem0 = arrayItem5.get("0");
+ let arraySubItem1 = arrayItem5.get("1");
+ let arraySubItem2 = arrayItem5.get("2");
+ let objectSubItem0 = arrayItem6.get("prop1");
+ let objectSubItem1 = arrayItem6.get("prop2");
+
+ is(arraySubItem0.value, 0, "The first array sub-item value is correct.");
+ is(arraySubItem1.value, 1, "The second array sub-item value is correct.");
+ is(arraySubItem2.value, 2, "The third array sub-item value is correct.");
+
+ is(objectSubItem0.value, 9, "The first object sub-item value is correct.");
+ is(objectSubItem1.value, 8, "The second object sub-item value is correct.");
+
+ let array__proto__ = arrayItem5.get("__proto__");
+ let object__proto__ = arrayItem6.get("__proto__");
+
+ ok(array__proto__, "The array should have a __proto__ property.");
+ ok(object__proto__, "The object should have a __proto__ property.");
+ })();
+
+ (function () {
+ let someProp6 = gVariable.get("someProp6");
+ let objectItem5 = someProp6.get("p5");
+ let objectItem6 = someProp6.get("p6");
+
+ is(objectItem5._store.size, 0, "No properties should be in objectItem5 before expanding");
+ objectItem5.expand();
+ is(objectItem5._store.size, 5, "Some properties should be in objectItem5 before expanding");
+
+ is(objectItem6._store.size, 0, "No properties should be in objectItem6 before expanding");
+ objectItem6.expand();
+ is(objectItem6._store.size, 3, "Some properties should be in objectItem6 before expanding");
+
+ let arraySubItem0 = objectItem5.get("0");
+ let arraySubItem1 = objectItem5.get("1");
+ let arraySubItem2 = objectItem5.get("2");
+ let objectSubItem0 = objectItem6.get("prop1");
+ let objectSubItem1 = objectItem6.get("prop2");
+
+ is(arraySubItem0.value, 3, "The first array sub-item value is correct.");
+ is(arraySubItem1.value, 4, "The second array sub-item value is correct.");
+ is(arraySubItem2.value, 5, "The third array sub-item value is correct.");
+
+ is(objectSubItem0.value, 7, "The first object sub-item value is correct.");
+ is(objectSubItem1.value, 6, "The second object sub-item value is correct.");
+
+ let array__proto__ = objectItem5.get("__proto__");
+ let object__proto__ = objectItem6.get("__proto__");
+
+ ok(array__proto__, "The array should have a __proto__ property.");
+ ok(object__proto__, "The object should have a __proto__ property.");
+ })();
+}
+
+function testOriginalRawDataIntegrity(arr, obj) {
+ is(arr[0], 42, "The first array item should not have changed.");
+ is(arr[1], true, "The second array item should not have changed.");
+ is(arr[2], "nasu", "The third array item should not have changed.");
+ is(arr[3], undefined, "The fourth array item should not have changed.");
+ is(arr[4], null, "The fifth array item should not have changed.");
+ ok(arr[5] instanceof Array, "The sixth array item should be an Array.");
+ is(arr[5][0], 0, "The sixth array item should not have changed.");
+ is(arr[5][1], 1, "The sixth array item should not have changed.");
+ is(arr[5][2], 2, "The sixth array item should not have changed.");
+ ok(arr[6] instanceof Object, "The seventh array item should be an Object.");
+ is(arr[6].prop1, 9, "The seventh array item should not have changed.");
+ is(arr[6].prop2, 8, "The seventh array item should not have changed.");
+
+ is(obj.p0, 42, "The first object property should not have changed.");
+ is(obj.p1, true, "The first object property should not have changed.");
+ is(obj.p2, "nasu", "The first object property should not have changed.");
+ is(obj.p3, undefined, "The first object property should not have changed.");
+ is(obj.p4, null, "The first object property should not have changed.");
+ ok(obj.p5 instanceof Array, "The sixth object property should be an Array.");
+ is(obj.p5[0], 3, "The sixth object property should not have changed.");
+ is(obj.p5[1], 4, "The sixth object property should not have changed.");
+ is(obj.p5[2], 5, "The sixth object property should not have changed.");
+ ok(obj.p6 instanceof Object, "The seventh object property should be an Object.");
+ is(obj.p6.prop1, 7, "The seventh object property should not have changed.");
+ is(obj.p6.prop2, 6, "The seventh object property should not have changed.");
+}
+
+function testAnonymousHeaders(fooScope, anonymousVar, anonymousScope, barVar, bazProperty) {
+ is(fooScope.header, true,
+ "A named scope should have a header visible.");
+ is(fooScope.target.hasAttribute("untitled"), false,
+ "The non-header attribute should not be applied to scopes with headers.");
+
+ is(anonymousScope.header, false,
+ "An anonymous scope should have a header visible.");
+ is(anonymousScope.target.hasAttribute("untitled"), true,
+ "The non-header attribute should not be applied to scopes without headers.");
+
+ is(barVar.header, true,
+ "A named variable should have a header visible.");
+ is(barVar.target.hasAttribute("untitled"), false,
+ "The non-header attribute should not be applied to variables with headers.");
+
+ is(anonymousVar.header, false,
+ "An anonymous variable should have a header visible.");
+ is(anonymousVar.target.hasAttribute("untitled"), true,
+ "The non-header attribute should not be applied to variables without headers.");
+}
+
+function testPropertyInheritance(fooScope, anonymousVar, anonymousScope, barVar, bazProperty) {
+ is(fooScope.preventDisableOnChange, gVariablesView.preventDisableOnChange,
+ "The preventDisableOnChange property should persist from the view to all scopes.");
+ is(fooScope.preventDescriptorModifiers, gVariablesView.preventDescriptorModifiers,
+ "The preventDescriptorModifiers property should persist from the view to all scopes.");
+ is(fooScope.editableNameTooltip, gVariablesView.editableNameTooltip,
+ "The editableNameTooltip property should persist from the view to all scopes.");
+ is(fooScope.editableValueTooltip, gVariablesView.editableValueTooltip,
+ "The editableValueTooltip property should persist from the view to all scopes.");
+ is(fooScope.editButtonTooltip, gVariablesView.editButtonTooltip,
+ "The editButtonTooltip property should persist from the view to all scopes.");
+ is(fooScope.deleteButtonTooltip, gVariablesView.deleteButtonTooltip,
+ "The deleteButtonTooltip property should persist from the view to all scopes.");
+ is(fooScope.contextMenuId, gVariablesView.contextMenuId,
+ "The contextMenuId property should persist from the view to all scopes.");
+ is(fooScope.separatorStr, gVariablesView.separatorStr,
+ "The separatorStr property should persist from the view to all scopes.");
+ is(fooScope.eval, gVariablesView.eval,
+ "The eval property should persist from the view to all scopes.");
+ is(fooScope.switch, gVariablesView.switch,
+ "The switch property should persist from the view to all scopes.");
+ is(fooScope.delete, gVariablesView.delete,
+ "The delete property should persist from the view to all scopes.");
+ is(fooScope.new, gVariablesView.new,
+ "The new property should persist from the view to all scopes.");
+ isnot(fooScope.eval, fooScope.switch,
+ "The eval and switch functions got mixed up in the scope.");
+ isnot(fooScope.switch, fooScope.delete,
+ "The eval and switch functions got mixed up in the scope.");
+
+ is(barVar.preventDisableOnChange, gVariablesView.preventDisableOnChange,
+ "The preventDisableOnChange property should persist from the view to all variables.");
+ is(barVar.preventDescriptorModifiers, gVariablesView.preventDescriptorModifiers,
+ "The preventDescriptorModifiers property should persist from the view to all variables.");
+ is(barVar.editableNameTooltip, gVariablesView.editableNameTooltip,
+ "The editableNameTooltip property should persist from the view to all variables.");
+ is(barVar.editableValueTooltip, gVariablesView.editableValueTooltip,
+ "The editableValueTooltip property should persist from the view to all variables.");
+ is(barVar.editButtonTooltip, gVariablesView.editButtonTooltip,
+ "The editButtonTooltip property should persist from the view to all variables.");
+ is(barVar.deleteButtonTooltip, gVariablesView.deleteButtonTooltip,
+ "The deleteButtonTooltip property should persist from the view to all variables.");
+ is(barVar.contextMenuId, gVariablesView.contextMenuId,
+ "The contextMenuId property should persist from the view to all variables.");
+ is(barVar.separatorStr, gVariablesView.separatorStr,
+ "The separatorStr property should persist from the view to all variables.");
+ is(barVar.eval, gVariablesView.eval,
+ "The eval property should persist from the view to all variables.");
+ is(barVar.switch, gVariablesView.switch,
+ "The switch property should persist from the view to all variables.");
+ is(barVar.delete, gVariablesView.delete,
+ "The delete property should persist from the view to all variables.");
+ is(barVar.new, gVariablesView.new,
+ "The new property should persist from the view to all variables.");
+ isnot(barVar.eval, barVar.switch,
+ "The eval and switch functions got mixed up in the variable.");
+ isnot(barVar.switch, barVar.delete,
+ "The eval and switch functions got mixed up in the variable.");
+
+ is(bazProperty.preventDisableOnChange, gVariablesView.preventDisableOnChange,
+ "The preventDisableOnChange property should persist from the view to all properties.");
+ is(bazProperty.preventDescriptorModifiers, gVariablesView.preventDescriptorModifiers,
+ "The preventDescriptorModifiers property should persist from the view to all properties.");
+ is(bazProperty.editableNameTooltip, gVariablesView.editableNameTooltip,
+ "The editableNameTooltip property should persist from the view to all properties.");
+ is(bazProperty.editableValueTooltip, gVariablesView.editableValueTooltip,
+ "The editableValueTooltip property should persist from the view to all properties.");
+ is(bazProperty.editButtonTooltip, gVariablesView.editButtonTooltip,
+ "The editButtonTooltip property should persist from the view to all properties.");
+ is(bazProperty.deleteButtonTooltip, gVariablesView.deleteButtonTooltip,
+ "The deleteButtonTooltip property should persist from the view to all properties.");
+ is(bazProperty.contextMenuId, gVariablesView.contextMenuId,
+ "The contextMenuId property should persist from the view to all properties.");
+ is(bazProperty.separatorStr, gVariablesView.separatorStr,
+ "The separatorStr property should persist from the view to all properties.");
+ is(bazProperty.eval, gVariablesView.eval,
+ "The eval property should persist from the view to all properties.");
+ is(bazProperty.switch, gVariablesView.switch,
+ "The switch property should persist from the view to all properties.");
+ is(bazProperty.delete, gVariablesView.delete,
+ "The delete property should persist from the view to all properties.");
+ is(bazProperty.new, gVariablesView.new,
+ "The new property should persist from the view to all properties.");
+ isnot(bazProperty.eval, bazProperty.switch,
+ "The eval and switch functions got mixed up in the property.");
+ isnot(bazProperty.switch, bazProperty.delete,
+ "The eval and switch functions got mixed up in the property.");
+}
+
+function testClearHierarchy() {
+ gVariablesView.clearHierarchy();
+ ok(!gVariablesView._prevHierarchy.size,
+ "The previous hierarchy should have been cleared.");
+ ok(!gVariablesView._currHierarchy.size,
+ "The current hierarchy should have been cleared.");
+}
+
+registerCleanupFunction(function () {
+ gTab = null;
+ gPanel = null;
+ gDebugger = null;
+ gVariablesView = null;
+ gScope = null;
+ gVariable = null;
+});
diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-edit-cancel.js b/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-edit-cancel.js
new file mode 100644
index 000000000..dd4954717
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-edit-cancel.js
@@ -0,0 +1,58 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Make sure that canceling a name change correctly unhides the separator and
+ * value elements.
+ */
+
+const TAB_URL = EXAMPLE_URL + "doc_watch-expressions.html";
+
+function test() {
+ Task.spawn(function* () {
+ let options = {
+ source: TAB_URL,
+ line: 1
+ };
+ let [tab,, panel] = yield initDebugger(TAB_URL, options);
+ let win = panel.panelWin;
+ let vars = win.DebuggerView.Variables;
+
+ win.DebuggerView.WatchExpressions.addExpression("this");
+
+ callInTab(tab, "ermahgerd");
+ yield waitForDebuggerEvents(panel, win.EVENTS.FETCHED_WATCH_EXPRESSIONS);
+
+ let exprScope = vars.getScopeAtIndex(0);
+ let {target} = exprScope.get("this");
+
+ let name = target.querySelector(".title > .name");
+ let separator = target.querySelector(".separator");
+ let value = target.querySelector(".value");
+
+ is(separator.hidden, false,
+ "The separator element should not be hidden.");
+ is(value.hidden, false,
+ "The value element should not be hidden.");
+
+ for (let key of ["ESCAPE", "RETURN"]) {
+ EventUtils.sendMouseEvent({ type: "dblclick" }, name, win);
+
+ is(separator.hidden, true,
+ "The separator element should be hidden.");
+ is(value.hidden, true,
+ "The value element should be hidden.");
+
+ EventUtils.sendKey(key, win);
+
+ is(separator.hidden, false,
+ "The separator element should not be hidden.");
+ is(value.hidden, false,
+ "The value element should not be hidden.");
+ }
+
+ yield resumeDebuggerThenCloseAndFinish(panel);
+ });
+}
diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-edit-click.js b/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-edit-click.js
new file mode 100644
index 000000000..9ea9230ef
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-edit-click.js
@@ -0,0 +1,58 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Check that the editing state of a Variable is correctly tracked. Clicking on
+ * the textbox while editing should not cancel editing.
+ */
+
+const TAB_URL = EXAMPLE_URL + "doc_watch-expressions.html";
+
+function test() {
+ Task.spawn(function* () {
+ let options = {
+ source: TAB_URL,
+ line: 1
+ };
+ let [tab, debuggee, panel] = yield initDebugger(TAB_URL, options);
+ let win = panel.panelWin;
+ let vars = win.DebuggerView.Variables;
+
+ win.DebuggerView.WatchExpressions.addExpression("this");
+
+ // Allow this generator function to yield first.
+ executeSoon(() => debuggee.ermahgerd());
+ yield waitForDebuggerEvents(panel, win.EVENTS.FETCHED_WATCH_EXPRESSIONS);
+
+ let exprScope = vars.getScopeAtIndex(0);
+ let exprVar = exprScope.get("this");
+ let name = exprVar.target.querySelector(".title > .name");
+
+ is(exprVar.editing, false,
+ "The expression should indicate it is not being edited.");
+
+ EventUtils.sendMouseEvent({ type: "dblclick" }, name, win);
+ let input = exprVar.target.querySelector(".title > .element-name-input");
+ is(exprVar.editing, true,
+ "The expression should indicate it is being edited.");
+ is(input.selectionStart !== input.selectionEnd, true,
+ "The expression text should be selected.");
+
+ EventUtils.synthesizeMouse(input, 2, 2, {}, win);
+ is(exprVar.editing, true,
+ "The expression should indicate it is still being edited after a click.");
+ is(input.selectionStart === input.selectionEnd, true,
+ "The expression text should not be selected.");
+
+ EventUtils.sendKey("ESCAPE", win);
+ is(exprVar.editing, false,
+ "The expression should indicate it is not being edited after cancelling.");
+
+ // Why is this needed?
+ EventUtils.synthesizeMouse(vars.parentNode, 2, 2, {}, win);
+
+ yield resumeDebuggerThenCloseAndFinish(panel);
+ });
+}
diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-edit-getset-01.js b/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-edit-getset-01.js
new file mode 100644
index 000000000..5b5fce266
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-edit-getset-01.js
@@ -0,0 +1,300 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Make sure that the variables view knows how to edit getters and setters.
+ */
+
+const TAB_URL = EXAMPLE_URL + "doc_frame-parameters.html";
+
+var gTab, gPanel, gDebugger;
+var gL10N, gEditor, gVars, gWatch;
+
+function test() {
+ // Debug test slaves are a bit slow at this test.
+ requestLongerTimeout(2);
+
+ let options = {
+ source: TAB_URL,
+ line: 1
+ };
+ initDebugger(TAB_URL, options).then(([aTab,, aPanel]) => {
+ gTab = aTab;
+ gPanel = aPanel;
+ gDebugger = gPanel.panelWin;
+ gL10N = gDebugger.L10N;
+ gEditor = gDebugger.DebuggerView.editor;
+ gVars = gDebugger.DebuggerView.Variables;
+ gWatch = gDebugger.DebuggerView.WatchExpressions;
+
+ gVars.switch = function () {};
+ gVars.delete = function () {};
+
+ waitForCaretAndScopes(gPanel, 24)
+ .then(() => addWatchExpressions())
+ .then(() => testEdit("set", "this._prop = value + ' BEER CAN'", {
+ "myVar.prop": "xlerb BEER CAN",
+ "myVar.prop + 42": "xlerb BEER CAN42",
+ "myVar.prop = 'xlerb'": "xlerb"
+ }))
+ .then(() => testEdit("set", "{ this._prop = value + ' BEACON' }", {
+ "myVar.prop": "xlerb BEACON",
+ "myVar.prop + 42": "xlerb BEACON42",
+ "myVar.prop = 'xlerb'": "xlerb"
+ }))
+ .then(() => testEdit("set", "{ this._prop = value + ' BEACON;'; }", {
+ "myVar.prop": "xlerb BEACON;",
+ "myVar.prop + 42": "xlerb BEACON;42",
+ "myVar.prop = 'xlerb'": "xlerb"
+ }))
+ .then(() => testEdit("set", "{ return this._prop = value + ' BEACON;;'; }", {
+ "myVar.prop": "xlerb BEACON;;",
+ "myVar.prop + 42": "xlerb BEACON;;42",
+ "myVar.prop = 'xlerb'": "xlerb"
+ }))
+ .then(() => testEdit("set", "function(value) { this._prop = value + ' BACON' }", {
+ "myVar.prop": "xlerb BACON",
+ "myVar.prop + 42": "xlerb BACON42",
+ "myVar.prop = 'xlerb'": "xlerb"
+ }))
+ .then(() => testEdit("get", "'brelx BEER CAN'", {
+ "myVar.prop": "brelx BEER CAN",
+ "myVar.prop + 42": "brelx BEER CAN42",
+ "myVar.prop = 'xlerb'": "xlerb"
+ }))
+ .then(() => testEdit("get", "{ 'brelx BEACON' }", {
+ "myVar.prop": undefined,
+ "myVar.prop + 42": NaN,
+ "myVar.prop = 'xlerb'": "xlerb"
+ }))
+ .then(() => testEdit("get", "{ 'brelx BEACON;'; }", {
+ "myVar.prop": undefined,
+ "myVar.prop + 42": NaN,
+ "myVar.prop = 'xlerb'": "xlerb"
+ }))
+ .then(() => testEdit("get", "{ return 'brelx BEACON;;'; }", {
+ "myVar.prop": "brelx BEACON;;",
+ "myVar.prop + 42": "brelx BEACON;;42",
+ "myVar.prop = 'xlerb'": "xlerb"
+ }))
+ .then(() => testEdit("get", "function() { return 'brelx BACON'; }", {
+ "myVar.prop": "brelx BACON",
+ "myVar.prop + 42": "brelx BACON42",
+ "myVar.prop = 'xlerb'": "xlerb"
+ }))
+ .then(() => testEdit("get", "bogus", {
+ "myVar.prop": "ReferenceError: bogus is not defined",
+ "myVar.prop + 42": "ReferenceError: bogus is not defined",
+ "myVar.prop = 'xlerb'": "xlerb"
+ }))
+ .then(() => testEdit("set", "sugob", {
+ "myVar.prop": "ReferenceError: bogus is not defined",
+ "myVar.prop + 42": "ReferenceError: bogus is not defined",
+ "myVar.prop = 'xlerb'": "ReferenceError: sugob is not defined"
+ }))
+ .then(() => testEdit("get", "", {
+ "myVar.prop": undefined,
+ "myVar.prop + 42": NaN,
+ "myVar.prop = 'xlerb'": "ReferenceError: sugob is not defined"
+ }))
+ .then(() => testEdit("set", "", {
+ "myVar.prop": "xlerb",
+ "myVar.prop + 42": NaN,
+ "myVar.prop = 'xlerb'": "xlerb"
+ }))
+ .then(() => deleteWatchExpression("myVar.prop = 'xlerb'"))
+ .then(() => testEdit("self", "2507", {
+ "myVar.prop": 2507,
+ "myVar.prop + 42": 2549
+ }))
+ .then(() => deleteWatchExpression("myVar.prop + 42"))
+ .then(() => testEdit("self", "0910", {
+ "myVar.prop": 910
+ }))
+ .then(() => deleteLastWatchExpression("myVar.prop"))
+ .then(() => testWatchExpressionsRemoved())
+ .then(() => resumeDebuggerThenCloseAndFinish(gPanel))
+ .then(null, aError => {
+ ok(false, "Got an error: " + aError.message + "\n" + aError.stack);
+ });
+
+ generateMouseClickInTab(gTab, "content.document.querySelector('button')");
+ });
+}
+
+function addWatchExpressions() {
+ return promise.resolve(null)
+ .then(() => {
+ let finished = waitForDebuggerEvents(gPanel, gDebugger.EVENTS.FETCHED_WATCH_EXPRESSIONS);
+ gWatch.addExpression("myVar.prop");
+ gEditor.focus();
+ return finished;
+ })
+ .then(() => {
+ let exprScope = gVars.getScopeAtIndex(0);
+ ok(exprScope,
+ "There should be a wach expressions scope in the variables view.");
+ is(exprScope.name, gL10N.getStr("watchExpressionsScopeLabel"),
+ "The scope's name should be marked as 'Watch Expressions'.");
+ is(exprScope._store.size, 1,
+ "There should be 1 evaluation available.");
+
+ let w1 = exprScope.get("myVar.prop");
+ let w2 = exprScope.get("myVar.prop + 42");
+ let w3 = exprScope.get("myVar.prop = 'xlerb'");
+
+ ok(w1, "The first watch expression should be present in the scope.");
+ ok(!w2, "The second watch expression should not be present in the scope.");
+ ok(!w3, "The third watch expression should not be present in the scope.");
+
+ is(w1.value, 42, "The first value is correct.");
+ })
+ .then(() => {
+ let finished = waitForDebuggerEvents(gPanel, gDebugger.EVENTS.FETCHED_WATCH_EXPRESSIONS);
+ gWatch.addExpression("myVar.prop + 42");
+ gEditor.focus();
+ return finished;
+ })
+ .then(() => {
+ let exprScope = gVars.getScopeAtIndex(0);
+ ok(exprScope,
+ "There should be a wach expressions scope in the variables view.");
+ is(exprScope.name, gL10N.getStr("watchExpressionsScopeLabel"),
+ "The scope's name should be marked as 'Watch Expressions'.");
+ is(exprScope._store.size, 2,
+ "There should be 2 evaluations available.");
+
+ let w1 = exprScope.get("myVar.prop");
+ let w2 = exprScope.get("myVar.prop + 42");
+ let w3 = exprScope.get("myVar.prop = 'xlerb'");
+
+ ok(w1, "The first watch expression should be present in the scope.");
+ ok(w2, "The second watch expression should be present in the scope.");
+ ok(!w3, "The third watch expression should not be present in the scope.");
+
+ is(w1.value, "42", "The first expression value is correct.");
+ is(w2.value, "84", "The second expression value is correct.");
+ })
+ .then(() => {
+ let finished = waitForDebuggerEvents(gPanel, gDebugger.EVENTS.FETCHED_WATCH_EXPRESSIONS);
+ gWatch.addExpression("myVar.prop = 'xlerb'");
+ gEditor.focus();
+ return finished;
+ })
+ .then(() => {
+ let exprScope = gVars.getScopeAtIndex(0);
+ ok(exprScope,
+ "There should be a wach expressions scope in the variables view.");
+ is(exprScope.name, gL10N.getStr("watchExpressionsScopeLabel"),
+ "The scope's name should be marked as 'Watch Expressions'.");
+ is(exprScope._store.size, 3,
+ "There should be 3 evaluations available.");
+
+ let w1 = exprScope.get("myVar.prop");
+ let w2 = exprScope.get("myVar.prop + 42");
+ let w3 = exprScope.get("myVar.prop = 'xlerb'");
+
+ ok(w1, "The first watch expression should be present in the scope.");
+ ok(w2, "The second watch expression should be present in the scope.");
+ ok(w3, "The third watch expression should be present in the scope.");
+
+ is(w1.value, "xlerb", "The first expression value is correct.");
+ is(w2.value, "xlerb42", "The second expression value is correct.");
+ is(w3.value, "xlerb", "The third expression value is correct.");
+ });
+}
+
+function deleteWatchExpression(aString) {
+ let finished = waitForDebuggerEvents(gPanel, gDebugger.EVENTS.FETCHED_WATCH_EXPRESSIONS);
+ gWatch.deleteExpression({ name: aString });
+ return finished;
+}
+
+function deleteLastWatchExpression(aString) {
+ let finished = waitForDebuggerEvents(gPanel, gDebugger.EVENTS.FETCHED_SCOPES);
+ gWatch.deleteExpression({ name: aString });
+ return finished;
+}
+
+function testEdit(aWhat, aString, aExpected) {
+ let localScope = gVars.getScopeAtIndex(1);
+ let myVar = localScope.get("myVar");
+
+ let finished = waitForDebuggerEvents(gPanel, gDebugger.EVENTS.FETCHED_PROPERTIES).then(() => {
+ let propVar = myVar.get("prop");
+ let getterOrSetterOrVar = aWhat != "self" ? propVar.get(aWhat) : propVar;
+
+ let finished = waitForDebuggerEvents(gPanel, gDebugger.EVENTS.FETCHED_WATCH_EXPRESSIONS).then(() => {
+ let exprScope = gVars.getScopeAtIndex(0);
+ ok(exprScope,
+ "There should be a wach expressions scope in the variables view.");
+ is(exprScope.name, gL10N.getStr("watchExpressionsScopeLabel"),
+ "The scope's name should be marked as 'Watch Expressions'.");
+ is(exprScope._store.size, Object.keys(aExpected).length,
+ "There should be a certain number of evaluations available.");
+
+ function testExpression(aExpression) {
+ if (!aExpression) {
+ return;
+ }
+ let value = aExpected[aExpression.name];
+ if (isNaN(value)) {
+ ok(isNaN(aExpression.value),
+ "The expression value is correct after the edit.");
+ } else if (value == null) {
+ is(aExpression.value.type, value + "",
+ "The expression value is correct after the edit.");
+ } else {
+ is(aExpression.value, value,
+ "The expression value is correct after the edit.");
+ }
+ }
+
+ testExpression(exprScope.get(Object.keys(aExpected)[0]));
+ testExpression(exprScope.get(Object.keys(aExpected)[1]));
+ testExpression(exprScope.get(Object.keys(aExpected)[2]));
+ });
+
+ let editTarget = getterOrSetterOrVar.target;
+
+ // Allow the target variable to get painted, so that clicking on
+ // its value would scroll the new textbox node into view.
+ executeSoon(() => {
+ let varValue = editTarget.querySelector(".title > .value");
+ EventUtils.sendMouseEvent({ type: "mousedown" }, varValue, gDebugger);
+
+ let varInput = editTarget.querySelector(".title > .element-value-input");
+ setText(varInput, aString);
+ EventUtils.sendKey("RETURN", gDebugger);
+ });
+
+ return finished;
+ });
+
+ myVar.expand();
+ gVars.clearHierarchy();
+
+ return finished;
+}
+
+function testWatchExpressionsRemoved() {
+ let scope = gVars.getScopeAtIndex(0);
+ ok(scope,
+ "There should be a local scope in the variables view.");
+ isnot(scope.name, gL10N.getStr("watchExpressionsScopeLabel"),
+ "The scope's name should not be marked as 'Watch Expressions'.");
+ isnot(scope._store.size, 0,
+ "There should be some variables available.");
+}
+
+registerCleanupFunction(function () {
+ gTab = null;
+ gPanel = null;
+ gDebugger = null;
+ gL10N = null;
+ gEditor = null;
+ gVars = null;
+ gWatch = null;
+});
diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-edit-getset-02.js b/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-edit-getset-02.js
new file mode 100644
index 000000000..c0455a189
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-edit-getset-02.js
@@ -0,0 +1,107 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Make sure that the variables view is able to override getter properties
+ * to plain value properties.
+ */
+
+const TAB_URL = EXAMPLE_URL + "doc_frame-parameters.html";
+
+var gTab, gPanel, gDebugger;
+var gL10N, gEditor, gVars, gWatch;
+
+function test() {
+ // Debug test slaves are a bit slow at this test.
+ requestLongerTimeout(2);
+
+ let options = {
+ source: TAB_URL,
+ line: 1
+ };
+ initDebugger(TAB_URL, options).then(([aTab,, aPanel]) => {
+ gTab = aTab;
+ gPanel = aPanel;
+ gDebugger = gPanel.panelWin;
+ gL10N = gDebugger.L10N;
+ gEditor = gDebugger.DebuggerView.editor;
+ gVars = gDebugger.DebuggerView.Variables;
+ gWatch = gDebugger.DebuggerView.WatchExpressions;
+
+ gVars.switch = function () {};
+ gVars.delete = function () {};
+
+ waitForCaretAndScopes(gPanel, 24)
+ .then(() => addWatchExpression())
+ .then(() => testEdit("\"xlerb\"", "xlerb"))
+ .then(() => resumeDebuggerThenCloseAndFinish(gPanel))
+ .then(null, aError => {
+ ok(false, "Got an error: " + aError.message + "\n" + aError.stack);
+ });
+
+ generateMouseClickInTab(gTab, "content.document.querySelector('button')");
+ });
+}
+
+function addWatchExpression() {
+ let finished = waitForDebuggerEvents(gPanel, gDebugger.EVENTS.FETCHED_WATCH_EXPRESSIONS);
+
+ gWatch.addExpression("myVar.prop");
+ gEditor.focus();
+
+ return finished;
+}
+
+function testEdit(aString, aExpected) {
+ let localScope = gVars.getScopeAtIndex(1);
+ let myVar = localScope.get("myVar");
+
+ let finished = waitForDebuggerEvents(gPanel, gDebugger.EVENTS.FETCHED_PROPERTIES).then(() => {
+ let finished = waitForDebuggerEvents(gPanel, gDebugger.EVENTS.FETCHED_WATCH_EXPRESSIONS).then(() => {
+ let exprScope = gVars.getScopeAtIndex(0);
+
+ ok(exprScope,
+ "There should be a wach expressions scope in the variables view.");
+ is(exprScope.name, gL10N.getStr("watchExpressionsScopeLabel"),
+ "The scope's name should be marked as 'Watch Expressions'.");
+ is(exprScope._store.size, 1,
+ "There should be one evaluation available.");
+
+ is(exprScope.get("myVar.prop").value, aExpected,
+ "The expression value is correct after the edit.");
+ });
+
+ let editTarget = myVar.get("prop").target;
+
+ // Allow the target variable to get painted, so that clicking on
+ // its value would scroll the new textbox node into view.
+ executeSoon(() => {
+ let varEdit = editTarget.querySelector(".title > .variables-view-edit");
+ EventUtils.sendMouseEvent({ type: "mousedown" }, varEdit, gDebugger);
+
+ let varInput = editTarget.querySelector(".title > .element-value-input");
+ setText(varInput, aString);
+ EventUtils.sendKey("RETURN", gDebugger);
+ });
+
+ return finished;
+ });
+
+ myVar.expand();
+ gVars.clearHierarchy();
+
+ return finished;
+}
+
+registerCleanupFunction(function () {
+ gTab = null;
+ gPanel = null;
+ gDebugger = null;
+ gL10N = null;
+ gEditor = null;
+ gVars = null;
+ gWatch = null;
+});
+
diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-edit-value.js b/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-edit-value.js
new file mode 100644
index 000000000..7fe887152
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-edit-value.js
@@ -0,0 +1,91 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Make sure that the editing variables or properties values works properly.
+ */
+
+const TAB_URL = EXAMPLE_URL + "doc_frame-parameters.html";
+
+var gTab, gPanel, gDebugger;
+var gVars;
+
+function test() {
+ let options = {
+ source: TAB_URL,
+ line: 1
+ };
+ initDebugger(TAB_URL, options).then(([aTab,, aPanel]) => {
+ gTab = aTab;
+ gPanel = aPanel;
+ gDebugger = gPanel.panelWin;
+ gVars = gDebugger.DebuggerView.Variables;
+
+ waitForCaretAndScopes(gPanel, 24)
+ .then(() => initialChecks())
+ .then(() => testModification("a", "1"))
+ .then(() => testModification("{ a: 1 }", "Object"))
+ .then(() => testModification("[a]", "Array[1]"))
+ .then(() => testModification("b", "Object"))
+ .then(() => testModification("b.a", "1"))
+ .then(() => testModification("c.a", "1"))
+ .then(() => testModification("Infinity", "Infinity"))
+ .then(() => testModification("NaN", "NaN"))
+ .then(() => testModification("new Function", "anonymous()"))
+ .then(() => testModification("+0", "0"))
+ .then(() => testModification("-0", "-0"))
+ .then(() => testModification("Object.keys({})", "Array[0]"))
+ .then(() => testModification("document.title", '"Debugger test page"'))
+ .then(() => resumeDebuggerThenCloseAndFinish(gPanel))
+ .then(null, aError => {
+ ok(false, "Got an error: " + aError.message + "\n" + aError.stack);
+ });
+
+ generateMouseClickInTab(gTab, "content.document.querySelector('button')");
+ });
+}
+
+function initialChecks() {
+ let localScope = gVars.getScopeAtIndex(0);
+ let aVar = localScope.get("a");
+
+ is(aVar.target.querySelector(".name").getAttribute("value"), "a",
+ "Should have the right name for 'a'.");
+ is(aVar.target.querySelector(".value").getAttribute("value"), "1",
+ "Should have the right initial value for 'a'.");
+}
+
+function testModification(aNewValue, aNewResult) {
+ let localScope = gVars.getScopeAtIndex(0);
+ let aVar = localScope.get("a");
+
+ // Allow the target variable to get painted, so that clicking on
+ // its value would scroll the new textbox node into view.
+ executeSoon(() => {
+ let varValue = aVar.target.querySelector(".title > .value");
+ EventUtils.sendMouseEvent({ type: "mousedown" }, varValue, gDebugger);
+
+ let varInput = aVar.target.querySelector(".title > .element-value-input");
+ setText(varInput, aNewValue);
+ EventUtils.sendKey("RETURN", gDebugger);
+ });
+
+ return waitForDebuggerEvents(gPanel, gDebugger.EVENTS.FETCHED_SCOPES).then(() => {
+ let localScope = gVars.getScopeAtIndex(0);
+ let aVar = localScope.get("a");
+
+ is(aVar.target.querySelector(".name").getAttribute("value"), "a",
+ "Should have the right name for 'a'.");
+ is(aVar.target.querySelector(".value").getAttribute("value"), aNewResult,
+ "Should have the right new value for 'a'.");
+ });
+}
+
+registerCleanupFunction(function () {
+ gTab = null;
+ gPanel = null;
+ gDebugger = null;
+ gVars = null;
+});
diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-edit-watch.js b/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-edit-watch.js
new file mode 100644
index 000000000..0271f3738
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-edit-watch.js
@@ -0,0 +1,510 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Make sure that the editing or removing watch expressions works properly.
+ */
+
+const TAB_URL = EXAMPLE_URL + "doc_watch-expressions.html";
+
+var gTab, gPanel, gDebugger;
+var gL10N, gEditor, gVars, gWatch;
+
+function test() {
+ let options = {
+ source: TAB_URL,
+ line: 1
+ };
+ initDebugger(TAB_URL, options).then(([aTab,, aPanel]) => {
+ gTab = aTab;
+ gPanel = aPanel;
+ gDebugger = gPanel.panelWin;
+ gL10N = gDebugger.L10N;
+ gEditor = gDebugger.DebuggerView.editor;
+ gVars = gDebugger.DebuggerView.Variables;
+ gWatch = gDebugger.DebuggerView.WatchExpressions;
+
+ promise.all([
+ waitForDebuggerEvents(gPanel, gDebugger.EVENTS.FETCHED_WATCH_EXPRESSIONS),
+ waitForCaretAndScopes(gPanel, 18)])
+ .then(() => testInitialVariablesInScope())
+ .then(() => testInitialExpressionsInScope())
+ .then(() => testModification("document.title = 42", "document.title = 43", "43", "undefined"))
+ .then(() => testIntegrity1())
+ .then(() => testModification("aArg", "aArg = 44", "44", "44"))
+ .then(() => testIntegrity2())
+ .then(() => testModification("aArg = 44", "\ \t\r\ndocument.title\ \t\r\n", "\"43\"", "44"))
+ .then(() => testIntegrity3())
+ .then(() => testModification("document.title = 43", "\ \t\r\ndocument.title\ \t\r\n", "\"43\"", "44"))
+ .then(() => testIntegrity4())
+ .then(() => testModification("document.title", "\ \t\r\n", "\"43\"", "44"))
+ .then(() => testIntegrity5())
+ .then(() => testExprDeletion("this", "44"))
+ .then(() => testIntegrity6())
+ .then(() => testExprFinalDeletion("ermahgerd", "44"))
+ .then(() => testIntegrity7())
+ .then(() => resumeDebuggerThenCloseAndFinish(gPanel))
+ .then(null, aError => {
+ ok(false, "Got an error: " + aError.message + "\n" + aError.stack);
+ });
+
+ addExpressions();
+ callInTab(gTab, "ermahgerd");
+ });
+}
+
+function addExpressions() {
+ addExpression("this");
+ addExpression("ermahgerd");
+ addExpression("aArg");
+ addExpression("document.title");
+ addCmdExpression("document.title = 42");
+
+ is(gWatch.itemCount, 5,
+ "There should be 5 items availalble in the watch expressions view.");
+
+ is(gWatch.getItemAtIndex(4).attachment.initialExpression, "this",
+ "The first expression's initial value should be correct.");
+ is(gWatch.getItemAtIndex(3).attachment.initialExpression, "ermahgerd",
+ "The second expression's initial value should be correct.");
+ is(gWatch.getItemAtIndex(2).attachment.initialExpression, "aArg",
+ "The third expression's initial value should be correct.");
+ is(gWatch.getItemAtIndex(1).attachment.initialExpression, "document.title",
+ "The fourth expression's initial value should be correct.");
+ is(gWatch.getItemAtIndex(0).attachment.initialExpression, "document.title = 42",
+ "The fifth expression's initial value should be correct.");
+
+ is(gWatch.getItemAtIndex(4).attachment.currentExpression, "this",
+ "The first expression's current value should be correct.");
+ is(gWatch.getItemAtIndex(3).attachment.currentExpression, "ermahgerd",
+ "The second expression's current value should be correct.");
+ is(gWatch.getItemAtIndex(2).attachment.currentExpression, "aArg",
+ "The third expression's current value should be correct.");
+ is(gWatch.getItemAtIndex(1).attachment.currentExpression, "document.title",
+ "The fourth expression's current value should be correct.");
+ is(gWatch.getItemAtIndex(0).attachment.currentExpression, "document.title = 42",
+ "The fifth expression's current value should be correct.");
+}
+
+function testInitialVariablesInScope() {
+ let localScope = gVars.getScopeAtIndex(1);
+ let argVar = localScope.get("aArg");
+
+ is(argVar.visible, true,
+ "Should have the right visibility state for 'aArg'.");
+ is(argVar.name, "aArg",
+ "Should have the right name for 'aArg'.");
+ is(argVar.value.type, "undefined",
+ "Should have the right initial value for 'aArg'.");
+}
+
+function testInitialExpressionsInScope() {
+ let exprScope = gVars.getScopeAtIndex(0);
+ let thisExpr = exprScope.get("this");
+ let ermExpr = exprScope.get("ermahgerd");
+ let argExpr = exprScope.get("aArg");
+ let docExpr = exprScope.get("document.title");
+ let docExpr2 = exprScope.get("document.title = 42");
+
+ ok(exprScope,
+ "There should be a wach expressions scope in the variables view.");
+ is(exprScope.name, gL10N.getStr("watchExpressionsScopeLabel"),
+ "The scope's name should be marked as 'Watch Expressions'.");
+ is(exprScope._store.size, 5,
+ "There should be 5 evaluations available.");
+
+ is(thisExpr.visible, true,
+ "Should have the right visibility state for 'this'.");
+ is(thisExpr.target.querySelectorAll(".variables-view-delete").length, 1,
+ "Should have the one close button visible for 'this'.");
+ is(thisExpr.name, "this",
+ "Should have the right name for 'this'.");
+ is(thisExpr.value.type, "object",
+ "Should have the right value type for 'this'.");
+ is(thisExpr.value.class, "Window",
+ "Should have the right value type for 'this'.");
+
+ is(ermExpr.visible, true,
+ "Should have the right visibility state for 'ermahgerd'.");
+ is(ermExpr.target.querySelectorAll(".variables-view-delete").length, 1,
+ "Should have the one close button visible for 'ermahgerd'.");
+ is(ermExpr.name, "ermahgerd",
+ "Should have the right name for 'ermahgerd'.");
+ is(ermExpr.value.type, "object",
+ "Should have the right value type for 'ermahgerd'.");
+ is(ermExpr.value.class, "Function",
+ "Should have the right value type for 'ermahgerd'.");
+
+ is(argExpr.visible, true,
+ "Should have the right visibility state for 'aArg'.");
+ is(argExpr.target.querySelectorAll(".variables-view-delete").length, 1,
+ "Should have the one close button visible for 'aArg'.");
+ is(argExpr.name, "aArg",
+ "Should have the right name for 'aArg'.");
+ is(argExpr.value.type, "undefined",
+ "Should have the right value for 'aArg'.");
+
+ is(docExpr.visible, true,
+ "Should have the right visibility state for 'document.title'.");
+ is(docExpr.target.querySelectorAll(".variables-view-delete").length, 1,
+ "Should have the one close button visible for 'document.title'.");
+ is(docExpr.name, "document.title",
+ "Should have the right name for 'document.title'.");
+ is(docExpr.value, "42",
+ "Should have the right value for 'document.title'.");
+
+ is(docExpr2.visible, true,
+ "Should have the right visibility state for 'document.title = 42'.");
+ is(docExpr2.target.querySelectorAll(".variables-view-delete").length, 1,
+ "Should have the one close button visible for 'document.title = 42'.");
+ is(docExpr2.name, "document.title = 42",
+ "Should have the right name for 'document.title = 42'.");
+ is(docExpr2.value, 42,
+ "Should have the right value for 'document.title = 42'.");
+
+ is(gDebugger.document.querySelectorAll(".dbg-expression[hidden=true]").length, 5,
+ "There should be 5 hidden nodes in the watch expressions container.");
+ is(gDebugger.document.querySelectorAll(".dbg-expression:not([hidden=true])").length, 0,
+ "There should be 0 visible nodes in the watch expressions container.");
+}
+
+function testModification(aName, aNewValue, aNewResult, aArgResult) {
+ let exprScope = gVars.getScopeAtIndex(0);
+ let exprVar = exprScope.get(aName);
+
+ let finished = promise.all([
+ waitForDebuggerEvents(gPanel, gDebugger.EVENTS.FETCHED_SCOPES),
+ waitForDebuggerEvents(gPanel, gDebugger.EVENTS.FETCHED_WATCH_EXPRESSIONS)
+ ])
+ .then(() => {
+ let localScope = gVars.getScopeAtIndex(1);
+ let argVar = localScope.get("aArg");
+
+ is(argVar.visible, true,
+ "Should have the right visibility state for 'aArg'.");
+ is(argVar.target.querySelector(".name").getAttribute("value"), "aArg",
+ "Should have the right name for 'aArg'.");
+ is(argVar.target.querySelector(".value").getAttribute("value"), aArgResult,
+ "Should have the right new value for 'aArg'.");
+
+ let exprScope = gVars.getScopeAtIndex(0);
+ let exprOldVar = exprScope.get(aName);
+ let exprNewVar = exprScope.get(aNewValue.trim());
+
+ if (!aNewValue.trim()) {
+ ok(!exprOldVar,
+ "The old watch expression should have been removed.");
+ ok(!exprNewVar,
+ "No new watch expression should have been added.");
+ } else {
+ ok(!exprOldVar,
+ "The old watch expression should have been removed.");
+ ok(exprNewVar,
+ "The new watch expression should have been added.");
+
+ is(exprNewVar.visible, true,
+ "Should have the right visibility state for the watch expression.");
+ is(exprNewVar.target.querySelector(".name").getAttribute("value"), aNewValue.trim(),
+ "Should have the right name for the watch expression.");
+ is(exprNewVar.target.querySelector(".value").getAttribute("value"), aNewResult,
+ "Should have the right new value for the watch expression.");
+ }
+ });
+
+ let varValue = exprVar.target.querySelector(".title > .name");
+ EventUtils.sendMouseEvent({ type: "dblclick" }, varValue, gDebugger);
+
+ let varInput = exprVar.target.querySelector(".title > .element-name-input");
+ setText(varInput, aNewValue);
+ EventUtils.sendKey("RETURN", gDebugger);
+
+ return finished;
+}
+
+function testExprDeletion(aName, aArgResult) {
+ let exprScope = gVars.getScopeAtIndex(0);
+ let exprVar = exprScope.get(aName);
+
+ let finished = promise.all([
+ waitForDebuggerEvents(gPanel, gDebugger.EVENTS.FETCHED_SCOPES),
+ waitForDebuggerEvents(gPanel, gDebugger.EVENTS.FETCHED_WATCH_EXPRESSIONS)
+ ])
+ .then(() => {
+ let localScope = gVars.getScopeAtIndex(1);
+ let argVar = localScope.get("aArg");
+
+ is(argVar.visible, true,
+ "Should have the right visibility state for 'aArg'.");
+ is(argVar.target.querySelector(".name").getAttribute("value"), "aArg",
+ "Should have the right name for 'aArg'.");
+ is(argVar.target.querySelector(".value").getAttribute("value"), aArgResult,
+ "Should have the right new value for 'aArg'.");
+
+ let exprScope = gVars.getScopeAtIndex(0);
+ let exprOldVar = exprScope.get(aName);
+
+ ok(!exprOldVar,
+ "The watch expression should have been deleted.");
+ });
+
+ let varDelete = exprVar.target.querySelector(".variables-view-delete");
+ EventUtils.sendMouseEvent({ type: "click" }, varDelete, gDebugger);
+
+ return finished;
+}
+
+function testExprFinalDeletion(aName, aArgResult) {
+ let exprScope = gVars.getScopeAtIndex(0);
+ let exprVar = exprScope.get(aName);
+
+ let finished = waitForDebuggerEvents(gPanel, gDebugger.EVENTS.FETCHED_SCOPES).then(() => {
+ let localScope = gVars.getScopeAtIndex(0);
+ let argVar = localScope.get("aArg");
+
+ is(argVar.visible, true,
+ "Should have the right visibility state for 'aArg'.");
+ is(argVar.target.querySelector(".name").getAttribute("value"), "aArg",
+ "Should have the right name for 'aArg'.");
+ is(argVar.target.querySelector(".value").getAttribute("value"), aArgResult,
+ "Should have the right new value for 'aArg'.");
+
+ let exprScope = gVars.getScopeAtIndex(0);
+ let exprOldVar = exprScope.get(aName);
+
+ ok(!exprOldVar,
+ "The watch expression should have been deleted.");
+ });
+
+ let varDelete = exprVar.target.querySelector(".variables-view-delete");
+ EventUtils.sendMouseEvent({ type: "click" }, varDelete, gDebugger);
+
+ return finished;
+}
+
+function testIntegrity1() {
+ is(gDebugger.document.querySelectorAll(".dbg-expression[hidden=true]").length, 5,
+ "There should be 5 hidden nodes in the watch expressions container.");
+ is(gDebugger.document.querySelectorAll(".dbg-expression:not([hidden=true])").length, 0,
+ "There should be 0 visible nodes in the watch expressions container.");
+
+ let exprScope = gVars.getScopeAtIndex(0);
+ ok(exprScope,
+ "There should be a wach expressions scope in the variables view.");
+ is(exprScope.name, gL10N.getStr("watchExpressionsScopeLabel"),
+ "The scope's name should be marked as 'Watch Expressions'.");
+ is(exprScope._store.size, 5,
+ "There should be 5 visible evaluations available.");
+
+ is(gWatch.itemCount, 5,
+ "There should be 5 hidden expression input available.");
+ is(gWatch.getItemAtIndex(0).attachment.view.inputNode.value, "document.title = 43",
+ "The first textbox input value is not the correct one.");
+ is(gWatch.getItemAtIndex(0).attachment.currentExpression, "document.title = 43",
+ "The first textbox input value is not the correct one.");
+ is(gWatch.getItemAtIndex(1).attachment.view.inputNode.value, "document.title",
+ "The second textbox input value is not the correct one.");
+ is(gWatch.getItemAtIndex(1).attachment.currentExpression, "document.title",
+ "The second textbox input value is not the correct one.");
+ is(gWatch.getItemAtIndex(2).attachment.view.inputNode.value, "aArg",
+ "The third textbox input value is not the correct one.");
+ is(gWatch.getItemAtIndex(2).attachment.currentExpression, "aArg",
+ "The third textbox input value is not the correct one.");
+ is(gWatch.getItemAtIndex(3).attachment.view.inputNode.value, "ermahgerd",
+ "The fourth textbox input value is not the correct one.");
+ is(gWatch.getItemAtIndex(3).attachment.currentExpression, "ermahgerd",
+ "The fourth textbox input value is not the correct one.");
+ is(gWatch.getItemAtIndex(4).attachment.view.inputNode.value, "this",
+ "The fifth textbox input value is not the correct one.");
+ is(gWatch.getItemAtIndex(4).attachment.currentExpression, "this",
+ "The fifth textbox input value is not the correct one.");
+}
+
+function testIntegrity2() {
+ is(gDebugger.document.querySelectorAll(".dbg-expression[hidden=true]").length, 5,
+ "There should be 5 hidden nodes in the watch expressions container.");
+ is(gDebugger.document.querySelectorAll(".dbg-expression:not([hidden=true])").length, 0,
+ "There should be 0 visible nodes in the watch expressions container.");
+
+ let exprScope = gVars.getScopeAtIndex(0);
+ ok(exprScope,
+ "There should be a wach expressions scope in the variables view.");
+ is(exprScope.name, gL10N.getStr("watchExpressionsScopeLabel"),
+ "The scope's name should be marked as 'Watch Expressions'.");
+ is(exprScope._store.size, 5,
+ "There should be 5 visible evaluations available.");
+
+ is(gWatch.itemCount, 5,
+ "There should be 5 hidden expression input available.");
+ is(gWatch.getItemAtIndex(0).attachment.view.inputNode.value, "document.title = 43",
+ "The first textbox input value is not the correct one.");
+ is(gWatch.getItemAtIndex(0).attachment.currentExpression, "document.title = 43",
+ "The first textbox input value is not the correct one.");
+ is(gWatch.getItemAtIndex(1).attachment.view.inputNode.value, "document.title",
+ "The second textbox input value is not the correct one.");
+ is(gWatch.getItemAtIndex(1).attachment.currentExpression, "document.title",
+ "The second textbox input value is not the correct one.");
+ is(gWatch.getItemAtIndex(2).attachment.view.inputNode.value, "aArg = 44",
+ "The third textbox input value is not the correct one.");
+ is(gWatch.getItemAtIndex(2).attachment.currentExpression, "aArg = 44",
+ "The third textbox input value is not the correct one.");
+ is(gWatch.getItemAtIndex(3).attachment.view.inputNode.value, "ermahgerd",
+ "The fourth textbox input value is not the correct one.");
+ is(gWatch.getItemAtIndex(3).attachment.currentExpression, "ermahgerd",
+ "The fourth textbox input value is not the correct one.");
+ is(gWatch.getItemAtIndex(4).attachment.view.inputNode.value, "this",
+ "The fifth textbox input value is not the correct one.");
+ is(gWatch.getItemAtIndex(4).attachment.currentExpression, "this",
+ "The fifth textbox input value is not the correct one.");
+}
+
+function testIntegrity3() {
+ is(gDebugger.document.querySelectorAll(".dbg-expression[hidden=true]").length, 4,
+ "There should be 4 hidden nodes in the watch expressions container.");
+ is(gDebugger.document.querySelectorAll(".dbg-expression:not([hidden=true])").length, 0,
+ "There should be 0 visible nodes in the watch expressions container.");
+
+ let exprScope = gVars.getScopeAtIndex(0);
+ ok(exprScope,
+ "There should be a wach expressions scope in the variables view.");
+ is(exprScope.name, gL10N.getStr("watchExpressionsScopeLabel"),
+ "The scope's name should be marked as 'Watch Expressions'.");
+ is(exprScope._store.size, 4,
+ "There should be 4 visible evaluations available.");
+
+ is(gWatch.itemCount, 4,
+ "There should be 4 hidden expression input available.");
+ is(gWatch.getItemAtIndex(0).attachment.view.inputNode.value, "document.title = 43",
+ "The first textbox input value is not the correct one.");
+ is(gWatch.getItemAtIndex(0).attachment.currentExpression, "document.title = 43",
+ "The first textbox input value is not the correct one.");
+ is(gWatch.getItemAtIndex(1).attachment.view.inputNode.value, "document.title",
+ "The second textbox input value is not the correct one.");
+ is(gWatch.getItemAtIndex(1).attachment.currentExpression, "document.title",
+ "The second textbox input value is not the correct one.");
+ is(gWatch.getItemAtIndex(2).attachment.view.inputNode.value, "ermahgerd",
+ "The third textbox input value is not the correct one.");
+ is(gWatch.getItemAtIndex(2).attachment.currentExpression, "ermahgerd",
+ "The third textbox input value is not the correct one.");
+ is(gWatch.getItemAtIndex(3).attachment.view.inputNode.value, "this",
+ "The fourth textbox input value is not the correct one.");
+ is(gWatch.getItemAtIndex(3).attachment.currentExpression, "this",
+ "The fourth textbox input value is not the correct one.");
+}
+
+function testIntegrity4() {
+ is(gDebugger.document.querySelectorAll(".dbg-expression[hidden=true]").length, 3,
+ "There should be 3 hidden nodes in the watch expressions container.");
+ is(gDebugger.document.querySelectorAll(".dbg-expression:not([hidden=true])").length, 0,
+ "There should be 0 visible nodes in the watch expressions container.");
+
+ let exprScope = gVars.getScopeAtIndex(0);
+ ok(exprScope,
+ "There should be a wach expressions scope in the variables view.");
+ is(exprScope.name, gL10N.getStr("watchExpressionsScopeLabel"),
+ "The scope's name should be marked as 'Watch Expressions'.");
+ is(exprScope._store.size, 3,
+ "There should be 3 visible evaluations available.");
+
+ is(gWatch.itemCount, 3,
+ "There should be 3 hidden expression input available.");
+ is(gWatch.getItemAtIndex(0).attachment.view.inputNode.value, "document.title",
+ "The first textbox input value is not the correct one.");
+ is(gWatch.getItemAtIndex(0).attachment.currentExpression, "document.title",
+ "The first textbox input value is not the correct one.");
+ is(gWatch.getItemAtIndex(1).attachment.view.inputNode.value, "ermahgerd",
+ "The second textbox input value is not the correct one.");
+ is(gWatch.getItemAtIndex(1).attachment.currentExpression, "ermahgerd",
+ "The second textbox input value is not the correct one.");
+ is(gWatch.getItemAtIndex(2).attachment.view.inputNode.value, "this",
+ "The third textbox input value is not the correct one.");
+ is(gWatch.getItemAtIndex(2).attachment.currentExpression, "this",
+ "The third textbox input value is not the correct one.");
+}
+
+function testIntegrity5() {
+ is(gDebugger.document.querySelectorAll(".dbg-expression[hidden=true]").length, 2,
+ "There should be 2 hidden nodes in the watch expressions container.");
+ is(gDebugger.document.querySelectorAll(".dbg-expression:not([hidden=true])").length, 0,
+ "There should be 0 visible nodes in the watch expressions container.");
+
+ let exprScope = gVars.getScopeAtIndex(0);
+ ok(exprScope,
+ "There should be a wach expressions scope in the variables view.");
+ is(exprScope.name, gL10N.getStr("watchExpressionsScopeLabel"),
+ "The scope's name should be marked as 'Watch Expressions'.");
+ is(exprScope._store.size, 2,
+ "There should be 2 visible evaluations available.");
+
+ is(gWatch.itemCount, 2,
+ "There should be 2 hidden expression input available.");
+ is(gWatch.getItemAtIndex(0).attachment.view.inputNode.value, "ermahgerd",
+ "The first textbox input value is not the correct one.");
+ is(gWatch.getItemAtIndex(0).attachment.currentExpression, "ermahgerd",
+ "The first textbox input value is not the correct one.");
+ is(gWatch.getItemAtIndex(1).attachment.view.inputNode.value, "this",
+ "The second textbox input value is not the correct one.");
+ is(gWatch.getItemAtIndex(1).attachment.currentExpression, "this",
+ "The second textbox input value is not the correct one.");
+}
+
+function testIntegrity6() {
+ is(gDebugger.document.querySelectorAll(".dbg-expression[hidden=true]").length, 1,
+ "There should be 1 hidden nodes in the watch expressions container.");
+ is(gDebugger.document.querySelectorAll(".dbg-expression:not([hidden=true])").length, 0,
+ "There should be 0 visible nodes in the watch expressions container.");
+
+ let exprScope = gVars.getScopeAtIndex(0);
+ ok(exprScope,
+ "There should be a wach expressions scope in the variables view.");
+ is(exprScope.name, gL10N.getStr("watchExpressionsScopeLabel"),
+ "The scope's name should be marked as 'Watch Expressions'.");
+ is(exprScope._store.size, 1,
+ "There should be 1 visible evaluation available.");
+
+ is(gWatch.itemCount, 1,
+ "There should be 1 hidden expression input available.");
+ is(gWatch.getItemAtIndex(0).attachment.view.inputNode.value, "ermahgerd",
+ "The first textbox input value is not the correct one.");
+ is(gWatch.getItemAtIndex(0).attachment.currentExpression, "ermahgerd",
+ "The first textbox input value is not the correct one.");
+}
+
+function testIntegrity7() {
+ is(gDebugger.document.querySelectorAll(".dbg-expression[hidden=true]").length, 0,
+ "There should be 0 hidden nodes in the watch expressions container.");
+ is(gDebugger.document.querySelectorAll(".dbg-expression:not([hidden=true])").length, 0,
+ "There should be 0 visible nodes in the watch expressions container.");
+
+ let localScope = gVars.getScopeAtIndex(0);
+ ok(localScope,
+ "There should be a local scope in the variables view.");
+ isnot(localScope.name, gL10N.getStr("watchExpressionsScopeLabel"),
+ "The scope's name should not be marked as 'Watch Expressions'.");
+ isnot(localScope._store.size, 0,
+ "There should be some variables available.");
+
+ is(gWatch.itemCount, 0,
+ "The watch expressions container should be empty.");
+}
+
+function addExpression(aString) {
+ gWatch.addExpression(aString);
+ gEditor.focus();
+}
+
+function addCmdExpression(aString) {
+ gWatch._onCmdAddExpression(aString);
+ gEditor.focus();
+}
+
+registerCleanupFunction(function () {
+ gTab = null;
+ gPanel = null;
+ gDebugger = null;
+ gL10N = null;
+ gEditor = null;
+ gVars = null;
+ gWatch = null;
+});
diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-filter-01.js b/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-filter-01.js
new file mode 100644
index 000000000..f2c142c85
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-filter-01.js
@@ -0,0 +1,241 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Make sure that the variables view correctly filters nodes by name.
+ */
+
+const TAB_URL = EXAMPLE_URL + "doc_with-frame.html";
+
+var gTab, gPanel, gDebugger;
+var gVariables, gSearchBox;
+
+function test() {
+ // Debug test slaves are quite slow at this test.
+ requestLongerTimeout(4);
+
+ let options = {
+ source: TAB_URL,
+ line: 1
+ };
+ initDebugger(TAB_URL, options).then(([aTab,, aPanel]) => {
+ gTab = aTab;
+ gPanel = aPanel;
+ gDebugger = gPanel.panelWin;
+ gVariables = gDebugger.DebuggerView.Variables;
+
+ gVariables._enableSearch();
+ gSearchBox = gVariables._searchboxNode;
+
+ // The first 'with' scope should be expanded by default, but the
+ // variables haven't been fetched yet. This is how 'with' scopes work.
+ promise.all([
+ waitForCaretAndScopes(gPanel, 22),
+ waitForDebuggerEvents(gPanel, gDebugger.EVENTS.FETCHED_VARIABLES)
+ ]).then(prepareVariablesAndProperties)
+ .then(testVariablesAndPropertiesFiltering)
+ .then(() => resumeDebuggerThenCloseAndFinish(gPanel))
+ .then(null, aError => {
+ ok(false, "Got an error: " + aError.message + "\n" + aError.stack);
+ });
+
+ generateMouseClickInTab(gTab, "content.document.querySelector('button')");
+ });
+}
+
+function testVariablesAndPropertiesFiltering() {
+ let localScope = gVariables.getScopeAtIndex(0);
+ let withScope = gVariables.getScopeAtIndex(1);
+ let functionScope = gVariables.getScopeAtIndex(2);
+ let globalLexicalScope = gVariables.getScopeAtIndex(3);
+ let globalScope = gVariables.getScopeAtIndex(4);
+ let protoVar = localScope.get("__proto__");
+ let constrVar = protoVar.get("constructor");
+ let proto2Var = constrVar.get("__proto__");
+ let constr2Var = proto2Var.get("constructor");
+
+ function testFiltered() {
+ is(localScope.expanded, true,
+ "The localScope should be expanded.");
+ is(withScope.expanded, true,
+ "The withScope should be expanded.");
+ is(functionScope.expanded, true,
+ "The functionScope should be expanded.");
+ is(globalLexicalScope.expanded, true,
+ "The globalLexicalScope should be expanded.");
+ is(globalScope.expanded, true,
+ "The globalScope should be expanded.");
+
+ is(protoVar.expanded, true,
+ "The protoVar should be expanded.");
+ is(constrVar.expanded, true,
+ "The constrVar should be expanded.");
+ is(proto2Var.expanded, true,
+ "The proto2Var should be expanded.");
+ is(constr2Var.expanded, true,
+ "The constr2Var should be expanded.");
+
+ is(localScope.target.querySelectorAll(".variables-view-variable:not([unmatched])").length, 1,
+ "There should be 1 variable displayed in the local scope.");
+ is(withScope.target.querySelectorAll(".variables-view-variable:not([unmatched])").length, 0,
+ "There should be 0 variables displayed in the with scope.");
+ is(functionScope.target.querySelectorAll(".variables-view-variable:not([unmatched])").length, 0,
+ "There should be 0 variables displayed in the function scope.");
+ is(globalLexicalScope.target.querySelectorAll(".variables-view-variable:not([unmatched])").length, 0,
+ "There should be 0 variables displayed in the global lexical scope.");
+ is(globalScope.target.querySelectorAll(".variables-view-variable:not([unmatched])").length, 0,
+ "There should be 0 variables displayed in the global scope.");
+
+ is(withScope.target.querySelectorAll(".variables-view-property:not([unmatched])").length, 0,
+ "There should be 0 properties displayed in the with scope.");
+ is(functionScope.target.querySelectorAll(".variables-view-property:not([unmatched])").length, 0,
+ "There should be 0 properties displayed in the function scope.");
+ is(globalLexicalScope.target.querySelectorAll(".variables-view-property:not([unmatched])").length, 0,
+ "There should be 0 properties displayed in the global lexical scope.");
+ is(globalScope.target.querySelectorAll(".variables-view-property:not([unmatched])").length, 0,
+ "There should be 0 properties displayed in the global scope.");
+
+ is(localScope.target.querySelectorAll(".variables-view-variable:not([unmatched]) > .title > .name")[0].getAttribute("value"),
+ "__proto__", "The only inner variable displayed should be '__proto__'");
+ is(localScope.target.querySelectorAll(".variables-view-property:not([unmatched]) > .title > .name")[0].getAttribute("value"),
+ "constructor", "The first inner property displayed should be 'constructor'");
+ is(localScope.target.querySelectorAll(".variables-view-property:not([unmatched]) > .title > .name")[1].getAttribute("value"),
+ "__proto__", "The second inner property displayed should be '__proto__'");
+ is(localScope.target.querySelectorAll(".variables-view-property:not([unmatched]) > .title > .name")[2].getAttribute("value"),
+ "constructor", "The third inner property displayed should be 'constructor'");
+ }
+
+ function firstFilter() {
+ let expanded = once(gVariables, "fetched");
+ typeText(gSearchBox, "constructor");
+ gSearchBox.doCommand();
+ return expanded.then(testFiltered);
+ }
+
+ function secondFilter() {
+ localScope.collapse();
+ withScope.collapse();
+ functionScope.collapse();
+ globalLexicalScope.collapse();
+ globalScope.collapse();
+ protoVar.collapse();
+ constrVar.collapse();
+ proto2Var.collapse();
+ constr2Var.collapse();
+
+ is(localScope.expanded, false,
+ "The localScope should not be expanded.");
+ is(withScope.expanded, false,
+ "The withScope should not be expanded.");
+ is(functionScope.expanded, false,
+ "The functionScope should not be expanded.");
+ is(globalLexicalScope.expanded, false,
+ "The globalLexicalScope should not be expanded.");
+ is(globalScope.expanded, false,
+ "The globalScope should not be expanded.");
+
+ is(protoVar.expanded, false,
+ "The protoVar should not be expanded.");
+ is(constrVar.expanded, false,
+ "The constrVar should not be expanded.");
+ is(proto2Var.expanded, false,
+ "The proto2Var should not be expanded.");
+ is(constr2Var.expanded, false,
+ "The constr2Var should not be expanded.");
+
+ let expanded = once(gVariables, "fetched");
+ clearText(gSearchBox);
+ typeText(gSearchBox, "constructor");
+ expanded.then(testFiltered);
+ }
+
+ firstFilter().then(secondFilter);
+}
+
+function prepareVariablesAndProperties() {
+ let deferred = promise.defer();
+
+ let localScope = gVariables.getScopeAtIndex(0);
+ let withScope = gVariables.getScopeAtIndex(1);
+ let functionScope = gVariables.getScopeAtIndex(2);
+ let globalLexicalScope = gVariables.getScopeAtIndex(3);
+ let globalScope = gVariables.getScopeAtIndex(4);
+
+ is(localScope.expanded, true,
+ "The localScope should be expanded.");
+ is(withScope.expanded, false,
+ "The withScope should not be expanded yet.");
+ is(functionScope.expanded, false,
+ "The functionScope should not be expanded yet.");
+ is(globalLexicalScope.expanded, false,
+ "The globalLexicalScope should not be expanded yet.");
+ is(globalScope.expanded, false,
+ "The globalScope should not be expanded yet.");
+
+ // Wait for only two events to be triggered, because the Function scope is
+ // an environment to which scope arguments and variables are already attached.
+ waitForDebuggerEvents(gPanel, gDebugger.EVENTS.FETCHED_VARIABLES, 2).then(() => {
+ is(localScope.expanded, true,
+ "The localScope should now be expanded.");
+ is(withScope.expanded, true,
+ "The withScope should now be expanded.");
+ is(functionScope.expanded, true,
+ "The functionScope should now be expanded.");
+ is(globalLexicalScope.expanded, true,
+ "The globalLexicalScope should be expanded.");
+ is(globalScope.expanded, true,
+ "The globalScope should now be expanded.");
+
+ let protoVar = localScope.get("__proto__");
+
+ waitForDebuggerEvents(gPanel, gDebugger.EVENTS.FETCHED_PROPERTIES, 1).then(() => {
+ let constrVar = protoVar.get("constructor");
+
+ waitForDebuggerEvents(gPanel, gDebugger.EVENTS.FETCHED_PROPERTIES, 1).then(() => {
+ let proto2Var = constrVar.get("__proto__");
+
+ waitForDebuggerEvents(gPanel, gDebugger.EVENTS.FETCHED_PROPERTIES, 1).then(() => {
+ let constr2Var = proto2Var.get("constructor");
+
+ waitForDebuggerEvents(gPanel, gDebugger.EVENTS.FETCHED_PROPERTIES, 1).then(() => {
+ is(protoVar.expanded, true,
+ "The local scope '__proto__' should be expanded.");
+ is(constrVar.expanded, true,
+ "The local scope '__proto__.constructor' should be expanded.");
+ is(proto2Var.expanded, true,
+ "The local scope '__proto__.constructor.__proto__' should be expanded.");
+ is(constr2Var.expanded, true,
+ "The local scope '__proto__.constructor.__proto__.constructor' should be expanded.");
+
+ deferred.resolve();
+ });
+
+ constr2Var.expand();
+ });
+
+ proto2Var.expand();
+ });
+
+ constrVar.expand();
+ });
+
+ protoVar.expand();
+ });
+
+ withScope.expand();
+ functionScope.expand();
+ globalLexicalScope.expand();
+ globalScope.expand();
+
+ return deferred.promise;
+}
+
+registerCleanupFunction(function () {
+ gTab = null;
+ gPanel = null;
+ gDebugger = null;
+ gVariables = null;
+ gSearchBox = null;
+});
diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-filter-02.js b/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-filter-02.js
new file mode 100644
index 000000000..967deb3a5
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-filter-02.js
@@ -0,0 +1,249 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Make sure that the variables view correctly filters nodes by value.
+ */
+
+const TAB_URL = EXAMPLE_URL + "doc_with-frame.html";
+
+var gTab, gPanel, gDebugger;
+var gVariables, gSearchBox;
+
+function test() {
+ // Debug test slaves are quite slow at this test.
+ requestLongerTimeout(4);
+
+ let options = {
+ source: TAB_URL,
+ line: 1
+ };
+ initDebugger(TAB_URL, options).then(([aTab,, aPanel]) => {
+ gTab = aTab;
+ gPanel = aPanel;
+ gDebugger = gPanel.panelWin;
+ gVariables = gDebugger.DebuggerView.Variables;
+
+ gVariables._enableSearch();
+ gSearchBox = gVariables._searchboxNode;
+
+ // The first 'with' scope should be expanded by default, but the
+ // variables haven't been fetched yet. This is how 'with' scopes work.
+ promise.all([
+ waitForCaretAndScopes(gPanel, 22),
+ waitForDebuggerEvents(gPanel, gDebugger.EVENTS.FETCHED_VARIABLES)
+ ]).then(prepareVariablesAndProperties)
+ .then(testVariablesAndPropertiesFiltering)
+ .then(() => resumeDebuggerThenCloseAndFinish(gPanel))
+ .then(null, aError => {
+ ok(false, "Got an error: " + aError.message + "\n" + aError.stack);
+ });
+
+ generateMouseClickInTab(gTab, "content.document.querySelector('button')");
+ });
+}
+
+function testVariablesAndPropertiesFiltering() {
+ let localScope = gVariables.getScopeAtIndex(0);
+ let withScope = gVariables.getScopeAtIndex(1);
+ let functionScope = gVariables.getScopeAtIndex(2);
+ let globalLexicalScope = gVariables.getScopeAtIndex(3);
+ let globalScope = gVariables.getScopeAtIndex(4);
+ let protoVar = localScope.get("__proto__");
+ let constrVar = protoVar.get("constructor");
+ let proto2Var = constrVar.get("__proto__");
+ let constr2Var = proto2Var.get("constructor");
+
+ function testFiltered() {
+ is(localScope.expanded, true,
+ "The localScope should be expanded.");
+ is(withScope.expanded, true,
+ "The withScope should be expanded.");
+ is(functionScope.expanded, true,
+ "The functionScope should be expanded.");
+ is(globalLexicalScope.expanded, true,
+ "The globalScope should be expanded.");
+ is(globalScope.expanded, true,
+ "The globalScope should be expanded.");
+
+ is(protoVar.expanded, true,
+ "The protoVar should be expanded.");
+ is(constrVar.expanded, true,
+ "The constrVar should be expanded.");
+ is(proto2Var.expanded, true,
+ "The proto2Var should be expanded.");
+ is(constr2Var.expanded, true,
+ "The constr2Var should be expanded.");
+
+ is(localScope.target.querySelectorAll(".variables-view-variable:not([unmatched])").length, 1,
+ "There should be 1 variable displayed in the local scope.");
+ is(withScope.target.querySelectorAll(".variables-view-variable:not([unmatched])").length, 0,
+ "There should be 0 variables displayed in the with scope.");
+ is(functionScope.target.querySelectorAll(".variables-view-variable:not([unmatched])").length, 0,
+ "There should be 0 variables displayed in the function scope.");
+ is(globalLexicalScope.target.querySelectorAll(".variables-view-variable:not([unmatched])").length, 0,
+ "There should be no variables displayed in the global lexical scope.");
+ is(globalScope.target.querySelectorAll(".variables-view-variable:not([unmatched])").length, 0,
+ "There should be no variables displayed in the global scope.");
+
+ is(localScope.target.querySelectorAll(".variables-view-property:not([unmatched])").length, 4,
+ "There should be 4 properties displayed in the local scope.");
+ is(withScope.target.querySelectorAll(".variables-view-property:not([unmatched])").length, 0,
+ "There should be 0 properties displayed in the with scope.");
+ is(functionScope.target.querySelectorAll(".variables-view-property:not([unmatched])").length, 0,
+ "There should be 0 properties displayed in the function scope.");
+ is(globalLexicalScope.target.querySelectorAll(".variables-view-property:not([unmatched])").length, 0,
+ "There should be 0 properties displayed in the global lexical scope.");
+ is(globalScope.target.querySelectorAll(".variables-view-property:not([unmatched])").length, 0,
+ "There should be 0 properties displayed in the global scope.");
+
+ is(localScope.target.querySelectorAll(".variables-view-variable:not([unmatched]) > .title > .name")[0].getAttribute("value"),
+ "__proto__", "The only inner variable displayed should be '__proto__'");
+ is(localScope.target.querySelectorAll(".variables-view-property:not([unmatched]) > .title > .name")[0].getAttribute("value"),
+ "constructor", "The first inner property displayed should be 'constructor'");
+ is(localScope.target.querySelectorAll(".variables-view-property:not([unmatched]) > .title > .name")[1].getAttribute("value"),
+ "__proto__", "The second inner property displayed should be '__proto__'");
+ is(localScope.target.querySelectorAll(".variables-view-property:not([unmatched]) > .title > .name")[2].getAttribute("value"),
+ "constructor", "The third inner property displayed should be 'constructor'");
+
+ is(localScope.target.querySelectorAll(".variables-view-property:not([unmatched]) > .title > .name")[3].getAttribute("value"),
+ "name", "The fourth inner property displayed should be 'name'");
+ is(localScope.target.querySelectorAll(".variables-view-property:not([unmatched]) > .title > .value")[3].getAttribute("value"),
+ "\"Function\"", "The fourth inner property displayed should be '\"Function\"'");
+ }
+
+ function firstFilter() {
+ let expanded = once(gVariables, "fetched");
+ typeText(gSearchBox, "\"Function\"");
+ gSearchBox.doCommand();
+ return expanded.then(testFiltered);
+ }
+
+ function secondFilter() {
+ localScope.collapse();
+ withScope.collapse();
+ functionScope.collapse();
+ globalLexicalScope.collapse();
+ globalScope.collapse();
+ protoVar.collapse();
+ constrVar.collapse();
+ proto2Var.collapse();
+ constr2Var.collapse();
+
+ is(localScope.expanded, false,
+ "The localScope should not be expanded.");
+ is(withScope.expanded, false,
+ "The withScope should not be expanded.");
+ is(functionScope.expanded, false,
+ "The functionScope should not be expanded.");
+ is(globalLexicalScope.expanded, false,
+ "The globalScope should not be expanded.");
+ is(globalScope.expanded, false,
+ "The globalScope should not be expanded.");
+
+ is(protoVar.expanded, false,
+ "The protoVar should not be expanded.");
+ is(constrVar.expanded, false,
+ "The constrVar should not be expanded.");
+ is(proto2Var.expanded, false,
+ "The proto2Var should not be expanded.");
+ is(constr2Var.expanded, false,
+ "The constr2Var should not be expanded.");
+
+ backspaceText(gSearchBox, 10);
+ let expanded = once(gVariables, "fetched");
+ typeText(gSearchBox, "\"Function\"");
+ gSearchBox.doCommand();
+ expanded.then(testFiltered);
+ }
+
+ firstFilter().then(secondFilter);
+}
+
+function prepareVariablesAndProperties() {
+ let deferred = promise.defer();
+
+ let localScope = gVariables.getScopeAtIndex(0);
+ let withScope = gVariables.getScopeAtIndex(1);
+ let functionScope = gVariables.getScopeAtIndex(2);
+ let globalLexicalScope = gVariables.getScopeAtIndex(3);
+ let globalScope = gVariables.getScopeAtIndex(4);
+
+ is(localScope.expanded, true,
+ "The localScope should be expanded.");
+ is(withScope.expanded, false,
+ "The withScope should not be expanded yet.");
+ is(functionScope.expanded, false,
+ "The functionScope should not be expanded yet.");
+ is(globalLexicalScope.expanded, false,
+ "The globalScope should not be expanded yet.");
+ is(globalScope.expanded, false,
+ "The globalScope should not be expanded yet.");
+
+ // Wait for only two events to be triggered, because the Function scope is
+ // an environment to which scope arguments and variables are already attached.
+ waitForDebuggerEvents(gPanel, gDebugger.EVENTS.FETCHED_VARIABLES, 2).then(() => {
+ is(localScope.expanded, true,
+ "The localScope should now be expanded.");
+ is(withScope.expanded, true,
+ "The withScope should now be expanded.");
+ is(functionScope.expanded, true,
+ "The functionScope should now be expanded.");
+ is(globalLexicalScope.expanded, true,
+ "The globalScope should now be expanded.");
+ is(globalScope.expanded, true,
+ "The globalScope should now be expanded.");
+
+ let protoVar = localScope.get("__proto__");
+
+ waitForDebuggerEvents(gPanel, gDebugger.EVENTS.FETCHED_PROPERTIES, 1).then(() => {
+ let constrVar = protoVar.get("constructor");
+
+ waitForDebuggerEvents(gPanel, gDebugger.EVENTS.FETCHED_PROPERTIES, 1).then(() => {
+ let proto2Var = constrVar.get("__proto__");
+
+ waitForDebuggerEvents(gPanel, gDebugger.EVENTS.FETCHED_PROPERTIES, 1).then(() => {
+ let constr2Var = proto2Var.get("constructor");
+
+ waitForDebuggerEvents(gPanel, gDebugger.EVENTS.FETCHED_PROPERTIES, 1).then(() => {
+ is(protoVar.expanded, true,
+ "The local scope '__proto__' should be expanded.");
+ is(constrVar.expanded, true,
+ "The local scope '__proto__.constructor' should be expanded.");
+ is(proto2Var.expanded, true,
+ "The local scope '__proto__.constructor.__proto__' should be expanded.");
+ is(constr2Var.expanded, true,
+ "The local scope '__proto__.constructor.__proto__.constructor' should be expanded.");
+
+ deferred.resolve();
+ });
+
+ constr2Var.expand();
+ });
+
+ proto2Var.expand();
+ });
+
+ constrVar.expand();
+ });
+
+ protoVar.expand();
+ });
+
+ withScope.expand();
+ functionScope.expand();
+ globalLexicalScope.expand();
+ globalScope.expand();
+
+ return deferred.promise;
+}
+
+registerCleanupFunction(function () {
+ gTab = null;
+ gPanel = null;
+ gDebugger = null;
+ gVariables = null;
+ gSearchBox = null;
+});
diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-filter-03.js b/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-filter-03.js
new file mode 100644
index 000000000..cd4927e0f
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-filter-03.js
@@ -0,0 +1,178 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Make sure that the variables view correctly filters nodes when triggered
+ * from the debugger's searchbox via an operator.
+ */
+
+const TAB_URL = EXAMPLE_URL + "doc_with-frame.html";
+
+var gTab, gPanel, gDebugger;
+var gVariables, gSearchBox;
+
+function test() {
+ // Debug test slaves are a bit slow at this test.
+ requestLongerTimeout(2);
+
+ let options = {
+ source: TAB_URL,
+ line: 1
+ };
+ initDebugger(TAB_URL, options).then(([aTab,, aPanel]) => {
+ gTab = aTab;
+ gPanel = aPanel;
+ gDebugger = gPanel.panelWin;
+ gVariables = gDebugger.DebuggerView.Variables;
+ gSearchBox = gDebugger.DebuggerView.Filtering._searchbox;
+
+ // The first 'with' scope should be expanded by default, but the
+ // variables haven't been fetched yet. This is how 'with' scopes work.
+ promise.all([
+ waitForCaretAndScopes(gPanel, 22),
+ waitForDebuggerEvents(gPanel, gDebugger.EVENTS.FETCHED_VARIABLES)
+ ]).then(prepareVariablesAndProperties)
+ .then(testVariablesAndPropertiesFiltering)
+ .then(() => resumeDebuggerThenCloseAndFinish(gPanel))
+ .then(null, aError => {
+ ok(false, "Got an error: " + aError.message + "\n" + aError.stack);
+ });
+
+ generateMouseClickInTab(gTab, "content.document.querySelector('button')");
+ });
+}
+
+function testVariablesAndPropertiesFiltering() {
+ let localScope = gVariables.getScopeAtIndex(0);
+ let withScope = gVariables.getScopeAtIndex(1);
+ let functionScope = gVariables.getScopeAtIndex(2);
+ let globalLexicalScope = gVariables.getScopeAtIndex(3);
+ let globalScope = gVariables.getScopeAtIndex(4);
+
+ function testFiltered() {
+ is(localScope.expanded, true,
+ "The localScope should be expanded.");
+ is(withScope.expanded, true,
+ "The withScope should be expanded.");
+ is(functionScope.expanded, true,
+ "The functionScope should be expanded.");
+ is(globalLexicalScope.expanded, true,
+ "The globalScope should be expanded.");
+ is(globalScope.expanded, true,
+ "The globalScope should be expanded.");
+
+ is(localScope.target.querySelectorAll(".variables-view-variable:not([unmatched])").length, 1,
+ "There should be 1 variable displayed in the local scope.");
+ is(withScope.target.querySelectorAll(".variables-view-variable:not([unmatched])").length, 0,
+ "There should be 0 variables displayed in the with scope.");
+ is(functionScope.target.querySelectorAll(".variables-view-variable:not([unmatched])").length, 0,
+ "There should be 0 variables displayed in the function scope.");
+ is(globalLexicalScope.target.querySelectorAll(".variables-view-variable:not([unmatched])").length, 0,
+ "There should be 0 variables displayed in the global scope.");
+ is(globalScope.target.querySelectorAll(".variables-view-variable:not([unmatched])").length, 0,
+ "There should be 0 variables displayed in the global scope.");
+
+ is(localScope.target.querySelectorAll(".variables-view-property:not([unmatched])").length, 0,
+ "There should be 0 properties displayed in the local scope.");
+ is(withScope.target.querySelectorAll(".variables-view-property:not([unmatched])").length, 0,
+ "There should be 0 properties displayed in the with scope.");
+ is(functionScope.target.querySelectorAll(".variables-view-property:not([unmatched])").length, 0,
+ "There should be 0 properties displayed in the function scope.");
+ is(globalLexicalScope.target.querySelectorAll(".variables-view-property:not([unmatched])").length, 0,
+ "There should be 0 properties displayed in the global scope.");
+ is(globalScope.target.querySelectorAll(".variables-view-property:not([unmatched])").length, 0,
+ "There should be 0 properties displayed in the global scope.");
+ }
+
+ function firstFilter() {
+ typeText(gSearchBox, "*alpha");
+ testFiltered("alpha");
+
+ is(localScope.target.querySelectorAll(".variables-view-variable:not([unmatched]) > .title > .name")[0].getAttribute("value"),
+ "alpha", "The only inner variable displayed should be 'alpha'");
+ }
+
+ function secondFilter() {
+ localScope.collapse();
+ withScope.collapse();
+ functionScope.collapse();
+ globalLexicalScope.collapse();
+ globalScope.collapse();
+
+ is(localScope.expanded, false,
+ "The localScope should not be expanded.");
+ is(withScope.expanded, false,
+ "The withScope should not be expanded.");
+ is(functionScope.expanded, false,
+ "The functionScope should not be expanded.");
+ is(globalLexicalScope.expanded, false,
+ "The globalScope should not be expanded.");
+ is(globalScope.expanded, false,
+ "The globalScope should not be expanded.");
+
+ backspaceText(gSearchBox, 6);
+ typeText(gSearchBox, "*beta");
+ testFiltered("beta");
+
+ is(localScope.target.querySelectorAll(".variables-view-variable:not([unmatched]) > .title > .name")[0].getAttribute("value"),
+ "beta", "The only inner variable displayed should be 'beta'");
+ }
+
+ firstFilter();
+ secondFilter();
+}
+
+function prepareVariablesAndProperties() {
+ let deferred = promise.defer();
+
+ let localScope = gVariables.getScopeAtIndex(0);
+ let withScope = gVariables.getScopeAtIndex(1);
+ let functionScope = gVariables.getScopeAtIndex(2);
+ let globalLexicalScope = gVariables.getScopeAtIndex(3);
+ let globalScope = gVariables.getScopeAtIndex(4);
+
+ is(localScope.expanded, true,
+ "The localScope should be expanded.");
+ is(withScope.expanded, false,
+ "The withScope should not be expanded yet.");
+ is(functionScope.expanded, false,
+ "The functionScope should not be expanded yet.");
+ is(globalLexicalScope.expanded, false,
+ "The globalScope should not be expanded yet.");
+ is(globalScope.expanded, false,
+ "The globalScope should not be expanded yet.");
+
+ // Wait for only two events to be triggered, because the Function scope is
+ // an environment to which scope arguments and variables are already attached.
+ waitForDebuggerEvents(gPanel, gDebugger.EVENTS.FETCHED_VARIABLES, 2).then(() => {
+ is(localScope.expanded, true,
+ "The localScope should now be expanded.");
+ is(withScope.expanded, true,
+ "The withScope should now be expanded.");
+ is(functionScope.expanded, true,
+ "The functionScope should now be expanded.");
+ is(globalLexicalScope.expanded, true,
+ "The globalScope should now be expanded.");
+ is(globalScope.expanded, true,
+ "The globalScope should now be expanded.");
+
+ deferred.resolve();
+ });
+
+ withScope.expand();
+ functionScope.expand();
+ globalLexicalScope.expand();
+ globalScope.expand();
+
+ return deferred.promise;
+}
+
+registerCleanupFunction(function () {
+ gTab = null;
+ gPanel = null;
+ gDebugger = null;
+ gVariables = null;
+ gSearchBox = null;
+});
diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-filter-04.js b/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-filter-04.js
new file mode 100644
index 000000000..0838f1517
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-filter-04.js
@@ -0,0 +1,243 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Make sure that the variables view correctly shows/hides nodes when various
+ * keyboard shortcuts are pressed in the debugger's searchbox.
+ */
+
+const TAB_URL = EXAMPLE_URL + "doc_with-frame.html";
+
+var gTab, gPanel, gDebugger;
+var gEditor, gVariables, gSearchBox;
+
+function test() {
+ // Debug test slaves are a bit slow at this test.
+ requestLongerTimeout(2);
+
+ let options = {
+ source: TAB_URL,
+ line: 1
+ };
+ initDebugger(TAB_URL, options).then(([aTab,, aPanel]) => {
+ gTab = aTab;
+ gPanel = aPanel;
+ gDebugger = gPanel.panelWin;
+ gEditor = gDebugger.DebuggerView.editor;
+ gVariables = gDebugger.DebuggerView.Variables;
+ gSearchBox = gDebugger.DebuggerView.Filtering._searchbox;
+
+ // The first 'with' scope should be expanded by default, but the
+ // variables haven't been fetched yet. This is how 'with' scopes work.
+ promise.all([
+ waitForCaretAndScopes(gPanel, 22),
+ waitForDebuggerEvents(gPanel, gDebugger.EVENTS.FETCHED_VARIABLES)
+ ]).then(prepareVariablesAndProperties)
+ .then(testVariablesAndPropertiesFiltering)
+ .then(() => resumeDebuggerThenCloseAndFinish(gPanel))
+ .then(null, aError => {
+ ok(false, "Got an error: " + aError.message + "\n" + aError.stack);
+ });
+
+ generateMouseClickInTab(gTab, "content.document.querySelector('button')");
+ });
+}
+
+function testVariablesAndPropertiesFiltering() {
+ let localScope = gVariables.getScopeAtIndex(0);
+ let withScope = gVariables.getScopeAtIndex(1);
+ let functionScope = gVariables.getScopeAtIndex(2);
+ let globalLexicalScope = gVariables.getScopeAtIndex(3);
+ let globalScope = gVariables.getScopeAtIndex(4);
+ let step = 0;
+
+ let tests = [
+ function () {
+ assertExpansion([true, false, false, false, false]);
+ EventUtils.sendKey("RETURN", gDebugger);
+ },
+ function () {
+ assertExpansion([true, false, false, false, false]);
+ EventUtils.sendKey("RETURN", gDebugger);
+ },
+ function () {
+ assertExpansion([true, false, false, false, false]);
+ gEditor.focus();
+ },
+ function () {
+ assertExpansion([true, false, false, false, false]);
+ typeText(gSearchBox, "*");
+ },
+ function () {
+ assertExpansion([true, true, true, true, true]);
+ EventUtils.sendKey("RETURN", gDebugger);
+ },
+ function () {
+ assertExpansion([true, true, true, true, true]);
+ EventUtils.sendKey("RETURN", gDebugger);
+ },
+ function () {
+ assertExpansion([true, true, true, true, true]);
+ gEditor.focus();
+ },
+ function () {
+ assertExpansion([true, true, true, true, true]);
+ backspaceText(gSearchBox, 1);
+ },
+ function () {
+ assertExpansion([true, true, true, true, true]);
+ EventUtils.sendKey("RETURN", gDebugger);
+ },
+ function () {
+ assertExpansion([true, true, true, true, true]);
+ EventUtils.sendKey("RETURN", gDebugger);
+ },
+ function () {
+ assertExpansion([true, true, true, true, true]);
+ gEditor.focus();
+ },
+ function () {
+ assertExpansion([true, true, true, true, true]);
+ localScope.collapse();
+ withScope.collapse();
+ functionScope.collapse();
+ globalLexicalScope.collapse();
+ globalScope.collapse();
+ },
+ function () {
+ assertExpansion([false, false, false, false, false]);
+ EventUtils.sendKey("RETURN", gDebugger);
+ },
+ function () {
+ assertExpansion([false, false, false, false, false]);
+ EventUtils.sendKey("RETURN", gDebugger);
+ },
+ function () {
+ assertExpansion([false, false, false, false, false]);
+ gEditor.focus();
+ },
+ function () {
+ assertExpansion([false, false, false, false, false]);
+ clearText(gSearchBox);
+ typeText(gSearchBox, "*");
+ },
+ function () {
+ assertExpansion([true, true, true, true, true]);
+ EventUtils.sendKey("RETURN", gDebugger);
+ },
+ function () {
+ assertExpansion([true, true, true, true, true]);
+ EventUtils.sendKey("RETURN", gDebugger);
+ },
+ function () {
+ assertExpansion([true, true, true, true, true]);
+ gEditor.focus();
+ },
+ function () {
+ assertExpansion([true, true, true, true, true]);
+ backspaceText(gSearchBox, 1);
+ },
+ function () {
+ assertExpansion([true, true, true, true, true]);
+ EventUtils.sendKey("RETURN", gDebugger);
+ },
+ function () {
+ assertExpansion([true, true, true, true, true]);
+ EventUtils.sendKey("RETURN", gDebugger);
+ },
+ function () {
+ assertExpansion([true, true, true, true, true]);
+ gEditor.focus();
+ },
+ function () {
+ assertExpansion([true, true, true, true, true]);
+ }
+ ];
+
+ function assertExpansion(aFlags) {
+ is(localScope.expanded, aFlags[0],
+ "The localScope should " + (aFlags[0] ? "" : "not ") +
+ "be expanded at this point (" + step + ").");
+
+ is(withScope.expanded, aFlags[1],
+ "The withScope should " + (aFlags[1] ? "" : "not ") +
+ "be expanded at this point (" + step + ").");
+
+ is(functionScope.expanded, aFlags[2],
+ "The functionScope should " + (aFlags[2] ? "" : "not ") +
+ "be expanded at this point (" + step + ").");
+
+ is(globalLexicalScope.expanded, aFlags[3],
+ "The globalLexicalScope should " + (aFlags[3] ? "" : "not ") +
+ "be expanded at this point (" + step + ").");
+
+ is(globalScope.expanded, aFlags[4],
+ "The globalScope should " + (aFlags[4] ? "" : "not ") +
+ "be expanded at this point (" + step + ").");
+
+ step++;
+ }
+
+ return promise.all(tests.map(f => f()));
+}
+
+function prepareVariablesAndProperties() {
+ let deferred = promise.defer();
+
+ let localScope = gVariables.getScopeAtIndex(0);
+ let withScope = gVariables.getScopeAtIndex(1);
+ let functionScope = gVariables.getScopeAtIndex(2);
+ let globalLexicalScope = gVariables.getScopeAtIndex(3);
+ let globalScope = gVariables.getScopeAtIndex(4);
+
+ is(localScope.expanded, true,
+ "The localScope should be expanded.");
+ is(withScope.expanded, false,
+ "The withScope should not be expanded yet.");
+ is(functionScope.expanded, false,
+ "The functionScope should not be expanded yet.");
+ is(globalLexicalScope.expanded, false,
+ "The globalLexicalScope should not be expanded yet.");
+ is(globalScope.expanded, false,
+ "The globalScope should not be expanded yet.");
+
+ // Wait for only two events to be triggered, because the Function scope is
+ // an environment to which scope arguments and variables are already attached.
+ waitForDebuggerEvents(gPanel, gDebugger.EVENTS.FETCHED_VARIABLES, 2).then(() => {
+ is(localScope.expanded, true,
+ "The localScope should now be expanded.");
+ is(withScope.expanded, true,
+ "The withScope should now be expanded.");
+ is(functionScope.expanded, true,
+ "The functionScope should now be expanded.");
+ is(globalLexicalScope.expanded, true,
+ "The globalLexicalScope should now be expanded.");
+ is(globalScope.expanded, true,
+ "The globalScope should now be expanded.");
+
+ withScope.collapse();
+ functionScope.collapse();
+ globalLexicalScope.collapse();
+ globalScope.collapse();
+
+ deferred.resolve();
+ });
+
+ withScope.expand();
+ functionScope.expand();
+ globalLexicalScope.expand();
+ globalScope.expand();
+
+ return deferred.promise;
+}
+
+registerCleanupFunction(function () {
+ gTab = null;
+ gPanel = null;
+ gDebugger = null;
+ gEditor = null;
+ gVariables = null;
+ gSearchBox = null;
+});
diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-filter-05.js b/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-filter-05.js
new file mode 100644
index 000000000..4390955eb
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-filter-05.js
@@ -0,0 +1,254 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Make sure that the variables view correctly shows/hides nodes when various
+ * keyboard shortcuts are pressed in the debugger's searchbox.
+ */
+
+const TAB_URL = EXAMPLE_URL + "doc_with-frame.html";
+
+var gTab, gPanel, gDebugger;
+var gVariables, gSearchBox;
+
+function test() {
+ // Debug test slaves are a bit slow at this test.
+ requestLongerTimeout(2);
+
+ let options = {
+ source: TAB_URL,
+ line: 1
+ };
+ initDebugger(TAB_URL, options).then(([aTab,, aPanel]) => {
+ gTab = aTab;
+ gPanel = aPanel;
+ gDebugger = gPanel.panelWin;
+ gVariables = gDebugger.DebuggerView.Variables;
+ gSearchBox = gDebugger.DebuggerView.Filtering._searchbox;
+
+ // The first 'with' scope should be expanded by default, but the
+ // variables haven't been fetched yet. This is how 'with' scopes work.
+ promise.all([
+ waitForCaretAndScopes(gPanel, 22),
+ waitForDebuggerEvents(gPanel, gDebugger.EVENTS.FETCHED_VARIABLES)
+ ]).then(prepareVariablesAndProperties)
+ .then(testVariablesAndPropertiesFiltering)
+ .then(() => resumeDebuggerThenCloseAndFinish(gPanel))
+ .then(null, aError => {
+ ok(false, "Got an error: " + aError.message + "\n" + aError.stack);
+ });
+
+ generateMouseClickInTab(gTab, "content.document.querySelector('button')");
+ });
+}
+
+function testVariablesAndPropertiesFiltering() {
+ let localScope = gVariables.getScopeAtIndex(0);
+ let withScope = gVariables.getScopeAtIndex(1);
+ let functionScope = gVariables.getScopeAtIndex(2);
+ let globalLexicalScope = gVariables.getScopeAtIndex(3);
+ let globalScope = gVariables.getScopeAtIndex(4);
+ let step = 0;
+
+ let tests = [
+ function () {
+ assertScopeExpansion([true, false, false, false, false]);
+ typeText(gSearchBox, "*arguments");
+ },
+ function () {
+ assertScopeExpansion([true, true, true, true, true]);
+ assertVariablesCountAtLeast([0, 0, 1, 0, 0]);
+
+ is(functionScope.target.querySelectorAll(".variables-view-variable:not([unmatched]) > .title > .name")[0].getAttribute("value"),
+ "arguments", "The arguments pseudoarray should be visible.");
+ is(functionScope.get("arguments").expanded, false,
+ "The arguments pseudoarray in functionScope should not be expanded.");
+
+ backspaceText(gSearchBox, 6);
+ },
+ function () {
+ assertScopeExpansion([true, true, true, true, true]);
+ assertVariablesCountAtLeast([0, 0, 1, 0, 1]);
+
+ is(functionScope.target.querySelectorAll(".variables-view-variable:not([unmatched]) > .title > .name")[0].getAttribute("value"),
+ "arguments", "The arguments pseudoarray should be visible.");
+ is(functionScope.get("arguments").expanded, false,
+ "The arguments pseudoarray in functionScope should not be expanded.");
+
+ is(globalScope.target.querySelectorAll(".variables-view-variable:not([unmatched]) > .title > .name")[0].getAttribute("value"),
+ "EventTarget", "The EventTarget object should be visible.");
+ is(globalScope.get("EventTarget").expanded, false,
+ "The EventTarget object in globalScope should not be expanded.");
+
+ backspaceText(gSearchBox, 2);
+ },
+ function () {
+ assertScopeExpansion([true, true, true, true, true]);
+ assertVariablesCountAtLeast([0, 1, 3, 0, 1]);
+
+ is(functionScope.target.querySelectorAll(".variables-view-variable:not([unmatched]) > .title > .name")[0].getAttribute("value"),
+ "aNumber", "The aNumber param should be visible.");
+ is(functionScope.get("aNumber").expanded, false,
+ "The aNumber param in functionScope should not be expanded.");
+
+ is(functionScope.target.querySelectorAll(".variables-view-variable:not([unmatched]) > .title > .name")[1].getAttribute("value"),
+ "a", "The a variable should be visible.");
+ is(functionScope.get("a").expanded, false,
+ "The a variable in functionScope should not be expanded.");
+
+ is(functionScope.target.querySelectorAll(".variables-view-variable:not([unmatched]) > .title > .name")[2].getAttribute("value"),
+ "arguments", "The arguments pseudoarray should be visible.");
+ is(functionScope.get("arguments").expanded, false,
+ "The arguments pseudoarray in functionScope should not be expanded.");
+
+ backspaceText(gSearchBox, 1);
+ },
+ function () {
+ assertScopeExpansion([true, true, true, true, true]);
+ assertVariablesCountAtLeast([4, 1, 3, 0, 1]);
+
+ is(localScope.target.querySelectorAll(".variables-view-variable:not([unmatched]) > .title > .name")[0].getAttribute("value"),
+ "this", "The this reference should be visible.");
+ is(localScope.get("this").expanded, false,
+ "The this reference in localScope should not be expanded.");
+
+ is(localScope.target.querySelectorAll(".variables-view-variable:not([unmatched]) > .title > .name")[1].getAttribute("value"),
+ "alpha", "The alpha variable should be visible.");
+ is(localScope.get("alpha").expanded, false,
+ "The alpha variable in localScope should not be expanded.");
+
+ is(localScope.target.querySelectorAll(".variables-view-variable:not([unmatched]) > .title > .name")[2].getAttribute("value"),
+ "beta", "The beta variable should be visible.");
+ is(localScope.get("beta").expanded, false,
+ "The beta variable in localScope should not be expanded.");
+
+ is(localScope.target.querySelectorAll(".variables-view-variable:not([unmatched]) > .title > .name")[3].getAttribute("value"),
+ "__proto__", "The __proto__ reference should be visible.");
+ is(localScope.get("__proto__").expanded, false,
+ "The __proto__ reference in localScope should not be expanded.");
+
+ is(functionScope.target.querySelectorAll(".variables-view-variable:not([unmatched]) > .title > .name")[0].getAttribute("value"),
+ "aNumber", "The aNumber param should be visible.");
+ is(functionScope.get("aNumber").expanded, false,
+ "The aNumber param in functionScope should not be expanded.");
+
+ is(functionScope.target.querySelectorAll(".variables-view-variable:not([unmatched]) > .title > .name")[1].getAttribute("value"),
+ "a", "The a variable should be visible.");
+ is(functionScope.get("a").expanded, false,
+ "The a variable in functionScope should not be expanded.");
+
+ is(functionScope.target.querySelectorAll(".variables-view-variable:not([unmatched]) > .title > .name")[2].getAttribute("value"),
+ "arguments", "The arguments pseudoarray should be visible.");
+ is(functionScope.get("arguments").expanded, false,
+ "The arguments pseudoarray in functionScope should not be expanded.");
+ }
+ ];
+
+ function assertScopeExpansion(aFlags) {
+ is(localScope.expanded, aFlags[0],
+ "The localScope should " + (aFlags[0] ? "" : "not ") +
+ "be expanded at this point (" + step + ").");
+
+ is(withScope.expanded, aFlags[1],
+ "The withScope should " + (aFlags[1] ? "" : "not ") +
+ "be expanded at this point (" + step + ").");
+
+ is(functionScope.expanded, aFlags[2],
+ "The functionScope should " + (aFlags[2] ? "" : "not ") +
+ "be expanded at this point (" + step + ").");
+
+ is(globalLexicalScope.expanded, aFlags[3],
+ "The globalLexicalScope should " + (aFlags[3] ? "" : "not ") +
+ "be expanded at this point (" + step + ").");
+
+ is(globalScope.expanded, aFlags[4],
+ "The globalScope should " + (aFlags[4] ? "" : "not ") +
+ "be expanded at this point (" + step + ").");
+ }
+
+ function assertVariablesCountAtLeast(aCounts) {
+ ok(localScope.target.querySelectorAll(".variables-view-variable:not([unmatched])").length >= aCounts[0],
+ "There should be " + aCounts[0] +
+ " variable displayed in the local scope (" + step + ").");
+
+ ok(withScope.target.querySelectorAll(".variables-view-variable:not([unmatched])").length >= aCounts[1],
+ "There should be " + aCounts[1] +
+ " variable displayed in the with scope (" + step + ").");
+
+ ok(functionScope.target.querySelectorAll(".variables-view-variable:not([unmatched])").length >= aCounts[2],
+ "There should be " + aCounts[2] +
+ " variable displayed in the function scope (" + step + ").");
+
+ ok(globalLexicalScope.target.querySelectorAll(".variables-view-variable:not([unmatched])").length >= aCounts[3],
+ "There should be " + aCounts[3] +
+ " variable displayed in the global scope (" + step + ").");
+
+ ok(globalScope.target.querySelectorAll(".variables-view-variable:not([unmatched])").length >= aCounts[4],
+ "There should be " + aCounts[4] +
+ " variable displayed in the global scope (" + step + ").");
+
+ step++;
+ }
+
+ return promise.all(tests.map(f => f()));
+}
+
+function prepareVariablesAndProperties() {
+ let deferred = promise.defer();
+
+ let localScope = gVariables.getScopeAtIndex(0);
+ let withScope = gVariables.getScopeAtIndex(1);
+ let functionScope = gVariables.getScopeAtIndex(2);
+ let globalLexicalScope = gVariables.getScopeAtIndex(3);
+ let globalScope = gVariables.getScopeAtIndex(4);
+
+ is(localScope.expanded, true,
+ "The localScope should be expanded.");
+ is(withScope.expanded, false,
+ "The withScope should not be expanded yet.");
+ is(functionScope.expanded, false,
+ "The functionScope should not be expanded yet.");
+ is(globalLexicalScope.expanded, false,
+ "The globalScope should not be expanded yet.");
+ is(globalScope.expanded, false,
+ "The globalScope should not be expanded yet.");
+
+ // Wait for only two events to be triggered, because the Function scope is
+ // an environment to which scope arguments and variables are already attached.
+ waitForDebuggerEvents(gPanel, gDebugger.EVENTS.FETCHED_VARIABLES, 2).then(() => {
+ is(localScope.expanded, true,
+ "The localScope should now be expanded.");
+ is(withScope.expanded, true,
+ "The withScope should now be expanded.");
+ is(functionScope.expanded, true,
+ "The functionScope should now be expanded.");
+ is(globalLexicalScope.expanded, true,
+ "The globalScope should now be expanded.");
+ is(globalScope.expanded, true,
+ "The globalScope should now be expanded.");
+
+ withScope.collapse();
+ functionScope.collapse();
+ globalLexicalScope.collapse();
+ globalScope.collapse();
+
+ deferred.resolve();
+ });
+
+ withScope.expand();
+ functionScope.expand();
+ globalLexicalScope.expand();
+ globalScope.expand();
+
+ return deferred.promise;
+}
+
+registerCleanupFunction(function () {
+ gTab = null;
+ gPanel = null;
+ gDebugger = null;
+ gVariables = null;
+ gSearchBox = null;
+});
diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-filter-pref.js b/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-filter-pref.js
new file mode 100644
index 000000000..783fc8a23
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-filter-pref.js
@@ -0,0 +1,85 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Make sure that the variables view filter prefs work properly.
+ */
+
+const TAB_URL = EXAMPLE_URL + "doc_with-frame.html";
+
+var gTab, gPanel, gDebugger;
+var gPrefs, gOptions, gVariables;
+
+function test() {
+ let options = {
+ source: TAB_URL,
+ line: 1
+ };
+ initDebugger(TAB_URL, options).then(([aTab,, aPanel]) => {
+ gTab = aTab;
+ gPanel = aPanel;
+ gDebugger = gPanel.panelWin;
+ gPrefs = gDebugger.Prefs;
+ gOptions = gDebugger.DebuggerView.Options;
+ gVariables = gDebugger.DebuggerView.Variables;
+
+ performTest();
+ });
+}
+
+function performTest() {
+ ok(!gVariables._searchboxNode,
+ "There should not initially be a searchbox available in the variables view.");
+ ok(!gVariables._searchboxContainer,
+ "There should not initially be a searchbox container available in the variables view.");
+ ok(!gVariables._parent.parentNode.querySelector(".variables-view-searchinput"),
+ "The searchbox element should not be found.");
+
+ is(gPrefs.variablesSearchboxVisible, false,
+ "The debugger searchbox should be preffed as hidden.");
+ isnot(gOptions._showVariablesFilterBoxItem.getAttribute("checked"), "true",
+ "The options menu item should not be checked.");
+
+ gOptions._showVariablesFilterBoxItem.setAttribute("checked", "true");
+ gOptions._toggleShowVariablesFilterBox();
+
+ ok(gVariables._searchboxNode,
+ "There should be a searchbox available in the variables view.");
+ ok(gVariables._searchboxContainer,
+ "There should be a searchbox container available in the variables view.");
+ ok(gVariables._parent.parentNode.querySelector(".variables-view-searchinput"),
+ "There searchbox element should be found.");
+
+ is(gPrefs.variablesSearchboxVisible, true,
+ "The debugger searchbox should now be preffed as visible.");
+ is(gOptions._showVariablesFilterBoxItem.getAttribute("checked"), "true",
+ "The options menu item should now be checked.");
+
+ gOptions._showVariablesFilterBoxItem.setAttribute("checked", "false");
+ gOptions._toggleShowVariablesFilterBox();
+
+ ok(!gVariables._searchboxNode,
+ "There should not be a searchbox available in the variables view.");
+ ok(!gVariables._searchboxContainer,
+ "There should not be a searchbox container available in the variables view.");
+ ok(!gVariables._parent.parentNode.querySelector(".variables-view-searchinput"),
+ "There searchbox element should not be found.");
+
+ is(gPrefs.variablesSearchboxVisible, false,
+ "The debugger searchbox should now be preffed as hidden.");
+ isnot(gOptions._showVariablesFilterBoxItem.getAttribute("checked"), "true",
+ "The options menu item should now be unchecked.");
+
+ closeDebuggerAndFinish(gPanel);
+}
+
+registerCleanupFunction(function () {
+ gTab = null;
+ gPanel = null;
+ gDebugger = null;
+ gPrefs = null;
+ gOptions = null;
+ gVariables = null;
+});
diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-filter-searchbox.js b/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-filter-searchbox.js
new file mode 100644
index 000000000..1030d105d
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-filter-searchbox.js
@@ -0,0 +1,150 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Make sure that the variables view correctly shows the searchbox
+ * when prompted.
+ */
+
+const TAB_URL = EXAMPLE_URL + "doc_with-frame.html";
+
+var gTab, gPanel, gDebugger;
+var gVariables;
+
+function test() {
+ let options = {
+ source: TAB_URL,
+ line: 1
+ };
+ initDebugger(TAB_URL, options).then(([aTab,, aPanel]) => {
+ gTab = aTab;
+ gPanel = aPanel;
+ gDebugger = gPanel.panelWin;
+ gVariables = gDebugger.DebuggerView.Variables;
+
+ performTest();
+ });
+}
+
+function performTest() {
+ // Step 1: the searchbox shouldn't initially be shown.
+
+ ok(!gVariables._searchboxNode,
+ "There should not initially be a searchbox available in the variables view.");
+ ok(!gVariables._searchboxContainer,
+ "There should not initially be a searchbox container available in the variables view.");
+ ok(!gVariables._parent.parentNode.querySelector(".variables-view-searchinput"),
+ "The searchbox element should not be found.");
+
+ // Step 2: test enable/disable cycles.
+
+ gVariables._enableSearch();
+ ok(gVariables._searchboxNode,
+ "There should be a searchbox available after enabling.");
+ ok(gVariables._searchboxContainer,
+ "There should be a searchbox container available after enabling.");
+ ok(gVariables._searchboxContainer.hidden,
+ "The searchbox container should be hidden at this point.");
+ ok(gVariables._parent.parentNode.querySelector(".variables-view-searchinput"),
+ "The searchbox element should be found.");
+
+ gVariables._disableSearch();
+ ok(!gVariables._searchboxNode,
+ "There shouldn't be a searchbox available after disabling.");
+ ok(!gVariables._searchboxContainer,
+ "There shouldn't be a searchbox container available after disabling.");
+ ok(!gVariables._parent.parentNode.querySelector(".variables-view-searchinput"),
+ "The searchbox element should not be found.");
+
+ // Step 3: add a placeholder while the searchbox is hidden.
+
+ var placeholder = "not freshly squeezed mango juice";
+
+ gVariables.searchPlaceholder = placeholder;
+ is(gVariables.searchPlaceholder, placeholder,
+ "The placeholder getter didn't return the expected string");
+
+ // Step 4: enable search and check the placeholder.
+
+ gVariables._enableSearch();
+ ok(gVariables._searchboxNode,
+ "There should be a searchbox available after enabling.");
+ ok(gVariables._searchboxContainer,
+ "There should be a searchbox container available after enabling.");
+ ok(gVariables._searchboxContainer.hidden,
+ "The searchbox container should be hidden at this point.");
+ ok(gVariables._parent.parentNode.querySelector(".variables-view-searchinput"),
+ "The searchbox element should be found.");
+
+ is(gVariables._searchboxNode.getAttribute("placeholder"),
+ placeholder, "There correct placeholder should be applied to the searchbox.");
+
+ // Step 5: add a placeholder while the searchbox is visible and check wether
+ // it has been immediatey applied.
+
+ var placeholder = "freshly squeezed mango juice";
+
+ gVariables.searchPlaceholder = placeholder;
+ is(gVariables.searchPlaceholder, placeholder,
+ "The placeholder getter didn't return the expected string");
+
+ is(gVariables._searchboxNode.getAttribute("placeholder"),
+ placeholder, "There correct placeholder should be applied to the searchbox.");
+
+ // Step 4: disable, enable, then test the placeholder.
+
+ gVariables._disableSearch();
+ ok(!gVariables._searchboxNode,
+ "There shouldn't be a searchbox available after disabling again.");
+ ok(!gVariables._searchboxContainer,
+ "There shouldn't be a searchbox container available after disabling again.");
+ ok(!gVariables._parent.parentNode.querySelector(".variables-view-searchinput"),
+ "The searchbox element should not be found.");
+
+ gVariables._enableSearch();
+ ok(gVariables._searchboxNode,
+ "There should be a searchbox available after enabling again.");
+ ok(gVariables._searchboxContainer,
+ "There should be a searchbox container available after enabling again.");
+ ok(gVariables._searchboxContainer.hidden,
+ "The searchbox container should be hidden at this point.");
+ ok(gVariables._parent.parentNode.querySelector(".variables-view-searchinput"),
+ "The searchbox element should be found.");
+
+ is(gVariables._searchboxNode.getAttribute("placeholder"),
+ placeholder, "There correct placeholder should be applied to the searchbox again.");
+
+ // Step 5: alternate disable, enable, then test the placeholder.
+
+ gVariables.searchEnabled = false;
+ ok(!gVariables._searchboxNode,
+ "There shouldn't be a searchbox available after disabling again.");
+ ok(!gVariables._searchboxContainer,
+ "There shouldn't be a searchbox container available after disabling again.");
+ ok(!gVariables._parent.parentNode.querySelector(".variables-view-searchinput"),
+ "The searchbox element should not be found.");
+
+ gVariables.searchEnabled = true;
+ ok(gVariables._searchboxNode,
+ "There should be a searchbox available after enabling again.");
+ ok(gVariables._searchboxContainer,
+ "There should be a searchbox container available after enabling again.");
+ ok(gVariables._searchboxContainer.hidden,
+ "The searchbox container should be hidden at this point.");
+ ok(gVariables._parent.parentNode.querySelector(".variables-view-searchinput"),
+ "The searchbox element should be found.");
+
+ is(gVariables._searchboxNode.getAttribute("placeholder"),
+ placeholder, "There correct placeholder should be applied to the searchbox again.");
+
+ closeDebuggerAndFinish(gPanel);
+}
+
+registerCleanupFunction(function () {
+ gTab = null;
+ gPanel = null;
+ gDebugger = null;
+ gVariables = null;
+});
diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-frame-parameters-01.js b/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-frame-parameters-01.js
new file mode 100644
index 000000000..03dcf1440
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-frame-parameters-01.js
@@ -0,0 +1,270 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Make sure that the variables view correctly displays the properties
+ * of objects when debugger is paused.
+ */
+
+const TAB_URL = EXAMPLE_URL + "doc_frame-parameters.html";
+
+var gTab, gPanel, gDebugger;
+var gVariables;
+
+function test() {
+ // Debug test slaves are a bit slow at this test.
+ requestLongerTimeout(2);
+
+ let options = {
+ source: TAB_URL,
+ line: 1
+ };
+ initDebugger(TAB_URL, options).then(([aTab,, aPanel]) => {
+ gTab = aTab;
+ gPanel = aPanel;
+ gDebugger = gPanel.panelWin;
+ gVariables = gDebugger.DebuggerView.Variables;
+
+ waitForCaretAndScopes(gPanel, 24)
+ .then(initialChecks)
+ .then(testExpandVariables)
+ .then(() => resumeDebuggerThenCloseAndFinish(gPanel))
+ .then(null, aError => {
+ ok(false, "Got an error: " + aError.message + "\n" + aError.stack);
+ });
+
+ generateMouseClickInTab(gTab, "content.document.querySelector('button')");
+ });
+}
+
+function initialChecks() {
+ let scopeNodes = gDebugger.document.querySelectorAll(".variables-view-scope");
+ is(scopeNodes.length, 3,
+ "There should be 3 scopes available.");
+
+ ok(scopeNodes[0].querySelector(".name").getAttribute("value").includes("[test]"),
+ "The local scope should be properly identified.");
+ ok(scopeNodes[1].querySelector(".name").getAttribute("value").includes("Block"),
+ "The global lexical scope should be properly identified.");
+ ok(scopeNodes[2].querySelector(".name").getAttribute("value").includes("[Window]"),
+ "The global scope should be properly identified.");
+
+ is(gVariables.getScopeAtIndex(0).target, scopeNodes[0],
+ "getScopeAtIndex(0) didn't return the expected scope.");
+ is(gVariables.getScopeAtIndex(1).target, scopeNodes[1],
+ "getScopeAtIndex(1) didn't return the expected scope.");
+ is(gVariables.getScopeAtIndex(2).target, scopeNodes[2],
+ "getScopeAtIndex(2) didn't return the expected scope.");
+
+ is(gVariables.getItemForNode(scopeNodes[0]).target, scopeNodes[0],
+ "getItemForNode([0]) didn't return the expected scope.");
+ is(gVariables.getItemForNode(scopeNodes[1]).target, scopeNodes[1],
+ "getItemForNode([1]) didn't return the expected scope.");
+ is(gVariables.getItemForNode(scopeNodes[2]).target, scopeNodes[2],
+ "getItemForNode([2]) didn't return the expected scope.");
+
+ is(gVariables.getItemForNode(scopeNodes[0]).expanded, true,
+ "The local scope should be expanded by default.");
+ is(gVariables.getItemForNode(scopeNodes[1]).expanded, false,
+ "The global lexical scope should not be collapsed by default.");
+ is(gVariables.getItemForNode(scopeNodes[2]).expanded, false,
+ "The global scope should not be collapsed by default.");
+}
+
+function testExpandVariables() {
+ let deferred = promise.defer();
+
+ let localScope = gVariables.getScopeAtIndex(0);
+ let localEnums = localScope.target.querySelector(".variables-view-element-details.enum").childNodes;
+
+ let thisVar = gVariables.getItemForNode(localEnums[0]);
+ let argsVar = gVariables.getItemForNode(localEnums[8]);
+ let cVar = gVariables.getItemForNode(localEnums[10]);
+
+ is(thisVar.target.querySelector(".name").getAttribute("value"), "this",
+ "Should have the right property name for 'this'.");
+ is(argsVar.target.querySelector(".name").getAttribute("value"), "arguments",
+ "Should have the right property name for 'arguments'.");
+ is(cVar.target.querySelector(".name").getAttribute("value"), "c",
+ "Should have the right property name for 'c'.");
+
+ is(thisVar.expanded, false,
+ "The thisVar should not be expanded at this point.");
+ is(argsVar.expanded, false,
+ "The argsVar should not be expanded at this point.");
+ is(cVar.expanded, false,
+ "The cVar should not be expanded at this point.");
+
+ waitForDebuggerEvents(gPanel, gDebugger.EVENTS.FETCHED_PROPERTIES, 3).then(() => {
+ is(thisVar.get("window").target.querySelector(".name").getAttribute("value"), "window",
+ "Should have the right property name for 'window'.");
+ is(thisVar.get("window").target.querySelector(".value").getAttribute("value"),
+ "Window \u2192 doc_frame-parameters.html",
+ "Should have the right property value for 'window'.");
+ ok(thisVar.get("window").target.querySelector(".value").className.includes("token-other"),
+ "Should have the right token class for 'window'.");
+
+ is(thisVar.get("document").target.querySelector(".name").getAttribute("value"), "document",
+ "Should have the right property name for 'document'.");
+ is(thisVar.get("document").target.querySelector(".value").getAttribute("value"),
+ "HTMLDocument \u2192 doc_frame-parameters.html",
+ "Should have the right property value for 'document'.");
+ ok(thisVar.get("document").target.querySelector(".value").className.includes("token-domnode"),
+ "Should have the right token class for 'document'.");
+
+ let argsProps = argsVar.target.querySelectorAll(".variables-view-property");
+ is(argsProps.length, 8,
+ "The 'arguments' variable should contain 5 enumerable and 3 non-enumerable properties");
+
+ is(argsProps[0].querySelector(".name").getAttribute("value"), "0",
+ "Should have the right property name for '0'.");
+ is(argsProps[0].querySelector(".value").getAttribute("value"), "Object",
+ "Should have the right property value for '0'.");
+ ok(argsProps[0].querySelector(".value").className.includes("token-other"),
+ "Should have the right token class for '0'.");
+
+ is(argsProps[1].querySelector(".name").getAttribute("value"), "1",
+ "Should have the right property name for '1'.");
+ is(argsProps[1].querySelector(".value").getAttribute("value"), "\"beta\"",
+ "Should have the right property value for '1'.");
+ ok(argsProps[1].querySelector(".value").className.includes("token-string"),
+ "Should have the right token class for '1'.");
+
+ is(argsProps[2].querySelector(".name").getAttribute("value"), "2",
+ "Should have the right property name for '2'.");
+ is(argsProps[2].querySelector(".value").getAttribute("value"), "3",
+ "Should have the right property name for '2'.");
+ ok(argsProps[2].querySelector(".value").className.includes("token-number"),
+ "Should have the right token class for '2'.");
+
+ is(argsProps[3].querySelector(".name").getAttribute("value"), "3",
+ "Should have the right property name for '3'.");
+ is(argsProps[3].querySelector(".value").getAttribute("value"), "false",
+ "Should have the right property value for '3'.");
+ ok(argsProps[3].querySelector(".value").className.includes("token-boolean"),
+ "Should have the right token class for '3'.");
+
+ is(argsProps[4].querySelector(".name").getAttribute("value"), "4",
+ "Should have the right property name for '4'.");
+ is(argsProps[4].querySelector(".value").getAttribute("value"), "null",
+ "Should have the right property name for '4'.");
+ ok(argsProps[4].querySelector(".value").className.includes("token-null"),
+ "Should have the right token class for '4'.");
+
+ is(gVariables.getItemForNode(argsProps[0]).target,
+ argsVar.target.querySelectorAll(".variables-view-property")[0],
+ "getItemForNode([0]) didn't return the expected property.");
+
+ is(gVariables.getItemForNode(argsProps[1]).target,
+ argsVar.target.querySelectorAll(".variables-view-property")[1],
+ "getItemForNode([1]) didn't return the expected property.");
+
+ is(gVariables.getItemForNode(argsProps[2]).target,
+ argsVar.target.querySelectorAll(".variables-view-property")[2],
+ "getItemForNode([2]) didn't return the expected property.");
+
+ is(argsVar.find(argsProps[0]).target,
+ argsVar.target.querySelectorAll(".variables-view-property")[0],
+ "find([0]) didn't return the expected property.");
+
+ is(argsVar.find(argsProps[1]).target,
+ argsVar.target.querySelectorAll(".variables-view-property")[1],
+ "find([1]) didn't return the expected property.");
+
+ is(argsVar.find(argsProps[2]).target,
+ argsVar.target.querySelectorAll(".variables-view-property")[2],
+ "find([2]) didn't return the expected property.");
+
+ let cProps = cVar.target.querySelectorAll(".variables-view-property");
+ is(cProps.length, 7,
+ "The 'c' variable should contain 6 enumerable and 1 non-enumerable properties");
+
+ is(cProps[0].querySelector(".name").getAttribute("value"), "a",
+ "Should have the right property name for 'a'.");
+ is(cProps[0].querySelector(".value").getAttribute("value"), "1",
+ "Should have the right property value for 'a'.");
+ ok(cProps[0].querySelector(".value").className.includes("token-number"),
+ "Should have the right token class for 'a'.");
+
+ is(cProps[1].querySelector(".name").getAttribute("value"), "b",
+ "Should have the right property name for 'b'.");
+ is(cProps[1].querySelector(".value").getAttribute("value"), "\"beta\"",
+ "Should have the right property value for 'b'.");
+ ok(cProps[1].querySelector(".value").className.includes("token-string"),
+ "Should have the right token class for 'b'.");
+
+ is(cProps[2].querySelector(".name").getAttribute("value"), "c",
+ "Should have the right property name for 'c'.");
+ is(cProps[2].querySelector(".value").getAttribute("value"), "3",
+ "Should have the right property value for 'c'.");
+ ok(cProps[2].querySelector(".value").className.includes("token-number"),
+ "Should have the right token class for 'c'.");
+
+ is(cProps[3].querySelector(".name").getAttribute("value"), "d",
+ "Should have the right property name for 'd'.");
+ is(cProps[3].querySelector(".value").getAttribute("value"), "false",
+ "Should have the right property value for 'd'.");
+ ok(cProps[3].querySelector(".value").className.includes("token-boolean"),
+ "Should have the right token class for 'd'.");
+
+ is(cProps[4].querySelector(".name").getAttribute("value"), "e",
+ "Should have the right property name for 'e'.");
+ is(cProps[4].querySelector(".value").getAttribute("value"), "null",
+ "Should have the right property value for 'e'.");
+ ok(cProps[4].querySelector(".value").className.includes("token-null"),
+ "Should have the right token class for 'e'.");
+
+ is(cProps[5].querySelector(".name").getAttribute("value"), "f",
+ "Should have the right property name for 'f'.");
+ is(cProps[5].querySelector(".value").getAttribute("value"), "undefined",
+ "Should have the right property value for 'f'.");
+ ok(cProps[5].querySelector(".value").className.includes("token-undefined"),
+ "Should have the right token class for 'f'.");
+
+ is(gVariables.getItemForNode(cProps[0]).target,
+ cVar.target.querySelectorAll(".variables-view-property")[0],
+ "getItemForNode([0]) didn't return the expected property.");
+
+ is(gVariables.getItemForNode(cProps[1]).target,
+ cVar.target.querySelectorAll(".variables-view-property")[1],
+ "getItemForNode([1]) didn't return the expected property.");
+
+ is(gVariables.getItemForNode(cProps[2]).target,
+ cVar.target.querySelectorAll(".variables-view-property")[2],
+ "getItemForNode([2]) didn't return the expected property.");
+
+ is(cVar.find(cProps[0]).target,
+ cVar.target.querySelectorAll(".variables-view-property")[0],
+ "find([0]) didn't return the expected property.");
+
+ is(cVar.find(cProps[1]).target,
+ cVar.target.querySelectorAll(".variables-view-property")[1],
+ "find([1]) didn't return the expected property.");
+
+ is(cVar.find(cProps[2]).target,
+ cVar.target.querySelectorAll(".variables-view-property")[2],
+ "find([2]) didn't return the expected property.");
+ });
+
+ // Expand the 'this', 'arguments' and 'c' variables view nodes. This causes
+ // their properties to be retrieved and displayed.
+ thisVar.expand();
+ argsVar.expand();
+ cVar.expand();
+
+ is(thisVar.expanded, true,
+ "The thisVar should be immediately marked as expanded.");
+ is(argsVar.expanded, true,
+ "The argsVar should be immediately marked as expanded.");
+ is(cVar.expanded, true,
+ "The cVar should be immediately marked as expanded.");
+}
+
+registerCleanupFunction(function () {
+ gTab = null;
+ gPanel = null;
+ gDebugger = null;
+ gVariables = null;
+});
diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-frame-parameters-02.js b/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-frame-parameters-02.js
new file mode 100644
index 000000000..61bfb5275
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-frame-parameters-02.js
@@ -0,0 +1,552 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Make sure that the variables view displays the right variables and
+ * properties when debugger is paused.
+ */
+
+const TAB_URL = EXAMPLE_URL + "doc_frame-parameters.html";
+
+var gTab, gPanel, gDebugger;
+var gVariables;
+
+function test() {
+ // Debug test slaves are a bit slow at this test.
+ requestLongerTimeout(2);
+
+ let options = {
+ source: TAB_URL,
+ line: 1
+ };
+ initDebugger(TAB_URL, options).then(([aTab,, aPanel]) => {
+ gTab = aTab;
+ gPanel = aPanel;
+ gDebugger = gPanel.panelWin;
+ gVariables = gDebugger.DebuggerView.Variables;
+
+ waitForCaretAndScopes(gPanel, 24)
+ .then(testScopeVariables)
+ .then(testArgumentsProperties)
+ .then(testSimpleObject)
+ .then(testComplexObject)
+ .then(testArgumentObject)
+ .then(testInnerArgumentObject)
+ .then(testGetterSetterObject)
+ .then(() => resumeDebuggerThenCloseAndFinish(gPanel))
+ .then(null, aError => {
+ ok(false, "Got an error: " + aError.message + "\n" + aError.stack);
+ });
+
+ generateMouseClickInTab(gTab, "content.document.querySelector('button')");
+ });
+}
+
+function testScopeVariables() {
+ let localScope = gVariables.getScopeAtIndex(0);
+ is(localScope.expanded, true,
+ "The local scope should be expanded by default.");
+
+ let localEnums = localScope.target.querySelector(".variables-view-element-details.enum").childNodes;
+ let localNonEnums = localScope.target.querySelector(".variables-view-element-details.nonenum").childNodes;
+
+ is(localEnums.length, 12,
+ "The local scope should contain all the created enumerable elements.");
+ is(localNonEnums.length, 0,
+ "The local scope should contain all the created non-enumerable elements.");
+
+ is(localEnums[0].querySelector(".name").getAttribute("value"), "this",
+ "Should have the right property name for 'this'.");
+ is(localEnums[0].querySelector(".value").getAttribute("value"),
+ "Window \u2192 doc_frame-parameters.html",
+ "Should have the right property value for 'this'.");
+ ok(localEnums[0].querySelector(".value").className.includes("token-other"),
+ "Should have the right token class for 'this'.");
+
+ is(localEnums[1].querySelector(".name").getAttribute("value"), "aArg",
+ "Should have the right property name for 'aArg'.");
+ is(localEnums[1].querySelector(".value").getAttribute("value"), "Object",
+ "Should have the right property value for 'aArg'.");
+ ok(localEnums[1].querySelector(".value").className.includes("token-other"),
+ "Should have the right token class for 'aArg'.");
+
+ is(localEnums[2].querySelector(".name").getAttribute("value"), "bArg",
+ "Should have the right property name for 'bArg'.");
+ is(localEnums[2].querySelector(".value").getAttribute("value"), "\"beta\"",
+ "Should have the right property value for 'bArg'.");
+ ok(localEnums[2].querySelector(".value").className.includes("token-string"),
+ "Should have the right token class for 'bArg'.");
+
+ is(localEnums[3].querySelector(".name").getAttribute("value"), "cArg",
+ "Should have the right property name for 'cArg'.");
+ is(localEnums[3].querySelector(".value").getAttribute("value"), "3",
+ "Should have the right property value for 'cArg'.");
+ ok(localEnums[3].querySelector(".value").className.includes("token-number"),
+ "Should have the right token class for 'cArg'.");
+
+ is(localEnums[4].querySelector(".name").getAttribute("value"), "dArg",
+ "Should have the right property name for 'dArg'.");
+ is(localEnums[4].querySelector(".value").getAttribute("value"), "false",
+ "Should have the right property value for 'dArg'.");
+ ok(localEnums[4].querySelector(".value").className.includes("token-boolean"),
+ "Should have the right token class for 'dArg'.");
+
+ is(localEnums[5].querySelector(".name").getAttribute("value"), "eArg",
+ "Should have the right property name for 'eArg'.");
+ is(localEnums[5].querySelector(".value").getAttribute("value"), "null",
+ "Should have the right property value for 'eArg'.");
+ ok(localEnums[5].querySelector(".value").className.includes("token-null"),
+ "Should have the right token class for 'eArg'.");
+
+ is(localEnums[6].querySelector(".name").getAttribute("value"), "fArg",
+ "Should have the right property name for 'fArg'.");
+ is(localEnums[6].querySelector(".value").getAttribute("value"), "undefined",
+ "Should have the right property value for 'fArg'.");
+ ok(localEnums[6].querySelector(".value").className.includes("token-undefined"),
+ "Should have the right token class for 'fArg'.");
+
+ is(localEnums[7].querySelector(".name").getAttribute("value"), "a",
+ "Should have the right property name for 'a'.");
+ is(localEnums[7].querySelector(".value").getAttribute("value"), "1",
+ "Should have the right property value for 'a'.");
+ ok(localEnums[7].querySelector(".value").className.includes("token-number"),
+ "Should have the right token class for 'a'.");
+
+ is(localEnums[8].querySelector(".name").getAttribute("value"), "arguments",
+ "Should have the right property name for 'arguments'.");
+ is(localEnums[8].querySelector(".value").getAttribute("value"), "Arguments",
+ "Should have the right property value for 'arguments'.");
+ ok(localEnums[8].querySelector(".value").className.includes("token-other"),
+ "Should have the right token class for 'arguments'.");
+
+ is(localEnums[9].querySelector(".name").getAttribute("value"), "b",
+ "Should have the right property name for 'b'.");
+ is(localEnums[9].querySelector(".value").getAttribute("value"), "Object",
+ "Should have the right property value for 'b'.");
+ ok(localEnums[9].querySelector(".value").className.includes("token-other"),
+ "Should have the right token class for 'b'.");
+
+ is(localEnums[10].querySelector(".name").getAttribute("value"), "c",
+ "Should have the right property name for 'c'.");
+ is(localEnums[10].querySelector(".value").getAttribute("value"), "Object",
+ "Should have the right property value for 'c'.");
+ ok(localEnums[10].querySelector(".value").className.includes("token-other"),
+ "Should have the right token class for 'c'.");
+
+ is(localEnums[11].querySelector(".name").getAttribute("value"), "myVar",
+ "Should have the right property name for 'myVar'.");
+ is(localEnums[11].querySelector(".value").getAttribute("value"), "Object",
+ "Should have the right property value for 'myVar'.");
+ ok(localEnums[11].querySelector(".value").className.includes("token-other"),
+ "Should have the right token class for 'myVar'.");
+}
+
+function testArgumentsProperties() {
+ let deferred = promise.defer();
+
+ let argsVar = gVariables.getScopeAtIndex(0).get("arguments");
+ is(argsVar.expanded, false,
+ "The 'arguments' variable should not be expanded by default.");
+
+ let argsEnums = argsVar.target.querySelector(".variables-view-element-details.enum").childNodes;
+ let argsNonEnums = argsVar.target.querySelector(".variables-view-element-details.nonenum").childNodes;
+
+ gDebugger.once(gDebugger.EVENTS.FETCHED_PROPERTIES, () => {
+ is(argsEnums.length, 5,
+ "The 'arguments' variable should contain all the created enumerable elements.");
+ is(argsNonEnums.length, 3,
+ "The 'arguments' variable should contain all the created non-enumerable elements.");
+
+ is(argsEnums[0].querySelector(".name").getAttribute("value"), "0",
+ "Should have the right property name for '0'.");
+ is(argsEnums[0].querySelector(".value").getAttribute("value"), "Object",
+ "Should have the right property value for '0'.");
+ ok(argsEnums[0].querySelector(".value").className.includes("token-other"),
+ "Should have the right token class for '0'.");
+
+ is(argsEnums[1].querySelector(".name").getAttribute("value"), "1",
+ "Should have the right property name for '1'.");
+ is(argsEnums[1].querySelector(".value").getAttribute("value"), "\"beta\"",
+ "Should have the right property value for '1'.");
+ ok(argsEnums[1].querySelector(".value").className.includes("token-string"),
+ "Should have the right token class for '1'.");
+
+ is(argsEnums[2].querySelector(".name").getAttribute("value"), "2",
+ "Should have the right property name for '2'.");
+ is(argsEnums[2].querySelector(".value").getAttribute("value"), "3",
+ "Should have the right property name for '2'.");
+ ok(argsEnums[2].querySelector(".value").className.includes("token-number"),
+ "Should have the right token class for '2'.");
+
+ is(argsEnums[3].querySelector(".name").getAttribute("value"), "3",
+ "Should have the right property name for '3'.");
+ is(argsEnums[3].querySelector(".value").getAttribute("value"), "false",
+ "Should have the right property value for '3'.");
+ ok(argsEnums[3].querySelector(".value").className.includes("token-boolean"),
+ "Should have the right token class for '3'.");
+
+ is(argsEnums[4].querySelector(".name").getAttribute("value"), "4",
+ "Should have the right property name for '4'.");
+ is(argsEnums[4].querySelector(".value").getAttribute("value"), "null",
+ "Should have the right property name for '4'.");
+ ok(argsEnums[4].querySelector(".value").className.includes("token-null"),
+ "Should have the right token class for '4'.");
+
+ is(argsNonEnums[0].querySelector(".name").getAttribute("value"), "callee",
+ "Should have the right property name for 'callee'.");
+ is(argsNonEnums[0].querySelector(".value").getAttribute("value"),
+ "test(aArg,bArg,cArg,dArg,eArg,fArg)",
+ "Should have the right property name for 'callee'.");
+ ok(argsNonEnums[0].querySelector(".value").className.includes("token-other"),
+ "Should have the right token class for 'callee'.");
+
+ is(argsNonEnums[1].querySelector(".name").getAttribute("value"), "length",
+ "Should have the right property name for 'length'.");
+ is(argsNonEnums[1].querySelector(".value").getAttribute("value"), "5",
+ "Should have the right property value for 'length'.");
+ ok(argsNonEnums[1].querySelector(".value").className.includes("token-number"),
+ "Should have the right token class for 'length'.");
+
+ is(argsNonEnums[2].querySelector(".name").getAttribute("value"), "__proto__",
+ "Should have the right property name for '__proto__'.");
+ is(argsNonEnums[2].querySelector(".value").getAttribute("value"), "Object",
+ "Should have the right property value for '__proto__'.");
+ ok(argsNonEnums[2].querySelector(".value").className.includes("token-other"),
+ "Should have the right token class for '__proto__'.");
+
+ deferred.resolve();
+ });
+
+ argsVar.expand();
+ return deferred.promise;
+}
+
+function testSimpleObject() {
+ let deferred = promise.defer();
+
+ let bVar = gVariables.getScopeAtIndex(0).get("b");
+ is(bVar.expanded, false,
+ "The 'b' variable should not be expanded by default.");
+
+ let bEnums = bVar.target.querySelector(".variables-view-element-details.enum").childNodes;
+ let bNonEnums = bVar.target.querySelector(".variables-view-element-details.nonenum").childNodes;
+
+ gDebugger.once(gDebugger.EVENTS.FETCHED_PROPERTIES, () => {
+ is(bEnums.length, 1,
+ "The 'b' variable should contain all the created enumerable elements.");
+ is(bNonEnums.length, 1,
+ "The 'b' variable should contain all the created non-enumerable elements.");
+
+ is(bEnums[0].querySelector(".name").getAttribute("value"), "a",
+ "Should have the right property name for 'a'.");
+ is(bEnums[0].querySelector(".value").getAttribute("value"), "1",
+ "Should have the right property value for 'a'.");
+ ok(bEnums[0].querySelector(".value").className.includes("token-number"),
+ "Should have the right token class for 'a'.");
+
+ is(bNonEnums[0].querySelector(".name").getAttribute("value"), "__proto__",
+ "Should have the right property name for '__proto__'.");
+ is(bNonEnums[0].querySelector(".value").getAttribute("value"), "Object",
+ "Should have the right property value for '__proto__'.");
+ ok(bNonEnums[0].querySelector(".value").className.includes("token-other"),
+ "Should have the right token class for '__proto__'.");
+
+ deferred.resolve();
+ });
+
+ bVar.expand();
+ return deferred.promise;
+}
+
+function testComplexObject() {
+ let deferred = promise.defer();
+
+ let cVar = gVariables.getScopeAtIndex(0).get("c");
+ is(cVar.expanded, false,
+ "The 'c' variable should not be expanded by default.");
+
+ let cEnums = cVar.target.querySelector(".variables-view-element-details.enum").childNodes;
+ let cNonEnums = cVar.target.querySelector(".variables-view-element-details.nonenum").childNodes;
+
+ gDebugger.once(gDebugger.EVENTS.FETCHED_PROPERTIES, () => {
+ is(cEnums.length, 6,
+ "The 'c' variable should contain all the created enumerable elements.");
+ is(cNonEnums.length, 1,
+ "The 'c' variable should contain all the created non-enumerable elements.");
+
+ is(cEnums[0].querySelector(".name").getAttribute("value"), "a",
+ "Should have the right property name for 'a'.");
+ is(cEnums[0].querySelector(".value").getAttribute("value"), "1",
+ "Should have the right property value for 'a'.");
+ ok(cEnums[0].querySelector(".value").className.includes("token-number"),
+ "Should have the right token class for 'a'.");
+
+ is(cEnums[1].querySelector(".name").getAttribute("value"), "b",
+ "Should have the right property name for 'b'.");
+ is(cEnums[1].querySelector(".value").getAttribute("value"), "\"beta\"",
+ "Should have the right property value for 'b'.");
+ ok(cEnums[1].querySelector(".value").className.includes("token-string"),
+ "Should have the right token class for 'b'.");
+
+ is(cEnums[2].querySelector(".name").getAttribute("value"), "c",
+ "Should have the right property name for 'c'.");
+ is(cEnums[2].querySelector(".value").getAttribute("value"), "3",
+ "Should have the right property value for 'c'.");
+ ok(cEnums[2].querySelector(".value").className.includes("token-number"),
+ "Should have the right token class for 'c'.");
+
+ is(cEnums[3].querySelector(".name").getAttribute("value"), "d",
+ "Should have the right property name for 'd'.");
+ is(cEnums[3].querySelector(".value").getAttribute("value"), "false",
+ "Should have the right property value for 'd'.");
+ ok(cEnums[3].querySelector(".value").className.includes("token-boolean"),
+ "Should have the right token class for 'd'.");
+
+ is(cEnums[4].querySelector(".name").getAttribute("value"), "e",
+ "Should have the right property name for 'e'.");
+ is(cEnums[4].querySelector(".value").getAttribute("value"), "null",
+ "Should have the right property value for 'e'.");
+ ok(cEnums[4].querySelector(".value").className.includes("token-null"),
+ "Should have the right token class for 'e'.");
+
+ is(cEnums[5].querySelector(".name").getAttribute("value"), "f",
+ "Should have the right property name for 'f'.");
+ is(cEnums[5].querySelector(".value").getAttribute("value"), "undefined",
+ "Should have the right property value for 'f'.");
+ ok(cEnums[5].querySelector(".value").className.includes("token-undefined"),
+ "Should have the right token class for 'f'.");
+
+ is(cNonEnums[0].querySelector(".name").getAttribute("value"), "__proto__",
+ "Should have the right property name for '__proto__'.");
+ is(cNonEnums[0].querySelector(".value").getAttribute("value"), "Object",
+ "Should have the right property value for '__proto__'.");
+ ok(cNonEnums[0].querySelector(".value").className.includes("token-other"),
+ "Should have the right token class for '__proto__'.");
+
+ deferred.resolve();
+ });
+
+ cVar.expand();
+ return deferred.promise;
+}
+
+function testArgumentObject() {
+ let deferred = promise.defer();
+
+ let argVar = gVariables.getScopeAtIndex(0).get("aArg");
+ is(argVar.expanded, false,
+ "The 'aArg' variable should not be expanded by default.");
+
+ let argEnums = argVar.target.querySelector(".variables-view-element-details.enum").childNodes;
+ let argNonEnums = argVar.target.querySelector(".variables-view-element-details.nonenum").childNodes;
+
+ gDebugger.once(gDebugger.EVENTS.FETCHED_PROPERTIES, () => {
+ is(argEnums.length, 6,
+ "The 'aArg' variable should contain all the created enumerable elements.");
+ is(argNonEnums.length, 1,
+ "The 'aArg' variable should contain all the created non-enumerable elements.");
+
+ is(argEnums[0].querySelector(".name").getAttribute("value"), "a",
+ "Should have the right property name for 'a'.");
+ is(argEnums[0].querySelector(".value").getAttribute("value"), "1",
+ "Should have the right property value for 'a'.");
+ ok(argEnums[0].querySelector(".value").className.includes("token-number"),
+ "Should have the right token class for 'a'.");
+
+ is(argEnums[1].querySelector(".name").getAttribute("value"), "b",
+ "Should have the right property name for 'b'.");
+ is(argEnums[1].querySelector(".value").getAttribute("value"), "\"beta\"",
+ "Should have the right property value for 'b'.");
+ ok(argEnums[1].querySelector(".value").className.includes("token-string"),
+ "Should have the right token class for 'b'.");
+
+ is(argEnums[2].querySelector(".name").getAttribute("value"), "c",
+ "Should have the right property name for 'c'.");
+ is(argEnums[2].querySelector(".value").getAttribute("value"), "3",
+ "Should have the right property value for 'c'.");
+ ok(argEnums[2].querySelector(".value").className.includes("token-number"),
+ "Should have the right token class for 'c'.");
+
+ is(argEnums[3].querySelector(".name").getAttribute("value"), "d",
+ "Should have the right property name for 'd'.");
+ is(argEnums[3].querySelector(".value").getAttribute("value"), "false",
+ "Should have the right property value for 'd'.");
+ ok(argEnums[3].querySelector(".value").className.includes("token-boolean"),
+ "Should have the right token class for 'd'.");
+
+ is(argEnums[4].querySelector(".name").getAttribute("value"), "e",
+ "Should have the right property name for 'e'.");
+ is(argEnums[4].querySelector(".value").getAttribute("value"), "null",
+ "Should have the right property value for 'e'.");
+ ok(argEnums[4].querySelector(".value").className.includes("token-null"),
+ "Should have the right token class for 'e'.");
+
+ is(argEnums[5].querySelector(".name").getAttribute("value"), "f",
+ "Should have the right property name for 'f'.");
+ is(argEnums[5].querySelector(".value").getAttribute("value"), "undefined",
+ "Should have the right property value for 'f'.");
+ ok(argEnums[5].querySelector(".value").className.includes("token-undefined"),
+ "Should have the right token class for 'f'.");
+
+ is(argNonEnums[0].querySelector(".name").getAttribute("value"), "__proto__",
+ "Should have the right property name for '__proto__'.");
+ is(argNonEnums[0].querySelector(".value").getAttribute("value"), "Object",
+ "Should have the right property value for '__proto__'.");
+ ok(argNonEnums[0].querySelector(".value").className.includes("token-other"),
+ "Should have the right token class for '__proto__'.");
+
+ deferred.resolve();
+ });
+
+ argVar.expand();
+ return deferred.promise;
+}
+
+function testInnerArgumentObject() {
+ let deferred = promise.defer();
+
+ let argProp = gVariables.getScopeAtIndex(0).get("arguments").get("0");
+ is(argProp.expanded, false,
+ "The 'arguments[0]' property should not be expanded by default.");
+
+ let argEnums = argProp.target.querySelector(".variables-view-element-details.enum").childNodes;
+ let argNonEnums = argProp.target.querySelector(".variables-view-element-details.nonenum").childNodes;
+
+ gDebugger.once(gDebugger.EVENTS.FETCHED_PROPERTIES, () => {
+ is(argEnums.length, 6,
+ "The 'arguments[0]' property should contain all the created enumerable elements.");
+ is(argNonEnums.length, 1,
+ "The 'arguments[0]' property should contain all the created non-enumerable elements.");
+
+ is(argEnums[0].querySelector(".name").getAttribute("value"), "a",
+ "Should have the right property name for 'a'.");
+ is(argEnums[0].querySelector(".value").getAttribute("value"), "1",
+ "Should have the right property value for 'a'.");
+ ok(argEnums[0].querySelector(".value").className.includes("token-number"),
+ "Should have the right token class for 'a'.");
+
+ is(argEnums[1].querySelector(".name").getAttribute("value"), "b",
+ "Should have the right property name for 'b'.");
+ is(argEnums[1].querySelector(".value").getAttribute("value"), "\"beta\"",
+ "Should have the right property value for 'b'.");
+ ok(argEnums[1].querySelector(".value").className.includes("token-string"),
+ "Should have the right token class for 'b'.");
+
+ is(argEnums[2].querySelector(".name").getAttribute("value"), "c",
+ "Should have the right property name for 'c'.");
+ is(argEnums[2].querySelector(".value").getAttribute("value"), "3",
+ "Should have the right property value for 'c'.");
+ ok(argEnums[2].querySelector(".value").className.includes("token-number"),
+ "Should have the right token class for 'c'.");
+
+ is(argEnums[3].querySelector(".name").getAttribute("value"), "d",
+ "Should have the right property name for 'd'.");
+ is(argEnums[3].querySelector(".value").getAttribute("value"), "false",
+ "Should have the right property value for 'd'.");
+ ok(argEnums[3].querySelector(".value").className.includes("token-boolean"),
+ "Should have the right token class for 'd'.");
+
+ is(argEnums[4].querySelector(".name").getAttribute("value"), "e",
+ "Should have the right property name for 'e'.");
+ is(argEnums[4].querySelector(".value").getAttribute("value"), "null",
+ "Should have the right property value for 'e'.");
+ ok(argEnums[4].querySelector(".value").className.includes("token-null"),
+ "Should have the right token class for 'e'.");
+
+ is(argEnums[5].querySelector(".name").getAttribute("value"), "f",
+ "Should have the right property name for 'f'.");
+ is(argEnums[5].querySelector(".value").getAttribute("value"), "undefined",
+ "Should have the right property value for 'f'.");
+ ok(argEnums[5].querySelector(".value").className.includes("token-undefined"),
+ "Should have the right token class for 'f'.");
+
+ is(argNonEnums[0].querySelector(".name").getAttribute("value"), "__proto__",
+ "Should have the right property name for '__proto__'.");
+ is(argNonEnums[0].querySelector(".value").getAttribute("value"), "Object",
+ "Should have the right property value for '__proto__'.");
+ ok(argNonEnums[0].querySelector(".value").className.includes("token-other"),
+ "Should have the right token class for '__proto__'.");
+
+ deferred.resolve();
+ });
+
+ argProp.expand();
+ return deferred.promise;
+}
+
+function testGetterSetterObject() {
+ let deferred = promise.defer();
+
+ let myVar = gVariables.getScopeAtIndex(0).get("myVar");
+ is(myVar.expanded, false,
+ "The myVar variable should not be expanded by default.");
+
+ let myVarEnums = myVar.target.querySelector(".variables-view-element-details.enum").childNodes;
+ let myVarNonEnums = myVar.target.querySelector(".variables-view-element-details.nonenum").childNodes;
+
+ gDebugger.once(gDebugger.EVENTS.FETCHED_PROPERTIES, () => {
+ is(myVarEnums.length, 2,
+ "The myVar should contain all the created enumerable elements.");
+ is(myVarNonEnums.length, 1,
+ "The myVar should contain all the created non-enumerable elements.");
+
+ is(myVarEnums[0].querySelector(".name").getAttribute("value"), "_prop",
+ "Should have the right property name for '_prop'.");
+ is(myVarEnums[0].querySelector(".value").getAttribute("value"), "42",
+ "Should have the right property value for '_prop'.");
+ ok(myVarEnums[0].querySelector(".value").className.includes("token-number"),
+ "Should have the right token class for '_prop'.");
+
+ is(myVarEnums[1].querySelector(".name").getAttribute("value"), "prop",
+ "Should have the right property name for 'prop'.");
+ is(myVarEnums[1].querySelector(".value").getAttribute("value"), "",
+ "Should have the right property value for 'prop'.");
+ ok(!myVarEnums[1].querySelector(".value").className.includes("token"),
+ "Should have no token class for 'prop'.");
+
+ is(myVarNonEnums[0].querySelector(".name").getAttribute("value"), "__proto__",
+ "Should have the right property name for '__proto__'.");
+ is(myVarNonEnums[0].querySelector(".value").getAttribute("value"), "Object",
+ "Should have the right property value for '__proto__'.");
+ ok(myVarNonEnums[0].querySelector(".value").className.includes("token-other"),
+ "Should have the right token class for '__proto__'.");
+
+ let propEnums = myVarEnums[1].querySelector(".variables-view-element-details.enum").childNodes;
+ let propNonEnums = myVarEnums[1].querySelector(".variables-view-element-details.nonenum").childNodes;
+
+ is(propEnums.length, 0,
+ "The propEnums should contain all the created enumerable elements.");
+ is(propNonEnums.length, 2,
+ "The propEnums should contain all the created non-enumerable elements.");
+
+ is(propNonEnums[0].querySelector(".name").getAttribute("value"), "get",
+ "Should have the right property name for 'get'.");
+ is(propNonEnums[0].querySelector(".value").getAttribute("value"),
+ "get prop()",
+ "Should have the right property value for 'get'.");
+ ok(propNonEnums[0].querySelector(".value").className.includes("token-other"),
+ "Should have the right token class for 'get'.");
+
+ is(propNonEnums[1].querySelector(".name").getAttribute("value"), "set",
+ "Should have the right property name for 'set'.");
+ is(propNonEnums[1].querySelector(".value").getAttribute("value"),
+ "set prop(val)",
+ "Should have the right property value for 'set'.");
+ ok(propNonEnums[1].querySelector(".value").className.includes("token-other"),
+ "Should have the right token class for 'set'.");
+
+ deferred.resolve();
+ });
+
+ myVar.expand();
+ return deferred.promise;
+}
+
+registerCleanupFunction(function () {
+ gTab = null;
+ gPanel = null;
+ gDebugger = null;
+ gVariables = null;
+});
diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-frame-parameters-03.js b/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-frame-parameters-03.js
new file mode 100644
index 000000000..04f23ac7c
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-frame-parameters-03.js
@@ -0,0 +1,157 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Make sure that the variables view displays the right variables and
+ * properties in the global scope when debugger is paused.
+ */
+
+const TAB_URL = EXAMPLE_URL + "doc_frame-parameters.html";
+
+var gTab, gPanel, gDebugger;
+var gVariables;
+
+function test() {
+ // Debug test slaves are a bit slow at this test.
+ requestLongerTimeout(2);
+
+ let options = {
+ source: TAB_URL,
+ line: 1
+ };
+ initDebugger(TAB_URL, options).then(([aTab,, aPanel]) => {
+ gTab = aTab;
+ gPanel = aPanel;
+ gDebugger = gPanel.panelWin;
+ gVariables = gDebugger.DebuggerView.Variables;
+
+ waitForCaretAndScopes(gPanel, 24)
+ .then(expandGlobalScope)
+ .then(testGlobalScope)
+ .then(expandWindowVariable)
+ .then(testWindowVariable)
+ .then(() => resumeDebuggerThenCloseAndFinish(gPanel))
+ .then(null, aError => {
+ ok(false, "Got an error: " + aError.message + "\n" + aError.stack);
+ });
+
+ generateMouseClickInTab(gTab, "content.document.querySelector('button')");
+ });
+}
+
+function expandGlobalScope() {
+ let deferred = promise.defer();
+
+ let globalScope = gVariables.getScopeAtIndex(2);
+ is(globalScope.expanded, false,
+ "The global scope should not be expanded by default.");
+
+ gDebugger.once(gDebugger.EVENTS.FETCHED_VARIABLES, deferred.resolve);
+
+ EventUtils.sendMouseEvent({ type: "mousedown" },
+ globalScope.target.querySelector(".name"),
+ gDebugger);
+
+ return deferred.promise;
+}
+
+function testGlobalScope() {
+ let globalScope = gVariables.getScopeAtIndex(2);
+ is(globalScope.expanded, true,
+ "The global scope should now be expanded.");
+
+ is(globalScope.get("InstallTrigger").target.querySelector(".name").getAttribute("value"), "InstallTrigger",
+ "Should have the right property name for 'InstallTrigger'.");
+ is(globalScope.get("InstallTrigger").target.querySelector(".value").getAttribute("value"), "InstallTriggerImpl",
+ "Should have the right property value for 'InstallTrigger'.");
+
+ is(globalScope.get("SpecialPowers").target.querySelector(".name").getAttribute("value"), "SpecialPowers",
+ "Should have the right property name for 'SpecialPowers'.");
+ is(globalScope.get("SpecialPowers").target.querySelector(".value").getAttribute("value"), "Object",
+ "Should have the right property value for 'SpecialPowers'.");
+
+ is(globalScope.get("window").target.querySelector(".name").getAttribute("value"), "window",
+ "Should have the right property name for 'window'.");
+ is(globalScope.get("window").target.querySelector(".value").getAttribute("value"),
+ "Window \u2192 doc_frame-parameters.html",
+ "Should have the right property value for 'window'.");
+
+ is(globalScope.get("document").target.querySelector(".name").getAttribute("value"), "document",
+ "Should have the right property name for 'document'.");
+ is(globalScope.get("document").target.querySelector(".value").getAttribute("value"),
+ "HTMLDocument \u2192 doc_frame-parameters.html",
+ "Should have the right property value for 'document'.");
+
+ is(globalScope.get("undefined").target.querySelector(".name").getAttribute("value"), "undefined",
+ "Should have the right property name for 'undefined'.");
+ is(globalScope.get("undefined").target.querySelector(".value").getAttribute("value"), "undefined",
+ "Should have the right property value for 'undefined'.");
+
+ is(globalScope.get("undefined").target.querySelector(".enum").childNodes.length, 0,
+ "Should have no child enumerable properties for 'undefined'.");
+ is(globalScope.get("undefined").target.querySelector(".nonenum").childNodes.length, 0,
+ "Should have no child non-enumerable properties for 'undefined'.");
+}
+
+function expandWindowVariable() {
+ let deferred = promise.defer();
+
+ let windowVar = gVariables.getScopeAtIndex(2).get("window");
+ is(windowVar.expanded, false,
+ "The window variable should not be expanded by default.");
+
+ gDebugger.once(gDebugger.EVENTS.FETCHED_PROPERTIES, deferred.resolve);
+
+ EventUtils.sendMouseEvent({ type: "mousedown" },
+ windowVar.target.querySelector(".name"),
+ gDebugger);
+
+ return deferred.promise;
+}
+
+function testWindowVariable() {
+ let windowVar = gVariables.getScopeAtIndex(2).get("window");
+ is(windowVar.expanded, true,
+ "The window variable should now be expanded.");
+
+ is(windowVar.get("InstallTrigger").target.querySelector(".name").getAttribute("value"), "InstallTrigger",
+ "Should have the right property name for 'InstallTrigger'.");
+ is(windowVar.get("InstallTrigger").target.querySelector(".value").getAttribute("value"), "InstallTriggerImpl",
+ "Should have the right property value for 'InstallTrigger'.");
+
+ is(windowVar.get("SpecialPowers").target.querySelector(".name").getAttribute("value"), "SpecialPowers",
+ "Should have the right property name for 'SpecialPowers'.");
+ is(windowVar.get("SpecialPowers").target.querySelector(".value").getAttribute("value"), "Object",
+ "Should have the right property value for 'SpecialPowers'.");
+
+ is(windowVar.get("window").target.querySelector(".name").getAttribute("value"), "window",
+ "Should have the right property name for 'window'.");
+ is(windowVar.get("window").target.querySelector(".value").getAttribute("value"),
+ "Window \u2192 doc_frame-parameters.html",
+ "Should have the right property value for 'window'.");
+
+ is(windowVar.get("document").target.querySelector(".name").getAttribute("value"), "document",
+ "Should have the right property name for 'document'.");
+ is(windowVar.get("document").target.querySelector(".value").getAttribute("value"),
+ "HTMLDocument \u2192 doc_frame-parameters.html",
+ "Should have the right property value for 'document'.");
+
+ is(windowVar.get("undefined").target.querySelector(".name").getAttribute("value"), "undefined",
+ "Should have the right property name for 'undefined'.");
+ is(windowVar.get("undefined").target.querySelector(".value").getAttribute("value"), "undefined",
+ "Should have the right property value for 'undefined'.");
+
+ is(windowVar.get("undefined").target.querySelector(".enum").childNodes.length, 0,
+ "Should have no child enumerable properties for 'undefined'.");
+ is(windowVar.get("undefined").target.querySelector(".nonenum").childNodes.length, 0,
+ "Should have no child non-enumerable properties for 'undefined'.");
+}
+
+registerCleanupFunction(function () {
+ gTab = null;
+ gPanel = null;
+ gDebugger = null;
+ gVariables = null;
+});
diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-frame-with.js b/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-frame-with.js
new file mode 100644
index 000000000..dc98e2d9b
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-frame-with.js
@@ -0,0 +1,212 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Make sure that the variables view is correctly populated in 'with' frames.
+ */
+
+const TAB_URL = EXAMPLE_URL + "doc_with-frame.html";
+
+var gTab, gPanel, gDebugger;
+var gVariables;
+
+function test() {
+ let options = {
+ source: TAB_URL,
+ line: 1
+ };
+ initDebugger(TAB_URL, options).then(([aTab,, aPanel]) => {
+ gTab = aTab;
+ gPanel = aPanel;
+ gDebugger = gPanel.panelWin;
+ gVariables = gDebugger.DebuggerView.Variables;
+
+ // The first 'with' scope should be expanded by default, but the
+ // variables haven't been fetched yet. This is how 'with' scopes work.
+ promise.all([
+ waitForCaretAndScopes(gPanel, 22),
+ waitForDebuggerEvents(gPanel, gDebugger.EVENTS.FETCHED_VARIABLES)
+ ]).then(testFirstWithScope)
+ .then(expandSecondWithScope)
+ .then(testSecondWithScope)
+ .then(expandFunctionScope)
+ .then(testFunctionScope)
+ .then(() => resumeDebuggerThenCloseAndFinish(gPanel))
+ .then(null, aError => {
+ ok(false, "Got an error: " + aError.message + "\n" + aError.stack);
+ });
+
+ generateMouseClickInTab(gTab, "content.document.querySelector('button')");
+ });
+}
+
+function testFirstWithScope() {
+ let firstWithScope = gVariables.getScopeAtIndex(0);
+ is(firstWithScope.expanded, true,
+ "The first 'with' scope should be expanded by default.");
+ ok(firstWithScope.target.querySelector(".name").getAttribute("value").includes("[Object]"),
+ "The first 'with' scope should be properly identified.");
+
+ let withEnums = firstWithScope._enum.childNodes;
+ let withNonEnums = firstWithScope._nonenum.childNodes;
+
+ is(withEnums.length, 3,
+ "The first 'with' scope should contain all the created enumerable elements.");
+ is(withNonEnums.length, 1,
+ "The first 'with' scope should contain all the created non-enumerable elements.");
+
+ is(withEnums[0].querySelector(".name").getAttribute("value"), "this",
+ "Should have the right property name for 'this'.");
+ is(withEnums[0].querySelector(".value").getAttribute("value"),
+ "Window \u2192 doc_with-frame.html",
+ "Should have the right property value for 'this'.");
+ ok(withEnums[0].querySelector(".value").className.includes("token-other"),
+ "Should have the right token class for 'this'.");
+
+ is(withEnums[1].querySelector(".name").getAttribute("value"), "alpha",
+ "Should have the right property name for 'alpha'.");
+ is(withEnums[1].querySelector(".value").getAttribute("value"), "1",
+ "Should have the right property value for 'alpha'.");
+ ok(withEnums[1].querySelector(".value").className.includes("token-number"),
+ "Should have the right token class for 'alpha'.");
+
+ is(withEnums[2].querySelector(".name").getAttribute("value"), "beta",
+ "Should have the right property name for 'beta'.");
+ is(withEnums[2].querySelector(".value").getAttribute("value"), "2",
+ "Should have the right property value for 'beta'.");
+ ok(withEnums[2].querySelector(".value").className.includes("token-number"),
+ "Should have the right token class for 'beta'.");
+
+ is(withNonEnums[0].querySelector(".name").getAttribute("value"), "__proto__",
+ "Should have the right property name for '__proto__'.");
+ is(withNonEnums[0].querySelector(".value").getAttribute("value"), "Object",
+ "Should have the right property value for '__proto__'.");
+ ok(withNonEnums[0].querySelector(".value").className.includes("token-other"),
+ "Should have the right token class for '__proto__'.");
+}
+
+function expandSecondWithScope() {
+ let deferred = promise.defer();
+
+ let secondWithScope = gVariables.getScopeAtIndex(1);
+ is(secondWithScope.expanded, false,
+ "The second 'with' scope should not be expanded by default.");
+
+ gDebugger.once(gDebugger.EVENTS.FETCHED_VARIABLES, deferred.resolve);
+
+ EventUtils.sendMouseEvent({ type: "mousedown" },
+ secondWithScope.target.querySelector(".name"),
+ gDebugger);
+
+ return deferred.promise;
+}
+
+function testSecondWithScope() {
+ let secondWithScope = gVariables.getScopeAtIndex(1);
+ is(secondWithScope.expanded, true,
+ "The second 'with' scope should now be expanded.");
+ ok(secondWithScope.target.querySelector(".name").getAttribute("value").includes("[Math]"),
+ "The second 'with' scope should be properly identified.");
+
+ let withEnums = secondWithScope._enum.childNodes;
+ let withNonEnums = secondWithScope._nonenum.childNodes;
+
+ is(withEnums.length, 0,
+ "The second 'with' scope should contain all the created enumerable elements.");
+ isnot(withNonEnums.length, 0,
+ "The second 'with' scope should contain all the created non-enumerable elements.");
+
+ is(secondWithScope.get("E").target.querySelector(".name").getAttribute("value"), "E",
+ "Should have the right property name for 'E'.");
+ is(secondWithScope.get("E").target.querySelector(".value").getAttribute("value"), "2.718281828459045",
+ "Should have the right property value for 'E'.");
+ ok(secondWithScope.get("E").target.querySelector(".value").className.includes("token-number"),
+ "Should have the right token class for 'E'.");
+
+ is(secondWithScope.get("PI").target.querySelector(".name").getAttribute("value"), "PI",
+ "Should have the right property name for 'PI'.");
+ is(secondWithScope.get("PI").target.querySelector(".value").getAttribute("value"), "3.141592653589793",
+ "Should have the right property value for 'PI'.");
+ ok(secondWithScope.get("PI").target.querySelector(".value").className.includes("token-number"),
+ "Should have the right token class for 'PI'.");
+
+ is(secondWithScope.get("random").target.querySelector(".name").getAttribute("value"), "random",
+ "Should have the right property name for 'random'.");
+ is(secondWithScope.get("random").target.querySelector(".value").getAttribute("value"), "random()",
+ "Should have the right property value for 'random'.");
+ ok(secondWithScope.get("random").target.querySelector(".value").className.includes("token-other"),
+ "Should have the right token class for 'random'.");
+
+ is(secondWithScope.get("__proto__").target.querySelector(".name").getAttribute("value"), "__proto__",
+ "Should have the right property name for '__proto__'.");
+ is(secondWithScope.get("__proto__").target.querySelector(".value").getAttribute("value"), "Object",
+ "Should have the right property value for '__proto__'.");
+ ok(secondWithScope.get("__proto__").target.querySelector(".value").className.includes("token-other"),
+ "Should have the right token class for '__proto__'.");
+}
+
+function expandFunctionScope() {
+ let funcScope = gVariables.getScopeAtIndex(2);
+ is(funcScope.expanded, false,
+ "The function scope shouldn't be expanded by default, but the " +
+ "variables have been already fetched. This is how local scopes work.");
+
+ EventUtils.sendMouseEvent({ type: "mousedown" },
+ funcScope.target.querySelector(".name"),
+ gDebugger);
+
+ return promise.resolve(null);
+}
+
+function testFunctionScope() {
+ let funcScope = gVariables.getScopeAtIndex(2);
+ is(funcScope.expanded, true,
+ "The function scope should now be expanded.");
+ ok(funcScope.target.querySelector(".name").getAttribute("value").includes("[test]"),
+ "The function scope should be properly identified.");
+
+ let funcEnums = funcScope._enum.childNodes;
+ let funcNonEnums = funcScope._nonenum.childNodes;
+
+ is(funcEnums.length, 6,
+ "The function scope should contain all the created enumerable elements.");
+ is(funcNonEnums.length, 0,
+ "The function scope should contain all the created non-enumerable elements.");
+
+ is(funcScope.get("aNumber").target.querySelector(".name").getAttribute("value"), "aNumber",
+ "Should have the right property name for 'aNumber'.");
+ is(funcScope.get("aNumber").target.querySelector(".value").getAttribute("value"), "10",
+ "Should have the right property value for 'aNumber'.");
+ ok(funcScope.get("aNumber").target.querySelector(".value").className.includes("token-number"),
+ "Should have the right token class for 'aNumber'.");
+
+ is(funcScope.get("a").target.querySelector(".name").getAttribute("value"), "a",
+ "Should have the right property name for 'a'.");
+ is(funcScope.get("a").target.querySelector(".value").getAttribute("value"), "314.1592653589793",
+ "Should have the right property value for 'a'.");
+ ok(funcScope.get("a").target.querySelector(".value").className.includes("token-number"),
+ "Should have the right token class for 'a'.");
+
+ is(funcScope.get("r").target.querySelector(".name").getAttribute("value"), "r",
+ "Should have the right property name for 'r'.");
+ is(funcScope.get("r").target.querySelector(".value").getAttribute("value"), "10",
+ "Should have the right property value for 'r'.");
+ ok(funcScope.get("r").target.querySelector(".value").className.includes("token-number"),
+ "Should have the right token class for 'r'.");
+
+ is(funcScope.get("foo").target.querySelector(".name").getAttribute("value"), "foo",
+ "Should have the right property name for 'foo'.");
+ is(funcScope.get("foo").target.querySelector(".value").getAttribute("value"), "6.283185307179586",
+ "Should have the right property value for 'foo'.");
+ ok(funcScope.get("foo").target.querySelector(".value").className.includes("token-number"),
+ "Should have the right token class for 'foo'.");
+}
+
+registerCleanupFunction(function () {
+ gTab = null;
+ gPanel = null;
+ gDebugger = null;
+ gVariables = null;
+});
diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-frozen-sealed-nonext.js b/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-frozen-sealed-nonext.js
new file mode 100644
index 000000000..736deeba1
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-frozen-sealed-nonext.js
@@ -0,0 +1,93 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * This test checks that we properly set the frozen, sealed, and non-extensbile
+ * attributes on variables so that the F/S/N is shown in the variables view.
+ */
+
+const TAB_URL = EXAMPLE_URL + "doc_frame-parameters.html";
+
+var gTab, gPanel, gDebugger;
+
+function test() {
+ let options = {
+ source: TAB_URL,
+ line: 1
+ };
+ initDebugger(TAB_URL, options).then(([aTab,, aPanel]) => {
+ gTab = aTab;
+ gPanel = aPanel;
+ gDebugger = gPanel.panelWin;
+
+ prepareTest();
+ });
+}
+
+function prepareTest() {
+ gDebugger.once(gDebugger.EVENTS.FETCHED_SCOPES, runTest);
+
+ evalInTab(gTab, "(" + function () {
+ var frozen = Object.freeze({});
+ var sealed = Object.seal({});
+ var nonExtensible = Object.preventExtensions({});
+ var extensible = {};
+ var string = "foo bar baz";
+
+ debugger;
+ } + "())");
+}
+
+function runTest() {
+ let hasNoneTester = function (aVariable) {
+ ok(!aVariable.hasAttribute("frozen"),
+ "The variable should not be frozen.");
+ ok(!aVariable.hasAttribute("sealed"),
+ "The variable should not be sealed.");
+ ok(!aVariable.hasAttribute("non-extensible"),
+ "The variable should be extensible.");
+ };
+
+ let testers = {
+ frozen: function (aVariable) {
+ ok(aVariable.hasAttribute("frozen"),
+ "The variable should be frozen.");
+ },
+ sealed: function (aVariable) {
+ ok(aVariable.hasAttribute("sealed"),
+ "The variable should be sealed.");
+ },
+ nonExtensible: function (aVariable) {
+ ok(aVariable.hasAttribute("non-extensible"),
+ "The variable should be non-extensible.");
+ },
+ extensible: hasNoneTester,
+ string: hasNoneTester,
+ arguments: hasNoneTester,
+ this: hasNoneTester
+ };
+
+ let variables = gDebugger.document.querySelectorAll(".variable-or-property");
+
+ for (let variable of variables) {
+ let name = variable.querySelector(".name").getAttribute("value");
+ let tester = testers[name];
+ delete testers[name];
+
+ ok(tester, "We should have a tester for the '" + name + "' variable.");
+ tester(variable);
+ }
+
+ is(Object.keys(testers).length, 0,
+ "We should have run and removed all the testers.");
+
+ resumeDebuggerThenCloseAndFinish(gPanel);
+}
+
+registerCleanupFunction(function () {
+ gTab = null;
+ gPanel = null;
+ gDebugger = null;
+});
diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-hide-non-enums.js b/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-hide-non-enums.js
new file mode 100644
index 000000000..8094cfdc2
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-hide-non-enums.js
@@ -0,0 +1,111 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Test that non-enumerable variables and properties can be hidden
+ * in the variables view.
+ */
+
+const TAB_URL = EXAMPLE_URL + "doc_recursion-stack.html";
+
+var gTab, gPanel, gDebugger;
+
+function test() {
+ let options = {
+ source: TAB_URL,
+ line: 1
+ };
+ initDebugger(TAB_URL, options).then(([aTab,, aPanel]) => {
+ gTab = aTab;
+ gPanel = aPanel;
+ gDebugger = gPanel.panelWin;
+
+ waitForCaretAndScopes(gPanel, 14).then(performTest);
+ callInTab(gTab, "simpleCall");
+ });
+}
+
+function performTest() {
+ let testScope = gDebugger.DebuggerView.Variables.addScope("test-scope");
+ let testVar = testScope.addItem("foo");
+
+ testVar.addItems({
+ foo: {
+ value: "bar",
+ enumerable: true
+ },
+ bar: {
+ value: "foo",
+ enumerable: false
+ }
+ });
+
+ // Expand the scope and variable.
+ testScope.expand();
+ testVar.expand();
+
+ // Expanding the non-enumerable container is synchronously dispatched
+ // on the main thread, so wait for the next tick.
+ executeSoon(() => {
+ let details = testVar._enum;
+ let nonenum = testVar._nonenum;
+
+ is(details.childNodes.length, 1,
+ "There should be just one property in the .details container.");
+ ok(details.hasAttribute("open"),
+ ".details container should be visible.");
+ ok(nonenum.hasAttribute("open"),
+ ".nonenum container should be visible.");
+ is(nonenum.childNodes.length, 1,
+ "There should be just one property in the .nonenum container.");
+
+ // Uncheck 'show hidden properties'.
+ gDebugger.DebuggerView.Options._showVariablesOnlyEnumItem.setAttribute("checked", "true");
+ gDebugger.DebuggerView.Options._toggleShowVariablesOnlyEnum();
+
+ ok(details.hasAttribute("open"),
+ ".details container should stay visible.");
+ ok(!nonenum.hasAttribute("open"),
+ ".nonenum container should become hidden.");
+
+ // Check 'show hidden properties'.
+ gDebugger.DebuggerView.Options._showVariablesOnlyEnumItem.setAttribute("checked", "false");
+ gDebugger.DebuggerView.Options._toggleShowVariablesOnlyEnum();
+
+ ok(details.hasAttribute("open"),
+ ".details container should stay visible.");
+ ok(nonenum.hasAttribute("open"),
+ ".nonenum container should become visible.");
+
+ // Collapse the variable. This is done on the current tick.
+ testVar.collapse();
+
+ ok(!details.hasAttribute("open"),
+ ".details container should be hidden.");
+ ok(!nonenum.hasAttribute("open"),
+ ".nonenum container should be hidden.");
+
+ // Uncheck 'show hidden properties'.
+ gDebugger.DebuggerView.Options._showVariablesOnlyEnumItem.setAttribute("checked", "true");
+ gDebugger.DebuggerView.Options._toggleShowVariablesOnlyEnum();
+
+ ok(!details.hasAttribute("open"),
+ ".details container should stay hidden.");
+ ok(!nonenum.hasAttribute("open"),
+ ".nonenum container should stay hidden.");
+
+ // Check 'show hidden properties'.
+ gDebugger.DebuggerView.Options._showVariablesOnlyEnumItem.setAttribute("checked", "false");
+ gDebugger.DebuggerView.Options._toggleShowVariablesOnlyEnum();
+
+ resumeDebuggerThenCloseAndFinish(gPanel);
+ });
+}
+
+registerCleanupFunction(function () {
+ gTab = null;
+ gPanel = null;
+ gDebugger = null;
+});
diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-large-array-buffer.js b/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-large-array-buffer.js
new file mode 100644
index 000000000..7ec1fe2f0
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-large-array-buffer.js
@@ -0,0 +1,253 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Make sure that the variables view remains responsive when faced with
+ * huge ammounts of data.
+ */
+
+"use strict";
+
+const TAB_URL = EXAMPLE_URL + "doc_large-array-buffer.html";
+const {ELLIPSIS} = require("devtools/shared/l10n");
+
+
+var gTab, gPanel, gDebugger, gVariables;
+
+function test() {
+ // this test does a lot of work on large objects, default 45s is not enough
+ requestLongerTimeout(4);
+
+ let options = {
+ source: TAB_URL,
+ line: 1
+ };
+ initDebugger(TAB_URL, options).then(([aTab,, aPanel]) => {
+ gTab = aTab;
+ gPanel = aPanel;
+ gDebugger = gPanel.panelWin;
+ gVariables = gDebugger.DebuggerView.Variables;
+
+ waitForCaretAndScopes(gPanel, 28, 1)
+ .then(() => performTests())
+ .then(() => resumeDebuggerThenCloseAndFinish(gPanel))
+ .then(null, error => {
+ ok(false, "Got an error: " + error.message + "\n" + error.stack);
+ });
+
+ generateMouseClickInTab(gTab, "content.document.querySelector('button')");
+ });
+}
+
+const VARS_TO_TEST = [
+ {
+ varName: "buffer",
+ stringified: "ArrayBuffer",
+ doNotExpand: true
+ },
+ {
+ varName: "largeArray",
+ stringified: "Int8Array[10000]",
+ extraProps: [
+ [ "buffer", "ArrayBuffer" ],
+ [ "byteLength", "10000" ],
+ [ "byteOffset", "0" ],
+ [ "length", "10000" ],
+ [ "__proto__", "Int8ArrayPrototype" ]
+ ]
+ },
+ {
+ varName: "largeObject",
+ stringified: "Object[10000]",
+ extraProps: [
+ [ "__proto__", "Object" ]
+ ]
+ },
+ {
+ varName: "largeMap",
+ stringified: "Map[10000]",
+ hasEntries: true,
+ extraProps: [
+ [ "size", "10000" ],
+ [ "__proto__", "Object" ]
+ ]
+ },
+ {
+ varName: "largeSet",
+ stringified: "Set[10000]",
+ hasEntries: true,
+ extraProps: [
+ [ "size", "10000" ],
+ [ "__proto__", "Object" ]
+ ]
+ }
+];
+
+const PAGE_RANGES = [
+ [0, 2499], [2500, 4999], [5000, 7499], [7500, 9999]
+];
+
+function toPageNames(ranges) {
+ return ranges.map(([ from, to ]) => "[" + from + ELLIPSIS + to + "]");
+}
+
+function performTests() {
+ let localScope = gVariables.getScopeAtIndex(0);
+
+ return promise.all(VARS_TO_TEST.map(spec => {
+ let { varName, stringified, doNotExpand } = spec;
+
+ let variable = localScope.get(varName);
+ ok(variable,
+ `There should be a '${varName}' variable present in the scope.`);
+
+ is(variable.target.querySelector(".name").getAttribute("value"), varName,
+ `Should have the right property name for '${varName}'.`);
+ is(variable.target.querySelector(".value").getAttribute("value"), stringified,
+ `Should have the right property value for '${varName}'.`);
+ ok(variable.target.querySelector(".value").className.includes("token-other"),
+ `Should have the right token class for '${varName}'.`);
+
+ is(variable.expanded, false,
+ `The '${varName}' variable shouldn't be expanded.`);
+
+ if (doNotExpand) {
+ return promise.resolve();
+ }
+
+ return variable.expand()
+ .then(() => verifyFirstLevel(variable, spec));
+ }));
+}
+
+// In objects and arrays, the sliced pages are at the top-level of
+// the expanded object, but with Maps and Sets, we have to expand
+// <entries> first and look there.
+function getExpandedPages(variable, hasEntries) {
+ let expandedPages = promise.defer();
+ if (hasEntries) {
+ let entries = variable.get("<entries>");
+ ok(entries, "<entries> retrieved");
+ entries.expand().then(() => expandedPages.resolve(entries));
+ } else {
+ expandedPages.resolve(variable);
+ }
+
+ return expandedPages.promise;
+}
+
+function verifyFirstLevel(variable, spec) {
+ let { varName, hasEntries, extraProps } = spec;
+
+ let enums = variable._enum.childNodes;
+ let nonEnums = variable._nonenum.childNodes;
+
+ is(enums.length, hasEntries ? 1 : 4,
+ `The '${varName}' contains the right number of enumerable elements.`);
+ is(nonEnums.length, extraProps.length,
+ `The '${varName}' contains the right number of non-enumerable elements.`);
+
+ // the sliced pages begin after <entries> row
+ let pagesOffset = hasEntries ? 1 : 0;
+ let expandedPages = getExpandedPages(variable, hasEntries);
+
+ return expandedPages.then((pagesList) => {
+ toPageNames(PAGE_RANGES).forEach((pageName, i) => {
+ let index = i + pagesOffset;
+
+ is(pagesList.target.querySelectorAll(".variables-view-property .name")[index].getAttribute("value"),
+ pageName, `The page #${i + 1} in the '${varName}' is named correctly.`);
+ is(pagesList.target.querySelectorAll(".variables-view-property .value")[index].getAttribute("value"),
+ "", `The page #${i + 1} in the '${varName}' should not have a corresponding value.`);
+ });
+ }).then(() => {
+ extraProps.forEach(([ propName, propValue ], i) => {
+ // the extra props start after the 4 pages
+ let index = i + pagesOffset + 4;
+
+ is(variable.target.querySelectorAll(".variables-view-property .name")[index].getAttribute("value"),
+ propName, `The other properties in '${varName}' are named correctly.`);
+ is(variable.target.querySelectorAll(".variables-view-property .value")[index].getAttribute("value"),
+ propValue, `The other properties in '${varName}' have the correct value.`);
+ });
+ }).then(() => verifyNextLevels(variable, spec));
+}
+
+function verifyNextLevels(variable, spec) {
+ let { varName, hasEntries } = spec;
+
+ // the entries are already expanded in verifyFirstLevel
+ let pagesList = hasEntries ? variable.get("<entries>") : variable;
+
+ let lastPage = pagesList.get(toPageNames(PAGE_RANGES)[3]);
+ ok(lastPage, `The last page in the 1st level of '${varName}' was retrieved successfully.`);
+
+ return lastPage.expand()
+ .then(() => verifyNextLevels2(lastPage, varName));
+}
+
+function verifyNextLevels2(lastPage1, varName) {
+ const PAGE_RANGES_IN_LAST_PAGE = [
+ [7500, 8124], [8125, 8749], [8750, 9374], [9375, 9999]
+ ];
+
+ let pageEnums1 = lastPage1._enum.childNodes;
+ let pageNonEnums1 = lastPage1._nonenum.childNodes;
+ is(pageEnums1.length, 4,
+ `The last page in the 1st level of '${varName}' should contain all the created enumerable elements.`);
+ is(pageNonEnums1.length, 0,
+ `The last page in the 1st level of '${varName}' should not contain any non-enumerable elements.`);
+
+ let pageNames = toPageNames(PAGE_RANGES_IN_LAST_PAGE);
+ pageNames.forEach((pageName, i) => {
+ is(lastPage1._enum.querySelectorAll(".variables-view-property .name")[i].getAttribute("value"),
+ pageName, `The page #${i + 1} in the 2nd level of '${varName}' is named correctly.`);
+ });
+
+ let lastPage2 = lastPage1.get(pageNames[3]);
+ ok(lastPage2, "The last page in the 2nd level was retrieved successfully.");
+
+ return lastPage2.expand()
+ .then(() => verifyNextLevels3(lastPage2, varName));
+}
+
+function verifyNextLevels3(lastPage2, varName) {
+ let pageEnums2 = lastPage2._enum.childNodes;
+ let pageNonEnums2 = lastPage2._nonenum.childNodes;
+ is(pageEnums2.length, 625,
+ `The last page in the 3rd level of '${varName}' should contain all the created enumerable elements.`);
+ is(pageNonEnums2.length, 0,
+ `The last page in the 3rd level of '${varName}' shouldn't contain any non-enumerable elements.`);
+
+ const LEAF_ITEMS = [
+ [0, 9375, 624],
+ [1, 9376, 623],
+ [623, 9998, 1],
+ [624, 9999, 0]
+ ];
+
+ function expectedValue(name, value) {
+ switch (varName) {
+ case "largeArray": return 0;
+ case "largeObject": return value;
+ case "largeMap": return name + " \u2192 " + value;
+ case "largeSet": return value;
+ }
+ }
+
+ LEAF_ITEMS.forEach(([index, name, value]) => {
+ is(lastPage2._enum.querySelectorAll(".variables-view-property .name")[index].getAttribute("value"),
+ name, `The properties in the leaf level of '${varName}' are named correctly.`);
+ is(lastPage2._enum.querySelectorAll(".variables-view-property .value")[index].getAttribute("value"),
+ expectedValue(name, value), `The properties in the leaf level of '${varName}' have the correct value.`);
+ });
+}
+
+registerCleanupFunction(function () {
+ gTab = null;
+ gPanel = null;
+ gDebugger = null;
+ gVariables = null;
+});
diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-map-set.js b/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-map-set.js
new file mode 100644
index 000000000..0c301c683
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-map-set.js
@@ -0,0 +1,117 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Test that Map and Set and their Weak friends are displayed in variables view.
+ */
+
+"use strict";
+
+const TAB_URL = EXAMPLE_URL + "doc_map-set.html";
+
+var test = Task.async(function* () {
+ let options = {
+ source: TAB_URL,
+ line: 1
+ };
+ const [tab,, panel] = yield initDebugger(TAB_URL, options);
+
+ const scopes = waitForCaretAndScopes(panel, 37);
+ callInTab(tab, "startTest");
+ yield scopes;
+
+ const variables = panel.panelWin.DebuggerView.Variables;
+ ok(variables, "Should get the variables view.");
+
+ const scope = variables.getScopeAtIndex(0);
+ ok(scope, "Should get the current function's scope.");
+
+ /* Test the maps */
+ for (let varName of ["map", "weakMap"]) {
+ const mapVar = scope.get(varName);
+ ok(mapVar, `Retrieved the '${varName}' variable from the scope`);
+
+ info(`Expanding '${varName}' variable`);
+ yield mapVar.expand();
+
+ const entries = mapVar.get("<entries>");
+ ok(entries, `Retrieved the '${varName}' entries`);
+
+ info(`Expanding '${varName}' entries`);
+ yield entries.expand();
+
+ // Check the entries. WeakMap returns its entries in a nondeterministic
+ // order, so we make our job easier by not testing the exact values.
+ let i = 0;
+ for (let [ name, entry ] of entries) {
+ is(name, i, `The '${varName}' entry's property name is correct`);
+ ok(entry.displayValue.startsWith("Object \u2192 "),
+ `The '${varName}' entry's property value is correct`);
+ yield entry.expand();
+
+ let key = entry.get("key");
+ ok(key, `The '${varName}' entry has the 'key' property`);
+ yield key.expand();
+
+ let keyProperty = key.get("a");
+ ok(keyProperty,
+ `The '${varName}' entry's 'key' has the correct property`);
+
+ let value = entry.get("value");
+ ok(value, `The '${varName}' entry has the 'value' property`);
+
+ i++;
+ }
+
+ is(i, 2, `The '${varName}' entry count is correct`);
+
+ // Check the extra property on the object
+ let extraProp = mapVar.get("extraProp");
+ ok(extraProp, `Retrieved the '${varName}' extraProp`);
+ is(extraProp.displayValue, "true",
+ `The '${varName}' extraProp's value is correct`);
+ }
+
+ /* Test the sets */
+ for (let varName of ["set", "weakSet"]) {
+ const setVar = scope.get(varName);
+ ok(setVar, `Retrieved the '${varName}' variable from the scope`);
+
+ info(`Expanding '${varName}' variable`);
+ yield setVar.expand();
+
+ const entries = setVar.get("<entries>");
+ ok(entries, `Retrieved the '${varName}' entries`);
+
+ info(`Expanding '${varName}' entries`);
+ yield entries.expand();
+
+ // Check the entries. WeakSet returns its entries in a nondeterministic
+ // order, so we make our job easier by not testing the exact values.
+ let i = 0;
+ for (let [ name, entry ] of entries) {
+ is(name, i, `The '${varName}' entry's property name is correct`);
+ is(entry.displayValue, "Object",
+ `The '${varName}' entry's property value is correct`);
+ yield entry.expand();
+
+ let entryProperty = entry.get("a");
+ ok(entryProperty,
+ `The '${varName}' entry's value has the correct property`);
+
+ i++;
+ }
+
+ is(i, 2, `The '${varName}' entry count is correct`);
+
+ // Check the extra property on the object
+ let extraProp = setVar.get("extraProp");
+ ok(extraProp, `Retrieved the '${varName}' extraProp`);
+ is(extraProp.displayValue, "true",
+ `The '${varName}' extraProp's value is correct`);
+ }
+
+ resumeDebuggerThenCloseAndFinish(panel);
+});
diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-override-01.js b/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-override-01.js
new file mode 100644
index 000000000..f923d7f53
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-override-01.js
@@ -0,0 +1,240 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Tests that VariablesView methods responsible for styling variables
+ * as overridden work properly.
+ */
+
+const TAB_URL = EXAMPLE_URL + "doc_scope-variable-2.html";
+
+function test() {
+ Task.spawn(function* () {
+ let options = {
+ source: TAB_URL,
+ line: 1
+ };
+ let [tab,, panel] = yield initDebugger(TAB_URL, options);
+ let win = panel.panelWin;
+ let events = win.EVENTS;
+ let variables = win.DebuggerView.Variables;
+
+ callInTab(tab, "test");
+ yield waitForCaretAndScopes(panel, 23);
+
+ let firstScope = variables.getScopeAtIndex(0);
+ let secondScope = variables.getScopeAtIndex(1);
+ let thirdScope = variables.getScopeAtIndex(2);
+ let globalLexicalScope = variables.getScopeAtIndex(3);
+ let globalScope = variables.getScopeAtIndex(4);
+
+ ok(firstScope, "The first scope is available.");
+ ok(secondScope, "The second scope is available.");
+ ok(thirdScope, "The third scope is available.");
+ ok(globalLexicalScope, "The global lexical scope is available.");
+ ok(globalScope, "The global scope is available.");
+
+ is(firstScope.name, "Function scope [secondNest]",
+ "The first scope's name is correct.");
+ is(secondScope.name, "Function scope [firstNest]",
+ "The second scope's name is correct.");
+ is(thirdScope.name, "Function scope [test]",
+ "The third scope's name is correct.");
+ is(globalLexicalScope.name, "Block scope",
+ "The global lexical scope's name is correct.");
+ is(globalScope.name, "Global scope [Window]",
+ "The global scope's name is correct.");
+
+ is(firstScope.expanded, true,
+ "The first scope's expansion state is correct.");
+ is(secondScope.expanded, false,
+ "The second scope's expansion state is correct.");
+ is(thirdScope.expanded, false,
+ "The third scope's expansion state is correct.");
+ is(globalLexicalScope.expanded, false,
+ "The global lexical scope's expansion state is correct.");
+ is(globalScope.expanded, false,
+ "The global scope's expansion state is correct.");
+
+ is(firstScope._store.size, 3,
+ "The first scope should have all the variables available.");
+ is(secondScope._store.size, 0,
+ "The second scope should have no variables available yet.");
+ is(thirdScope._store.size, 0,
+ "The third scope should have no variables available yet.");
+ is(globalLexicalScope._store.size, 0,
+ "The global scope should have no variables available yet.");
+ is(globalScope._store.size, 0,
+ "The global scope should have no variables available yet.");
+
+ // Test getOwnerScopeForVariableOrProperty with simple variables.
+
+ let thisVar = firstScope.get("this");
+ let thisOwner = variables.getOwnerScopeForVariableOrProperty(thisVar);
+ is(thisOwner, firstScope,
+ "The getOwnerScopeForVariableOrProperty method works properly (1).");
+
+ let someVar1 = firstScope.get("a");
+ let someOwner1 = variables.getOwnerScopeForVariableOrProperty(someVar1);
+ is(someOwner1, firstScope,
+ "The getOwnerScopeForVariableOrProperty method works properly (2).");
+
+ // Test getOwnerScopeForVariableOrProperty with first-degree properties.
+
+ let argsVar1 = firstScope.get("arguments");
+ let fetched = waitForDebuggerEvents(panel, events.FETCHED_PROPERTIES);
+ argsVar1.expand();
+ yield fetched;
+
+ let calleeProp1 = argsVar1.get("callee");
+ let calleeOwner1 = variables.getOwnerScopeForVariableOrProperty(calleeProp1);
+ is(calleeOwner1, firstScope,
+ "The getOwnerScopeForVariableOrProperty method works properly (3).");
+
+ // Test getOwnerScopeForVariableOrProperty with second-degree properties.
+
+ let protoVar1 = argsVar1.get("__proto__");
+ fetched = waitForDebuggerEvents(panel, events.FETCHED_PROPERTIES);
+ protoVar1.expand();
+ yield fetched;
+
+ let constrProp1 = protoVar1.get("constructor");
+ let constrOwner1 = variables.getOwnerScopeForVariableOrProperty(constrProp1);
+ is(constrOwner1, firstScope,
+ "The getOwnerScopeForVariableOrProperty method works properly (4).");
+
+ // Test getOwnerScopeForVariableOrProperty with a simple variable
+ // from non-topmost scopes.
+
+ // Only need to wait for a single FETCHED_VARIABLES event, just for the
+ // global scope, because the other local scopes already have the
+ // arguments and variables available as evironment bindings.
+ fetched = waitForDebuggerEvents(panel, events.FETCHED_VARIABLES);
+ secondScope.expand();
+ thirdScope.expand();
+ globalLexicalScope.expand();
+ globalScope.expand();
+ yield fetched;
+
+ let someVar2 = secondScope.get("a");
+ let someOwner2 = variables.getOwnerScopeForVariableOrProperty(someVar2);
+ is(someOwner2, secondScope,
+ "The getOwnerScopeForVariableOrProperty method works properly (5).");
+
+ let someVar3 = thirdScope.get("a");
+ let someOwner3 = variables.getOwnerScopeForVariableOrProperty(someVar3);
+ is(someOwner3, thirdScope,
+ "The getOwnerScopeForVariableOrProperty method works properly (6).");
+
+ // Test getOwnerScopeForVariableOrProperty with first-degree properies
+ // from non-topmost scopes.
+
+ let argsVar2 = secondScope.get("arguments");
+ fetched = waitForDebuggerEvents(panel, events.FETCHED_PROPERTIES);
+ argsVar2.expand();
+ yield fetched;
+
+ let calleeProp2 = argsVar2.get("callee");
+ let calleeOwner2 = variables.getOwnerScopeForVariableOrProperty(calleeProp2);
+ is(calleeOwner2, secondScope,
+ "The getOwnerScopeForVariableOrProperty method works properly (7).");
+
+ let argsVar3 = thirdScope.get("arguments");
+ fetched = waitForDebuggerEvents(panel, events.FETCHED_PROPERTIES);
+ argsVar3.expand();
+ yield fetched;
+
+ let calleeProp3 = argsVar3.get("callee");
+ let calleeOwner3 = variables.getOwnerScopeForVariableOrProperty(calleeProp3);
+ is(calleeOwner3, thirdScope,
+ "The getOwnerScopeForVariableOrProperty method works properly (8).");
+
+ // Test getOwnerScopeForVariableOrProperty with second-degree properties
+ // from non-topmost scopes.
+
+ let protoVar2 = argsVar2.get("__proto__");
+ fetched = waitForDebuggerEvents(panel, events.FETCHED_PROPERTIES);
+ protoVar2.expand();
+ yield fetched;
+
+ let constrProp2 = protoVar2.get("constructor");
+ let constrOwner2 = variables.getOwnerScopeForVariableOrProperty(constrProp2);
+ is(constrOwner2, secondScope,
+ "The getOwnerScopeForVariableOrProperty method works properly (9).");
+
+ let protoVar3 = argsVar3.get("__proto__");
+ fetched = waitForDebuggerEvents(panel, events.FETCHED_PROPERTIES);
+ protoVar3.expand();
+ yield fetched;
+
+ let constrProp3 = protoVar3.get("constructor");
+ let constrOwner3 = variables.getOwnerScopeForVariableOrProperty(constrProp3);
+ is(constrOwner3, thirdScope,
+ "The getOwnerScopeForVariableOrProperty method works properly (10).");
+
+ // Test getParentScopesForVariableOrProperty with simple variables.
+
+ let varOwners1 = variables.getParentScopesForVariableOrProperty(someVar1);
+ let varOwners2 = variables.getParentScopesForVariableOrProperty(someVar2);
+ let varOwners3 = variables.getParentScopesForVariableOrProperty(someVar3);
+
+ is(varOwners1.length, 0,
+ "There should be no owner scopes for the first variable.");
+
+ is(varOwners2.length, 1,
+ "There should be one owner scope for the second variable.");
+ is(varOwners2[0], firstScope,
+ "The only owner scope for the second variable is correct.");
+
+ is(varOwners3.length, 2,
+ "There should be two owner scopes for the third variable.");
+ is(varOwners3[0], firstScope,
+ "The first owner scope for the third variable is correct.");
+ is(varOwners3[1], secondScope,
+ "The second owner scope for the third variable is correct.");
+
+ // Test getParentScopesForVariableOrProperty with first-degree properties.
+
+ let propOwners1 = variables.getParentScopesForVariableOrProperty(calleeProp1);
+ let propOwners2 = variables.getParentScopesForVariableOrProperty(calleeProp2);
+ let propOwners3 = variables.getParentScopesForVariableOrProperty(calleeProp3);
+
+ is(propOwners1.length, 0,
+ "There should be no owner scopes for the first property.");
+
+ is(propOwners2.length, 1,
+ "There should be one owner scope for the second property.");
+ is(propOwners2[0], firstScope,
+ "The only owner scope for the second property is correct.");
+
+ is(propOwners3.length, 2,
+ "There should be two owner scopes for the third property.");
+ is(propOwners3[0], firstScope,
+ "The first owner scope for the third property is correct.");
+ is(propOwners3[1], secondScope,
+ "The second owner scope for the third property is correct.");
+
+ // Test getParentScopesForVariableOrProperty with second-degree properties.
+
+ let secPropOwners1 = variables.getParentScopesForVariableOrProperty(constrProp1);
+ let secPropOwners2 = variables.getParentScopesForVariableOrProperty(constrProp2);
+ let secPropOwners3 = variables.getParentScopesForVariableOrProperty(constrProp3);
+
+ is(secPropOwners1.length, 0,
+ "There should be no owner scopes for the first inner property.");
+
+ is(secPropOwners2.length, 1,
+ "There should be one owner scope for the second inner property.");
+ is(secPropOwners2[0], firstScope,
+ "The only owner scope for the second inner property is correct.");
+
+ is(secPropOwners3.length, 2,
+ "There should be two owner scopes for the third inner property.");
+ is(secPropOwners3[0], firstScope,
+ "The first owner scope for the third inner property is correct.");
+ is(secPropOwners3[1], secondScope,
+ "The second owner scope for the third inner property is correct.");
+
+ yield resumeDebuggerThenCloseAndFinish(panel);
+ });
+}
diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-override-02.js b/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-override-02.js
new file mode 100644
index 000000000..276efb665
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-override-02.js
@@ -0,0 +1,75 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Tests that overridden variables in the VariablesView are styled properly.
+ */
+
+const TAB_URL = EXAMPLE_URL + "doc_scope-variable-2.html";
+
+function test() {
+ Task.spawn(function* () {
+ let options = {
+ source: TAB_URL,
+ line: 1
+ };
+ let [tab,, panel] = yield initDebugger(TAB_URL, options);
+ let win = panel.panelWin;
+ let events = win.EVENTS;
+ let variables = win.DebuggerView.Variables;
+
+ // Wait for the hierarchy to be committed by the VariablesViewController.
+ let committedLocalScopeHierarchy = promise.defer();
+ variables.oncommit = committedLocalScopeHierarchy.resolve;
+
+
+ let onCaretAndScopes = waitForCaretAndScopes(panel, 23);
+ callInTab(tab, "test");
+ yield onCaretAndScopes;
+ yield committedLocalScopeHierarchy.promise;
+
+ let firstScope = variables.getScopeAtIndex(0);
+ let secondScope = variables.getScopeAtIndex(1);
+ let thirdScope = variables.getScopeAtIndex(2);
+
+ let someVar1 = firstScope.get("a");
+ let argsVar1 = firstScope.get("arguments");
+
+ is(someVar1.target.hasAttribute("overridden"), false,
+ "The first 'a' variable should not be marked as being overridden.");
+ is(argsVar1.target.hasAttribute("overridden"), false,
+ "The first 'arguments' variable should not be marked as being overridden.");
+
+ // Wait for the hierarchy to be committed by the VariablesViewController.
+ let committedSecondScopeHierarchy = promise.defer();
+ variables.oncommit = committedSecondScopeHierarchy.resolve;
+ secondScope.expand();
+ yield committedSecondScopeHierarchy.promise;
+
+ let someVar2 = secondScope.get("a");
+ let argsVar2 = secondScope.get("arguments");
+
+ is(someVar2.target.hasAttribute("overridden"), true,
+ "The second 'a' variable should be marked as being overridden.");
+ is(argsVar2.target.hasAttribute("overridden"), true,
+ "The second 'arguments' variable should be marked as being overridden.");
+
+ // Wait for the hierarchy to be committed by the VariablesViewController.
+ let committedThirdScopeHierarchy = promise.defer();
+ variables.oncommit = committedThirdScopeHierarchy.resolve;
+ thirdScope.expand();
+ yield committedThirdScopeHierarchy.promise;
+
+ let someVar3 = thirdScope.get("a");
+ let argsVar3 = thirdScope.get("arguments");
+
+ is(someVar3.target.hasAttribute("overridden"), true,
+ "The third 'a' variable should be marked as being overridden.");
+ is(argsVar3.target.hasAttribute("overridden"), true,
+ "The third 'arguments' variable should be marked as being overridden.");
+
+ yield resumeDebuggerThenCloseAndFinish(panel);
+ });
+}
diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-popup-01.js b/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-popup-01.js
new file mode 100644
index 000000000..2e7244fad
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-popup-01.js
@@ -0,0 +1,67 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Tests opening the variable inspection popup on a variable which has a
+ * simple literal as the value.
+ */
+
+const TAB_URL = EXAMPLE_URL + "doc_frame-parameters.html";
+
+function test() {
+ Task.spawn(function* () {
+ let options = {
+ source: TAB_URL,
+ line: 1
+ };
+ let [tab,, panel] = yield initDebugger(TAB_URL, options);
+ let win = panel.panelWin;
+ let bubble = win.DebuggerView.VariableBubble;
+ let tooltip = bubble._tooltip.panel;
+
+ bubble._ignoreLiterals = false;
+
+ function verifyContents(textContent, className) {
+ is(tooltip.querySelectorAll(".variables-view-container").length, 0,
+ "There should be no variables view containers added to the tooltip.");
+ is(tooltip.querySelectorAll(".devtools-tooltip-simple-text").length, 1,
+ "There should be a simple text node added to the tooltip instead.");
+
+ is(tooltip.querySelector(".devtools-tooltip-simple-text").textContent, textContent,
+ "The inspected property's value is correct.");
+ ok(tooltip.querySelector(".devtools-tooltip-simple-text").className.includes(className),
+ "The inspected property's value is colorized correctly.");
+ }
+
+
+ let onCaretAndScopes = waitForCaretAndScopes(panel, 24);
+ callInTab(tab, "start");
+ yield onCaretAndScopes;
+
+ // Inspect variables.
+ yield openVarPopup(panel, { line: 15, ch: 12 });
+ verifyContents("1", "token-number");
+
+ yield reopenVarPopup(panel, { line: 16, ch: 21 });
+ verifyContents("1", "token-number");
+
+ yield reopenVarPopup(panel, { line: 17, ch: 21 });
+ verifyContents("1", "token-number");
+
+ yield reopenVarPopup(panel, { line: 17, ch: 27 });
+ verifyContents("\"beta\"", "token-string");
+
+ yield reopenVarPopup(panel, { line: 17, ch: 44 });
+ verifyContents("false", "token-boolean");
+
+ yield reopenVarPopup(panel, { line: 17, ch: 54 });
+ verifyContents("null", "token-null");
+
+ yield reopenVarPopup(panel, { line: 17, ch: 63 });
+ verifyContents("undefined", "token-undefined");
+
+ yield resumeDebuggerThenCloseAndFinish(panel);
+ });
+}
diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-popup-02.js b/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-popup-02.js
new file mode 100644
index 000000000..0f9843fc2
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-popup-02.js
@@ -0,0 +1,53 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Tests opening the variable inspection popup on a variable which has a
+ * a property accessible via getters and setters.
+ */
+
+const TAB_URL = EXAMPLE_URL + "doc_frame-parameters.html";
+
+function test() {
+ Task.spawn(function* () {
+ let options = {
+ source: TAB_URL,
+ line: 1
+ };
+ let [tab,, panel] = yield initDebugger(TAB_URL, options);
+ let win = panel.panelWin;
+ let bubble = win.DebuggerView.VariableBubble;
+ let tooltip = bubble._tooltip.panel;
+
+ function verifyContents(textContent, className) {
+ is(tooltip.querySelectorAll(".variables-view-container").length, 0,
+ "There should be no variables view containers added to the tooltip.");
+ is(tooltip.querySelectorAll(".devtools-tooltip-simple-text").length, 1,
+ "There should be a simple text node added to the tooltip instead.");
+
+ is(tooltip.querySelector(".devtools-tooltip-simple-text").textContent, textContent,
+ "The inspected property's value is correct.");
+ ok(tooltip.querySelector(".devtools-tooltip-simple-text").className.includes(className),
+ "The inspected property's value is colorized correctly.");
+ }
+
+
+ let onCaretAndScopes = waitForCaretAndScopes(panel, 24);
+ callInTab(tab, "start");
+ yield onCaretAndScopes;
+
+ // Inspect properties.
+ yield openVarPopup(panel, { line: 19, ch: 10 });
+ verifyContents("42", "token-number");
+
+ yield reopenVarPopup(panel, { line: 20, ch: 14 });
+ verifyContents("42", "token-number");
+
+ yield reopenVarPopup(panel, { line: 21, ch: 14 });
+ verifyContents("42", "token-number");
+
+ yield resumeDebuggerThenCloseAndFinish(panel);
+ });
+}
diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-popup-03.js b/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-popup-03.js
new file mode 100644
index 000000000..2b59fcfc5
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-popup-03.js
@@ -0,0 +1,49 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Tests that the inspected indentifier is highlighted.
+ */
+
+const TAB_URL = EXAMPLE_URL + "doc_frame-parameters.html";
+
+function test() {
+ Task.spawn(function* () {
+ let options = {
+ source: TAB_URL,
+ line: 1
+ };
+ let [tab,, panel] = yield initDebugger(TAB_URL, options);
+ let win = panel.panelWin;
+ let bubble = win.DebuggerView.VariableBubble;
+
+ let onCaretAndScopes = waitForCaretAndScopes(panel, 24);
+ callInTab(tab, "start");
+ yield onCaretAndScopes;
+
+ // Inspect variable.
+ yield openVarPopup(panel, { line: 15, ch: 12 });
+
+ ok(bubble.contentsShown(),
+ "The variable should register as being shown.");
+ ok(!bubble._tooltip.isEmpty(),
+ "The variable inspection popup isn't empty.");
+ ok(bubble._markedText,
+ "There's some marked text in the editor.");
+ ok(bubble._markedText.clear,
+ "The marked text in the editor can be cleared.");
+
+ yield hideVarPopup(panel);
+
+ ok(!bubble.contentsShown(),
+ "The variable should register as being hidden.");
+ ok(bubble._tooltip.isEmpty(),
+ "The variable inspection popup is now empty.");
+ ok(!bubble._markedText,
+ "The marked text in the editor was removed.");
+
+ yield resumeDebuggerThenCloseAndFinish(panel);
+ });
+}
diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-popup-04.js b/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-popup-04.js
new file mode 100644
index 000000000..b175b7a50
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-popup-04.js
@@ -0,0 +1,38 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Tests that the variable inspection popup is hidden when the editor scrolls.
+ */
+
+const TAB_URL = EXAMPLE_URL + "doc_frame-parameters.html";
+
+function test() {
+ Task.spawn(function* () {
+ let options = {
+ source: TAB_URL,
+ line: 1
+ };
+ let [tab,, panel] = yield initDebugger(TAB_URL, options);
+ let win = panel.panelWin;
+ let bubble = win.DebuggerView.VariableBubble;
+
+ let onCaretAndScopes = waitForCaretAndScopes(panel, 24);
+ callInTab(tab, "start");
+ yield onCaretAndScopes;
+
+ // Inspect variable.
+ yield openVarPopup(panel, { line: 15, ch: 12 });
+ yield hideVarPopupByScrollingEditor(panel);
+ ok(true, "The variable inspection popup was hidden.");
+
+ ok(bubble._tooltip.isEmpty(),
+ "The variable inspection popup is now empty.");
+ ok(!bubble._markedText,
+ "The marked text in the editor was removed.");
+
+ yield resumeDebuggerThenCloseAndFinish(panel);
+ });
+}
diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-popup-05.js b/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-popup-05.js
new file mode 100644
index 000000000..5f974efdf
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-popup-05.js
@@ -0,0 +1,57 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Tests opening the variable inspection popup on a variable which has a
+ * simple object as the value.
+ */
+
+const TAB_URL = EXAMPLE_URL + "doc_frame-parameters.html";
+
+function test() {
+ Task.spawn(function* () {
+ let options = {
+ source: TAB_URL,
+ line: 1
+ };
+ let [tab,, panel] = yield initDebugger(TAB_URL, options);
+ let win = panel.panelWin;
+ let bubble = win.DebuggerView.VariableBubble;
+ let tooltip = bubble._tooltip.panel;
+
+ function verifyContents() {
+ is(tooltip.querySelectorAll(".variables-view-container").length, 1,
+ "There should be one variables view container added to the tooltip.");
+
+ is(tooltip.querySelectorAll(".variables-view-scope[untitled]").length, 1,
+ "There should be one scope with no header displayed.");
+ is(tooltip.querySelectorAll(".variables-view-variable[untitled]").length, 1,
+ "There should be one variable with no header displayed.");
+
+ is(tooltip.querySelectorAll(".variables-view-property").length, 2,
+ "There should be 2 properties displayed.");
+
+ is(tooltip.querySelectorAll(".variables-view-property .name")[0].getAttribute("value"), "a",
+ "The first property's name is correct.");
+ is(tooltip.querySelectorAll(".variables-view-property .value")[0].getAttribute("value"), "1",
+ "The first property's value is correct.");
+
+ is(tooltip.querySelectorAll(".variables-view-property .name")[1].getAttribute("value"), "__proto__",
+ "The second property's name is correct.");
+ is(tooltip.querySelectorAll(".variables-view-property .value")[1].getAttribute("value"), "Object",
+ "The second property's value is correct.");
+ }
+
+ let onCaretAndScopes = waitForCaretAndScopes(panel, 24);
+ callInTab(tab, "start");
+ yield onCaretAndScopes;
+
+ // Inspect variable.
+ yield openVarPopup(panel, { line: 16, ch: 12 }, true);
+ verifyContents();
+
+ yield resumeDebuggerThenCloseAndFinish(panel);
+ });
+}
diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-popup-06.js b/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-popup-06.js
new file mode 100644
index 000000000..2d0d5f06a
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-popup-06.js
@@ -0,0 +1,83 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Tests opening the variable inspection popup on a variable which has a
+ * complext object as the value.
+ */
+
+const TAB_URL = EXAMPLE_URL + "doc_frame-parameters.html";
+
+function test() {
+ requestLongerTimeout(2);
+ Task.spawn(function* () {
+ let options = {
+ source: TAB_URL,
+ line: 1
+ };
+ let [tab,, panel] = yield initDebugger(TAB_URL, options);
+ let win = panel.panelWin;
+ let bubble = win.DebuggerView.VariableBubble;
+ let tooltip = bubble._tooltip.panel;
+
+ function verifyContents() {
+ is(tooltip.querySelectorAll(".variables-view-container").length, 1,
+ "There should be one variables view container added to the tooltip.");
+
+ is(tooltip.querySelectorAll(".variables-view-scope[untitled]").length, 1,
+ "There should be one scope with no header displayed.");
+ is(tooltip.querySelectorAll(".variables-view-variable[untitled]").length, 1,
+ "There should be one variable with no header displayed.");
+
+ is(tooltip.querySelectorAll(".variables-view-property").length, 7,
+ "There should be 7 properties displayed.");
+
+ is(tooltip.querySelectorAll(".variables-view-property .name")[0].getAttribute("value"), "a",
+ "The first property's name is correct.");
+ is(tooltip.querySelectorAll(".variables-view-property .value")[0].getAttribute("value"), "1",
+ "The first property's value is correct.");
+
+ is(tooltip.querySelectorAll(".variables-view-property .name")[1].getAttribute("value"), "b",
+ "The second property's name is correct.");
+ is(tooltip.querySelectorAll(".variables-view-property .value")[1].getAttribute("value"), "\"beta\"",
+ "The second property's value is correct.");
+
+ is(tooltip.querySelectorAll(".variables-view-property .name")[2].getAttribute("value"), "c",
+ "The third property's name is correct.");
+ is(tooltip.querySelectorAll(".variables-view-property .value")[2].getAttribute("value"), "3",
+ "The third property's value is correct.");
+
+ is(tooltip.querySelectorAll(".variables-view-property .name")[3].getAttribute("value"), "d",
+ "The fourth property's name is correct.");
+ is(tooltip.querySelectorAll(".variables-view-property .value")[3].getAttribute("value"), "false",
+ "The fourth property's value is correct.");
+
+ is(tooltip.querySelectorAll(".variables-view-property .name")[4].getAttribute("value"), "e",
+ "The fifth property's name is correct.");
+ is(tooltip.querySelectorAll(".variables-view-property .value")[4].getAttribute("value"), "null",
+ "The fifth property's value is correct.");
+
+ is(tooltip.querySelectorAll(".variables-view-property .name")[5].getAttribute("value"), "f",
+ "The sixth property's name is correct.");
+ is(tooltip.querySelectorAll(".variables-view-property .value")[5].getAttribute("value"), "undefined",
+ "The sixth property's value is correct.");
+
+ is(tooltip.querySelectorAll(".variables-view-property .name")[6].getAttribute("value"), "__proto__",
+ "The seventh property's name is correct.");
+ is(tooltip.querySelectorAll(".variables-view-property .value")[6].getAttribute("value"), "Object",
+ "The seventh property's value is correct.");
+ }
+
+ let onCaretAndScopes = waitForCaretAndScopes(panel, 24);
+ callInTab(tab, "start");
+ yield onCaretAndScopes;
+
+ // Inspect variable.
+ yield openVarPopup(panel, { line: 17, ch: 12 }, true);
+ verifyContents();
+
+ yield resumeDebuggerThenCloseAndFinish(panel);
+ });
+}
diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-popup-07.js b/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-popup-07.js
new file mode 100644
index 000000000..1340f14c6
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-popup-07.js
@@ -0,0 +1,70 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Tests the variable inspection popup behaves correctly when switching
+ * between simple and complex objects.
+ */
+
+const TAB_URL = EXAMPLE_URL + "doc_frame-parameters.html";
+
+function test() {
+ Task.spawn(function* () {
+ let options = {
+ source: TAB_URL,
+ line: 1
+ };
+ let [tab,, panel] = yield initDebugger(TAB_URL, options);
+ let win = panel.panelWin;
+ let bubble = win.DebuggerView.VariableBubble;
+ let tooltip = bubble._tooltip.panel;
+
+ function verifySimpleContents(textContent, className) {
+ is(tooltip.querySelectorAll(".variables-view-container").length, 0,
+ "There should be no variables view container added to the tooltip.");
+ is(tooltip.querySelectorAll(".devtools-tooltip-simple-text").length, 1,
+ "There should be one simple text node added to the tooltip.");
+
+ is(tooltip.querySelector(".devtools-tooltip-simple-text").textContent, textContent,
+ "The inspected property's value is correct.");
+ ok(tooltip.querySelector(".devtools-tooltip-simple-text").className.includes(className),
+ "The inspected property's value is colorized correctly.");
+ }
+
+ function verifyComplexContents(propertyCount) {
+ is(tooltip.querySelectorAll(".variables-view-container").length, 1,
+ "There should be one variables view container added to the tooltip.");
+ is(tooltip.querySelectorAll(".devtools-tooltip-simple-text").length, 0,
+ "There should be no simple text node added to the tooltip.");
+
+ is(tooltip.querySelectorAll(".variables-view-scope[untitled]").length, 1,
+ "There should be one scope with no header displayed.");
+ is(tooltip.querySelectorAll(".variables-view-variable[untitled]").length, 1,
+ "There should be one variable with no header displayed.");
+
+ ok(tooltip.querySelectorAll(".variables-view-property").length >= propertyCount,
+ "There should be some properties displayed.");
+ }
+
+ let onCaretAndScopes = waitForCaretAndScopes(panel, 24);
+ callInTab(tab, "start");
+ yield onCaretAndScopes;
+
+ // Inspect variables.
+ yield openVarPopup(panel, { line: 15, ch: 12 });
+ verifySimpleContents("1", "token-number");
+
+ yield reopenVarPopup(panel, { line: 16, ch: 12 }, true);
+ verifyComplexContents(2);
+
+ yield reopenVarPopup(panel, { line: 19, ch: 10 });
+ verifySimpleContents("42", "token-number");
+
+ yield reopenVarPopup(panel, { line: 31, ch: 10 }, true);
+ verifyComplexContents(100);
+
+ yield resumeDebuggerThenCloseAndFinish(panel);
+ });
+}
diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-popup-08.js b/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-popup-08.js
new file mode 100644
index 000000000..d3ef69e7e
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-popup-08.js
@@ -0,0 +1,75 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Tests opening inspecting variables works across scopes.
+ */
+
+const TAB_URL = EXAMPLE_URL + "doc_scope-variable.html";
+
+function test() {
+ Task.spawn(function* () {
+ let options = {
+ source: TAB_URL,
+ line: 1
+ };
+ let [tab,, panel] = yield initDebugger(TAB_URL, options);
+ let win = panel.panelWin;
+ let events = win.EVENTS;
+ let editor = win.DebuggerView.editor;
+ let frames = win.DebuggerView.StackFrames;
+ let bubble = win.DebuggerView.VariableBubble;
+ let tooltip = bubble._tooltip.panel;
+
+ function verifyContents(textContent, className) {
+ is(tooltip.querySelectorAll(".variables-view-container").length, 0,
+ "There should be no variables view containers added to the tooltip.");
+ is(tooltip.querySelectorAll(".devtools-tooltip-simple-text").length, 1,
+ "There should be a simple text node added to the tooltip instead.");
+
+ is(tooltip.querySelector(".devtools-tooltip-simple-text").textContent, textContent,
+ "The inspected property's value is correct.");
+ ok(tooltip.querySelector(".devtools-tooltip-simple-text").className.includes(className),
+ "The inspected property's value is colorized correctly.");
+ }
+
+ function checkView(selectedFrame, caretLine) {
+ is(win.gThreadClient.state, "paused",
+ "Should only be getting stack frames while paused.");
+ is(frames.itemCount, 2,
+ "Should have two frames.");
+ is(frames.selectedDepth, selectedFrame,
+ "The correct frame is selected in the widget.");
+ ok(isCaretPos(panel, caretLine),
+ "Editor caret location is correct.");
+ }
+
+ let onCaretAndScopes = waitForCaretAndScopes(panel, 20);
+ callInTab(tab, "test");
+ yield onCaretAndScopes;
+
+ checkView(0, 20);
+
+ // Inspect variable in topmost frame.
+ yield openVarPopup(panel, { line: 18, ch: 12 });
+ verifyContents("\"second scope\"", "token-string");
+ checkView(0, 20);
+
+ // Hide the popup and change the frame.
+ yield hideVarPopup(panel);
+
+ let updatedFrame = waitForDebuggerEvents(panel, events.FETCHED_SCOPES);
+ frames.selectedDepth = 1;
+ yield updatedFrame;
+ checkView(1, 15);
+
+ // Inspect variable in oldest frame.
+ yield openVarPopup(panel, { line: 13, ch: 12 });
+ verifyContents("\"first scope\"", "token-string");
+ checkView(1, 15);
+
+ yield resumeDebuggerThenCloseAndFinish(panel);
+ });
+}
diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-popup-09.js b/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-popup-09.js
new file mode 100644
index 000000000..41824cb76
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-popup-09.js
@@ -0,0 +1,39 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Tests opening inspecting variables works across scopes.
+ */
+
+const TAB_URL = EXAMPLE_URL + "doc_scope-variable-3.html";
+
+function test() {
+ Task.spawn(function* () {
+ let options = {
+ source: TAB_URL,
+ line: 1
+ };
+ let [tab,, panel] = yield initDebugger(TAB_URL, options);
+ let win = panel.panelWin;
+ let bubble = win.DebuggerView.VariableBubble;
+ let tooltip = bubble._tooltip.panel;
+
+ let onCaretAndScopes = waitForCaretAndScopes(panel, 15);
+ callInTab(tab, "test");
+ yield onCaretAndScopes;
+
+ yield openVarPopup(panel, { line: 12, ch: 10 });
+ ok(true, "The variable inspection popup was shown for the real variable.");
+
+ once(tooltip, "popupshown").then(() => {
+ ok(false, "The variable inspection popup shouldn't have been opened.");
+ });
+
+ reopenVarPopup(panel, { line: 18, ch: 10 });
+ yield waitForTime(1000);
+
+ yield resumeDebuggerThenCloseAndFinish(panel);
+ });
+}
diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-popup-10.js b/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-popup-10.js
new file mode 100644
index 000000000..15dd02c44
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-popup-10.js
@@ -0,0 +1,67 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Makes sure the source editor's scroll location doesn't change when
+ * a variable inspection popup is opened and a watch expression is
+ * also evaluated at the same time.
+ */
+
+const TAB_URL = EXAMPLE_URL + "doc_frame-parameters.html";
+
+function test() {
+ Task.spawn(function* () {
+ let options = {
+ source: TAB_URL,
+ line: 1
+ };
+ let [tab,, panel] = yield initDebugger(TAB_URL, options);
+ let win = panel.panelWin;
+ let events = win.EVENTS;
+ let editor = win.DebuggerView.editor;
+ let editorContainer = win.document.getElementById("editor");
+ let bubble = win.DebuggerView.VariableBubble;
+ let expressions = win.DebuggerView.WatchExpressions;
+ let tooltip = bubble._tooltip.panel;
+
+ let onCaretAndScopes = waitForCaretAndScopes(panel, 24);
+ callInTab(tab, "start");
+ yield onCaretAndScopes;
+
+ let expressionsEvaluated = waitForDebuggerEvents(panel, events.FETCHED_WATCH_EXPRESSIONS);
+ expressions.addExpression("this");
+ editor.focus();
+ yield expressionsEvaluated;
+
+ // Scroll to the top of the editor and inspect variables.
+ let breakpointScrollPosition = editor.getScrollInfo().top;
+ editor.setFirstVisibleLine(0);
+ let topmostScrollPosition = editor.getScrollInfo().top;
+
+ ok(topmostScrollPosition < breakpointScrollPosition,
+ "The editor is now scrolled to the top (0).");
+ is(editor.getFirstVisibleLine(), 0,
+ "The editor is now scrolled to the top (1).");
+
+ let failPopup = () => ok(false, "The popup has got unexpectedly hidden.");
+ let failScroll = () => ok(false, "The editor has got unexpectedly scrolled.");
+ tooltip.addEventListener("popuphiding", failPopup);
+ editorContainer.addEventListener("scroll", failScroll);
+ editor.on("scroll", () => {
+ if (editor.getScrollInfo().top > topmostScrollPosition) {
+ ok(false, "The editor scrolled back to the breakpoint location.");
+ }
+ });
+
+ expressionsEvaluated = waitForDebuggerEvents(panel, events.FETCHED_WATCH_EXPRESSIONS);
+ yield openVarPopup(panel, { line: 14, ch: 15 });
+ yield expressionsEvaluated;
+
+ tooltip.removeEventListener("popuphiding", failPopup);
+ editorContainer.removeEventListener("scroll", failScroll);
+
+ yield resumeDebuggerThenCloseAndFinish(panel);
+ });
+}
diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-popup-11.js b/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-popup-11.js
new file mode 100644
index 000000000..57d31c727
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-popup-11.js
@@ -0,0 +1,84 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Tests that the watch expression button is added in variable view popup.
+ */
+
+const TAB_URL = EXAMPLE_URL + "doc_watch-expression-button.html";
+
+function test() {
+ Task.spawn(function* () {
+ let options = {
+ source: TAB_URL,
+ line: 1
+ };
+ let [tab,, panel] = yield initDebugger(TAB_URL, options);
+ let win = panel.panelWin;
+ let events = win.EVENTS;
+ let watch = win.DebuggerView.WatchExpressions;
+ let bubble = win.DebuggerView.VariableBubble;
+ let tooltip = bubble._tooltip.panel;
+
+ let label = win.L10N.getStr("addWatchExpressionButton");
+ let className = "dbg-expression-button";
+
+ function testExpressionButton(aLabel, aClassName, aExpression) {
+ ok(tooltip.querySelector("button"),
+ "There should be a button available in variable view popup.");
+ is(tooltip.querySelector("button").label, aLabel,
+ "The button available is labeled correctly.");
+ is(tooltip.querySelector("button").className, aClassName,
+ "The button available is styled correctly.");
+
+ tooltip.querySelector("button").click();
+
+ ok(!tooltip.querySelector("button"),
+ "There should be no button available in variable view popup.");
+ ok(watch.getItemAtIndex(0),
+ "The expression at index 0 should be available.");
+ is(watch.getItemAtIndex(0).attachment.initialExpression, aExpression,
+ "The expression at index 0 is correct.");
+ }
+
+ let onCaretAndScopes = waitForCaretAndScopes(panel, 19);
+ callInTab(tab, "start");
+ yield onCaretAndScopes;
+
+ // Inspect primitive value variable.
+ yield openVarPopup(panel, { line: 15, ch: 12 });
+ let popupHiding = once(tooltip, "popuphiding");
+ let expressionsEvaluated = waitForDebuggerEvents(panel, events.FETCHED_WATCH_EXPRESSIONS);
+ testExpressionButton(label, className, "a");
+ yield promise.all([popupHiding, expressionsEvaluated]);
+ ok(true, "The new watch expressions were re-evaluated and the panel got hidden (1).");
+
+ // Inspect non primitive value variable.
+ expressionsEvaluated = waitForDebuggerEvents(panel, events.FETCHED_WATCH_EXPRESSIONS);
+ yield openVarPopup(panel, { line: 16, ch: 12 }, true);
+ yield expressionsEvaluated;
+ ok(true, "The watch expressions were re-evaluated when a new panel opened (1).");
+
+ popupHiding = once(tooltip, "popuphiding");
+ expressionsEvaluated = waitForDebuggerEvents(panel, events.FETCHED_WATCH_EXPRESSIONS);
+ testExpressionButton(label, className, "b");
+ yield promise.all([popupHiding, expressionsEvaluated]);
+ ok(true, "The new watch expressions were re-evaluated and the panel got hidden (2).");
+
+ // Inspect property of an object.
+ expressionsEvaluated = waitForDebuggerEvents(panel, events.FETCHED_WATCH_EXPRESSIONS);
+ yield openVarPopup(panel, { line: 17, ch: 10 });
+ yield expressionsEvaluated;
+ ok(true, "The watch expressions were re-evaluated when a new panel opened (2).");
+
+ popupHiding = once(tooltip, "popuphiding");
+ expressionsEvaluated = waitForDebuggerEvents(panel, events.FETCHED_WATCH_EXPRESSIONS);
+ testExpressionButton(label, className, "b.a");
+ yield promise.all([popupHiding, expressionsEvaluated]);
+ ok(true, "The new watch expressions were re-evaluated and the panel got hidden (3).");
+
+ yield resumeDebuggerThenCloseAndFinish(panel);
+ });
+}
diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-popup-12.js b/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-popup-12.js
new file mode 100644
index 000000000..588276434
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-popup-12.js
@@ -0,0 +1,77 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Tests that the clicking "Watch" button twice, for the same expression, only adds it
+ * once.
+ */
+
+const TAB_URL = EXAMPLE_URL + "doc_watch-expression-button.html";
+
+function test() {
+ Task.spawn(function* () {
+ let options = {
+ source: TAB_URL,
+ line: 1
+ };
+ let [tab,, panel] = yield initDebugger(TAB_URL, options);
+ let win = panel.panelWin;
+ let events = win.EVENTS;
+ let watch = win.DebuggerView.WatchExpressions;
+ let bubble = win.DebuggerView.VariableBubble;
+ let tooltip = bubble._tooltip.panel;
+
+ function verifyContent(aExpression, aItemCount) {
+
+ ok(watch.getItemAtIndex(0),
+ "The expression at index 0 should be available.");
+ is(watch.getItemAtIndex(0).attachment.initialExpression, aExpression,
+ "The expression at index 0 is correct.");
+ is(watch.itemCount, aItemCount,
+ "The expression count is correct.");
+ }
+
+ let onCaretAndScopes = waitForCaretAndScopes(panel, 19);
+ callInTab(tab, "start");
+ yield onCaretAndScopes;
+
+ // Inspect primitive value variable.
+ yield openVarPopup(panel, { line: 15, ch: 12 });
+ let popupHiding = once(tooltip, "popuphiding");
+ let expressionsEvaluated = waitForDebuggerEvents(panel, events.FETCHED_WATCH_EXPRESSIONS);
+ tooltip.querySelector("button").click();
+ verifyContent("a", 1);
+ yield promise.all([popupHiding, expressionsEvaluated]);
+ ok(true, "The new watch expressions were re-evaluated and the panel got hidden (1).");
+
+ // Inspect property of an object.
+ expressionsEvaluated = waitForDebuggerEvents(panel, events.FETCHED_WATCH_EXPRESSIONS);
+ yield openVarPopup(panel, { line: 17, ch: 10 });
+ yield expressionsEvaluated;
+ ok(true, "The watch expressions were re-evaluated when a new panel opened (1).");
+
+ popupHiding = once(tooltip, "popuphiding");
+ expressionsEvaluated = waitForDebuggerEvents(panel, events.FETCHED_WATCH_EXPRESSIONS);
+ tooltip.querySelector("button").click();
+ verifyContent("b.a", 2);
+ yield promise.all([popupHiding, expressionsEvaluated]);
+ ok(true, "The new watch expressions were re-evaluated and the panel got hidden (2).");
+
+ // Re-inspect primitive value variable.
+ expressionsEvaluated = waitForDebuggerEvents(panel, events.FETCHED_WATCH_EXPRESSIONS);
+ yield openVarPopup(panel, { line: 15, ch: 12 });
+ yield expressionsEvaluated;
+ ok(true, "The watch expressions were re-evaluated when a new panel opened (2).");
+
+ popupHiding = once(tooltip, "popuphiding");
+ expressionsEvaluated = waitForDebuggerEvents(panel, events.FETCHED_WATCH_EXPRESSIONS);
+ tooltip.querySelector("button").click();
+ verifyContent("b.a", 2);
+ yield promise.all([popupHiding, expressionsEvaluated]);
+ ok(true, "The new watch expressions were re-evaluated and the panel got hidden (3).");
+
+ yield resumeDebuggerThenCloseAndFinish(panel);
+ });
+}
diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-popup-13.js b/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-popup-13.js
new file mode 100644
index 000000000..e8769ced7
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-popup-13.js
@@ -0,0 +1,68 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Tests that the variable inspection popup has inspector links for DOMNode
+ * properties and that the popup closes when the link is clicked
+ */
+
+const TAB_URL = EXAMPLE_URL + "doc_domnode-variables.html";
+
+function test() {
+ Task.spawn(function* () {
+ let options = {
+ source: TAB_URL,
+ line: 1
+ };
+ let [tab,, panel] = yield initDebugger(TAB_URL, options);
+ let win = panel.panelWin;
+ let bubble = win.DebuggerView.VariableBubble;
+ let tooltip = bubble._tooltip.panel;
+ let toolbox = gDevTools.getToolbox(panel.target);
+
+ function getDomNodeInTooltip(propertyName) {
+ let domNodeProperties = tooltip.querySelectorAll(".token-domnode");
+ for (let prop of domNodeProperties) {
+ let propName = prop.parentNode.querySelector(".name");
+ if (propName.getAttribute("value") === propertyName) {
+ ok(true, "DOMNode " + propertyName + " was found in the tooltip");
+ return prop;
+ }
+ }
+ ok(false, "DOMNode " + propertyName + " wasn't found in the tooltip");
+ }
+
+ let onCaretAndScopes = waitForCaretAndScopes(panel, 19);
+ callInTab(tab, "start");
+ yield onCaretAndScopes;
+
+ // Inspect the div DOM variable.
+ yield openVarPopup(panel, { line: 17, ch: 38 }, true);
+ let property = getDomNodeInTooltip("firstElementChild");
+
+ // Simulate mouseover on the property value
+ let highlighted = once(toolbox, "node-highlight");
+ EventUtils.sendMouseEvent({ type: "mouseover" }, property,
+ property.ownerDocument.defaultView);
+ yield highlighted;
+ ok(true, "The node-highlight event was fired on hover of the DOMNode");
+
+ // Simulate a click on the "select in inspector" button
+ let button = property.parentNode.querySelector(".variables-view-open-inspector");
+ ok(button, "The select-in-inspector button is present");
+ let inspectorSelected = once(toolbox, "inspector-selected");
+ EventUtils.sendMouseEvent({ type: "mousedown" }, button,
+ button.ownerDocument.defaultView);
+ yield inspectorSelected;
+ ok(true, "The inspector got selected when clicked on the select-in-inspector");
+
+ // Make sure the inspector's initialization is finalized before ending the test
+ // Listening to the event *after* triggering the switch to the inspector isn't
+ // a problem as the inspector is asynchronously loaded.
+ yield once(toolbox.getPanel("inspector"), "inspector-updated");
+
+ yield resumeDebuggerThenCloseAndFinish(panel);
+ });
+}
diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-popup-14.js b/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-popup-14.js
new file mode 100644
index 000000000..b1cefc8b8
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-popup-14.js
@@ -0,0 +1,55 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Tests that the variable inspection popup is hidden when
+ * selecting text in the editor.
+ */
+
+const TAB_URL = EXAMPLE_URL + "doc_frame-parameters.html";
+
+function test() {
+ Task.spawn(function* () {
+ let options = {
+ source: TAB_URL,
+ line: 1
+ };
+ let [tab,, panel] = yield initDebugger(TAB_URL, options);
+ let win = panel.panelWin;
+ let bubble = win.DebuggerView.VariableBubble;
+
+ let onCaretAndScopes = waitForCaretAndScopes(panel, 24);
+ callInTab(tab, "start");
+ yield onCaretAndScopes;
+
+ // Select some text.
+ let cursor = win.DebuggerView.editor.getOffset({ line: 15, ch: 12 });
+ let [ anchor, head ] = win.DebuggerView.editor.getPosition(
+ cursor,
+ cursor + 3
+ );
+ win.DebuggerView.editor.setSelection(anchor, head);
+
+ // Try to Inspect variable during selection.
+ let popupOpened = yield intendOpenVarPopup(panel, { line: 15, ch: 12 }, true);
+
+ // Ensure the bubble is not there
+ ok(!popupOpened,
+ "The popup is not opened");
+ ok(!bubble._markedText,
+ "The marked text in the editor is not there.");
+
+ // Try to Inspect variable after selection.
+ popupOpened = yield intendOpenVarPopup(panel, { line: 15, ch: 12 }, false);
+
+ // Ensure the bubble is not there
+ ok(popupOpened,
+ "The popup is opened");
+ ok(bubble._markedText,
+ "The marked text in the editor is there.");
+
+ yield resumeDebuggerThenCloseAndFinish(panel);
+ });
+}
diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-popup-15.js b/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-popup-15.js
new file mode 100644
index 000000000..01c72df8c
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-popup-15.js
@@ -0,0 +1,39 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Tests opening the variable inspection popup directly on literals.
+ */
+
+const TAB_URL = EXAMPLE_URL + "doc_frame-parameters.html";
+
+function test() {
+ Task.spawn(function* () {
+ let options = {
+ source: TAB_URL,
+ line: 1
+ };
+ let [tab,, panel] = yield initDebugger(TAB_URL, options);
+ let win = panel.panelWin;
+ let bubble = win.DebuggerView.VariableBubble;
+ let tooltip = bubble._tooltip.panel;
+
+ let onCaretAndScopes = waitForCaretAndScopes(panel, 24);
+ callInTab(tab, "start");
+ yield onCaretAndScopes;
+
+ yield openVarPopup(panel, { line: 15, ch: 12 });
+ ok(true, "The variable inspection popup was shown for the real variable.");
+
+ once(tooltip, "popupshown").then(() => {
+ ok(false, "The variable inspection popup shouldn't have been opened.");
+ });
+
+ reopenVarPopup(panel, { line: 17, ch: 27 });
+ yield waitForTime(1000);
+
+ yield resumeDebuggerThenCloseAndFinish(panel);
+ });
+}
diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-popup-16.js b/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-popup-16.js
new file mode 100644
index 000000000..055517810
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-popup-16.js
@@ -0,0 +1,77 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+requestLongerTimeout(2);
+
+/**
+ * Tests if opening the variables inspection popup preserves the highlighting
+ * associated with the currently debugged line.
+ */
+
+const TAB_URL = EXAMPLE_URL + "doc_recursion-stack.html";
+
+function test() {
+ Task.spawn(function* () {
+ let options = {
+ source: TAB_URL,
+ line: 1
+ };
+ let [tab,, panel] = yield initDebugger(TAB_URL, options);
+ let win = panel.panelWin;
+ let events = win.EVENTS;
+ let editor = win.DebuggerView.editor;
+ let frames = win.DebuggerView.StackFrames;
+ let variables = win.DebuggerView.Variables;
+ let bubble = win.DebuggerView.VariableBubble;
+ let tooltip = bubble._tooltip.panel;
+
+ function checkView(selectedFrame, caretLine, debugLine = caretLine) {
+ let deferred = promise.defer();
+
+ is(win.gThreadClient.state, "paused",
+ "Should only be getting stack frames while paused.");
+ is(frames.itemCount, 25,
+ "Should have 25 frames.");
+ is(frames.selectedDepth, selectedFrame,
+ "The correct frame is selected in the widget.");
+ ok(isCaretPos(panel, caretLine),
+ "Editor caret location is correct.");
+
+ // The editor's debug location takes a tick to update.
+ executeSoon(() => {
+ ok(isCaretPos(panel, caretLine), "Editor caret location is still correct.");
+ ok(isDebugPos(panel, debugLine), "Editor debug location is correct.");
+ deferred.resolve();
+ });
+
+ return deferred.promise;
+ }
+
+ function expandGlobalScope() {
+ let globalScope = variables.getScopeAtIndex(2);
+ is(globalScope.expanded, false,
+ "The globalScope should not be expanded yet.");
+
+ let finished = waitForDebuggerEvents(panel, events.FETCHED_VARIABLES);
+ globalScope.expand();
+ return finished;
+ }
+
+ let onCaretAndScopes = waitForCaretAndScopes(panel, 26);
+ callInTab(tab, "recurse");
+ yield onCaretAndScopes;
+
+ yield checkView(0, 26);
+
+ yield expandGlobalScope();
+ yield checkView(0, 26);
+
+ // Inspect variable in topmost frame.
+ yield openVarPopup(panel, { line: 26, ch: 11 });
+ yield checkView(0, 26);
+
+ yield resumeDebuggerThenCloseAndFinish(panel);
+ });
+}
diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-popup-17.js b/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-popup-17.js
new file mode 100644
index 000000000..bdfe1a42b
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-popup-17.js
@@ -0,0 +1,80 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Tests opening the variable inspection popup while stopped at a debugger statement,
+ * clicking "step in" and verifying that the popup is gone.
+ */
+
+const TAB_URL = EXAMPLE_URL + "doc_with-frame.html";
+
+let gTab, gPanel, gDebugger;
+let actions, gSources, gVariables;
+
+function test() {
+ let options = {
+ source: TAB_URL,
+ line: 1
+ };
+ initDebugger(TAB_URL, options).then(([aTab,, aPanel]) => {
+ gTab = aTab;
+ gPanel = aPanel;
+ gDebugger = gPanel.panelWin;
+ actions = bindActionCreators(gPanel);
+ gSources = gDebugger.DebuggerView.Sources;
+ gVariables = gDebugger.DebuggerView.Variables;
+ let bubble = gDebugger.DebuggerView.VariableBubble;
+ let tooltip = bubble._tooltip.panel;
+ let testPopupHiding = Task.async(function* () {
+ yield addBreakpoint();
+ yield ensureThreadClientState(gPanel, "resumed");
+ yield pauseDebuggee();
+ yield openVarPopup(gPanel, { line: 20, ch: 17 });
+ is(tooltip.querySelectorAll(".devtools-tooltip-simple-text").length, 1,
+ "The popup should be open with a simple text entry");
+ // Now we're stopped at a breakpoint with an open popup
+ // we'll send a keypress and check if the popup closes
+ executeSoon(() => EventUtils.synthesizeKey("VK_F11", {}));
+ // The keypress should cause one resumed event and one paused event
+ yield waitForThreadEvents(gPanel, "resumed");
+ yield waitForThreadEvents(gPanel, "paused");
+ // Here's the state we're actually interested in checking..
+ checkVariablePopupClosed(bubble);
+ yield resumeDebuggerThenCloseAndFinish(gPanel);
+ });
+ testPopupHiding();
+ });
+}
+
+function addBreakpoint() {
+ return actions.addBreakpoint({ actor: gSources.selectedValue, line: 21 });
+}
+
+function pauseDebuggee() {
+ generateMouseClickInTab(gTab, "content.document.querySelector('button')");
+
+ // The first 'with' scope should be expanded by default, but the
+ // variables haven't been fetched yet. This is how 'with' scopes work.
+ return promise.all([
+ waitForDebuggerEvents(gPanel, gDebugger.EVENTS.FETCHED_SCOPES),
+ waitForDebuggerEvents(gPanel, gDebugger.EVENTS.FETCHED_VARIABLES)
+ ]);
+}
+
+function checkVariablePopupClosed(bubble) {
+ ok(!bubble.contentsShown(),
+ "When stepping, popup should close and be hidden.");
+ ok(bubble._tooltip.isEmpty(),
+ "The variable inspection popup should now be empty.");
+ ok(!bubble._markedText,
+ "The marked text in the editor was removed.");
+}
+
+registerCleanupFunction(function () {
+ gTab = null;
+ gPanel = null;
+ gDebugger = null;
+ actions = null;
+ gSources = null;
+ gVariables = null;
+});
diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-reexpand-01.js b/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-reexpand-01.js
new file mode 100644
index 000000000..4b68fb052
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-reexpand-01.js
@@ -0,0 +1,211 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Make sure that the variables view correctly re-expands nodes after pauses.
+ */
+
+const TAB_URL = EXAMPLE_URL + "doc_with-frame.html";
+
+function test() {
+ // Debug test slaves are a bit slow at this test.
+ requestLongerTimeout(4);
+
+ let options = {
+ source: TAB_URL,
+ line: 1
+ };
+ initDebugger(TAB_URL, options).then(([aTab,, aPanel]) => {
+ const gTab = aTab;
+ const gPanel = aPanel;
+ const gDebugger = gPanel.panelWin;
+ const gSources = gDebugger.DebuggerView.Sources;
+ const gVariables = gDebugger.DebuggerView.Variables;
+ const queries = gDebugger.require("./content/queries");
+ const getState = gDebugger.DebuggerController.getState;
+ const actions = bindActionCreators(gPanel);
+
+ // Always expand all items between pauses except 'window' variables.
+ gVariables.commitHierarchyIgnoredItems = Object.create(null, { window: { value: true } });
+
+ function addBreakpoint() {
+ return actions.addBreakpoint({
+ actor: gSources.selectedValue,
+ line: 21
+ });
+ }
+
+ function pauseDebuggee() {
+ generateMouseClickInTab(gTab, "content.document.querySelector('button')");
+
+ // The first 'with' scope should be expanded by default, but the
+ // variables haven't been fetched yet. This is how 'with' scopes work.
+ return promise.all([
+ waitForDebuggerEvents(gPanel, gDebugger.EVENTS.FETCHED_SCOPES),
+ waitForDebuggerEvents(gPanel, gDebugger.EVENTS.FETCHED_VARIABLES)
+ ]);
+ }
+
+ function stepInDebuggee() {
+ // Spin the event loop before causing the debuggee to pause, to allow
+ // this function to return first.
+ executeSoon(() => {
+ EventUtils.sendMouseEvent({ type: "mousedown" },
+ gDebugger.document.querySelector("#step-in"),
+ gDebugger);
+ });
+
+ return promise.all([
+ waitForDebuggerEvents(gPanel, gDebugger.EVENTS.FETCHED_SCOPES, 1),
+ waitForDebuggerEvents(gPanel, gDebugger.EVENTS.FETCHED_VARIABLES, 3),
+ waitForDebuggerEvents(gPanel, gDebugger.EVENTS.FETCHED_PROPERTIES, 1),
+ ]);
+ }
+
+ function testVariablesExpand() {
+ let localScope = gVariables.getScopeAtIndex(0);
+ let withScope = gVariables.getScopeAtIndex(1);
+ let functionScope = gVariables.getScopeAtIndex(2);
+ let globalLexicalScope = gVariables.getScopeAtIndex(3);
+ let globalScope = gVariables.getScopeAtIndex(4);
+
+ let thisVar = localScope.get("this");
+ let windowVar = thisVar.get("window");
+
+ is(localScope.target.querySelector(".arrow").hasAttribute("open"), true,
+ "The localScope arrow should still be expanded.");
+ is(withScope.target.querySelector(".arrow").hasAttribute("open"), true,
+ "The withScope arrow should still be expanded.");
+ is(functionScope.target.querySelector(".arrow").hasAttribute("open"), true,
+ "The functionScope arrow should still be expanded.");
+ is(globalLexicalScope.target.querySelector(".arrow").hasAttribute("open"), true,
+ "The globalLexicalScope arrow should still be expanded.");
+ is(globalScope.target.querySelector(".arrow").hasAttribute("open"), true,
+ "The globalScope arrow should still be expanded.");
+ is(thisVar.target.querySelector(".arrow").hasAttribute("open"), true,
+ "The thisVar arrow should still be expanded.");
+ is(windowVar.target.querySelector(".arrow").hasAttribute("open"), false,
+ "The windowVar arrow should not be expanded.");
+
+ is(localScope.target.querySelector(".variables-view-element-details").hasAttribute("open"), true,
+ "The localScope enumerables should still be expanded.");
+ is(withScope.target.querySelector(".variables-view-element-details").hasAttribute("open"), true,
+ "The withScope enumerables should still be expanded.");
+ is(functionScope.target.querySelector(".variables-view-element-details").hasAttribute("open"), true,
+ "The functionScope enumerables should still be expanded.");
+ is(globalLexicalScope.target.querySelector(".variables-view-element-details").hasAttribute("open"), true,
+ "The globalLexicalScope enumerables should still be expanded.");
+ is(globalScope.target.querySelector(".variables-view-element-details").hasAttribute("open"), true,
+ "The globalScope enumerables should still be expanded.");
+ is(thisVar.target.querySelector(".variables-view-element-details").hasAttribute("open"), true,
+ "The thisVar enumerables should still be expanded.");
+ is(windowVar.target.querySelector(".variables-view-element-details").hasAttribute("open"), false,
+ "The windowVar enumerables should not be expanded.");
+
+ is(localScope.expanded, true,
+ "The localScope expanded getter should return true.");
+ is(withScope.expanded, true,
+ "The withScope expanded getter should return true.");
+ is(functionScope.expanded, true,
+ "The functionScope expanded getter should return true.");
+ is(globalLexicalScope.expanded, true,
+ "The globalScope expanded getter should return true.");
+ is(globalScope.expanded, true,
+ "The globalScope expanded getter should return true.");
+ is(thisVar.expanded, true,
+ "The thisVar expanded getter should return true.");
+ is(windowVar.expanded, false,
+ "The windowVar expanded getter should return true.");
+ }
+
+ function prepareVariablesAndProperties() {
+ let deferred = promise.defer();
+
+ let localScope = gVariables.getScopeAtIndex(0);
+ let withScope = gVariables.getScopeAtIndex(1);
+ let functionScope = gVariables.getScopeAtIndex(2);
+ let globalLexicalScope = gVariables.getScopeAtIndex(3);
+ let globalScope = gVariables.getScopeAtIndex(4);
+
+ is(localScope.expanded, true,
+ "The localScope should be expanded.");
+ is(withScope.expanded, false,
+ "The withScope should not be expanded yet.");
+ is(functionScope.expanded, false,
+ "The functionScope should not be expanded yet.");
+ is(globalLexicalScope.expanded, false,
+ "The globalLexicalScope should not be expanded yet.");
+ is(globalScope.expanded, false,
+ "The globalScope should not be expanded yet.");
+
+ // Wait for only two events to be triggered, because the Function scope is
+ // an environment to which scope arguments and variables are already attached.
+ waitForDebuggerEvents(gPanel, gDebugger.EVENTS.FETCHED_VARIABLES, 2).then(() => {
+ is(localScope.expanded, true,
+ "The localScope should now be expanded.");
+ is(withScope.expanded, true,
+ "The withScope should now be expanded.");
+ is(functionScope.expanded, true,
+ "The functionScope should now be expanded.");
+ is(globalLexicalScope.expanded, true,
+ "The globalLexicalScope should now be expanded.");
+ is(globalScope.expanded, true,
+ "The globalScope should now be expanded.");
+
+ let thisVar = localScope.get("this");
+
+ waitForDebuggerEvents(gPanel, gDebugger.EVENTS.FETCHED_PROPERTIES, 1).then(() => {
+ let windowVar = thisVar.get("window");
+
+ waitForDebuggerEvents(gPanel, gDebugger.EVENTS.FETCHED_PROPERTIES, 1).then(() => {
+ let documentVar = windowVar.get("document");
+
+ waitForDebuggerEvents(gPanel, gDebugger.EVENTS.FETCHED_PROPERTIES, 1).then(() => {
+ let locationVar = documentVar.get("location");
+
+ waitForDebuggerEvents(gPanel, gDebugger.EVENTS.FETCHED_PROPERTIES, 1).then(() => {
+ is(thisVar.expanded, true,
+ "The local scope 'this' should be expanded.");
+ is(windowVar.expanded, true,
+ "The local scope 'this.window' should be expanded.");
+ is(documentVar.expanded, true,
+ "The local scope 'this.window.document' should be expanded.");
+ is(locationVar.expanded, true,
+ "The local scope 'this.window.document.location' should be expanded.");
+
+ deferred.resolve();
+ });
+
+ locationVar.expand();
+ });
+
+ documentVar.expand();
+ });
+
+ windowVar.expand();
+ });
+
+ thisVar.expand();
+ });
+
+ withScope.expand();
+ functionScope.expand();
+ globalLexicalScope.expand();
+ globalScope.expand();
+
+ return deferred.promise;
+ }
+
+ Task.spawn(function* () {
+ yield addBreakpoint();
+ yield ensureThreadClientState(gPanel, "resumed");
+ yield pauseDebuggee();
+ yield prepareVariablesAndProperties();
+ yield stepInDebuggee();
+ yield testVariablesExpand();
+ resumeDebuggerThenCloseAndFinish(gPanel);
+ });
+ });
+}
diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-reexpand-02.js b/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-reexpand-02.js
new file mode 100644
index 000000000..e292c6804
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-reexpand-02.js
@@ -0,0 +1,226 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Make sure that the variables view correctly re-expands nodes after pauses,
+ * with the caveat that there are no ignored items in the hierarchy.
+ */
+
+const TAB_URL = EXAMPLE_URL + "doc_with-frame.html";
+
+function test() {
+ // Debug test slaves are a bit slow at this test.
+ requestLongerTimeout(4);
+
+ let options = {
+ source: TAB_URL,
+ line: 1
+ };
+ initDebugger(TAB_URL, options).then(([aTab,, aPanel]) => {
+ const gTab = aTab;
+ const gPanel = aPanel;
+ const gDebugger = gPanel.panelWin;
+ const gSources = gDebugger.DebuggerView.Sources;
+ const gVariables = gDebugger.DebuggerView.Variables;
+ const queries = gDebugger.require("./content/queries");
+ const getState = gDebugger.DebuggerController.getState;
+ const actions = bindActionCreators(gPanel);
+
+ // Always expand all items between pauses.
+ gVariables.commitHierarchyIgnoredItems = Object.create(null);
+
+ function addBreakpoint() {
+ return actions.addBreakpoint({
+ actor: gSources.selectedValue,
+ line: 21
+ });
+ }
+
+ function pauseDebuggee() {
+ generateMouseClickInTab(gTab, "content.document.querySelector('button')");
+
+ // The first 'with' scope should be expanded by default, but the
+ // variables haven't been fetched yet. This is how 'with' scopes work.
+ return promise.all([
+ waitForDebuggerEvents(gPanel, gDebugger.EVENTS.FETCHED_SCOPES),
+ waitForDebuggerEvents(gPanel, gDebugger.EVENTS.FETCHED_VARIABLES)
+ ]);
+ }
+
+ function stepInDebuggee() {
+ // Spin the event loop before causing the debuggee to pause, to allow
+ // this function to return first.
+ executeSoon(() => {
+ EventUtils.sendMouseEvent({ type: "mousedown" },
+ gDebugger.document.querySelector("#step-in"),
+ gDebugger);
+ });
+
+ return promise.all([
+ waitForDebuggerEvents(gPanel, gDebugger.EVENTS.FETCHED_SCOPES, 1),
+ waitForDebuggerEvents(gPanel, gDebugger.EVENTS.FETCHED_VARIABLES, 3),
+ waitForDebuggerEvents(gPanel, gDebugger.EVENTS.FETCHED_PROPERTIES, 4),
+ ]);
+ }
+
+ function testVariablesExpand() {
+ let localScope = gVariables.getScopeAtIndex(0);
+ let withScope = gVariables.getScopeAtIndex(1);
+ let functionScope = gVariables.getScopeAtIndex(2);
+ let globalLexicalScope = gVariables.getScopeAtIndex(3);
+ let globalScope = gVariables.getScopeAtIndex(4);
+
+ let thisVar = localScope.get("this");
+ let windowVar = thisVar.get("window");
+ let documentVar = windowVar.get("document");
+ let locationVar = documentVar.get("location");
+
+ is(localScope.target.querySelector(".arrow").hasAttribute("open"), true,
+ "The localScope arrow should still be expanded.");
+ is(withScope.target.querySelector(".arrow").hasAttribute("open"), true,
+ "The withScope arrow should still be expanded.");
+ is(functionScope.target.querySelector(".arrow").hasAttribute("open"), true,
+ "The functionScope arrow should still be expanded.");
+ is(globalLexicalScope.target.querySelector(".arrow").hasAttribute("open"), true,
+ "The globalLexicalScope arrow should still be expanded.");
+ is(globalScope.target.querySelector(".arrow").hasAttribute("open"), true,
+ "The globalScope arrow should still be expanded.");
+ is(thisVar.target.querySelector(".arrow").hasAttribute("open"), true,
+ "The thisVar arrow should still be expanded.");
+ is(windowVar.target.querySelector(".arrow").hasAttribute("open"), true,
+ "The windowVar arrow should still be expanded.");
+ is(documentVar.target.querySelector(".arrow").hasAttribute("open"), true,
+ "The documentVar arrow should still be expanded.");
+ is(locationVar.target.querySelector(".arrow").hasAttribute("open"), true,
+ "The locationVar arrow should still be expanded.");
+
+ is(localScope.target.querySelector(".variables-view-element-details").hasAttribute("open"), true,
+ "The localScope enumerables should still be expanded.");
+ is(withScope.target.querySelector(".variables-view-element-details").hasAttribute("open"), true,
+ "The withScope enumerables should still be expanded.");
+ is(functionScope.target.querySelector(".variables-view-element-details").hasAttribute("open"), true,
+ "The functionScope enumerables should still be expanded.");
+ is(globalLexicalScope.target.querySelector(".variables-view-element-details").hasAttribute("open"), true,
+ "The globalLexicalScope enumerables should still be expanded.");
+ is(globalScope.target.querySelector(".variables-view-element-details").hasAttribute("open"), true,
+ "The globalScope enumerables should still be expanded.");
+ is(thisVar.target.querySelector(".variables-view-element-details").hasAttribute("open"), true,
+ "The thisVar enumerables should still be expanded.");
+ is(windowVar.target.querySelector(".variables-view-element-details").hasAttribute("open"), true,
+ "The windowVar enumerables should still be expanded.");
+ is(documentVar.target.querySelector(".variables-view-element-details").hasAttribute("open"), true,
+ "The documentVar enumerables should still be expanded.");
+ is(locationVar.target.querySelector(".variables-view-element-details").hasAttribute("open"), true,
+ "The locationVar enumerables should still be expanded.");
+
+ is(localScope.expanded, true,
+ "The localScope expanded getter should return true.");
+ is(withScope.expanded, true,
+ "The withScope expanded getter should return true.");
+ is(functionScope.expanded, true,
+ "The functionScope expanded getter should return true.");
+ is(globalLexicalScope.expanded, true,
+ "The globalLexicalScope expanded getter should return true.");
+ is(globalScope.expanded, true,
+ "The globalScope expanded getter should return true.");
+ is(thisVar.expanded, true,
+ "The thisVar expanded getter should return true.");
+ is(windowVar.expanded, true,
+ "The windowVar expanded getter should return true.");
+ is(documentVar.expanded, true,
+ "The documentVar expanded getter should return true.");
+ is(locationVar.expanded, true,
+ "The locationVar expanded getter should return true.");
+ }
+
+ function prepareVariablesAndProperties() {
+ let deferred = promise.defer();
+
+ let localScope = gVariables.getScopeAtIndex(0);
+ let withScope = gVariables.getScopeAtIndex(1);
+ let functionScope = gVariables.getScopeAtIndex(2);
+ let globalLexicalScope = gVariables.getScopeAtIndex(3);
+ let globalScope = gVariables.getScopeAtIndex(4);
+
+ is(localScope.expanded, true,
+ "The localScope should be expanded.");
+ is(withScope.expanded, false,
+ "The withScope should not be expanded yet.");
+ is(functionScope.expanded, false,
+ "The functionScope should not be expanded yet.");
+ is(globalLexicalScope.expanded, false,
+ "The globalLexicalScope should not be expanded yet.");
+ is(globalScope.expanded, false,
+ "The globalScope should not be expanded yet.");
+
+ // Wait for only two events to be triggered, because the Function scope is
+ // an environment to which scope arguments and variables are already attached.
+ waitForDebuggerEvents(gPanel, gDebugger.EVENTS.FETCHED_VARIABLES, 2).then(() => {
+ is(localScope.expanded, true,
+ "The localScope should now be expanded.");
+ is(withScope.expanded, true,
+ "The withScope should now be expanded.");
+ is(functionScope.expanded, true,
+ "The functionScope should now be expanded.");
+ is(globalLexicalScope.expanded, true,
+ "The globalLexicalScope should now be expanded.");
+ is(globalScope.expanded, true,
+ "The globalScope should now be expanded.");
+
+ let thisVar = localScope.get("this");
+
+ waitForDebuggerEvents(gPanel, gDebugger.EVENTS.FETCHED_PROPERTIES, 1).then(() => {
+ let windowVar = thisVar.get("window");
+
+ waitForDebuggerEvents(gPanel, gDebugger.EVENTS.FETCHED_PROPERTIES, 1).then(() => {
+ let documentVar = windowVar.get("document");
+
+ waitForDebuggerEvents(gPanel, gDebugger.EVENTS.FETCHED_PROPERTIES, 1).then(() => {
+ let locationVar = documentVar.get("location");
+
+ waitForDebuggerEvents(gPanel, gDebugger.EVENTS.FETCHED_PROPERTIES, 1).then(() => {
+ is(thisVar.expanded, true,
+ "The local scope 'this' should be expanded.");
+ is(windowVar.expanded, true,
+ "The local scope 'this.window' should be expanded.");
+ is(documentVar.expanded, true,
+ "The local scope 'this.window.document' should be expanded.");
+ is(locationVar.expanded, true,
+ "The local scope 'this.window.document.location' should be expanded.");
+
+ deferred.resolve();
+ });
+
+ locationVar.expand();
+ });
+
+ documentVar.expand();
+ });
+
+ windowVar.expand();
+ });
+
+ thisVar.expand();
+ });
+
+ withScope.expand();
+ functionScope.expand();
+ globalLexicalScope.expand();
+ globalScope.expand();
+
+ return deferred.promise;
+ }
+
+ Task.spawn(function* () {
+ yield addBreakpoint();
+ yield ensureThreadClientState(gPanel, "resumed");
+ yield pauseDebuggee();
+ yield prepareVariablesAndProperties();
+ yield stepInDebuggee();
+ yield testVariablesExpand();
+ resumeDebuggerThenCloseAndFinish(gPanel);
+ });
+ });
+}
diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-reexpand-03.js b/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-reexpand-03.js
new file mode 100644
index 000000000..258fbed26
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-reexpand-03.js
@@ -0,0 +1,120 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Make sure that the variables view correctly re-expands *scopes* after pauses.
+ */
+
+const TAB_URL = EXAMPLE_URL + "doc_scope-variable-4.html";
+
+function test() {
+ // Debug test slaves are a bit slow at this test.
+ requestLongerTimeout(4);
+
+ let options = {
+ source: TAB_URL,
+ line: 1
+ };
+ initDebugger(TAB_URL, options).then(([aTab,, aPanel]) => {
+ const gTab = aTab;
+ const gPanel = aPanel;
+ const gDebugger = gPanel.panelWin;
+ const gSources = gDebugger.DebuggerView.Sources;
+ const gVariables = gDebugger.DebuggerView.Variables;
+ const queries = gDebugger.require("./content/queries");
+ const getState = gDebugger.DebuggerController.getState;
+ const actions = bindActionCreators(gPanel);
+
+ // Always expand all items between pauses.
+ gVariables.commitHierarchyIgnoredItems = Object.create(null);
+
+ function addBreakpoint() {
+ return actions.addBreakpoint({
+ actor: gSources.selectedValue,
+ line: 18
+ });
+ }
+
+ function pauseDebuggee() {
+ callInTab(gTab, "test");
+
+ return waitForDebuggerEvents(gPanel, gDebugger.EVENTS.FETCHED_SCOPES);
+ }
+
+ function resumeDebuggee() {
+ // Spin the event loop before causing the debuggee to pause, to allow
+ // this function to return first.
+ executeSoon(() => {
+ EventUtils.sendMouseEvent({ type: "mousedown" },
+ gDebugger.document.querySelector("#resume"),
+ gDebugger);
+ });
+
+ return waitForDebuggerEvents(gPanel, gDebugger.EVENTS.FETCHED_SCOPES);
+ }
+
+ function testVariablesExpand() {
+ let localScope = gVariables.getScopeAtIndex(0);
+ let functionScope = gVariables.getScopeAtIndex(1);
+ let globalScope = gVariables.getScopeAtIndex(2);
+
+ is(localScope.target.querySelector(".arrow").hasAttribute("open"), true,
+ "The localScope arrow should still be expanded.");
+ is(functionScope.target.querySelector(".arrow").hasAttribute("open"), true,
+ "The functionScope arrow should still be expanded.");
+ is(globalScope.target.querySelector(".arrow").hasAttribute("open"), false,
+ "The globalScope arrow should not be expanded.");
+
+ is(localScope.target.querySelector(".variables-view-element-details").hasAttribute("open"), true,
+ "The localScope enumerables should still be expanded.");
+ is(functionScope.target.querySelector(".variables-view-element-details").hasAttribute("open"), true,
+ "The functionScope enumerables should still be expanded.");
+ is(globalScope.target.querySelector(".variables-view-element-details").hasAttribute("open"), false,
+ "The globalScope enumerables should not be expanded.");
+
+ is(localScope.expanded, true,
+ "The localScope expanded getter should return true.");
+ is(functionScope.expanded, true,
+ "The functionScope expanded getter should return true.");
+ is(globalScope.expanded, false,
+ "The globalScope expanded getter should return false.");
+ }
+
+ function prepareScopes() {
+ let localScope = gVariables.getScopeAtIndex(0);
+ let functionScope = gVariables.getScopeAtIndex(1);
+ let globalScope = gVariables.getScopeAtIndex(2);
+
+ is(localScope.expanded, true,
+ "The localScope should be expanded.");
+ is(functionScope.expanded, false,
+ "The functionScope should not be expanded yet.");
+ is(globalScope.expanded, false,
+ "The globalScope should not be expanded yet.");
+
+ localScope.collapse();
+ functionScope.expand();
+
+ // Don't for any events to be triggered, because the Function scope is
+ // an environment to which scope arguments and variables are already attached.
+ is(localScope.expanded, false,
+ "The localScope should not be expanded anymore.");
+ is(functionScope.expanded, true,
+ "The functionScope should now be expanded.");
+ is(globalScope.expanded, false,
+ "The globalScope should still not be expanded.");
+ }
+
+ Task.spawn(function* () {
+ yield addBreakpoint();
+ yield ensureThreadClientState(gPanel, "resumed");
+ yield pauseDebuggee();
+ yield prepareScopes();
+ yield resumeDebuggee();
+ yield testVariablesExpand();
+ resumeDebuggerThenCloseAndFinish(gPanel);
+ });
+ });
+}
diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-webidl.js b/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-webidl.js
new file mode 100644
index 000000000..4499ec18f
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-webidl.js
@@ -0,0 +1,262 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Make sure that the variables view correctly displays WebIDL attributes in DOM
+ * objects.
+ */
+
+const TAB_URL = EXAMPLE_URL + "doc_frame-parameters.html";
+
+var gTab, gPanel, gDebugger;
+var gVariables;
+
+function test() {
+ // Debug test slaves are a bit slow at this test.
+ requestLongerTimeout(2);
+
+ let options = {
+ source: TAB_URL,
+ line: 1
+ };
+ initDebugger(TAB_URL, options).then(([aTab,, aPanel]) => {
+ gTab = aTab;
+ gPanel = aPanel;
+ gDebugger = gPanel.panelWin;
+ gVariables = gDebugger.DebuggerView.Variables;
+
+ waitForCaretAndScopes(gPanel, 24)
+ .then(expandGlobalScope)
+ .then(performTest)
+ .then(() => resumeDebuggerThenCloseAndFinish(gPanel))
+ .then(null, aError => {
+ ok(false, "Got an error: " + aError.message + "\n" + aError.stack);
+ });
+
+ generateMouseClickInTab(gTab, "content.document.querySelector('button')");
+ });
+}
+
+function expandGlobalScope() {
+ let deferred = promise.defer();
+
+ let globalScope = gVariables.getScopeAtIndex(2);
+ is(globalScope.expanded, false,
+ "The global scope should not be expanded by default.");
+
+ gDebugger.once(gDebugger.EVENTS.FETCHED_VARIABLES, deferred.resolve);
+
+ EventUtils.sendMouseEvent({ type: "mousedown" },
+ globalScope.target.querySelector(".name"),
+ gDebugger);
+
+ return deferred.promise;
+}
+
+function performTest() {
+ let deferred = promise.defer();
+ let globalScope = gVariables.getScopeAtIndex(2);
+
+ let buttonVar = globalScope.get("button");
+ let buttonAsProtoVar = globalScope.get("buttonAsProto");
+ let documentVar = globalScope.get("document");
+
+ is(buttonVar.target.querySelector(".name").getAttribute("value"), "button",
+ "Should have the right property name for 'button'.");
+ is(buttonVar.target.querySelector(".value").getAttribute("value"), "<button>",
+ "Should have the right property value for 'button'.");
+ ok(buttonVar.target.querySelector(".value").className.includes("token-domnode"),
+ "Should have the right token class for 'button'.");
+
+ is(buttonAsProtoVar.target.querySelector(".name").getAttribute("value"), "buttonAsProto",
+ "Should have the right property name for 'buttonAsProto'.");
+ is(buttonAsProtoVar.target.querySelector(".value").getAttribute("value"), "Object",
+ "Should have the right property value for 'buttonAsProto'.");
+ ok(buttonAsProtoVar.target.querySelector(".value").className.includes("token-other"),
+ "Should have the right token class for 'buttonAsProto'.");
+
+ is(documentVar.target.querySelector(".name").getAttribute("value"), "document",
+ "Should have the right property name for 'document'.");
+ is(documentVar.target.querySelector(".value").getAttribute("value"),
+ "HTMLDocument \u2192 doc_frame-parameters.html",
+ "Should have the right property value for 'document'.");
+ ok(documentVar.target.querySelector(".value").className.includes("token-domnode"),
+ "Should have the right token class for 'document'.");
+
+ is(buttonVar.expanded, false,
+ "The buttonVar should not be expanded at this point.");
+ is(buttonAsProtoVar.expanded, false,
+ "The buttonAsProtoVar should not be expanded at this point.");
+ is(documentVar.expanded, false,
+ "The documentVar should not be expanded at this point.");
+
+ waitForDebuggerEvents(gPanel, gDebugger.EVENTS.FETCHED_PROPERTIES, 3).then(() => {
+ is(buttonVar.get("type").target.querySelector(".name").getAttribute("value"), "type",
+ "Should have the right property name for 'type'.");
+ is(buttonVar.get("type").target.querySelector(".value").getAttribute("value"), "\"submit\"",
+ "Should have the right property value for 'type'.");
+ ok(buttonVar.get("type").target.querySelector(".value").className.includes("token-string"),
+ "Should have the right token class for 'type'.");
+
+ is(buttonVar.get("childNodes").target.querySelector(".name").getAttribute("value"), "childNodes",
+ "Should have the right property name for 'childNodes'.");
+ is(buttonVar.get("childNodes").target.querySelector(".value").getAttribute("value"), "NodeList[1]",
+ "Should have the right property value for 'childNodes'.");
+ ok(buttonVar.get("childNodes").target.querySelector(".value").className.includes("token-other"),
+ "Should have the right token class for 'childNodes'.");
+
+ is(buttonVar.get("onclick").target.querySelector(".name").getAttribute("value"), "onclick",
+ "Should have the right property name for 'onclick'.");
+ is(buttonVar.get("onclick").target.querySelector(".value").getAttribute("value"), "onclick(event)",
+ "Should have the right property value for 'onclick'.");
+ ok(buttonVar.get("onclick").target.querySelector(".value").className.includes("token-other"),
+ "Should have the right token class for 'onclick'.");
+
+ is(documentVar.get("title").target.querySelector(".name").getAttribute("value"), "title",
+ "Should have the right property name for 'title'.");
+ is(documentVar.get("title").target.querySelector(".value").getAttribute("value"), "\"Debugger test page\"",
+ "Should have the right property value for 'title'.");
+ ok(documentVar.get("title").target.querySelector(".value").className.includes("token-string"),
+ "Should have the right token class for 'title'.");
+
+ is(documentVar.get("childNodes").target.querySelector(".name").getAttribute("value"), "childNodes",
+ "Should have the right property name for 'childNodes'.");
+ is(documentVar.get("childNodes").target.querySelector(".value").getAttribute("value"), "NodeList[3]",
+ "Should have the right property value for 'childNodes'.");
+ ok(documentVar.get("childNodes").target.querySelector(".value").className.includes("token-other"),
+ "Should have the right token class for 'childNodes'.");
+
+ is(documentVar.get("onclick").target.querySelector(".name").getAttribute("value"), "onclick",
+ "Should have the right property name for 'onclick'.");
+ is(documentVar.get("onclick").target.querySelector(".value").getAttribute("value"), "null",
+ "Should have the right property value for 'onclick'.");
+ ok(documentVar.get("onclick").target.querySelector(".value").className.includes("token-null"),
+ "Should have the right token class for 'onclick'.");
+
+ let buttonProtoVar = buttonVar.get("__proto__");
+ let buttonAsProtoProtoVar = buttonAsProtoVar.get("__proto__");
+ let documentProtoVar = documentVar.get("__proto__");
+
+ is(buttonProtoVar.target.querySelector(".name").getAttribute("value"), "__proto__",
+ "Should have the right property name for '__proto__'.");
+ is(buttonProtoVar.target.querySelector(".value").getAttribute("value"), "HTMLButtonElementPrototype",
+ "Should have the right property value for '__proto__'.");
+ ok(buttonProtoVar.target.querySelector(".value").className.includes("token-other"),
+ "Should have the right token class for '__proto__'.");
+
+ is(buttonAsProtoProtoVar.target.querySelector(".name").getAttribute("value"), "__proto__",
+ "Should have the right property name for '__proto__'.");
+ is(buttonAsProtoProtoVar.target.querySelector(".value").getAttribute("value"), "<button>",
+ "Should have the right property value for '__proto__'.");
+ ok(buttonAsProtoProtoVar.target.querySelector(".value").className.includes("token-domnode"),
+ "Should have the right token class for '__proto__'.");
+
+ is(documentProtoVar.target.querySelector(".name").getAttribute("value"), "__proto__",
+ "Should have the right property name for '__proto__'.");
+ is(documentProtoVar.target.querySelector(".value").getAttribute("value"), "HTMLDocumentPrototype",
+ "Should have the right property value for '__proto__'.");
+ ok(documentProtoVar.target.querySelector(".value").className.includes("token-other"),
+ "Should have the right token class for '__proto__'.");
+
+ is(buttonProtoVar.expanded, false,
+ "The buttonProtoVar should not be expanded at this point.");
+ is(buttonAsProtoProtoVar.expanded, false,
+ "The buttonAsProtoProtoVar should not be expanded at this point.");
+ is(documentProtoVar.expanded, false,
+ "The documentProtoVar should not be expanded at this point.");
+
+ waitForDebuggerEvents(gPanel, gDebugger.EVENTS.FETCHED_PROPERTIES, 3).then(() => {
+ is(buttonAsProtoProtoVar.get("type").target.querySelector(".name").getAttribute("value"), "type",
+ "Should have the right property name for 'type'.");
+ is(buttonAsProtoProtoVar.get("type").target.querySelector(".value").getAttribute("value"), "\"submit\"",
+ "Should have the right property value for 'type'.");
+ ok(buttonAsProtoProtoVar.get("type").target.querySelector(".value").className.includes("token-string"),
+ "Should have the right token class for 'type'.");
+
+ is(buttonAsProtoProtoVar.get("childNodes").target.querySelector(".name").getAttribute("value"), "childNodes",
+ "Should have the right property name for 'childNodes'.");
+ is(buttonAsProtoProtoVar.get("childNodes").target.querySelector(".value").getAttribute("value"), "NodeList[1]",
+ "Should have the right property value for 'childNodes'.");
+ ok(buttonAsProtoProtoVar.get("childNodes").target.querySelector(".value").className.includes("token-other"),
+ "Should have the right token class for 'childNodes'.");
+
+ is(buttonAsProtoProtoVar.get("onclick").target.querySelector(".name").getAttribute("value"), "onclick",
+ "Should have the right property name for 'onclick'.");
+ is(buttonAsProtoProtoVar.get("onclick").target.querySelector(".value").getAttribute("value"), "onclick(event)",
+ "Should have the right property value for 'onclick'.");
+ ok(buttonAsProtoProtoVar.get("onclick").target.querySelector(".value").className.includes("token-other"),
+ "Should have the right token class for 'onclick'.");
+
+ let buttonProtoProtoVar = buttonProtoVar.get("__proto__");
+ let buttonAsProtoProtoProtoVar = buttonAsProtoProtoVar.get("__proto__");
+ let documentProtoProtoVar = documentProtoVar.get("__proto__");
+
+ is(buttonProtoProtoVar.target.querySelector(".name").getAttribute("value"), "__proto__",
+ "Should have the right property name for '__proto__'.");
+ is(buttonProtoProtoVar.target.querySelector(".value").getAttribute("value"), "HTMLElementPrototype",
+ "Should have the right property value for '__proto__'.");
+ ok(buttonProtoProtoVar.target.querySelector(".value").className.includes("token-other"),
+ "Should have the right token class for '__proto__'.");
+
+ is(buttonAsProtoProtoProtoVar.target.querySelector(".name").getAttribute("value"), "__proto__",
+ "Should have the right property name for '__proto__'.");
+ is(buttonAsProtoProtoProtoVar.target.querySelector(".value").getAttribute("value"), "HTMLButtonElementPrototype",
+ "Should have the right property value for '__proto__'.");
+ ok(buttonAsProtoProtoProtoVar.target.querySelector(".value").className.includes("token-other"),
+ "Should have the right token class for '__proto__'.");
+
+ is(documentProtoProtoVar.target.querySelector(".name").getAttribute("value"), "__proto__",
+ "Should have the right property name for '__proto__'.");
+ is(documentProtoProtoVar.target.querySelector(".value").getAttribute("value"), "DocumentPrototype",
+ "Should have the right property value for '__proto__'.");
+ ok(documentProtoProtoVar.target.querySelector(".value").className.includes("token-other"),
+ "Should have the right token class for '__proto__'.");
+
+ is(buttonAsProtoProtoProtoVar.expanded, false,
+ "The buttonAsProtoProtoProtoVar should not be expanded at this point.");
+ is(buttonAsProtoProtoProtoVar.expanded, false,
+ "The buttonAsProtoProtoProtoVar should not be expanded at this point.");
+ is(documentProtoProtoVar.expanded, false,
+ "The documentProtoProtoVar should not be expanded at this point.");
+
+ deferred.resolve();
+ });
+
+ // Similarly, expand the 'button.__proto__', 'buttonAsProto.__proto__' and
+ // 'document.__proto__' variables view nodes.
+ buttonProtoVar.expand();
+ buttonAsProtoProtoVar.expand();
+ documentProtoVar.expand();
+
+ is(buttonProtoVar.expanded, true,
+ "The buttonProtoVar should be immediately marked as expanded.");
+ is(buttonAsProtoProtoVar.expanded, true,
+ "The buttonAsProtoProtoVar should be immediately marked as expanded.");
+ is(documentProtoVar.expanded, true,
+ "The documentProtoVar should be immediately marked as expanded.");
+ });
+
+ // Expand the 'button', 'buttonAsProto' and 'document' variables view nodes.
+ // This causes their properties to be retrieved and displayed.
+ buttonVar.expand();
+ buttonAsProtoVar.expand();
+ documentVar.expand();
+
+ is(buttonVar.expanded, true,
+ "The buttonVar should be immediately marked as expanded.");
+ is(buttonAsProtoVar.expanded, true,
+ "The buttonAsProtoVar should be immediately marked as expanded.");
+ is(documentVar.expanded, true,
+ "The documentVar should be immediately marked as expanded.");
+
+ return deferred.promise;
+}
+
+registerCleanupFunction(function () {
+ gTab = null;
+ gPanel = null;
+ gDebugger = null;
+ gVariables = null;
+});
diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_watch-expressions-01.js b/devtools/client/debugger/test/mochitest/browser_dbg_watch-expressions-01.js
new file mode 100644
index 000000000..fe55a5561
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_watch-expressions-01.js
@@ -0,0 +1,227 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Bug 727429: Test the debugger watch expressions.
+ */
+
+const TAB_URL = EXAMPLE_URL + "doc_watch-expressions.html";
+
+function test() {
+ // Debug test slaves are a bit slow at this test.
+ requestLongerTimeout(2);
+
+ let gTab, gPanel, gDebugger;
+ let gEditor, gWatch, gVariables;
+
+ let options = {
+ source: TAB_URL,
+ line: 1
+ };
+ initDebugger(TAB_URL, options).then(([aTab,, aPanel]) => {
+ gTab = aTab;
+ gPanel = aPanel;
+ gDebugger = gPanel.panelWin;
+ gEditor = gDebugger.DebuggerView.editor;
+ gWatch = gDebugger.DebuggerView.WatchExpressions;
+ gVariables = gDebugger.DebuggerView.Variables;
+
+ gDebugger.DebuggerView.toggleInstrumentsPane({ visible: true, animated: false });
+
+ performTest();
+ closeDebuggerAndFinish(gPanel);
+ });
+
+ function performTest() {
+ is(gWatch.getAllStrings().length, 0,
+ "There should initially be no watch expressions.");
+
+ addAndCheckExpressions(1, 0, "a");
+ addAndCheckExpressions(2, 0, "b");
+ addAndCheckExpressions(3, 0, "c");
+
+ removeAndCheckExpression(2, 1, "a");
+ removeAndCheckExpression(1, 0, "a");
+
+ addAndCheckExpressions(2, 0, "", true);
+ gEditor.focus();
+ is(gWatch.getAllStrings().length, 1,
+ "Empty watch expressions are automatically removed.");
+
+ addAndCheckExpressions(2, 0, "a", true);
+ gEditor.focus();
+ is(gWatch.getAllStrings().length, 1,
+ "Duplicate watch expressions are automatically removed.");
+
+ addAndCheckExpressions(2, 0, "a\t", true);
+ addAndCheckExpressions(2, 0, "a\r", true);
+ addAndCheckExpressions(2, 0, "a\n", true);
+ gEditor.focus();
+ is(gWatch.getAllStrings().length, 1,
+ "Duplicate watch expressions are automatically removed.");
+
+ addAndCheckExpressions(2, 0, "\ta", true);
+ addAndCheckExpressions(2, 0, "\ra", true);
+ addAndCheckExpressions(2, 0, "\na", true);
+ gEditor.focus();
+ is(gWatch.getAllStrings().length, 1,
+ "Duplicate watch expressions are automatically removed.");
+
+ addAndCheckCustomExpression(2, 0, "bazΩΩka");
+ addAndCheckCustomExpression(3, 0, "bambøøcha");
+
+ EventUtils.sendMouseEvent({ type: "click" },
+ gWatch.getItemAtIndex(0).attachment.view.closeNode,
+ gDebugger);
+
+ is(gWatch.getAllStrings().length, 2,
+ "Watch expressions are removed when the close button is pressed.");
+ is(gWatch.getAllStrings()[0], "bazΩΩka",
+ "The expression at index " + 0 + " should be correct (1).");
+ is(gWatch.getAllStrings()[1], "a",
+ "The expression at index " + 1 + " should be correct (2).");
+
+ EventUtils.sendMouseEvent({ type: "click" },
+ gWatch.getItemAtIndex(0).attachment.view.closeNode,
+ gDebugger);
+
+ is(gWatch.getAllStrings().length, 1,
+ "Watch expressions are removed when the close button is pressed.");
+ is(gWatch.getAllStrings()[0], "a",
+ "The expression at index " + 0 + " should be correct (3).");
+
+ EventUtils.sendMouseEvent({ type: "click" },
+ gWatch.getItemAtIndex(0).attachment.view.closeNode,
+ gDebugger);
+
+ is(gWatch.getAllStrings().length, 0,
+ "Watch expressions are removed when the close button is pressed.");
+
+ EventUtils.sendMouseEvent({ type: "click" },
+ gWatch.widget._parent,
+ gDebugger);
+
+ is(gWatch.getAllStrings().length, 1,
+ "Watch expressions are added when the view container is pressed.");
+ }
+
+ function addAndCheckCustomExpression(aTotal, aIndex, aString, noBlur) {
+ addAndCheckExpressions(aTotal, aIndex, "", true);
+
+ EventUtils.sendString(aString, gDebugger);
+
+ gEditor.focus();
+
+ let element = gWatch.getItemAtIndex(aIndex).target;
+
+ is(gWatch.getItemAtIndex(aIndex).attachment.initialExpression, "",
+ "The initial expression at index " + aIndex + " should be correct (1).");
+ is(gWatch.getItemForElement(element).attachment.initialExpression, "",
+ "The initial expression at index " + aIndex + " should be correct (2).");
+
+ is(gWatch.getItemAtIndex(aIndex).attachment.currentExpression, aString,
+ "The expression at index " + aIndex + " should be correct (1).");
+ is(gWatch.getItemForElement(element).attachment.currentExpression, aString,
+ "The expression at index " + aIndex + " should be correct (2).");
+
+ is(gWatch.getString(aIndex), aString,
+ "The expression at index " + aIndex + " should be correct (3).");
+ is(gWatch.getAllStrings()[aIndex], aString,
+ "The expression at index " + aIndex + " should be correct (4).");
+ }
+
+ function addAndCheckExpressions(aTotal, aIndex, aString, noBlur) {
+ gWatch.addExpression(aString);
+
+ is(gWatch.getAllStrings().length, aTotal,
+ "There should be " + aTotal + " watch expressions available (1).");
+ is(gWatch.itemCount, aTotal,
+ "There should be " + aTotal + " watch expressions available (2).");
+
+ ok(gWatch.getItemAtIndex(aIndex),
+ "The expression at index " + aIndex + " should be available.");
+ is(gWatch.getItemAtIndex(aIndex).attachment.initialExpression, aString,
+ "The expression at index " + aIndex + " should have an initial expression.");
+
+ let element = gWatch.getItemAtIndex(aIndex).target;
+
+ ok(element,
+ "There should be a new expression item in the view.");
+ ok(gWatch.getItemForElement(element),
+ "The watch expression item should be accessible.");
+ is(gWatch.getItemForElement(element), gWatch.getItemAtIndex(aIndex),
+ "The correct watch expression item was accessed.");
+
+ ok(gWatch.widget.getItemAtIndex(aIndex) instanceof XULElement,
+ "The correct watch expression element was accessed (1).");
+ is(element, gWatch.widget.getItemAtIndex(aIndex),
+ "The correct watch expression element was accessed (2).");
+
+ is(gWatch.getItemForElement(element).attachment.view.arrowNode.hidden, false,
+ "The arrow node should be visible.");
+ is(gWatch.getItemForElement(element).attachment.view.closeNode.hidden, false,
+ "The close button should be visible.");
+ is(gWatch.getItemForElement(element).attachment.view.inputNode.getAttribute("focused"), "true",
+ "The textbox input should be focused.");
+
+ is(gVariables.parentNode.scrollTop, 0,
+ "The variables view should be scrolled to top");
+
+ is(gWatch.items[0], gWatch.getItemAtIndex(aIndex),
+ "The correct watch expression was added to the cache (1).");
+ is(gWatch.items[0], gWatch.getItemForElement(element),
+ "The correct watch expression was added to the cache (2).");
+
+ if (!noBlur) {
+ gEditor.focus();
+
+ is(gWatch.getItemAtIndex(aIndex).attachment.initialExpression, aString,
+ "The initial expression at index " + aIndex + " should be correct (1).");
+ is(gWatch.getItemForElement(element).attachment.initialExpression, aString,
+ "The initial expression at index " + aIndex + " should be correct (2).");
+
+ is(gWatch.getItemAtIndex(aIndex).attachment.currentExpression, aString,
+ "The expression at index " + aIndex + " should be correct (1).");
+ is(gWatch.getItemForElement(element).attachment.currentExpression, aString,
+ "The expression at index " + aIndex + " should be correct (2).");
+
+ is(gWatch.getString(aIndex), aString,
+ "The expression at index " + aIndex + " should be correct (3).");
+ is(gWatch.getAllStrings()[aIndex], aString,
+ "The expression at index " + aIndex + " should be correct (4).");
+ }
+ }
+
+ function removeAndCheckExpression(aTotal, aIndex, aString) {
+ gWatch.removeAt(aIndex);
+
+ is(gWatch.getAllStrings().length, aTotal,
+ "There should be " + aTotal + " watch expressions available (1).");
+ is(gWatch.itemCount, aTotal,
+ "There should be " + aTotal + " watch expressions available (2).");
+
+ ok(gWatch.getItemAtIndex(aIndex),
+ "The expression at index " + aIndex + " should still be available.");
+ is(gWatch.getItemAtIndex(aIndex).attachment.initialExpression, aString,
+ "The expression at index " + aIndex + " should still have an initial expression.");
+
+ let element = gWatch.getItemAtIndex(aIndex).target;
+
+ is(gWatch.getItemAtIndex(aIndex).attachment.initialExpression, aString,
+ "The initial expression at index " + aIndex + " should be correct (1).");
+ is(gWatch.getItemForElement(element).attachment.initialExpression, aString,
+ "The initial expression at index " + aIndex + " should be correct (2).");
+
+ is(gWatch.getItemAtIndex(aIndex).attachment.currentExpression, aString,
+ "The expression at index " + aIndex + " should be correct (1).");
+ is(gWatch.getItemForElement(element).attachment.currentExpression, aString,
+ "The expression at index " + aIndex + " should be correct (2).");
+
+ is(gWatch.getString(aIndex), aString,
+ "The expression at index " + aIndex + " should be correct (3).");
+ is(gWatch.getAllStrings()[aIndex], aString,
+ "The expression at index " + aIndex + " should be correct (4).");
+ }
+}
diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_watch-expressions-02.js b/devtools/client/debugger/test/mochitest/browser_dbg_watch-expressions-02.js
new file mode 100644
index 000000000..a9b22708d
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_watch-expressions-02.js
@@ -0,0 +1,383 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Bug 727429: Test the debugger watch expressions.
+ */
+
+const TAB_URL = EXAMPLE_URL + "doc_watch-expressions.html";
+
+function test() {
+ // Debug test slaves are a bit slow at this test.
+ requestLongerTimeout(2);
+
+ let gTab, gPanel, gDebugger;
+ let gWatch, gVariables;
+
+ let options = {
+ source: TAB_URL,
+ line: 1
+ };
+ initDebugger(TAB_URL, options).then(([aTab,, aPanel]) => {
+ gTab = aTab;
+ gPanel = aPanel;
+ gDebugger = gPanel.panelWin;
+ gWatch = gDebugger.DebuggerView.WatchExpressions;
+ gVariables = gDebugger.DebuggerView.Variables;
+
+ gDebugger.DebuggerView.toggleInstrumentsPane({ visible: true, animated: false });
+
+ addExpressions();
+ performTest()
+ .then(finishTest)
+ .then(() => closeDebuggerAndFinish(gPanel))
+ .then(null, aError => {
+ ok(false, "Got an error: " + aError.message + "\n" + aError.stack);
+ });
+ });
+
+ function addExpressions() {
+ // https://bugzilla.mozilla.org/show_bug.cgi?id=1205353
+ // BZ#1205353 - wrong result for string replace with backslash-dollar.
+ gWatch.addExpression("'$$$'.replace(/\\$/, 'foo')");
+ gWatch.addExpression("'a'");
+ gWatch.addExpression("\"a\"");
+ gWatch.addExpression("'a\"\"'");
+ gWatch.addExpression("\"a''\"");
+ gWatch.addExpression("?");
+ gWatch.addExpression("a");
+ gWatch.addExpression("this");
+ gWatch.addExpression("this.canada");
+ gWatch.addExpression("[1, 2, 3]");
+ gWatch.addExpression("x = [1, 2, 3]");
+ gWatch.addExpression("y = [1, 2, 3]; y.test = 4");
+ gWatch.addExpression("z = [1, 2, 3]; z.test = 4; z");
+ gWatch.addExpression("t = [1, 2, 3]; t.test = 4; !t");
+ gWatch.addExpression("arguments[0]");
+ gWatch.addExpression("encodeURI(\"\\\")");
+ gWatch.addExpression("decodeURI(\"\\\")");
+ gWatch.addExpression("decodeURIComponent(\"%\")");
+ gWatch.addExpression("//");
+ gWatch.addExpression("// 42");
+ gWatch.addExpression("{}.foo");
+ gWatch.addExpression("{}.foo()");
+ gWatch.addExpression("({}).foo()");
+ gWatch.addExpression("new Array(-1)");
+ gWatch.addExpression("4.2.toExponential(-4.2)");
+ gWatch.addExpression("throw new Error(\"bazinga\")");
+ gWatch.addExpression("({ get error() { throw new Error(\"bazinga\") } }).error");
+ gWatch.addExpression("throw { get name() { throw \"bazinga\" } }");
+
+ }
+
+ function performTest() {
+ let deferred = promise.defer();
+
+ is(gDebugger.document.querySelectorAll(".dbg-expression[hidden=true]").length, 0,
+ "There should be 0 hidden nodes in the watch expressions container");
+ is(gDebugger.document.querySelectorAll(".dbg-expression:not([hidden=true])").length, 28,
+ "There should be 28 visible nodes in the watch expressions container");
+
+ test1(function () {
+ test2(function () {
+ test3(function () {
+ test4(function () {
+ test5(function () {
+ test6(function () {
+ test7(function () {
+ test8(function () {
+ test9(function () {
+ deferred.resolve();
+ });
+ });
+ });
+ });
+ });
+ });
+ });
+ });
+ });
+
+ return deferred.promise;
+ }
+
+ function finishTest() {
+ is(gDebugger.document.querySelectorAll(".dbg-expression[hidden=true]").length, 0,
+ "There should be 0 hidden nodes in the watch expressions container");
+ is(gDebugger.document.querySelectorAll(".dbg-expression:not([hidden=true])").length, 28,
+ "There should be 28 visible nodes in the watch expressions container");
+ }
+
+ function test1(aCallback) {
+ gDebugger.once(gDebugger.EVENTS.FETCHED_WATCH_EXPRESSIONS, () => {
+ checkWatchExpressions(27, {
+ a: "ReferenceError: a is not defined",
+ this: { type: "object", class: "Object" },
+ prop: { type: "object", class: "String" },
+ args: { type: "undefined" }
+ });
+ aCallback();
+ });
+
+ callInTab(gTab, "test");
+ }
+
+ function test2(aCallback) {
+ gDebugger.once(gDebugger.EVENTS.FETCHED_WATCH_EXPRESSIONS, () => {
+ checkWatchExpressions(27, {
+ a: { type: "undefined" },
+ this: { type: "object", class: "Window" },
+ prop: { type: "undefined" },
+ args: "sensational"
+ });
+ aCallback();
+ });
+
+ EventUtils.sendMouseEvent({ type: "mousedown" },
+ gDebugger.document.getElementById("resume"),
+ gDebugger);
+ }
+
+ function test3(aCallback) {
+ gDebugger.once(gDebugger.EVENTS.FETCHED_WATCH_EXPRESSIONS, () => {
+ checkWatchExpressions(27, {
+ a: { type: "object", class: "Object" },
+ this: { type: "object", class: "Window" },
+ prop: { type: "undefined" },
+ args: "sensational"
+ });
+ aCallback();
+ });
+
+ EventUtils.sendMouseEvent({ type: "mousedown" },
+ gDebugger.document.getElementById("resume"),
+ gDebugger);
+ }
+
+ function test4(aCallback) {
+ gDebugger.once(gDebugger.EVENTS.FETCHED_WATCH_EXPRESSIONS, () => {
+ checkWatchExpressions(28, {
+ a: 5,
+ this: { type: "object", class: "Window" },
+ prop: { type: "undefined" },
+ args: "sensational"
+ });
+ aCallback();
+ });
+
+ gWatch.addExpression("a = 5");
+ EventUtils.sendKey("RETURN", gDebugger);
+ }
+
+ function test5(aCallback) {
+ gDebugger.once(gDebugger.EVENTS.FETCHED_WATCH_EXPRESSIONS, () => {
+ checkWatchExpressions(28, {
+ a: 5,
+ this: { type: "object", class: "Window" },
+ prop: { type: "undefined" },
+ args: "sensational"
+ });
+ aCallback();
+ });
+
+ gWatch.addExpression("encodeURI(\"\\\")");
+ EventUtils.sendKey("RETURN", gDebugger);
+ }
+
+ function test6(aCallback) {
+ gDebugger.once(gDebugger.EVENTS.FETCHED_WATCH_EXPRESSIONS, () => {
+ checkWatchExpressions(28, {
+ a: 5,
+ this: { type: "object", class: "Window" },
+ prop: { type: "undefined" },
+ args: "sensational"
+ });
+ aCallback();
+ });
+
+ gWatch.addExpression("decodeURI(\"\\\")");
+ EventUtils.sendKey("RETURN", gDebugger);
+ }
+
+ function test7(aCallback) {
+ gDebugger.once(gDebugger.EVENTS.FETCHED_WATCH_EXPRESSIONS, () => {
+ checkWatchExpressions(28, {
+ a: 5,
+ this: { type: "object", class: "Window" },
+ prop: { type: "undefined" },
+ args: "sensational"
+ });
+ aCallback();
+ });
+
+ gWatch.addExpression("?");
+ EventUtils.sendKey("RETURN", gDebugger);
+ }
+
+ function test8(aCallback) {
+ gDebugger.once(gDebugger.EVENTS.FETCHED_WATCH_EXPRESSIONS, () => {
+ checkWatchExpressions(28, {
+ a: 5,
+ this: { type: "object", class: "Window" },
+ prop: { type: "undefined" },
+ args: "sensational"
+ });
+ aCallback();
+ });
+
+ gWatch.addExpression("a");
+ EventUtils.sendKey("RETURN", gDebugger);
+ }
+
+ function test9(aCallback) {
+ gDebugger.once(gDebugger.EVENTS.AFTER_FRAMES_CLEARED, () => {
+ aCallback();
+ });
+
+ EventUtils.sendMouseEvent({ type: "mousedown" },
+ gDebugger.document.getElementById("resume"),
+ gDebugger);
+ }
+
+ function checkWatchExpressions(aTotal, aExpectedExpressions) {
+ let {
+ a: expected_a,
+ this: expected_this,
+ prop: expected_prop,
+ args: expected_args
+ } = aExpectedExpressions;
+
+ is(gDebugger.document.querySelectorAll(".dbg-expression[hidden=true]").length, aTotal,
+ "There should be " + aTotal + " hidden nodes in the watch expressions container.");
+ is(gDebugger.document.querySelectorAll(".dbg-expression:not([hidden=true])").length, 0,
+ "There should be 0 visible nodes in the watch expressions container.");
+
+ let label = gDebugger.L10N.getStr("watchExpressionsScopeLabel");
+ let scope = gVariables._currHierarchy.get(label);
+
+ ok(scope, "There should be a wach expressions scope in the variables view.");
+ is(scope._store.size, aTotal, "There should be " + aTotal + " evaluations availalble.");
+
+ let w1 = scope.get("'a'");
+ let w2 = scope.get("\"a\"");
+ let w3 = scope.get("'a\"\"'");
+ let w4 = scope.get("\"a''\"");
+ let w5 = scope.get("?");
+ let w6 = scope.get("a");
+ let w7 = scope.get("this");
+ let w8 = scope.get("this.canada");
+ let w9 = scope.get("[1, 2, 3]");
+ let w10 = scope.get("x = [1, 2, 3]");
+ let w11 = scope.get("y = [1, 2, 3]; y.test = 4");
+ let w12 = scope.get("z = [1, 2, 3]; z.test = 4; z");
+ let w13 = scope.get("t = [1, 2, 3]; t.test = 4; !t");
+ let w14 = scope.get("arguments[0]");
+ let w15 = scope.get("encodeURI(\"\\\")");
+ let w16 = scope.get("decodeURI(\"\\\")");
+ let w17 = scope.get("decodeURIComponent(\"%\")");
+ let w18 = scope.get("//");
+ let w19 = scope.get("// 42");
+ let w20 = scope.get("{}.foo");
+ let w21 = scope.get("{}.foo()");
+ let w22 = scope.get("({}).foo()");
+ let w23 = scope.get("new Array(-1)");
+ let w24 = scope.get("4.2.toExponential(-4.2)");
+ let w25 = scope.get("throw new Error(\"bazinga\")");
+ let w26 = scope.get("({ get error() { throw new Error(\"bazinga\") } }).error");
+ let w27 = scope.get("throw { get name() { throw \"bazinga\" } }");
+ let w28 = scope.get("'$$$'.replace(/\\$/, 'foo')");
+
+ ok(w1, "The first watch expression should be present in the scope.");
+ ok(w2, "The second watch expression should be present in the scope.");
+ ok(w3, "The third watch expression should be present in the scope.");
+ ok(w4, "The fourth watch expression should be present in the scope.");
+ ok(w5, "The fifth watch expression should be present in the scope.");
+ ok(w6, "The sixth watch expression should be present in the scope.");
+ ok(w7, "The seventh watch expression should be present in the scope.");
+ ok(w8, "The eight watch expression should be present in the scope.");
+ ok(w9, "The ninth watch expression should be present in the scope.");
+ ok(w10, "The tenth watch expression should be present in the scope.");
+ ok(w11, "The eleventh watch expression should be present in the scope.");
+ ok(w12, "The twelfth watch expression should be present in the scope.");
+ ok(w13, "The 13th watch expression should be present in the scope.");
+ ok(w14, "The 14th watch expression should be present in the scope.");
+ ok(w15, "The 15th watch expression should be present in the scope.");
+ ok(w16, "The 16th watch expression should be present in the scope.");
+ ok(w17, "The 17th watch expression should be present in the scope.");
+ ok(w18, "The 18th watch expression should be present in the scope.");
+ ok(w19, "The 19th watch expression should be present in the scope.");
+ ok(w20, "The 20th watch expression should be present in the scope.");
+ ok(w21, "The 21st watch expression should be present in the scope.");
+ ok(w22, "The 22nd watch expression should be present in the scope.");
+ ok(w23, "The 23nd watch expression should be present in the scope.");
+ ok(w24, "The 24th watch expression should be present in the scope.");
+ ok(w25, "The 25th watch expression should be present in the scope.");
+ ok(w26, "The 26th watch expression should be present in the scope.");
+ ok(!w27, "The 27th watch expression should not be present in the scope.");
+ ok(w28, "The 28th watch expression should be present in the scope.");
+
+ is(w1.value, "a", "The first value is correct.");
+ is(w2.value, "a", "The second value is correct.");
+ is(w3.value, "a\"\"", "The third value is correct.");
+ is(w4.value, "a''", "The fourth value is correct.");
+ is(w5.value, "SyntaxError: expected expression, got '?'", "The fifth value is correct.");
+
+ if (typeof expected_a == "object") {
+ is(w6.value.type, expected_a.type, "The sixth value type is correct.");
+ is(w6.value.class, expected_a.class, "The sixth value class is correct.");
+ } else {
+ is(w6.value, expected_a, "The sixth value is correct.");
+ }
+
+ if (typeof expected_this == "object") {
+ is(w7.value.type, expected_this.type, "The seventh value type is correct.");
+ is(w7.value.class, expected_this.class, "The seventh value class is correct.");
+ } else {
+ is(w7.value, expected_this, "The seventh value is correct.");
+ }
+
+ if (typeof expected_prop == "object") {
+ is(w8.value.type, expected_prop.type, "The eighth value type is correct.");
+ is(w8.value.class, expected_prop.class, "The eighth value class is correct.");
+ } else {
+ is(w8.value, expected_prop, "The eighth value is correct.");
+ }
+
+ is(w9.value.type, "object", "The ninth value type is correct.");
+ is(w9.value.class, "Array", "The ninth value class is correct.");
+ is(w10.value.type, "object", "The tenth value type is correct.");
+ is(w10.value.class, "Array", "The tenth value class is correct.");
+ is(w11.value, "4", "The eleventh value is correct.");
+ is(w12.value.type, "object", "The eleventh value type is correct.");
+ is(w12.value.class, "Array", "The twelfth value class is correct.");
+ is(w13.value, false, "The 13th value is correct.");
+
+ if (typeof expected_args == "object") {
+ is(w14.value.type, expected_args.type, "The 14th value type is correct.");
+ is(w14.value.class, expected_args.class, "The 14th value class is correct.");
+ } else {
+ is(w14.value, expected_args, "The 14th value is correct.");
+ }
+
+ is(w15.value, "SyntaxError: unterminated string literal", "The 15th value is correct.");
+ is(w16.value, "SyntaxError: unterminated string literal", "The 16th value is correct.");
+ is(w17.value, "URIError: malformed URI sequence", "The 17th value is correct.");
+
+ is(w18.value.type, "undefined", "The 18th value type is correct.");
+ is(w18.value.class, undefined, "The 18th value class is correct.");
+
+ is(w19.value.type, "undefined", "The 19th value type is correct.");
+ is(w19.value.class, undefined, "The 19th value class is correct.");
+
+ is(w20.value, "SyntaxError: expected expression, got '.'", "The 20th value is correct.");
+ is(w21.value, "SyntaxError: expected expression, got '.'", "The 21th value is correct.");
+ is(w22.value, "TypeError: (intermediate value).foo is not a function", "The 22th value is correct.");
+ is(w23.value, "RangeError: invalid array length", "The 23th value is correct.");
+ is(w24.value, "RangeError: precision -4 out of range", "The 24th value is correct.");
+ is(w25.value, "Error: bazinga", "The 25th value is correct.");
+ is(w26.value, "Error: bazinga", "The 26th value is correct.");
+ is(w28.value, "foo$$", "The 28th value is correct.");
+ }
+}
diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_worker-console-01.js b/devtools/client/debugger/test/mochitest/browser_dbg_worker-console-01.js
new file mode 100644
index 000000000..72b0cbd16
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_worker-console-01.js
@@ -0,0 +1,21 @@
+// Check to make sure that a worker can be attached to a toolbox
+// and that the console works.
+
+var TAB_URL = EXAMPLE_URL + "doc_WorkerActor.attachThread-tab.html";
+var WORKER_URL = "code_WorkerActor.attachThread-worker.js";
+
+add_task(function* testNormalExecution() {
+ let {client, tab, tabClient, workerClient, toolbox, gDebugger} =
+ yield initWorkerDebugger(TAB_URL, WORKER_URL);
+
+ let jsterm = yield getSplitConsole(toolbox);
+ let executed = yield jsterm.execute("this.location.toString()");
+ ok(executed.textContent.includes(WORKER_URL),
+ "Evaluating the global's location works");
+
+ terminateWorkerInTab(tab, WORKER_URL);
+ yield waitForWorkerClose(workerClient);
+ yield gDevTools.closeToolbox(TargetFactory.forWorker(workerClient));
+ yield close(client);
+ yield removeTab(tab);
+});
diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_worker-console-02.js b/devtools/client/debugger/test/mochitest/browser_dbg_worker-console-02.js
new file mode 100644
index 000000000..b6e8d12af
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_worker-console-02.js
@@ -0,0 +1,58 @@
+// Check to make sure that a worker can be attached to a toolbox
+// and that the console works.
+
+var TAB_URL = EXAMPLE_URL + "doc_WorkerActor.attachThread-tab.html";
+var WORKER_URL = "code_WorkerActor.attachThread-worker.js";
+
+add_task(function* testWhilePaused() {
+ let {client, tab, tabClient, workerClient, toolbox, gDebugger} =
+ yield initWorkerDebugger(TAB_URL, WORKER_URL);
+
+ let gTarget = gDebugger.gTarget;
+ let gResumeButton = gDebugger.document.getElementById("resume");
+ let gResumeKey = gDebugger.document.getElementById("resumeKey");
+
+ // Execute some basic math to make sure evaluations are working.
+ let jsterm = yield getSplitConsole(toolbox);
+ let executed = yield jsterm.execute("10000+1");
+ ok(executed.textContent.includes("10001"), "Text for message appeared correct");
+
+ // Pause the worker by waiting for next execution and then sending a message to
+ // it from the main thread.
+ let oncePaused = gTarget.once("thread-paused");
+ EventUtils.sendMouseEvent({ type: "mousedown" }, gResumeButton, gDebugger);
+ once(gDebugger.gClient, "willInterrupt").then(() => {
+ info("Posting message to worker, then waiting for a pause");
+ postMessageToWorkerInTab(tab, WORKER_URL, "ping");
+ });
+ yield oncePaused;
+
+ let command1 = jsterm.execute("10000+2");
+ let command2 = jsterm.execute("10000+3");
+ let command3 = jsterm.execute("foobar"); // throw an error
+
+ info("Trying to get the result of command1");
+ executed = yield command1;
+ ok(executed.textContent.includes("10002"),
+ "command1 executed successfully");
+
+ info("Trying to get the result of command2");
+ executed = yield command2;
+ ok(executed.textContent.includes("10003"),
+ "command2 executed successfully");
+
+ info("Trying to get the result of command3");
+ executed = yield command3;
+ ok(executed.textContent.includes("ReferenceError: foobar is not defined"),
+ "command3 executed successfully");
+
+ let onceResumed = gTarget.once("thread-resumed");
+ EventUtils.sendMouseEvent({ type: "mousedown" }, gResumeButton, gDebugger);
+ yield onceResumed;
+
+ terminateWorkerInTab(tab, WORKER_URL);
+ yield waitForWorkerClose(workerClient);
+ yield gDevTools.closeToolbox(TargetFactory.forWorker(workerClient));
+ yield close(client);
+ yield removeTab(tab);
+});
diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_worker-console-03.js b/devtools/client/debugger/test/mochitest/browser_dbg_worker-console-03.js
new file mode 100644
index 000000000..49821492f
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_worker-console-03.js
@@ -0,0 +1,46 @@
+// Check to make sure that a worker can be attached to a toolbox
+// and that the console works.
+
+var TAB_URL = EXAMPLE_URL + "doc_WorkerActor.attachThread-tab.html";
+var WORKER_URL = "code_WorkerActor.attachThread-worker.js";
+
+// Test to see if creating the pause from the console works.
+add_task(function* testPausedByConsole() {
+ let {client, tab, tabClient, workerClient, toolbox, gDebugger} =
+ yield initWorkerDebugger(TAB_URL, WORKER_URL);
+
+ let gTarget = gDebugger.gTarget;
+ let gResumeButton = gDebugger.document.getElementById("resume");
+ let gResumeKey = gDebugger.document.getElementById("resumeKey");
+
+ let jsterm = yield getSplitConsole(toolbox);
+ let executed = yield jsterm.execute("10000+1");
+ ok(executed.textContent.includes("10001"),
+ "Text for message appeared correct");
+
+ let oncePaused = gTarget.once("thread-paused");
+ EventUtils.sendMouseEvent({ type: "mousedown" }, gResumeButton, gDebugger);
+ let pausedExecution = jsterm.execute("10000+2");
+
+ info("Executed a command with 'break on next' active, waiting for pause");
+ yield oncePaused;
+
+ executed = yield jsterm.execute("10000+3");
+ ok(executed.textContent.includes("10003"),
+ "Text for message appeared correct");
+
+ info("Waiting for a resume");
+ let onceResumed = gTarget.once("thread-resumed");
+ EventUtils.sendMouseEvent({ type: "mousedown" }, gResumeButton, gDebugger);
+ yield onceResumed;
+
+ executed = yield pausedExecution;
+ ok(executed.textContent.includes("10002"),
+ "Text for message appeared correct");
+
+ terminateWorkerInTab(tab, WORKER_URL);
+ yield waitForWorkerClose(workerClient);
+ yield gDevTools.closeToolbox(TargetFactory.forWorker(workerClient));
+ yield close(client);
+ yield removeTab(tab);
+});
diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_worker-source-map.js b/devtools/client/debugger/test/mochitest/browser_dbg_worker-source-map.js
new file mode 100644
index 000000000..c4e8841d5
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_worker-source-map.js
@@ -0,0 +1,89 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+const TAB_URL = EXAMPLE_URL + "doc_worker-source-map.html";
+const WORKER_URL = "code_worker-source-map.js";
+const COFFEE_URL = EXAMPLE_URL + "code_worker-source-map.coffee";
+
+function selectWorker(aPanel, aURL) {
+ let panelWin = aPanel.panelWin;
+ let promise = waitForDebuggerEvents(aPanel, panelWin.EVENTS.WORKER_SELECTED);
+ let Workers = panelWin.DebuggerView.Workers;
+ let item = Workers.getItemForAttachment((workerForm) => {
+ return workerForm.url === aURL;
+ });
+ Workers.selectedItem = item;
+ return promise;
+}
+
+function test() {
+ return Task.spawn(function* () {
+ yield pushPrefs(["devtools.debugger.workers", true]);
+
+ let options = {
+ source: TAB_URL,
+ line: 1
+ };
+ let [tab,, panel] = yield initDebugger(TAB_URL, options);
+ let toolbox = yield selectWorker(panel, WORKER_URL);
+ let workerPanel = toolbox.getCurrentPanel();
+ yield waitForSourceShown(workerPanel, ".coffee");
+ let panelWin = workerPanel.panelWin;
+ let Sources = panelWin.DebuggerView.Sources;
+ let editor = panelWin.DebuggerView.editor;
+ let threadClient = panelWin.gThreadClient;
+
+ isnot(Sources.selectedItem.attachment.source.url.indexOf(".coffee"), -1,
+ "The debugger should show the source mapped coffee source file.");
+ is(Sources.selectedValue.indexOf(".js"), -1,
+ "The debugger should not show the generated js source file.");
+ is(editor.getText().indexOf("isnt"), 211,
+ "The debugger's editor should have the coffee source source displayed.");
+ is(editor.getText().indexOf("function"), -1,
+ "The debugger's editor should not have the JS source displayed.");
+
+ yield threadClient.interrupt();
+ let sourceForm = getSourceForm(Sources, COFFEE_URL);
+ let source = threadClient.source(sourceForm);
+ let response = yield source.setBreakpoint({ line: 5 });
+
+ ok(!response.error,
+ "Should be able to set a breakpoint in a coffee source file.");
+ ok(!response.actualLocation,
+ "Should be able to set a breakpoint on line 5.");
+
+ let promise = new Promise((resolve) => {
+ threadClient.addOneTimeListener("paused", (event, packet) => {
+ is(packet.type, "paused",
+ "We should now be paused again.");
+ is(packet.why.type, "breakpoint",
+ "and the reason we should be paused is because we hit a breakpoint.");
+
+ // Check that we stopped at the right place, by making sure that the
+ // environment is in the state that we expect.
+ is(packet.frame.environment.bindings.variables.start.value, 0,
+ "'start' is 0.");
+ is(packet.frame.environment.bindings.variables.stop.value.type, "undefined",
+ "'stop' hasn't been assigned to yet.");
+ is(packet.frame.environment.bindings.variables.pivot.value.type, "undefined",
+ "'pivot' hasn't been assigned to yet.");
+
+ waitForCaretUpdated(workerPanel, 5).then(resolve);
+ });
+ });
+
+ // This will cause the breakpoint to be hit, and put us back in the
+ // paused state.
+ yield threadClient.resume();
+ callInTab(tab, "binary_search", [0, 2, 3, 5, 7, 10], 5);
+ yield promise;
+
+ yield threadClient.resume();
+ yield toolbox.destroy();
+ yield closeDebuggerAndFinish(panel);
+
+ yield popPrefs();
+ });
+}
diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_worker-window.js b/devtools/client/debugger/test/mochitest/browser_dbg_worker-window.js
new file mode 100644
index 000000000..46198d31c
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_worker-window.js
@@ -0,0 +1,61 @@
+// Check to make sure that a worker can be attached to a toolbox
+// directly, and that the toolbox has expected properties.
+
+"use strict";
+
+// Whitelisting this test.
+// As part of bug 1077403, the leaking uncaught rejections should be fixed.
+thisTestLeaksUncaughtRejectionsAndShouldBeFixed("[object Object]");
+
+var TAB_URL = EXAMPLE_URL + "doc_WorkerActor.attachThread-tab.html";
+var WORKER_URL = "code_WorkerActor.attachThread-worker.js";
+
+add_task(function* () {
+ yield pushPrefs(["devtools.scratchpad.enabled", true]);
+
+ DebuggerServer.init();
+ DebuggerServer.addBrowserActors();
+
+ let client = new DebuggerClient(DebuggerServer.connectPipe());
+ yield connect(client);
+
+ let tab = yield addTab(TAB_URL);
+ let { tabs } = yield listTabs(client);
+ let [, tabClient] = yield attachTab(client, findTab(tabs, TAB_URL));
+
+ yield listWorkers(tabClient);
+ yield createWorkerInTab(tab, WORKER_URL);
+
+ let { workers } = yield listWorkers(tabClient);
+ let [, workerClient] = yield attachWorker(tabClient,
+ findWorker(workers, WORKER_URL));
+
+ let toolbox = yield gDevTools.showToolbox(TargetFactory.forWorker(workerClient),
+ "jsdebugger",
+ Toolbox.HostType.WINDOW);
+
+ is(toolbox.hostType, "window", "correct host");
+
+ yield new Promise(done => {
+ toolbox.win.parent.addEventListener("message", function onmessage(event) {
+ if (event.data.name == "set-host-title") {
+ toolbox.win.parent.removeEventListener("message", onmessage);
+ done();
+ }
+ });
+ });
+ ok(toolbox.win.parent.document.title.includes(WORKER_URL),
+ "worker URL in host title");
+
+ let toolTabs = toolbox.doc.querySelectorAll(".devtools-tab");
+ let activeTools = [...toolTabs].map(tab=>tab.getAttribute("toolid"));
+
+ is(activeTools.join(","), "webconsole,jsdebugger,scratchpad,options",
+ "Correct set of tools supported by worker");
+
+ terminateWorkerInTab(tab, WORKER_URL);
+ yield waitForWorkerClose(workerClient);
+ yield close(client);
+
+ yield toolbox.destroy();
+});
diff --git a/devtools/client/debugger/test/mochitest/code_WorkerActor.attach-worker1.js b/devtools/client/debugger/test/mochitest/code_WorkerActor.attach-worker1.js
new file mode 100644
index 000000000..18f14864d
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/code_WorkerActor.attach-worker1.js
@@ -0,0 +1,5 @@
+"use strict";
+
+self.onmessage = function () {};
+
+postMessage("load");
diff --git a/devtools/client/debugger/test/mochitest/code_WorkerActor.attach-worker2.js b/devtools/client/debugger/test/mochitest/code_WorkerActor.attach-worker2.js
new file mode 100644
index 000000000..18f14864d
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/code_WorkerActor.attach-worker2.js
@@ -0,0 +1,5 @@
+"use strict";
+
+self.onmessage = function () {};
+
+postMessage("load");
diff --git a/devtools/client/debugger/test/mochitest/code_WorkerActor.attachThread-worker.js b/devtools/client/debugger/test/mochitest/code_WorkerActor.attachThread-worker.js
new file mode 100644
index 000000000..881eab0b8
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/code_WorkerActor.attachThread-worker.js
@@ -0,0 +1,16 @@
+"use strict";
+
+function f() {
+ var a = 1;
+ var b = 2;
+ var c = 3;
+}
+
+self.onmessage = function (event) {
+ if (event.data == "ping") {
+ f();
+ postMessage("pong");
+ }
+};
+
+postMessage("load");
diff --git a/devtools/client/debugger/test/mochitest/code_binary_search.coffee b/devtools/client/debugger/test/mochitest/code_binary_search.coffee
new file mode 100644
index 000000000..e3dacdaaa
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/code_binary_search.coffee
@@ -0,0 +1,18 @@
+# Uses a binary search algorithm to locate a value in the specified array.
+window.binary_search = (items, value) ->
+
+ start = 0
+ stop = items.length - 1
+ pivot = Math.floor (start + stop) / 2
+
+ while items[pivot] isnt value and start < stop
+
+ # Adjust the search area.
+ stop = pivot - 1 if value < items[pivot]
+ start = pivot + 1 if value > items[pivot]
+
+ # Recalculate the pivot.
+ pivot = Math.floor (stop + start) / 2
+
+ # Make sure we've found the correct value.
+ if items[pivot] is value then pivot else -1 \ No newline at end of file
diff --git a/devtools/client/debugger/test/mochitest/code_binary_search.js b/devtools/client/debugger/test/mochitest/code_binary_search.js
new file mode 100644
index 000000000..c43848a60
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/code_binary_search.js
@@ -0,0 +1,29 @@
+// Generated by CoffeeScript 1.6.1
+(function() {
+
+ window.binary_search = function(items, value) {
+ var pivot, start, stop;
+ start = 0;
+ stop = items.length - 1;
+ pivot = Math.floor((start + stop) / 2);
+ while (items[pivot] !== value && start < stop) {
+ if (value < items[pivot]) {
+ stop = pivot - 1;
+ }
+ if (value > items[pivot]) {
+ start = pivot + 1;
+ }
+ pivot = Math.floor((stop + start) / 2);
+ }
+ if (items[pivot] === value) {
+ return pivot;
+ } else {
+ return -1;
+ }
+ };
+
+}).call(this);
+
+/*
+//# sourceMappingURL=code_binary_search.map
+*/
diff --git a/devtools/client/debugger/test/mochitest/code_binary_search.map b/devtools/client/debugger/test/mochitest/code_binary_search.map
new file mode 100644
index 000000000..8d2251125
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/code_binary_search.map
@@ -0,0 +1,10 @@
+{
+ "version": 3,
+ "file": "code_binary_search.js",
+ "sourceRoot": "",
+ "sources": [
+ "code_binary_search.coffee"
+ ],
+ "names": [],
+ "mappings": ";AACA;CAAA;CAAA,CAAA,CAAuB,EAAA,CAAjB,GAAkB,IAAxB;CAEE,OAAA,UAAA;CAAA,EAAQ,CAAR,CAAA;CAAA,EACQ,CAAR,CAAa,CAAL;CADR,EAEQ,CAAR,CAAA;CAEA,EAA0C,CAAR,CAAtB,MAAN;CAGJ,EAA6B,CAAR,CAAA,CAArB;CAAA,EAAQ,CAAR,CAAQ,GAAR;QAAA;CACA,EAA6B,CAAR,CAAA,CAArB;CAAA,EAAQ,EAAR,GAAA;QADA;CAAA,EAIQ,CAAI,CAAZ,CAAA;CAXF,IAIA;CAUA,GAAA,CAAS;CAAT,YAA8B;MAA9B;AAA0C,CAAD,YAAA;MAhBpB;CAAvB,EAAuB;CAAvB"
+}
diff --git a/devtools/client/debugger/test/mochitest/code_blackboxing_blackboxme.js b/devtools/client/debugger/test/mochitest/code_blackboxing_blackboxme.js
new file mode 100644
index 000000000..713b3d50d
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/code_blackboxing_blackboxme.js
@@ -0,0 +1,9 @@
+function blackboxme(fn) {
+ (function one() {
+ (function two() {
+ (function three() {
+ fn();
+ }());
+ }());
+ }());
+}
diff --git a/devtools/client/debugger/test/mochitest/code_blackboxing_one.js b/devtools/client/debugger/test/mochitest/code_blackboxing_one.js
new file mode 100644
index 000000000..7f37b02ad
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/code_blackboxing_one.js
@@ -0,0 +1,4 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+function one() { two(); }
diff --git a/devtools/client/debugger/test/mochitest/code_blackboxing_three.js b/devtools/client/debugger/test/mochitest/code_blackboxing_three.js
new file mode 100644
index 000000000..55ed6c4da
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/code_blackboxing_three.js
@@ -0,0 +1,4 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+function three() { doDebuggerStatement(); }
diff --git a/devtools/client/debugger/test/mochitest/code_blackboxing_two.js b/devtools/client/debugger/test/mochitest/code_blackboxing_two.js
new file mode 100644
index 000000000..4790ea4a7
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/code_blackboxing_two.js
@@ -0,0 +1,4 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+function two() { three(); }
diff --git a/devtools/client/debugger/test/mochitest/code_blackboxing_unblackbox.min.js b/devtools/client/debugger/test/mochitest/code_blackboxing_unblackbox.min.js
new file mode 100644
index 000000000..b8b285589
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/code_blackboxing_unblackbox.min.js
@@ -0,0 +1 @@
+function blackboxme() {one();} function one() {}
diff --git a/devtools/client/debugger/test/mochitest/code_breakpoints-break-on-last-line-of-script-on-reload.js b/devtools/client/debugger/test/mochitest/code_breakpoints-break-on-last-line-of-script-on-reload.js
new file mode 100644
index 000000000..839f22883
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/code_breakpoints-break-on-last-line-of-script-on-reload.js
@@ -0,0 +1,6 @@
+debugger;
+var a = (function () {
+ var b = 9;
+ console.log("x", b);
+ return b;
+})();
diff --git a/devtools/client/debugger/test/mochitest/code_breakpoints-other-tabs.js b/devtools/client/debugger/test/mochitest/code_breakpoints-other-tabs.js
new file mode 100644
index 000000000..2cf53ba2d
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/code_breakpoints-other-tabs.js
@@ -0,0 +1,4 @@
+function testCase() {
+ var foo = "break on me";
+ debugger;
+}
diff --git a/devtools/client/debugger/test/mochitest/code_bug-896139.js b/devtools/client/debugger/test/mochitest/code_bug-896139.js
new file mode 100644
index 000000000..65313bcb2
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/code_bug-896139.js
@@ -0,0 +1,8 @@
+<!-- Any copyright is dedicated to the Public Domain.
+ http:// creativecommons.org/publicdomain/zero/1.0/ -->
+
+function f() {
+ var a = 1;
+ var b = 2;
+ var c = 3;
+}
diff --git a/devtools/client/debugger/test/mochitest/code_frame-script.js b/devtools/client/debugger/test/mochitest/code_frame-script.js
new file mode 100644
index 000000000..35a950b01
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/code_frame-script.js
@@ -0,0 +1,106 @@
+"use strict";
+
+var { classes: Cc, interfaces: Ci, utils: Cu } = Components;
+const { loadSubScript } = Cc["@mozilla.org/moz/jssubscript-loader;1"].
+ getService(Ci.mozIJSSubScriptLoader);
+
+// Set up a dummy environment so that EventUtils works. We need to be careful to
+// pass a window object into each EventUtils method we call rather than having
+// it rely on the |window| global.
+let EventUtils = {};
+EventUtils.window = content;
+EventUtils.parent = EventUtils.window;
+EventUtils._EU_Ci = Components.interfaces;
+EventUtils._EU_Cc = Components.classes;
+EventUtils.navigator = content.navigator;
+EventUtils.KeyboardEvent = content.KeyboardEvent;
+loadSubScript("chrome://mochikit/content/tests/SimpleTest/EventUtils.js", EventUtils);
+
+dump("Frame script loaded.\n");
+
+var workers = {};
+
+this.call = function (name, args) {
+ dump("Calling function with name " + name + ".\n");
+
+ dump("args " + JSON.stringify(args) + "\n");
+ return XPCNativeWrapper.unwrap(content)[name].apply(undefined, args);
+};
+
+this._eval = function (string) {
+ dump("Evalling string.\n");
+
+ return content.eval(string);
+};
+
+this.generateMouseClick = function (path) {
+ dump("Generating mouse click.\n");
+
+ let target = eval(path);
+ EventUtils.synthesizeMouseAtCenter(target, {},
+ target.ownerDocument.defaultView);
+};
+
+this.createWorker = function (url) {
+ dump("Creating worker with url '" + url + "'.\n");
+
+ return new Promise(function (resolve, reject) {
+ let worker = new content.Worker(url);
+ worker.addEventListener("message", function listener() {
+ worker.removeEventListener("message", listener);
+ workers[url] = worker;
+ resolve();
+ });
+ });
+};
+
+this.terminateWorker = function (url) {
+ dump("Terminating worker with url '" + url + "'.\n");
+
+ workers[url].terminate();
+ delete workers[url];
+};
+
+this.postMessageToWorker = function (url, message) {
+ dump("Posting message to worker with url '" + url + "'.\n");
+
+ return new Promise(function (resolve) {
+ let worker = workers[url];
+ worker.postMessage(message);
+ worker.addEventListener("message", function listener() {
+ worker.removeEventListener("message", listener);
+ resolve();
+ });
+ });
+};
+
+addMessageListener("jsonrpc", function ({ data: { method, params, id } }) {
+ method = this[method];
+ Promise.resolve().then(function () {
+ return method.apply(undefined, params);
+ }).then(function (result) {
+ sendAsyncMessage("jsonrpc", {
+ result: result,
+ error: null,
+ id: id
+ });
+ }, function (error) {
+ sendAsyncMessage("jsonrpc", {
+ result: null,
+ error: error.message.toString(),
+ id: id
+ });
+ });
+});
+
+addMessageListener("test:postMessageToWorker", function (message) {
+ dump("Posting message '" + message.data.message + "' to worker with url '" +
+ message.data.url + "'.\n");
+
+ let worker = workers[message.data.url];
+ worker.postMessage(message.data.message);
+ worker.addEventListener("message", function listener() {
+ worker.removeEventListener("message", listener);
+ sendAsyncMessage("test:postMessageToWorker");
+ });
+});
diff --git a/devtools/client/debugger/test/mochitest/code_function-jump-01.js b/devtools/client/debugger/test/mochitest/code_function-jump-01.js
new file mode 100644
index 000000000..d71cbfa93
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/code_function-jump-01.js
@@ -0,0 +1,6 @@
+
+function foo() {
+ // some function
+}
+
+foo();
diff --git a/devtools/client/debugger/test/mochitest/code_function-search-01.js b/devtools/client/debugger/test/mochitest/code_function-search-01.js
new file mode 100644
index 000000000..77331d722
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/code_function-search-01.js
@@ -0,0 +1,42 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+function test() {
+ // Blah! First source!
+}
+
+test.prototype = {
+ anonymousExpression: function () {
+ },
+ namedExpression: function NAME() {
+ },
+ sub: {
+ sub: {
+ sub: {
+ }
+ }
+ }
+};
+
+var foo = {
+ a_test: function () {
+ },
+ n_test: function x() {
+ },
+ sub: {
+ a_test: function () {
+ },
+ n_test: function y() {
+ },
+ sub: {
+ a_test: function () {
+ },
+ n_test: function z() {
+ },
+ sub: {
+ test_SAME_NAME: function test_SAME_NAME() {
+ }
+ }
+ }
+ }
+};
diff --git a/devtools/client/debugger/test/mochitest/code_function-search-02.js b/devtools/client/debugger/test/mochitest/code_function-search-02.js
new file mode 100644
index 000000000..ab25641d2
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/code_function-search-02.js
@@ -0,0 +1,21 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+var test2 = function () {
+ // Blah! Second source!
+};
+
+var test3 = function test3_NAME() {
+};
+
+var test4_SAME_NAME = function test4_SAME_NAME() {
+};
+
+test.prototype.x = function X() {
+};
+test.prototype.sub.y = function Y() {
+};
+test.prototype.sub.sub.z = function Z() {
+};
+test.prototype.sub.sub.sub.t = this.x = this.y = this.z = function () {
+};
diff --git a/devtools/client/debugger/test/mochitest/code_function-search-03.js b/devtools/client/debugger/test/mochitest/code_function-search-03.js
new file mode 100644
index 000000000..e64292a92
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/code_function-search-03.js
@@ -0,0 +1,32 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+window.addEventListener("bogus", function namedEventListener() {
+ // Blah! Third source!
+});
+
+try {
+ var bar = foo.sub.sub.test({
+ a: function A() {
+ }
+ });
+
+ bar.alpha = foo.sub.sub.test({
+ b: function B() {
+ }
+ });
+
+ bar.alpha.beta = new X(Y(Z(foo.sub.sub.test({
+ c: function C() {
+ }
+ }))));
+
+ this.theta = new X(new Y(new Z(new foo.sub.sub.test({
+ d: function D() {
+ }
+ }))));
+
+ var fun = foo = bar = this.t_foo = window.w_bar = function baz() {};
+
+} catch (e) {
+}
diff --git a/devtools/client/debugger/test/mochitest/code_listworkers-worker1.js b/devtools/client/debugger/test/mochitest/code_listworkers-worker1.js
new file mode 100644
index 000000000..8cee6809e
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/code_listworkers-worker1.js
@@ -0,0 +1,3 @@
+"use strict";
+
+self.onmessage = function () {};
diff --git a/devtools/client/debugger/test/mochitest/code_listworkers-worker2.js b/devtools/client/debugger/test/mochitest/code_listworkers-worker2.js
new file mode 100644
index 000000000..8cee6809e
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/code_listworkers-worker2.js
@@ -0,0 +1,3 @@
+"use strict";
+
+self.onmessage = function () {};
diff --git a/devtools/client/debugger/test/mochitest/code_location-changes.js b/devtools/client/debugger/test/mochitest/code_location-changes.js
new file mode 100644
index 000000000..d164b8bdf
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/code_location-changes.js
@@ -0,0 +1,7 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+function myFunction() {
+ var a = 1;
+ debugger;
+}
diff --git a/devtools/client/debugger/test/mochitest/code_math.js b/devtools/client/debugger/test/mochitest/code_math.js
new file mode 100644
index 000000000..f765817bb
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/code_math.js
@@ -0,0 +1,45 @@
+function add(a, b, k) {
+ var result = a + b;
+ return k(result);
+}
+
+function sub(a, b, k) {
+ var result = a - b;
+ return k(result);
+}
+
+function mul(a, b, k) {
+ var result = a * b;
+ return k(result);
+}
+
+function div(a, b, k) {
+ var result = a / b;
+ return k(result);
+}
+
+function arithmetic() {
+ add(4, 4, function (a) {
+ // 8
+ sub(a, 2, function (b) {
+ // 6
+ mul(b, 3, function (c) {
+ // 18
+ div(c, 2, function (d) {
+ // 9
+ console.log(d);
+ });
+ });
+ });
+ });
+}
+
+// Compile with closure compiler and the following flags:
+//
+// --compilation_level WHITESPACE_ONLY
+// --source_map_format V3
+// --create_source_map code_math.map
+// --js_output_file code_math.min.js
+//
+// And then append the sourceMappingURL comment directive to code_math.min.js
+// manually.
diff --git a/devtools/client/debugger/test/mochitest/code_math.map b/devtools/client/debugger/test/mochitest/code_math.map
new file mode 100644
index 000000000..474304c39
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/code_math.map
@@ -0,0 +1,8 @@
+{
+"version":3,
+"file":"code_math.min.js",
+"lineCount":1,
+"mappings":"AAAAA,QAASA,IAAG,CAACC,CAAD,CAAIC,CAAJ,CAAOC,CAAP,CAAU,CACpB,IAAIC,OAASH,CAATG,CAAaF,CACjB,OAAOC,EAAA,CAAEC,MAAF,CAFa,CAKtBC,QAASA,IAAG,CAACJ,CAAD,CAAIC,CAAJ,CAAOC,CAAP,CAAU,CACpB,IAAIC,OAASH,CAATG,CAAaF,CACjB,OAAOC,EAAA,CAAEC,MAAF,CAFa,CAKtBE,QAASA,IAAG,CAACL,CAAD,CAAIC,CAAJ,CAAOC,CAAP,CAAU,CACpB,IAAIC,OAASH,CAATG,CAAaF,CACjB,OAAOC,EAAA,CAAEC,MAAF,CAFa,CAKtBG,QAASA,IAAG,CAACN,CAAD,CAAIC,CAAJ,CAAOC,CAAP,CAAU,CACpB,IAAIC,OAASH,CAATG,CAAaF,CACjB,OAAOC,EAAA,CAAEC,MAAF,CAFa,CAKtBI,QAASA,WAAU,EAAG,CACpBR,GAAA,CAAI,CAAJ,CAAO,CAAP,CAAU,QAAS,CAACC,CAAD,CAAI,CAErBI,GAAA,CAAIJ,CAAJ,CAAO,CAAP,CAAU,QAAS,CAACC,CAAD,CAAI,CAErBI,GAAA,CAAIJ,CAAJ,CAAO,CAAP,CAAU,QAAS,CAACO,CAAD,CAAI,CAErBF,GAAA,CAAIE,CAAJ,CAAO,CAAP,CAAU,QAAS,CAACC,CAAD,CAAI,CAErBC,OAAAC,IAAA,CAAYF,CAAZ,CAFqB,CAAvB,CAFqB,CAAvB,CAFqB,CAAvB,CAFqB,CAAvB,CADoB;",
+"sources":["code_math.js"],
+"names":["add","a","b","k","result","sub","mul","div","arithmetic","c","d","console","log"]
+}
diff --git a/devtools/client/debugger/test/mochitest/code_math.min.js b/devtools/client/debugger/test/mochitest/code_math.min.js
new file mode 100644
index 000000000..7d1fb48f0
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/code_math.min.js
@@ -0,0 +1,2 @@
+function add(a,b,k){var result=a+b;return k(result)}function sub(a,b,k){var result=a-b;return k(result)}function mul(a,b,k){var result=a*b;return k(result)}function div(a,b,k){var result=a/b;return k(result)}function arithmetic(){add(4,4,function(a){sub(a,2,function(b){mul(b,3,function(c){div(c,2,function(d){console.log(d)})})})})};
+//@ sourceMappingURL=code_math.map
diff --git a/devtools/client/debugger/test/mochitest/code_math_bogus_map.js b/devtools/client/debugger/test/mochitest/code_math_bogus_map.js
new file mode 100644
index 000000000..82e156b10
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/code_math_bogus_map.js
@@ -0,0 +1,4 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+function stopMe(){throw Error("boom");}try{stopMe();var a=1;a=a*2;}catch(e){};
+//# sourceMappingURL=bogus.map
diff --git a/devtools/client/debugger/test/mochitest/code_same-line-functions.js b/devtools/client/debugger/test/mochitest/code_same-line-functions.js
new file mode 100644
index 000000000..60a6c6ab1
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/code_same-line-functions.js
@@ -0,0 +1 @@
+function first() { var a = "first"; second(); function second() { var a = "second"; } }
diff --git a/devtools/client/debugger/test/mochitest/code_script-eval.js b/devtools/client/debugger/test/mochitest/code_script-eval.js
new file mode 100644
index 000000000..0d7ceba66
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/code_script-eval.js
@@ -0,0 +1,14 @@
+
+var bar;
+
+function evalSource() {
+ eval("bar = function() {\nvar x = 5;\n}");
+}
+
+function evalSourceWithSourceURL() {
+ eval("bar = function() {\nvar x = 6;\n} //# sourceURL=bar.js");
+}
+
+function evalSourceWithDebugger() {
+ eval("bar = function() {\nvar x = 7;\ndebugger; }\n bar();");
+}
diff --git a/devtools/client/debugger/test/mochitest/code_script-switching-01.js b/devtools/client/debugger/test/mochitest/code_script-switching-01.js
new file mode 100644
index 000000000..4ba2772de
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/code_script-switching-01.js
@@ -0,0 +1,6 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+function firstCall() {
+ secondCall();
+}
diff --git a/devtools/client/debugger/test/mochitest/code_script-switching-02.js b/devtools/client/debugger/test/mochitest/code_script-switching-02.js
new file mode 100644
index 000000000..feb74315f
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/code_script-switching-02.js
@@ -0,0 +1,13 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+function secondCall() {
+ // This comment is useful: ☺
+ debugger;
+ function foo() {}
+ if (x) {
+ foo();
+ }
+}
+
+var x = true;
diff --git a/devtools/client/debugger/test/mochitest/code_test-editor-mode b/devtools/client/debugger/test/mochitest/code_test-editor-mode
new file mode 100644
index 000000000..ca8a90889
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/code_test-editor-mode
@@ -0,0 +1,6 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+function secondCall() {
+ debugger;
+}
diff --git a/devtools/client/debugger/test/mochitest/code_ugly-2.js b/devtools/client/debugger/test/mochitest/code_ugly-2.js
new file mode 100644
index 000000000..15fba0701
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/code_ugly-2.js
@@ -0,0 +1 @@
+function main2() { var a = 1 + 3; var b = a++; return b + a; }
diff --git a/devtools/client/debugger/test/mochitest/code_ugly-3.js b/devtools/client/debugger/test/mochitest/code_ugly-3.js
new file mode 100644
index 000000000..0424b288c
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/code_ugly-3.js
@@ -0,0 +1 @@
+function main3() { var a = 1; debugger; noop(a); return 10; };
diff --git a/devtools/client/debugger/test/mochitest/code_ugly-4.js b/devtools/client/debugger/test/mochitest/code_ugly-4.js
new file mode 100644
index 000000000..dbd596dc3
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/code_ugly-4.js
@@ -0,0 +1,25 @@
+function a(){b()}function b(){debugger}
+//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiYWJjLmpzIiwic291cmNlcyI6WyJkYXRhOnRleHQvamF2YXNjcmlwdCxmdW5jdGlvbiBhKCl7YigpfSIsImRhdGE6dGV4dC9qYXZhc2NyaXB0LGZ1bmN0aW9uIGIoKXtkZWJ1Z2dlcn0iXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsaUJDQUEsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMifQ==
+
+// Generate this file by evaluating the following in a browser-environment
+// scratchpad:
+//
+// let { require } = Components.utils.import('resource://devtools/shared/Loader.jsm', {});
+// let { SourceNode } = require("source-map");
+//
+// let dataUrl = s => "data:text/javascript," + s;
+//
+// let A = "function a(){b()}";
+// let A_URL = dataUrl(A);
+// let B = "function b(){debugger}";
+// let B_URL = dataUrl(B);
+//
+// let result = (new SourceNode(null, null, null, [
+// new SourceNode(1, 0, A_URL, A),
+// B.split("").map((ch, i) => new SourceNode(1, i, B_URL, ch))
+// ])).toStringWithSourceMap({
+// file: "abc.js"
+// });
+//
+// result.code + "\n//# " + "sourceMappingURL=data:application/json;base64," + btoa(JSON.stringify(result.map));
+
diff --git a/devtools/client/debugger/test/mochitest/code_ugly-5.js b/devtools/client/debugger/test/mochitest/code_ugly-5.js
new file mode 100644
index 000000000..a94f521dc
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/code_ugly-5.js
@@ -0,0 +1,14 @@
+/*1385419625,181944095,JIT Construction: v1021776,en_US*/
+/**
+ * Copyright Test Inc.
+ *
+ * Licensed under the Apache License, Version 2.0
+ * http://www.apache.org/licenses/LICENSE-2.0
+ */
+// Copyright Test Inc.
+//
+// etc...
+// etc...
+function foo(){var a=1;var b=2;bar(a,b);}
+function bar(c,d){return 3;}
+foo();
diff --git a/devtools/client/debugger/test/mochitest/code_ugly-6.js b/devtools/client/debugger/test/mochitest/code_ugly-6.js
new file mode 100644
index 000000000..0c678c140
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/code_ugly-6.js
@@ -0,0 +1,5 @@
+// Copyright Test Inc.
+//
+// etc...
+// etc...
+function main(){ return 0; } \ No newline at end of file
diff --git a/devtools/client/debugger/test/mochitest/code_ugly-7.js b/devtools/client/debugger/test/mochitest/code_ugly-7.js
new file mode 100644
index 000000000..8ce53b305
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/code_ugly-7.js
@@ -0,0 +1,5 @@
+// Copyright Test Inc.
+//
+// etc...
+// etc...
+function foo(){}; foo(); \ No newline at end of file
diff --git a/devtools/client/debugger/test/mochitest/code_ugly-8 b/devtools/client/debugger/test/mochitest/code_ugly-8
new file mode 100644
index 000000000..dc0d18500
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/code_ugly-8
@@ -0,0 +1,3 @@
+function foo() { var a=1; var b=2; bar(a, b); }
+function bar(c, d) { debugger; }
+foo();
diff --git a/devtools/client/debugger/test/mochitest/code_ugly-8^headers^ b/devtools/client/debugger/test/mochitest/code_ugly-8^headers^
new file mode 100644
index 000000000..a17a9a3a1
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/code_ugly-8^headers^
@@ -0,0 +1 @@
+Content-Type: application/javascript
diff --git a/devtools/client/debugger/test/mochitest/code_ugly.js b/devtools/client/debugger/test/mochitest/code_ugly.js
new file mode 100644
index 000000000..dc0d18500
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/code_ugly.js
@@ -0,0 +1,3 @@
+function foo() { var a=1; var b=2; bar(a, b); }
+function bar(c, d) { debugger; }
+foo();
diff --git a/devtools/client/debugger/test/mochitest/code_worker-source-map.coffee b/devtools/client/debugger/test/mochitest/code_worker-source-map.coffee
new file mode 100644
index 000000000..446e3aefe
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/code_worker-source-map.coffee
@@ -0,0 +1,22 @@
+# Uses a binary search algorithm to locate a value in the specified array.
+binary_search = (items, value) ->
+
+ start = 0
+ stop = items.length - 1
+ pivot = Math.floor (start + stop) / 2
+
+ while items[pivot] isnt value and start < stop
+
+ # Adjust the search area.
+ stop = pivot - 1 if value < items[pivot]
+ start = pivot + 1 if value > items[pivot]
+
+ # Recalculate the pivot.
+ pivot = Math.floor (stop + start) / 2
+
+ # Make sure we've found the correct value.
+ if items[pivot] is value then pivot else -1
+
+self.onmessage = (event) ->
+ data = event.data
+ binary_search(data.items, data.value)
diff --git a/devtools/client/debugger/test/mochitest/code_worker-source-map.js b/devtools/client/debugger/test/mochitest/code_worker-source-map.js
new file mode 100644
index 000000000..9a9f541a9
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/code_worker-source-map.js
@@ -0,0 +1,35 @@
+// Generated by CoffeeScript 1.10.0
+(function() {
+ var binary_search;
+
+ binary_search = function(items, value) {
+ var pivot, start, stop;
+ start = 0;
+ stop = items.length - 1;
+ pivot = Math.floor((start + stop) / 2);
+ while (items[pivot] !== value && start < stop) {
+ if (value < items[pivot]) {
+ stop = pivot - 1;
+ }
+ if (value > items[pivot]) {
+ start = pivot + 1;
+ }
+ pivot = Math.floor((stop + start) / 2);
+ }
+ if (items[pivot] === value) {
+ return pivot;
+ } else {
+ return -1;
+ }
+ };
+
+ self.onmessage = function(event) {
+ console.log("EUTA");
+ var data;
+ data = event.data;
+ return binary_search(data.items, data.value);
+ };
+
+}).call(this);
+
+//# sourceMappingURL=code_worker-source-map.js.map
diff --git a/devtools/client/debugger/test/mochitest/code_worker-source-map.js.map b/devtools/client/debugger/test/mochitest/code_worker-source-map.js.map
new file mode 100644
index 000000000..97c801a58
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/code_worker-source-map.js.map
@@ -0,0 +1,10 @@
+{
+ "version": 3,
+ "file": "code_worker-source-map.js",
+ "sourceRoot": "",
+ "sources": [
+ "code_worker-source-map.coffee"
+ ],
+ "names": [],
+ "mappings": ";AACA;AAAA,MAAA;;EAAA,aAAA,GAAgB,SAAC,KAAD,EAAQ,KAAR;AAEd,QAAA;IAAA,KAAA,GAAQ;IACR,IAAA,GAAQ,KAAK,CAAC,MAAN,GAAe;IACvB,KAAA,GAAQ,IAAI,CAAC,KAAL,CAAW,CAAC,KAAA,GAAQ,IAAT,CAAA,GAAiB,CAA5B;AAER,WAAM,KAAM,CAAA,KAAA,CAAN,KAAkB,KAAlB,IAA4B,KAAA,GAAQ,IAA1C;MAGE,IAAqB,KAAA,GAAQ,KAAM,CAAA,KAAA,CAAnC;QAAA,IAAA,GAAQ,KAAA,GAAQ,EAAhB;;MACA,IAAqB,KAAA,GAAQ,KAAM,CAAA,KAAA,CAAnC;QAAA,KAAA,GAAQ,KAAA,GAAQ,EAAhB;;MAGA,KAAA,GAAQ,IAAI,CAAC,KAAL,CAAW,CAAC,IAAA,GAAO,KAAR,CAAA,GAAiB,CAA5B;IAPV;IAUA,IAAG,KAAM,CAAA,KAAA,CAAN,KAAgB,KAAnB;aAA8B,MAA9B;KAAA,MAAA;aAAyC,CAAC,EAA1C;;EAhBc;;EAkBhB,IAAI,CAAC,SAAL,GAAiB,SAAC,KAAD;AACf,QAAA;IAAA,IAAA,GAAO,KAAK,CAAC;WACb,aAAA,CAAc,IAAI,CAAC,KAAnB,EAA0B,IAAI,CAAC,KAA/B;EAFe;AAlBjB"
+}
diff --git a/devtools/client/debugger/test/mochitest/code_workeractor-worker.js b/devtools/client/debugger/test/mochitest/code_workeractor-worker.js
new file mode 100644
index 000000000..18f14864d
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/code_workeractor-worker.js
@@ -0,0 +1,5 @@
+"use strict";
+
+self.onmessage = function () {};
+
+postMessage("load");
diff --git a/devtools/client/debugger/test/mochitest/doc_WorkerActor.attach-tab1.html b/devtools/client/debugger/test/mochitest/doc_WorkerActor.attach-tab1.html
new file mode 100644
index 000000000..62ab9be7d
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/doc_WorkerActor.attach-tab1.html
@@ -0,0 +1,8 @@
+<!DOCTYPE html>
+<html lang="en">
+ <head>
+ <meta charset="utf-8"/>
+ </head>
+ <body>
+ </body>
+</html>
diff --git a/devtools/client/debugger/test/mochitest/doc_WorkerActor.attach-tab2.html b/devtools/client/debugger/test/mochitest/doc_WorkerActor.attach-tab2.html
new file mode 100644
index 000000000..62ab9be7d
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/doc_WorkerActor.attach-tab2.html
@@ -0,0 +1,8 @@
+<!DOCTYPE html>
+<html lang="en">
+ <head>
+ <meta charset="utf-8"/>
+ </head>
+ <body>
+ </body>
+</html>
diff --git a/devtools/client/debugger/test/mochitest/doc_WorkerActor.attachThread-tab.html b/devtools/client/debugger/test/mochitest/doc_WorkerActor.attachThread-tab.html
new file mode 100644
index 000000000..62ab9be7d
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/doc_WorkerActor.attachThread-tab.html
@@ -0,0 +1,8 @@
+<!DOCTYPE html>
+<html lang="en">
+ <head>
+ <meta charset="utf-8"/>
+ </head>
+ <body>
+ </body>
+</html>
diff --git a/devtools/client/debugger/test/mochitest/doc_auto-pretty-print-01.html b/devtools/client/debugger/test/mochitest/doc_auto-pretty-print-01.html
new file mode 100644
index 000000000..dee2d52f2
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/doc_auto-pretty-print-01.html
@@ -0,0 +1,14 @@
+<!-- Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ -->
+
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta charset="utf-8" />
+ <title>Auto Pretty Printing Test Page</title>
+ </head>
+ <body>
+ <script src="code_ugly-5.js"></script>
+ <script src="code_ugly-6.js"></script>
+ </body>
+</html> \ No newline at end of file
diff --git a/devtools/client/debugger/test/mochitest/doc_auto-pretty-print-02.html b/devtools/client/debugger/test/mochitest/doc_auto-pretty-print-02.html
new file mode 100644
index 000000000..e96a63d9e
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/doc_auto-pretty-print-02.html
@@ -0,0 +1,14 @@
+<!-- Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ -->
+
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta charset="utf-8" />
+ <title>Auto Pretty Printing Test Page</title>
+ </head>
+ <body>
+ <script src="code_ugly-6.js"></script>
+ <script src="code_ugly-7.js"></script>
+ </body>
+</html> \ No newline at end of file
diff --git a/devtools/client/debugger/test/mochitest/doc_binary_search.html b/devtools/client/debugger/test/mochitest/doc_binary_search.html
new file mode 100644
index 000000000..803106fc5
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/doc_binary_search.html
@@ -0,0 +1,15 @@
+<!-- Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ -->
+<!doctype html>
+
+<html>
+ <head>
+ <meta charset="utf-8"/>
+ <title>Debugger test page</title>
+ </head>
+
+ <body>
+ <script type="text/javascript" src="code_binary_search.js"></script>
+ </body>
+
+</html>
diff --git a/devtools/client/debugger/test/mochitest/doc_blackboxing.html b/devtools/client/debugger/test/mochitest/doc_blackboxing.html
new file mode 100644
index 000000000..a83b16de5
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/doc_blackboxing.html
@@ -0,0 +1,26 @@
+<!-- Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ -->
+<!doctype html>
+
+<html>
+ <head>
+ <meta charset="utf-8"/>
+ <title>Debugger test page</title>
+ </head>
+
+ <body>
+ <script type="text/javascript" src="code_blackboxing_blackboxme.js"></script>
+ <script type="text/javascript" src="code_blackboxing_one.js"></script>
+ <script type="text/javascript" src="code_blackboxing_two.js"></script>
+ <script type="text/javascript" src="code_blackboxing_three.js"></script>
+ <script>
+ function runTest() {
+ blackboxme(doDebuggerStatement);
+ }
+ function doDebuggerStatement() {
+ debugger;
+ }
+ </script>
+ </body>
+
+</html>
diff --git a/devtools/client/debugger/test/mochitest/doc_blackboxing_unblackbox.html b/devtools/client/debugger/test/mochitest/doc_blackboxing_unblackbox.html
new file mode 100644
index 000000000..3b26b25e3
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/doc_blackboxing_unblackbox.html
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Debugger test page</title>
+ <script type="text/javascript" src="code_blackboxing_unblackbox.min.js"></script>
+</head>
+<body>
+
+</body>
+</html> \ No newline at end of file
diff --git a/devtools/client/debugger/test/mochitest/doc_breakpoint-move.html b/devtools/client/debugger/test/mochitest/doc_breakpoint-move.html
new file mode 100644
index 000000000..5124bbbcf
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/doc_breakpoint-move.html
@@ -0,0 +1,25 @@
+<!-- Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ -->
+<!doctype html>
+
+<html>
+ <head>
+ <meta charset="utf-8"/>
+ <title>Debugger test page</title>
+ </head>
+
+ <body>
+ <button onclick="ermahgerd()">Click me!</button>
+
+ <script type="text/javascript">
+ function ermahgerd() {
+ debugger;
+ // This is just a line
+ // and here we are
+ var x = 5;
+ return x;
+ }
+ </script>
+ </body>
+
+</html>
diff --git a/devtools/client/debugger/test/mochitest/doc_breakpoints-break-on-last-line-of-script-on-reload.html b/devtools/client/debugger/test/mochitest/doc_breakpoints-break-on-last-line-of-script-on-reload.html
new file mode 100644
index 000000000..c1730e506
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/doc_breakpoints-break-on-last-line-of-script-on-reload.html
@@ -0,0 +1,8 @@
+<!-- Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ -->
+<!DOCTYPE html>
+<head>
+ <meta charset="utf-8"/>
+ <title>Debugger Break on Last Line of Script on Reload Test Page</title>
+</head>
+<script src="code_breakpoints-break-on-last-line-of-script-on-reload.js"></script>
diff --git a/devtools/client/debugger/test/mochitest/doc_breakpoints-other-tabs.html b/devtools/client/debugger/test/mochitest/doc_breakpoints-other-tabs.html
new file mode 100644
index 000000000..4273dbdd8
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/doc_breakpoints-other-tabs.html
@@ -0,0 +1,8 @@
+<!-- Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ -->
+<!DOCTYPE html>
+<head>
+ <meta charset="utf-8"/>
+ <title>Debugger Breakpoints Other Tabs Test Page</title>
+</head>
+<script src="code_breakpoints-other-tabs.js"></script>
diff --git a/devtools/client/debugger/test/mochitest/doc_breakpoints-reload.html b/devtools/client/debugger/test/mochitest/doc_breakpoints-reload.html
new file mode 100644
index 000000000..0c6059c6d
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/doc_breakpoints-reload.html
@@ -0,0 +1,13 @@
+<!-- Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ -->
+<!DOCTYPE html>
+<head>
+ <meta charset="utf-8"/>
+ <title>Debugger Breakpoints Other Tabs Test Page</title>
+</head>
+<script>
+ function theTest() {
+ window.foo = "break on me";
+ }
+ theTest();
+</script>
diff --git a/devtools/client/debugger/test/mochitest/doc_bug-896139.html b/devtools/client/debugger/test/mochitest/doc_bug-896139.html
new file mode 100644
index 000000000..166ad604f
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/doc_bug-896139.html
@@ -0,0 +1,18 @@
+<!-- Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ -->
+
+<!DOCTYPE html>
+<html lang="en">
+ <head>
+ <meta charset="utf-8"/>
+ <script>
+ window.onload = function () {
+ var script = document.createElement("script");
+ script.setAttribute("src", "code_bug-896139.js");
+ document.body.appendChild(script);
+ }
+ </script>
+ </head>
+ <body>
+ </body>
+</html>
diff --git a/devtools/client/debugger/test/mochitest/doc_closure-optimized-out.html b/devtools/client/debugger/test/mochitest/doc_closure-optimized-out.html
new file mode 100644
index 000000000..3ad4e8fc0
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/doc_closure-optimized-out.html
@@ -0,0 +1,34 @@
+<!DOCTYPE HTML>
+<html>
+ <head>
+ <meta charset='utf-8'/>
+ <title>Debugger Test for Inspecting Optimized-Out Variables</title>
+ <!-- Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ -->
+ <script type="text/javascript">
+ window.addEventListener("load", function onload() {
+ window.removeEventListener("load", onload);
+ function clickHandler(event) {
+ button.removeEventListener("click", clickHandler, false);
+ function outer(arg) {
+ var upvar = arg * 2;
+ // The inner lambda only aliases arg, so the frontend alias analysis decides
+ // that upvar is not aliased and is not in the CallObject.
+ return function () {
+ arg += 2;
+ };
+ }
+
+ var f = outer(42);
+ f();
+ }
+ var button = document.querySelector("button");
+ button.addEventListener("click", clickHandler, false);
+ });
+ </script>
+
+ </head>
+ <body>
+ <button>Click me!</button>
+ </body>
+</html>
diff --git a/devtools/client/debugger/test/mochitest/doc_closures.html b/devtools/client/debugger/test/mochitest/doc_closures.html
new file mode 100644
index 000000000..1ba91601a
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/doc_closures.html
@@ -0,0 +1,32 @@
+<!DOCTYPE HTML>
+<html>
+ <head>
+ <meta charset='utf-8'/>
+ <title>Debugger Test for Closure Inspection</title>
+ <!-- Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ -->
+ <script type="text/javascript">
+ window.addEventListener("load", function onload() {
+ window.removeEventListener("load", onload);
+ function clickHandler(event) {
+ button.removeEventListener("click", clickHandler, false);
+ var PersonFactory = function _pfactory(name) {
+ var foo = 10;
+ return {
+ getName: function() { return name; },
+ getFoo: function() { foo = Date.now(); return foo; }
+ };
+ };
+ var person = new PersonFactory("Bob");
+ debugger;
+ }
+ var button = document.querySelector("button");
+ button.addEventListener("click", clickHandler, false);
+ });
+ </script>
+
+ </head>
+ <body>
+ <button>Click me!</button>
+ </body>
+</html>
diff --git a/devtools/client/debugger/test/mochitest/doc_cmd-break.html b/devtools/client/debugger/test/mochitest/doc_cmd-break.html
new file mode 100644
index 000000000..4f434746e
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/doc_cmd-break.html
@@ -0,0 +1,22 @@
+<!-- Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ -->
+<!doctype html>
+
+<html>
+ <head>
+ <meta charset="utf-8"/>
+ <title>Debugger test page</title>
+ </head>
+
+ <body>
+ <script type="text/javascript">
+ function firstCall() {
+ window.gLineNumber = Error().lineNumber; secondCall();
+ }
+ function secondCall() {
+ debugger;
+ }
+ </script>
+ </body>
+
+</html>
diff --git a/devtools/client/debugger/test/mochitest/doc_cmd-dbg.html b/devtools/client/debugger/test/mochitest/doc_cmd-dbg.html
new file mode 100644
index 000000000..5ab41eb1b
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/doc_cmd-dbg.html
@@ -0,0 +1,40 @@
+<!-- Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ -->
+<!doctype html>
+
+<html>
+ <head>
+ <meta charset="utf-8"/>
+ <title>Debugger test page</title>
+ </head>
+
+ <body>
+ <input type="text" value=""/>
+ <input type="button" value="Click me!" onclick="test()"/>
+
+ <script type="application/javascript;version=1.7">
+ let output = document.querySelector("input");
+ output.value = "";
+
+ function test() {
+ debugger;
+ stepIntoMe(); // step in
+
+ output.value = "dbg continue";
+ debugger;
+ }
+
+ function stepIntoMe() {
+ output.value = "step in"; // step in
+ stepOverMe(); // step over
+ let x = 0; // step out
+ output.value = "step out";
+ }
+
+ function stepOverMe() {
+ output.value = "step over";
+ }
+ </script>
+ </body>
+
+</html>
diff --git a/devtools/client/debugger/test/mochitest/doc_conditional-breakpoints.html b/devtools/client/debugger/test/mochitest/doc_conditional-breakpoints.html
new file mode 100644
index 000000000..7adce7a18
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/doc_conditional-breakpoints.html
@@ -0,0 +1,35 @@
+<!-- Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ -->
+<!doctype html>
+
+<html>
+ <head>
+ <meta charset="utf-8"/>
+ <title>Debugger test page</title>
+ </head>
+
+ <body>
+ <button onclick="ermahgerd()">Click me!</button>
+
+ <script type="text/javascript">
+ function ermahgerd() {
+ var a = {};
+ debugger;
+ a = "undefined";
+ a = "null";
+ a = "42";
+ a = "true";
+ a = "'nasu'";
+ a = "/regexp/";
+ a = "{}";
+ a = "function() {}";
+ a = "(function { return false; })()";
+ a = "a";
+ a = "a !== undefined";
+ a = "a !== null";
+ a = "b";
+ }
+ </script>
+ </body>
+
+</html>
diff --git a/devtools/client/debugger/test/mochitest/doc_domnode-variables.html b/devtools/client/debugger/test/mochitest/doc_domnode-variables.html
new file mode 100644
index 000000000..9e7531036
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/doc_domnode-variables.html
@@ -0,0 +1,24 @@
+<!-- Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ -->
+<!doctype html>
+
+<html>
+ <head>
+ <meta charset="utf-8"/>
+ <title>Debugger test page</title>
+ </head>
+
+ <body>
+ <div>Look at this DIV! Just look at it!</div>
+
+ <script type="text/javascript">
+ function start() {
+ var theDiv = document.querySelector("div");
+ var theBody = document.body;
+ var manyDomNodes = [theDiv, theBody];
+ debugger;
+ }
+ </script>
+ </body>
+
+</html>
diff --git a/devtools/client/debugger/test/mochitest/doc_editor-mode.html b/devtools/client/debugger/test/mochitest/doc_editor-mode.html
new file mode 100644
index 000000000..8e3573cea
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/doc_editor-mode.html
@@ -0,0 +1,20 @@
+<!-- Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ -->
+<!doctype html>
+
+<html>
+ <head>
+ <meta charset="utf-8"/>
+ <title>Debugger test page</title>
+ </head>
+
+ <body>
+ <script type="text/javascript" src="code_script-switching-01.js?a=b"></script>
+ <script type="text/javascript" src="code_test-editor-mode?c=d"></script>
+ <script type="text/javascript">
+ function banana() {
+ }
+ </script>
+ </body>
+
+</html>
diff --git a/devtools/client/debugger/test/mochitest/doc_empty-tab-01.html b/devtools/client/debugger/test/mochitest/doc_empty-tab-01.html
new file mode 100644
index 000000000..28398f776
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/doc_empty-tab-01.html
@@ -0,0 +1,14 @@
+<!-- Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ -->
+<!doctype html>
+
+<html>
+ <head>
+ <meta charset="utf-8"/>
+ <title>Empty test page 1</title>
+ </head>
+
+ <body>
+ </body>
+
+</html>
diff --git a/devtools/client/debugger/test/mochitest/doc_empty-tab-02.html b/devtools/client/debugger/test/mochitest/doc_empty-tab-02.html
new file mode 100644
index 000000000..5db150844
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/doc_empty-tab-02.html
@@ -0,0 +1,14 @@
+<!-- Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ -->
+<!doctype html>
+
+<html>
+ <head>
+ <meta charset="utf-8"/>
+ <title>Empty test page 2</title>
+ </head>
+
+ <body>
+ </body>
+
+</html>
diff --git a/devtools/client/debugger/test/mochitest/doc_event-listeners-01.html b/devtools/client/debugger/test/mochitest/doc_event-listeners-01.html
new file mode 100644
index 000000000..b44400311
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/doc_event-listeners-01.html
@@ -0,0 +1,43 @@
+<!-- Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ -->
+<!doctype html>
+<html>
+ <head>
+ <meta charset="utf-8"/>
+ <title>Debugger test page</title>
+ </head>
+
+ <body>
+ <button>Click me!</button>
+ <input type="text" onchange="changeHandler()">
+
+ <script type="text/javascript">
+ window.addEventListener("load", function onload() {
+ window.removeEventListener("load", onload);
+ function initialSetup(event) {
+ debugger;
+ var button = document.querySelector("button");
+ button.onclick = clickHandler;
+ }
+ function clickHandler(event) {
+ window.foobar = "clickHandler";
+ }
+ function changeHandler(event) {
+ window.foobar = "changeHandler";
+ }
+ function keyupHandler(event) {
+ window.foobar = "keyupHandler";
+ }
+
+ var button = document.querySelector("button");
+ button.onclick = initialSetup;
+
+ var input = document.querySelector("input");
+ input.addEventListener("keyup", keyupHandler, true);
+
+ window.changeHandler = changeHandler;
+ });
+ </script>
+ </body>
+
+</html>
diff --git a/devtools/client/debugger/test/mochitest/doc_event-listeners-02.html b/devtools/client/debugger/test/mochitest/doc_event-listeners-02.html
new file mode 100644
index 000000000..6a4649de9
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/doc_event-listeners-02.html
@@ -0,0 +1,53 @@
+<!-- Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ -->
+<!doctype html>
+<html>
+ <head>
+ <meta charset="utf-8"/>
+ <title>Debugger test page</title>
+ </head>
+
+ <body>
+ <button>Click me!</button>
+ <input type="text" onchange="changeHandler()">
+
+ <script type="text/javascript">
+ window.addEventListener("load", function onload() {
+ window.removeEventListener("load", onload);
+ function initialSetup(event) {
+ debugger;
+ var button = document.querySelector("button");
+ button.onclick = clickHandler;
+ }
+ function clickHandler(event) {
+ window.foobar = "clickHandler";
+ }
+ function changeHandler(event) {
+ window.foobar = "changeHandler";
+ }
+ function keyupHandler(event) {
+ window.foobar = "keyupHandler";
+ }
+ function keydownHandler(event) {
+ window.foobar = "keydownHandler";
+ }
+
+ var button = document.querySelector("button");
+ button.onclick = initialSetup;
+
+ var input = document.querySelector("input");
+ input.addEventListener("keyup", keyupHandler, true);
+
+ window.addEventListener("keydown", keydownHandler, true);
+ document.body.addEventListener("keydown", keydownHandler, true);
+
+ window.changeHandler = changeHandler;
+ });
+
+ function addBodyClickEventListener() {
+ document.body.addEventListener("click", function() { debugger; });
+ }
+ </script>
+ </body>
+
+</html>
diff --git a/devtools/client/debugger/test/mochitest/doc_event-listeners-03.html b/devtools/client/debugger/test/mochitest/doc_event-listeners-03.html
new file mode 100644
index 000000000..b672a4360
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/doc_event-listeners-03.html
@@ -0,0 +1,63 @@
+<!-- Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ -->
+<!doctype html>
+<html>
+ <head>
+ <meta charset="utf-8"/>
+ <title>Bound event listeners test page</title>
+ </head>
+
+ <body>
+ <button id="initialSetup">initialSetup</button>
+ <button id="clicker">clicker</button>
+ <button id="handleEventClick">handleEventClick</button>
+ <button id="boundHandleEventClick">boundHandleEventClick</button>
+
+ <script type="text/javascript">
+ window.addEventListener("load", function onload() {
+ window.removeEventListener("load", onload);
+ function initialSetup(event) {
+ var button = document.getElementById("initialSetup");
+ button.removeEventListener("click", initialSetup);
+ debugger;
+ }
+
+ function clicker(event) {
+ window.foobar = "clicker";
+ }
+
+ function handleEventClick() {
+ var button = document.getElementById("handleEventClick");
+ // Create a long prototype chain to test for weird edge cases.
+ button.addEventListener("click", Object.create(Object.create(this)));
+ }
+
+ handleEventClick.prototype.handleEvent = function() {
+ window.foobar = "handleEventClick";
+ };
+
+ function boundHandleEventClick() {
+ var button = document.getElementById("boundHandleEventClick");
+ this.handleEvent = this.handleEvent.bind(this);
+ button.addEventListener("click", this);
+ }
+
+ boundHandleEventClick.prototype.handleEvent = function() {
+ window.foobar = "boundHandleEventClick";
+ };
+
+ var button = document.getElementById("clicker");
+ // Bind more than once to test for weird edge cases.
+ var boundClicker = clicker.bind(this).bind(this).bind(this);
+ button.addEventListener("click", boundClicker);
+
+ new handleEventClick();
+ new boundHandleEventClick();
+
+ var initButton = document.getElementById("initialSetup");
+ initButton.addEventListener("click", initialSetup);
+ });
+ </script>
+ </body>
+
+</html>
diff --git a/devtools/client/debugger/test/mochitest/doc_event-listeners-04.html b/devtools/client/debugger/test/mochitest/doc_event-listeners-04.html
new file mode 100644
index 000000000..d92488a70
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/doc_event-listeners-04.html
@@ -0,0 +1,23 @@
+<!-- Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ -->
+<!doctype html>
+<html>
+ <head>
+ <meta charset="utf-8"/>
+ <title>Debugger test page</title>
+ </head>
+
+ <body>
+ <button>Click me!</button>
+
+ <script type="text/javascript">
+ window.addEventListener("load", function onload() {
+ var button = document.querySelector("button");
+ button.onclick = function () {
+ debugger;
+ };
+ });
+ </script>
+ </body>
+
+</html>
diff --git a/devtools/client/debugger/test/mochitest/doc_frame-parameters.html b/devtools/client/debugger/test/mochitest/doc_frame-parameters.html
new file mode 100644
index 000000000..b3108d6bf
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/doc_frame-parameters.html
@@ -0,0 +1,37 @@
+<!-- Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ -->
+<!doctype html>
+
+<html>
+ <head>
+ <meta charset="utf-8"/>
+ <title>Debugger test page</title>
+ </head>
+
+ <body>
+ <button onclick="start()">Click me!</button>
+
+ <script type="text/javascript">
+ function test(aArg, bArg, cArg, dArg, eArg, fArg) {
+ var a = 1;
+ var b = { a: a };
+ var c = { a: 1, b: "beta", c: 3, d: false, e: null, f: fArg };
+ var myVar = {
+ _prop: 42,
+ get prop() { return this._prop; },
+ set prop(val) { this._prop = val; }
+ };
+ debugger;
+ }
+
+ function start() {
+ var a = { a: 1, b: "beta", c: 3, d: false, e: null, f: undefined };
+ var e = eval("test(a, 'beta', 3, false, null);");
+ }
+
+ var button = document.querySelector("button");
+ var buttonAsProto = Object.create(button);
+ </script>
+ </body>
+
+</html>
diff --git a/devtools/client/debugger/test/mochitest/doc_function-display-name.html b/devtools/client/debugger/test/mochitest/doc_function-display-name.html
new file mode 100644
index 000000000..84e8ce6e1
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/doc_function-display-name.html
@@ -0,0 +1,31 @@
+<!-- Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ -->
+<!doctype html>
+
+<html>
+ <head>
+ <meta charset="utf-8"/>
+ <title>Debugger test page</title>
+ </head>
+
+ <body>
+ <script type="text/javascript">
+ var a = function() {
+ return function() {
+ debugger;
+ }
+ }
+
+ var anon = a();
+ anon.displayName = "anonFunc";
+
+ var inferred = a();
+
+ function evalCall() {
+ eval("anon();");
+ eval("inferred();");
+ }
+ </script>
+ </body>
+
+</html>
diff --git a/devtools/client/debugger/test/mochitest/doc_function-jump.html b/devtools/client/debugger/test/mochitest/doc_function-jump.html
new file mode 100644
index 000000000..0cd99e662
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/doc_function-jump.html
@@ -0,0 +1,17 @@
+<!-- Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ -->
+<!doctype html>
+
+<html>
+ <head>
+ <meta charset="utf-8"/>
+ <title>Debugger test page</title>
+ </head>
+
+ <body>
+ <p>Foo bar, bar, bazz, bar foo bar!</p>
+
+ <script type="text/javascript" src="code_function-jump-01.js"></script>
+ </body>
+
+</html>
diff --git a/devtools/client/debugger/test/mochitest/doc_function-search.html b/devtools/client/debugger/test/mochitest/doc_function-search.html
new file mode 100644
index 000000000..eb0e7eaea
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/doc_function-search.html
@@ -0,0 +1,30 @@
+<!-- Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ -->
+<!doctype html>
+
+<html>
+ <head>
+ <meta charset="utf-8"/>
+ <title>Debugger test page</title>
+ </head>
+
+ <body>
+ <p>Peanut butter jelly time!</p>
+
+ <script type="text/javascript" src="code_function-search-01.js"></script>
+ <script type="text/javascript" src="code_function-search-02.js"></script>
+ <script type="text/javascript" src="code_function-search-03.js"></script>
+
+ <script type="text/javascript;version=1.8">
+ function inline() {}
+ var arrow = () => {}
+
+ var foo = bar => {}
+ var foo2 = bar2 = baz2 => 42;
+
+ setTimeout((foo, bar, baz) => {});
+ setTimeout((foo, bar, baz) => 42);
+ </script>
+ </body>
+
+</html>
diff --git a/devtools/client/debugger/test/mochitest/doc_global-method-override.html b/devtools/client/debugger/test/mochitest/doc_global-method-override.html
new file mode 100644
index 000000000..d8cf750fc
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/doc_global-method-override.html
@@ -0,0 +1,16 @@
+<!-- Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ -->
+<!doctype html>
+
+<html>
+ <head>
+ <meta charset="utf-8">
+ <title>Debugger global method override test page</title>
+ </head>
+ <body>
+ <script type="text/javascript">
+ console.log( "Error: " + toString( { x: 0, y: 0 } ) );
+ function toString(v) { return "[ " + v.x + ", " + v.y + " ]"; }
+ </script>
+ </body>
+</html>
diff --git a/devtools/client/debugger/test/mochitest/doc_iframes.html b/devtools/client/debugger/test/mochitest/doc_iframes.html
new file mode 100644
index 000000000..e5a76c280
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/doc_iframes.html
@@ -0,0 +1,15 @@
+<!-- Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ -->
+<!doctype html>
+
+<html>
+ <head>
+ <meta charset="utf-8"/>
+ <title>Debugger test page</title>
+ </head>
+
+ <body>
+ <iframe src="doc_inline-debugger-statement.html"></iframe>
+ </body>
+
+</html>
diff --git a/devtools/client/debugger/test/mochitest/doc_included-script.html b/devtools/client/debugger/test/mochitest/doc_included-script.html
new file mode 100644
index 000000000..8b134dd42
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/doc_included-script.html
@@ -0,0 +1,22 @@
+<!-- Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ -->
+<!doctype html>
+
+<html>
+ <head>
+ <meta charset="utf-8"/>
+ <title>Debugger test page</title>
+ </head>
+
+ <body>
+ <button onclick="myFunction()">Click me!</button>
+
+ <script type="text/javascript" src="code_location-changes.js"></script>
+ <script type="text/javascript">
+ function runDebuggerStatement() {
+ debugger;
+ }
+ </script>
+ </body>
+
+</html>
diff --git a/devtools/client/debugger/test/mochitest/doc_inline-debugger-statement.html b/devtools/client/debugger/test/mochitest/doc_inline-debugger-statement.html
new file mode 100644
index 000000000..406e9d9da
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/doc_inline-debugger-statement.html
@@ -0,0 +1,21 @@
+<!-- Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ -->
+<!doctype html>
+
+<html>
+ <head>
+ <meta charset="utf-8"/>
+ <title>Debugger test page</title>
+ </head>
+
+ <body>
+ <button>Click me!</button>
+
+ <script type="text/javascript">
+ function runDebuggerStatement() {
+ debugger;
+ }
+ </script>
+ </body>
+
+</html>
diff --git a/devtools/client/debugger/test/mochitest/doc_inline-script.html b/devtools/client/debugger/test/mochitest/doc_inline-script.html
new file mode 100644
index 000000000..d071cc084
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/doc_inline-script.html
@@ -0,0 +1,25 @@
+<!-- Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ -->
+<!doctype html>
+
+<html>
+ <head>
+ <meta charset="utf-8"/>
+ <title>Debugger test page</title>
+ </head>
+
+ <body>
+ <button onclick="myFunction()">Click me!</button>
+
+ <script type="text/javascript">
+ function runDebuggerStatement() {
+ debugger;
+ }
+ function myFunction() {
+ var a = 1;
+ debugger;
+ }
+ </script>
+ </body>
+
+</html>
diff --git a/devtools/client/debugger/test/mochitest/doc_large-array-buffer.html b/devtools/client/debugger/test/mochitest/doc_large-array-buffer.html
new file mode 100644
index 000000000..25b1b4d4e
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/doc_large-array-buffer.html
@@ -0,0 +1,32 @@
+<!-- Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ -->
+<!doctype html>
+
+<html>
+ <head>
+ <meta charset="utf-8"/>
+ <title>Debugger test page</title>
+ </head>
+
+ <body>
+ <button onclick="test(10000)">Click me!</button>
+
+ <script type="text/javascript">
+ function test(aNumber) {
+ var buffer = new ArrayBuffer(aNumber);
+ var largeArray = new Int8Array(buffer);
+ var largeObject = {};
+ var largeMap = new Map();
+ var largeSet = new Set();
+
+ for (var i = 0; i < aNumber; i++) {
+ let value = aNumber - i - 1;
+ largeObject[i] = value;
+ largeMap.set(i, value);
+ largeSet.add(value);
+ }
+ debugger;
+ }
+ </script>
+ </body>
+</html>
diff --git a/devtools/client/debugger/test/mochitest/doc_listworkers-tab.html b/devtools/client/debugger/test/mochitest/doc_listworkers-tab.html
new file mode 100644
index 000000000..62ab9be7d
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/doc_listworkers-tab.html
@@ -0,0 +1,8 @@
+<!DOCTYPE html>
+<html lang="en">
+ <head>
+ <meta charset="utf-8"/>
+ </head>
+ <body>
+ </body>
+</html>
diff --git a/devtools/client/debugger/test/mochitest/doc_map-set.html b/devtools/client/debugger/test/mochitest/doc_map-set.html
new file mode 100644
index 000000000..7c2c624e1
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/doc_map-set.html
@@ -0,0 +1,42 @@
+<!-- Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ -->
+<!DOCTYPE html>
+
+<html>
+ <head>
+ <meta charset="utf-8"/>
+ <title>Debugger test page for Maps and Sets</title>
+ </head>
+
+ <body>
+ <script>
+ function startTest() {
+ let obj0 = { a: 0 };
+ let obj1 = { a: 1 };
+
+ let map = new Map();
+ map.set(obj0, 0);
+ map.set(obj1, 1);
+ map.extraProp = true;
+
+ let weakMap = new WeakMap();
+ weakMap.set(obj0, 0);
+ weakMap.set(obj1, 1);
+ weakMap.extraProp = true;
+
+ let set = new Set();
+ set.add(obj0);
+ set.add(obj1);
+ set.extraProp = true;
+
+ let weakSet = new WeakSet();
+ weakSet.add(obj0);
+ weakSet.add(obj1);
+ weakSet.extraProp = true;
+
+ debugger;
+ };
+ </script>
+ </body>
+
+</html>
diff --git a/devtools/client/debugger/test/mochitest/doc_minified.html b/devtools/client/debugger/test/mochitest/doc_minified.html
new file mode 100644
index 000000000..b229e079f
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/doc_minified.html
@@ -0,0 +1,14 @@
+<!-- Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ -->
+<!doctype html>
+
+<html>
+ <head>
+ <meta charset="utf-8"/>
+ <title>Debugger test page</title>
+ </head>
+
+ <body>
+ <script src="code_math.min.js"></script>
+ </body>
+</html>
diff --git a/devtools/client/debugger/test/mochitest/doc_minified_bogus_map.html b/devtools/client/debugger/test/mochitest/doc_minified_bogus_map.html
new file mode 100644
index 000000000..d6670a7e1
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/doc_minified_bogus_map.html
@@ -0,0 +1,14 @@
+<!-- Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ -->
+<!doctype html>
+
+<html>
+ <head>
+ <meta charset="utf-8"/>
+ <title>Debugger test page</title>
+ </head>
+
+ <body>
+ <script src="code_math_bogus_map.js"></script>
+ </body>
+</html>
diff --git a/devtools/client/debugger/test/mochitest/doc_native-event-handler.html b/devtools/client/debugger/test/mochitest/doc_native-event-handler.html
new file mode 100644
index 000000000..cd2a656bf
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/doc_native-event-handler.html
@@ -0,0 +1,22 @@
+<!-- Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ -->
+<!doctype html>
+<html lang="en">
+ <head>
+ <meta charset="utf-8">
+ <title>A video element with native event handlers</title>
+ <script type="text/javascript">
+ function initialSetup(event) {
+ debugger;
+ }
+
+ window.addEventListener("load", function() {}, false);
+ </script>
+ </head>
+ <body>
+ <button onclick="initialSetup()">Click me!</button>
+ <!-- the "controls" attribute ensures that there are extra event handlers in
+ the element. -->
+ <video controls></video>
+ </body>
+</html>
diff --git a/devtools/client/debugger/test/mochitest/doc_no-page-sources.html b/devtools/client/debugger/test/mochitest/doc_no-page-sources.html
new file mode 100644
index 000000000..5131578ad
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/doc_no-page-sources.html
@@ -0,0 +1,11 @@
+<!-- Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ -->
+<!doctype html>
+<html>
+ <head>
+ <meta charset="utf-8"/>
+ <title>This page has no sources</title>
+ </head>
+ <body>
+ </body>
+</html> \ No newline at end of file
diff --git a/devtools/client/debugger/test/mochitest/doc_pause-exceptions.html b/devtools/client/debugger/test/mochitest/doc_pause-exceptions.html
new file mode 100644
index 000000000..7766fb49d
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/doc_pause-exceptions.html
@@ -0,0 +1,35 @@
+<!-- Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ -->
+<!doctype html>
+
+<html>
+ <head>
+ <meta charset="utf-8"/>
+ <title>Debugger test page</title>
+ </head>
+
+ <body>
+ <button>Click me!</button>
+ <ul></ul>
+
+ <script type="text/javascript">
+ window.addEventListener("load", function() {
+ function test() {
+ try {
+ throw new Error("boom");
+ } catch (e) {
+ var list = document.querySelector("ul");
+ var item = document.createElement("li");
+ item.innerHTML = e.message;
+ list.appendChild(item);
+ } finally {
+ debugger;
+ }
+ }
+ var button = document.querySelector("button");
+ button.addEventListener("click", test, false);
+ });
+ </script>
+ </body>
+
+</html>
diff --git a/devtools/client/debugger/test/mochitest/doc_pretty-print-2.html b/devtools/client/debugger/test/mochitest/doc_pretty-print-2.html
new file mode 100644
index 000000000..509f57d6b
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/doc_pretty-print-2.html
@@ -0,0 +1,15 @@
+<!-- Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ -->
+<!DOCTYPE html>
+<head>
+ <meta charset="utf-8"/>
+ <title>Debugger Pretty Printing Test Page</title>
+</head>
+<script src="code_ugly-2.js"></script>
+<script src="code_ugly-3.js"></script>
+<script src="code_ugly-4.js"></script>
+<script>
+ function noop(x) {
+ return x;
+ }
+</script>
diff --git a/devtools/client/debugger/test/mochitest/doc_pretty-print-3.html b/devtools/client/debugger/test/mochitest/doc_pretty-print-3.html
new file mode 100644
index 000000000..6192642f3
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/doc_pretty-print-3.html
@@ -0,0 +1,8 @@
+<!-- Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ -->
+<!DOCTYPE html>
+<head>
+ <meta charset="utf-8"/>
+ <title>Debugger Pretty Printing Test Page</title>
+</head>
+<script src="code_ugly-8"></script>
diff --git a/devtools/client/debugger/test/mochitest/doc_pretty-print-on-paused.html b/devtools/client/debugger/test/mochitest/doc_pretty-print-on-paused.html
new file mode 100644
index 000000000..a431d0898
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/doc_pretty-print-on-paused.html
@@ -0,0 +1,14 @@
+<!-- Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ -->
+
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta charset="utf-8" />
+ <title>Pretty printing when debugger is paused Test Page</title>
+ </head>
+ <body>
+ <script src="code_ugly-2.js"></script>
+ <script src="code_script-switching-02.js"></script>
+ </body>
+</html>
diff --git a/devtools/client/debugger/test/mochitest/doc_pretty-print.html b/devtools/client/debugger/test/mochitest/doc_pretty-print.html
new file mode 100644
index 000000000..dcf595a8d
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/doc_pretty-print.html
@@ -0,0 +1,8 @@
+<!-- Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ -->
+<!DOCTYPE html>
+<head>
+ <meta charset="utf-8"/>
+ <title>Debugger Pretty Printing Test Page</title>
+</head>
+<script src="code_ugly.js"></script>
diff --git a/devtools/client/debugger/test/mochitest/doc_promise-get-allocation-stack.html b/devtools/client/debugger/test/mochitest/doc_promise-get-allocation-stack.html
new file mode 100644
index 000000000..48546c967
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/doc_promise-get-allocation-stack.html
@@ -0,0 +1,24 @@
+<!-- Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ -->
+<!DOCTYPE html>
+
+<html>
+ <head>
+ <meta charset="utf-8"/>
+ <title>Promise test page</title>
+ </head>
+
+ <body>
+ <script type="text/javascript">
+ function makePromises() {
+ var p = new Promise(() => {});
+ p.name = "p";
+ var q = p.then();
+ q.name = "q";
+ var r = p.then(null, () => {});
+ r.name = "r";
+ }
+ </script>
+ </body>
+
+</html>
diff --git a/devtools/client/debugger/test/mochitest/doc_promise-get-fulfillment-stack.html b/devtools/client/debugger/test/mochitest/doc_promise-get-fulfillment-stack.html
new file mode 100644
index 000000000..0b311a69a
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/doc_promise-get-fulfillment-stack.html
@@ -0,0 +1,24 @@
+<!-- Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ -->
+<!DOCTYPE html>
+
+<html>
+ <head>
+ <meta charset="utf-8"/>
+ <title>Promise test page</title>
+ </head>
+
+ <body>
+ <script type="text/javascript">
+ function makePromise() {
+ var p = returnPromise();
+ p.name = "p";
+ }
+
+ function returnPromise() {
+ return new Promise(resolve => resolve("hello"));
+ }
+ </script>
+ </body>
+
+</html>
diff --git a/devtools/client/debugger/test/mochitest/doc_promise-get-rejection-stack.html b/devtools/client/debugger/test/mochitest/doc_promise-get-rejection-stack.html
new file mode 100644
index 000000000..9fe203595
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/doc_promise-get-rejection-stack.html
@@ -0,0 +1,24 @@
+<!-- Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ -->
+<!DOCTYPE html>
+
+<html>
+ <head>
+ <meta charset="utf-8"/>
+ <title>Promise test page</title>
+ </head>
+
+ <body>
+ <script type="text/javascript">
+ function makePromise() {
+ var p = returnPromise();
+ p.name = "p";
+ }
+
+ function returnPromise() {
+ return new Promise((resolve, reject) => reject("hello"));
+ }
+ </script>
+ </body>
+
+</html>
diff --git a/devtools/client/debugger/test/mochitest/doc_promise.html b/devtools/client/debugger/test/mochitest/doc_promise.html
new file mode 100644
index 000000000..fe6c1d807
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/doc_promise.html
@@ -0,0 +1,30 @@
+<!-- Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ -->
+<!DOCTYPE html>
+
+<html>
+ <head>
+ <meta charset="utf-8"/>
+ <title>Debugger + Promise test page</title>
+ </head>
+
+ <body>
+ <script>
+ window.pending = new Promise(function () {});
+ window.fulfilled = Promise.resolve({ a: 1, b: 2, c: 3 });
+ window.rejected = Promise.reject(new Error("uh oh"));
+
+ window.doPause = function () {
+ var p = window.pending;
+ var f = window.fulfilled;
+ var r = window.rejected;
+ debugger;
+ };
+
+ // Attach an error handler so that the logs don't have a warning about an
+ // unhandled, rejected promise.
+ window.rejected.then(null, function () {});
+ </script>
+ </body>
+
+</html>
diff --git a/devtools/client/debugger/test/mochitest/doc_proxy.html b/devtools/client/debugger/test/mochitest/doc_proxy.html
new file mode 100644
index 000000000..e2f35104a
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/doc_proxy.html
@@ -0,0 +1,39 @@
+<!-- Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ -->
+<!DOCTYPE html>
+
+<html>
+ <head>
+ <meta charset="utf-8"/>
+ <title>Debugger + Proxy test page</title>
+ </head>
+
+ <body>
+ <script>
+ window.target = {name: "target"};
+ window.handler = { /* Debugging a proxy shouldn't run any trap */
+ name: "handler",
+ getPrototypeOf() { throw new Error("proxy getPrototypeOf trap was called"); },
+ setPrototypeOf() { throw new Error("proxy setPrototypeOf trap was called"); },
+ isExtensible() { throw new Error("proxy isExtensible trap was called"); },
+ preventExtensions() { throw new Error("proxy preventExtensions trap was called"); },
+ getOwnPropertyDescriptor() { throw new Error("proxy getOwnPropertyDescriptor trap was called"); },
+ defineProperty() { throw new Error("proxy defineProperty trap was called"); },
+ has() { throw new Error("proxy has trap was called"); },
+ get() { throw new Error("proxy get trap was called"); },
+ set() { throw new Error("proxy set trap was called"); },
+ deleteProperty() { throw new Error("proxy deleteProperty trap was called"); },
+ ownKeys() { throw new Error("proxy ownKeys trap was called"); },
+ apply() { throw new Error("proxy apply trap was called"); },
+ construct() { throw new Error("proxy construct trap was called"); }
+ };
+ window.proxy = new Proxy(target, handler);
+
+ window.doPause = function () {
+ var proxy = window.proxy;
+ debugger;
+ };
+ </script>
+ </body>
+
+</html>
diff --git a/devtools/client/debugger/test/mochitest/doc_random-javascript.html b/devtools/client/debugger/test/mochitest/doc_random-javascript.html
new file mode 100644
index 000000000..69269e409
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/doc_random-javascript.html
@@ -0,0 +1,15 @@
+<!-- Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ -->
+<!doctype html>
+
+<html>
+ <head>
+ <meta charset="utf-8"/>
+ <title>Debugger test page</title>
+ </head>
+
+ <body>
+ <script src="sjs_random-javascript.sjs"></script>
+ </body>
+
+</html>
diff --git a/devtools/client/debugger/test/mochitest/doc_recursion-stack.html b/devtools/client/debugger/test/mochitest/doc_recursion-stack.html
new file mode 100644
index 000000000..d68fb1d18
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/doc_recursion-stack.html
@@ -0,0 +1,35 @@
+<!-- Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ -->
+<!doctype html>
+
+<html>
+ <head>
+ <meta charset="utf-8"/>
+ <title>Debugger test page</title>
+ </head>
+
+ <body>
+ <script type="text/javascript">
+ function simpleCall() {
+ debugger;
+ }
+
+ function evalCall() {
+ eval("debugger;");
+ }
+
+ var gRecurseLimit = 100;
+ var gRecurseDepth = 0;
+
+ function recurse() {
+ if (++gRecurseDepth == gRecurseLimit) {
+ debugger;
+ gRecurseDepth = 0;
+ return;
+ }
+ recurse();
+ }
+ </script>
+ </body>
+
+</html>
diff --git a/devtools/client/debugger/test/mochitest/doc_scope-variable-2.html b/devtools/client/debugger/test/mochitest/doc_scope-variable-2.html
new file mode 100644
index 000000000..afbfd166a
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/doc_scope-variable-2.html
@@ -0,0 +1,30 @@
+<!-- Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ -->
+<!doctype html>
+
+<html>
+ <head>
+ <meta charset="utf-8"/>
+ <title>Debugger test page</title>
+ </head>
+
+ <body>
+ <script type="text/javascript">
+ function test() {
+ var a = "first scope";
+ firstNest();
+
+ function firstNest() {
+ var a = "second scope";
+ secondNest();
+
+ function secondNest() {
+ var a = "third scope";
+ debugger;
+ }
+ }
+ }
+ </script>
+ </body>
+
+</html>
diff --git a/devtools/client/debugger/test/mochitest/doc_scope-variable-3.html b/devtools/client/debugger/test/mochitest/doc_scope-variable-3.html
new file mode 100644
index 000000000..fcd45cc0a
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/doc_scope-variable-3.html
@@ -0,0 +1,23 @@
+<!-- Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ -->
+<!doctype html>
+
+<html>
+ <head>
+ <meta charset="utf-8"/>
+ <title>Debugger test page</title>
+ </head>
+
+ <body>
+ <script type="text/javascript">
+ var trap = "first script";
+ function test() {
+ debugger;
+ }
+ </script>
+ <script type="text/javascript">/*
+ trololol
+ */</script>
+ </body>
+
+</html>
diff --git a/devtools/client/debugger/test/mochitest/doc_scope-variable-4.html b/devtools/client/debugger/test/mochitest/doc_scope-variable-4.html
new file mode 100644
index 000000000..17b0e3b10
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/doc_scope-variable-4.html
@@ -0,0 +1,25 @@
+<!-- Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ -->
+<!doctype html>
+
+<html>
+ <head>
+ <meta charset="utf-8"/>
+ <title>Debugger test page</title>
+ </head>
+
+ <body>
+ <script type="text/javascript">
+ function test() {
+ var a = "first scope";
+ nest();
+
+ function nest() {
+ var a = "second scope";
+ debugger;
+ }
+ }
+ </script>
+ </body>
+
+</html>
diff --git a/devtools/client/debugger/test/mochitest/doc_scope-variable.html b/devtools/client/debugger/test/mochitest/doc_scope-variable.html
new file mode 100644
index 000000000..3fa28fab9
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/doc_scope-variable.html
@@ -0,0 +1,25 @@
+<!-- Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ -->
+<!doctype html>
+
+<html>
+ <head>
+ <meta charset="utf-8"/>
+ <title>Debugger test page</title>
+ </head>
+
+ <body>
+ <script type="text/javascript">
+ function test() {
+ var a = "first scope";
+ nest();
+ }
+
+ function nest() {
+ var a = "second scope";
+ debugger;
+ }
+ </script>
+ </body>
+
+</html>
diff --git a/devtools/client/debugger/test/mochitest/doc_script-bookmarklet.html b/devtools/client/debugger/test/mochitest/doc_script-bookmarklet.html
new file mode 100644
index 000000000..922010062
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/doc_script-bookmarklet.html
@@ -0,0 +1,14 @@
+<!-- Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ -->
+<!doctype html>
+
+<html>
+ <head>
+ <meta charset="utf-8"/>
+ <title>Debugger test page</title>
+ </head>
+
+ <body>
+ <script>function injectBookmarklet(bookmarklet) { setTimeout(function() { window.location = "javascript:" + bookmarklet; }, 0); }</script>
+ </body>
+</html>
diff --git a/devtools/client/debugger/test/mochitest/doc_script-eval.html b/devtools/client/debugger/test/mochitest/doc_script-eval.html
new file mode 100644
index 000000000..7e3f253bb
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/doc_script-eval.html
@@ -0,0 +1,16 @@
+<!-- Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ -->
+<!doctype html>
+
+<html>
+ <head>
+ <meta charset="utf-8"/>
+ <title>Debugger test page</title>
+ </head>
+
+ <body>
+ <button onclick="evalSource()">Click me!</button>
+
+ <script type="text/javascript" src="code_script-eval.js"></script>
+ </body>
+</html>
diff --git a/devtools/client/debugger/test/mochitest/doc_script-switching-01.html b/devtools/client/debugger/test/mochitest/doc_script-switching-01.html
new file mode 100644
index 000000000..afb4484b5
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/doc_script-switching-01.html
@@ -0,0 +1,18 @@
+<!-- Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ -->
+<!doctype html>
+
+<html>
+ <head>
+ <meta charset="utf-8"/>
+ <title>Debugger test page</title>
+ </head>
+
+ <body>
+ <button onclick="firstCall()">Click me!</button>
+
+ <script type="text/javascript" src="code_script-switching-01.js"></script>
+ <script type="text/javascript" src="code_script-switching-02.js"></script>
+ </body>
+
+</html>
diff --git a/devtools/client/debugger/test/mochitest/doc_script-switching-02.html b/devtools/client/debugger/test/mochitest/doc_script-switching-02.html
new file mode 100644
index 000000000..cceeea2c8
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/doc_script-switching-02.html
@@ -0,0 +1,18 @@
+<!-- Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ -->
+<!doctype html>
+
+<html>
+ <head>
+ <meta charset="utf-8"/>
+ <title>Debugger test page</title>
+ </head>
+
+ <body>
+ <button onclick="firstCall()">Click me!</button>
+
+ <script type="text/javascript" src="code_script-switching-01.js"></script>
+ <script type="text/javascript" src="code_script-switching-02.js?foo=bar,baz|lol"></script>
+ </body>
+
+</html>
diff --git a/devtools/client/debugger/test/mochitest/doc_script_webext_contentscript.html b/devtools/client/debugger/test/mochitest/doc_script_webext_contentscript.html
new file mode 100644
index 000000000..8e88997db
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/doc_script_webext_contentscript.html
@@ -0,0 +1,13 @@
+<!-- Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ -->
+<!doctype html>
+
+<html>
+ <head>
+ <meta charset="utf-8"/>
+ <title>Debugger test page</title>
+ </head>
+
+ <body>
+ </body>
+</html>
diff --git a/devtools/client/debugger/test/mochitest/doc_split-console-paused-reload.html b/devtools/client/debugger/test/mochitest/doc_split-console-paused-reload.html
new file mode 100644
index 000000000..113c53468
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/doc_split-console-paused-reload.html
@@ -0,0 +1,22 @@
+<!-- Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ -->
+<!doctype html>
+
+<html>
+ <head>
+ <meta charset="utf-8"/>
+ <title>Test page for opening a split-console when execution is paused</title>
+ </head>
+
+ <body>
+ <script type="text/javascript">
+ function executeFunction() {
+ let privateVar = { propKey: "privateVarValue" };
+
+ window.foobar = "foobar";
+ }
+ executeFunction();
+ </script>
+ </body>
+
+</html>
diff --git a/devtools/client/debugger/test/mochitest/doc_step-many-statements.html b/devtools/client/debugger/test/mochitest/doc_step-many-statements.html
new file mode 100644
index 000000000..edd0e2882
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/doc_step-many-statements.html
@@ -0,0 +1,50 @@
+<!-- Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ -->
+<!doctype html>
+
+<html>
+ <head>
+ <meta charset="utf-8"/>
+ <title>Debugger test page</title>
+ </head>
+
+ <body>
+ <button id="start">Start!</button>
+
+ <script type="text/javascript">
+ function normal(aArg) {
+ debugger;
+ var r = 10;
+ var a = squareAndOne(r);
+ var b = squareUntil(r, 99999999999); //recurses 3 times, returns on 4th call
+ var c = addUntil(r, 5, 1050); // recurses 208 times and returns on the 209th call
+ return a + b + c;
+
+ }
+
+ function squareAndOne(arg){
+ return (arg * arg) + 1;
+ }
+ function squareUntil(arg, limit){
+ if(arg * arg >= limit){
+ return arg * arg;
+ }else{
+ return squareUntil(arg * arg, limit);
+ }
+ }
+
+ function addUntil(arg1, arg2, limit){
+ if(arg1 + arg2 > limit){
+ return arg1 + arg2;
+ }else{
+ return addUntil(arg1 + arg2, arg2, limit);
+ }
+ }
+
+ var normalBtn = document.getElementById("start");
+ normalBtn.addEventListener("click", normal, false);
+
+ </script>
+ </body>
+
+</html>
diff --git a/devtools/client/debugger/test/mochitest/doc_step-out.html b/devtools/client/debugger/test/mochitest/doc_step-out.html
new file mode 100644
index 000000000..89eda2be1
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/doc_step-out.html
@@ -0,0 +1,42 @@
+<!-- Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ -->
+<!doctype html>
+
+<html>
+ <head>
+ <meta charset="utf-8"/>
+ <title>Debugger test page</title>
+ </head>
+
+ <body>
+ <button id="return">Return me!</button>
+ <button id="throw">Throw me!</button>
+
+ <script type="text/javascript">
+ function normal(aArg) {
+ debugger;
+ var r = 10;
+ return r;
+ }
+
+ function error(aArg) {
+ function inner(aArg) {
+ debugger;
+ var r = 10;
+ throw "boom";
+ return r;
+ }
+ try {
+ inner(aArg);
+ } catch (e) {}
+ }
+
+ var normalBtn = document.getElementById("return");
+ normalBtn.addEventListener("click", normal, false);
+
+ var throwBtn = document.getElementById("throw");
+ throwBtn.addEventListener("click", error, false);
+ </script>
+ </body>
+
+</html>
diff --git a/devtools/client/debugger/test/mochitest/doc_terminate-on-tab-close.html b/devtools/client/debugger/test/mochitest/doc_terminate-on-tab-close.html
new file mode 100644
index 000000000..2101b3103
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/doc_terminate-on-tab-close.html
@@ -0,0 +1,20 @@
+<!-- Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ -->
+<!doctype html>
+
+<html>
+ <head>
+ <meta charset="utf-8"/>
+ <title>Debugger test page</title>
+ </head>
+
+ <body>
+ <script type="text/javascript">
+ function debuggerThenThrow() {
+ debugger;
+ throw "unreachable";
+ }
+ </script>
+ </body>
+
+</html>
diff --git a/devtools/client/debugger/test/mochitest/doc_watch-expression-button.html b/devtools/client/debugger/test/mochitest/doc_watch-expression-button.html
new file mode 100644
index 000000000..a4a5be26e
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/doc_watch-expression-button.html
@@ -0,0 +1,31 @@
+<!-- Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ -->
+<!doctype html>
+
+<html>
+ <head>
+ <meta charset="utf-8"/>
+ <title>Debugger test page</title>
+ </head>
+
+ <body>
+ <button onclick="start()">Click me!</button>
+
+ <script type="text/javascript">
+ function test() {
+ var a = 1;
+ var b = { a: a };
+ b.a = 2;
+ debugger;
+ }
+
+ function start() {
+ var e = eval('test();');
+ }
+
+ var button = document.querySelector("button");
+ var buttonAsProto = Object.create(button);
+ </script>
+ </body>
+
+</html>
diff --git a/devtools/client/debugger/test/mochitest/doc_watch-expressions.html b/devtools/client/debugger/test/mochitest/doc_watch-expressions.html
new file mode 100644
index 000000000..487b5a5a5
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/doc_watch-expressions.html
@@ -0,0 +1,29 @@
+<!-- Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ -->
+<!doctype html>
+
+<html>
+ <head>
+ <meta charset="utf-8"/>
+ <title>Debugger test page</title>
+ </head>
+
+ <body>
+ <script type="text/javascript">
+ function test() {
+ ermahgerd.call({ canada: new String("eh") });
+ }
+ function ermahgerd(aArg) {
+ var t = document.title;
+ debugger;
+ (function() {
+ var a = undefined;
+ debugger;
+ var a = {};
+ debugger;
+ }("sensational"));
+ }
+ </script>
+ </body>
+
+</html>
diff --git a/devtools/client/debugger/test/mochitest/doc_whitespace-property-names.html b/devtools/client/debugger/test/mochitest/doc_whitespace-property-names.html
new file mode 100644
index 000000000..6479a5978
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/doc_whitespace-property-names.html
@@ -0,0 +1,29 @@
+<!-- Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ -->
+<!DOCTYPE html>
+
+<html>
+ <head>
+ <meta charset="utf-8"/>
+ <title>Debugger + Whitespace property name test page</title>
+ </head>
+
+ <body>
+ <script>
+ window.doPause = function () {
+ var obj = {
+ "": 0,
+ " ": 1,
+ "\r": 2,
+ "\n": 3,
+ "\t": 4,
+ "\f": 5,
+ "\uFEFF": 6,
+ "\xA0": 7
+ };
+ debugger;
+ };
+ </script>
+ </body>
+
+</html>
diff --git a/devtools/client/debugger/test/mochitest/doc_with-frame.html b/devtools/client/debugger/test/mochitest/doc_with-frame.html
new file mode 100644
index 000000000..8fa202b18
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/doc_with-frame.html
@@ -0,0 +1,29 @@
+<!-- Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ -->
+<!doctype html>
+
+<html>
+ <head>
+ <meta charset="utf-8"/>
+ <title>Debugger test page</title>
+ </head>
+
+ <body>
+ <button onclick="test(10)">Click me!</button>
+
+ <script type="text/javascript">
+ function test(aNumber) {
+ var a, obj = { alpha: 1, beta: 2 };
+ var r = aNumber;
+ with (Math) {
+ a = PI * r * r;
+ with (obj) {
+ var foo = beta * PI;
+ debugger;
+ }
+ }
+ }
+ </script>
+ </body>
+
+</html>
diff --git a/devtools/client/debugger/test/mochitest/doc_worker-source-map.html b/devtools/client/debugger/test/mochitest/doc_worker-source-map.html
new file mode 100644
index 000000000..20a14e351
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/doc_worker-source-map.html
@@ -0,0 +1,18 @@
+<!DOCTYPE html>
+<html lang="en">
+ <head>
+ <meta charset="utf-8"/>
+ <script>
+ var worker = new Worker("code_worker-source-map.js");
+
+ function binary_search(items, value) {
+ worker.postMessage({
+ items: items,
+ value: value
+ });
+ }
+ </script>
+ </head>
+ <body>
+ </body>
+</html>
diff --git a/devtools/client/debugger/test/mochitest/head.js b/devtools/client/debugger/test/mochitest/head.js
new file mode 100644
index 000000000..1f9d38b82
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/head.js
@@ -0,0 +1,1351 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// shared-head.js handles imports, constants, and utility functions
+Services.scriptloader.loadSubScript("chrome://mochitests/content/browser/devtools/client/framework/test/shared-head.js", this);
+
+// Disable logging for faster test runs. Set this pref to true if you want to
+// debug a test in your try runs. Both the debugger server and frontend will
+// be affected by this pref.
+var gEnableLogging = Services.prefs.getBoolPref("devtools.debugger.log");
+Services.prefs.setBoolPref("devtools.debugger.log", false);
+
+var { BrowserToolboxProcess } = Cu.import("resource://devtools/client/framework/ToolboxProcess.jsm", {});
+var { DebuggerServer } = require("devtools/server/main");
+var { DebuggerClient, ObjectClient } = require("devtools/shared/client/main");
+var { AddonManager } = Cu.import("resource://gre/modules/AddonManager.jsm", {});
+var EventEmitter = require("devtools/shared/event-emitter");
+var { Toolbox } = require("devtools/client/framework/toolbox");
+
+const chromeRegistry = Cc["@mozilla.org/chrome/chrome-registry;1"].getService(Ci.nsIChromeRegistry);
+
+// Override promise with deprecated-sync-thenables
+promise = Cu.import("resource://devtools/shared/deprecated-sync-thenables.js", {}).Promise;
+
+const EXAMPLE_URL = "http://example.com/browser/devtools/client/debugger/test/mochitest/";
+const FRAME_SCRIPT_URL = getRootDirectory(gTestPath) + "code_frame-script.js";
+const CHROME_URL = "chrome://mochitests/content/browser/devtools/client/debugger/test/mochitest/";
+const CHROME_URI = Services.io.newURI(CHROME_URL, null, null);
+
+Services.prefs.setBoolPref("devtools.debugger.new-debugger-frontend", false);
+
+registerCleanupFunction(function* () {
+ Services.prefs.clearUserPref("devtools.debugger.new-debugger-frontend");
+
+ info("finish() was called, cleaning up...");
+ Services.prefs.setBoolPref("devtools.debugger.log", gEnableLogging);
+
+ while (gBrowser && gBrowser.tabs && gBrowser.tabs.length > 1) {
+ info("Destroying toolbox.");
+ let target = TargetFactory.forTab(gBrowser.selectedTab);
+ yield gDevTools.closeToolbox(target);
+
+ info("Removing tab.");
+ gBrowser.removeCurrentTab();
+ }
+
+ // Properly shut down the server to avoid memory leaks.
+ DebuggerServer.destroy();
+
+ // Debugger tests use a lot of memory, so force a GC to help fragmentation.
+ info("Forcing GC after debugger test.");
+ Cu.forceGC();
+});
+
+// Import the GCLI test helper
+var testDir = gTestPath.substr(0, gTestPath.lastIndexOf("/"));
+testDir = testDir.replace(/\/\//g, "/");
+testDir = testDir.replace("chrome:/mochitest", "chrome://mochitest");
+var helpersjs = testDir + "/../../../commandline/test/helpers.js";
+Services.scriptloader.loadSubScript(helpersjs, this);
+
+function addWindow(aUrl) {
+ info("Adding window: " + aUrl);
+ return promise.resolve(getChromeWindow(window.open(aUrl)));
+}
+
+function getChromeWindow(aWindow) {
+ return aWindow
+ .QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIWebNavigation)
+ .QueryInterface(Ci.nsIDocShellTreeItem).rootTreeItem
+ .QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindow);
+}
+
+// Override addTab/removeTab as defined by shared-head, since these have
+// an extra window parameter and add a frame script
+this.addTab = function addTab(aUrl, aWindow) {
+ info("Adding tab: " + aUrl);
+
+ let deferred = promise.defer();
+ let targetWindow = aWindow || window;
+ let targetBrowser = targetWindow.gBrowser;
+
+ targetWindow.focus();
+ let tab = targetBrowser.selectedTab = targetBrowser.addTab(aUrl);
+ let linkedBrowser = tab.linkedBrowser;
+
+ info("Loading frame script with url " + FRAME_SCRIPT_URL + ".");
+ linkedBrowser.messageManager.loadFrameScript(FRAME_SCRIPT_URL, false);
+
+ BrowserTestUtils.browserLoaded(linkedBrowser)
+ .then(function () {
+ info("Tab added and finished loading: " + aUrl);
+ deferred.resolve(tab);
+ });
+
+ return deferred.promise;
+};
+
+this.removeTab = function removeTab(aTab, aWindow) {
+ info("Removing tab.");
+
+ let deferred = promise.defer();
+ let targetWindow = aWindow || window;
+ let targetBrowser = targetWindow.gBrowser;
+ let tabContainer = targetBrowser.tabContainer;
+
+ tabContainer.addEventListener("TabClose", function onClose(aEvent) {
+ tabContainer.removeEventListener("TabClose", onClose, false);
+
+ info("Tab removed and finished closing.");
+ deferred.resolve();
+ }, false);
+
+ targetBrowser.removeTab(aTab);
+ return deferred.promise;
+};
+
+function getAddonURIFromPath(aPath) {
+ let chromeURI = Services.io.newURI(aPath, null, CHROME_URI);
+ return chromeRegistry.convertChromeURL(chromeURI).QueryInterface(Ci.nsIFileURL);
+}
+
+function getTemporaryAddonURLFromPath(aPath) {
+ return getAddonURIFromPath(aPath).spec;
+}
+
+function addTemporaryAddon(aPath) {
+ let addonFile = getAddonURIFromPath(aPath).file;
+ info("Installing addon: " + addonFile.path);
+
+ return AddonManager.installTemporaryAddon(addonFile);
+}
+
+function removeAddon(aAddon) {
+ info("Removing addon.");
+
+ let deferred = promise.defer();
+
+ let listener = {
+ onUninstalled: function (aUninstalledAddon) {
+ if (aUninstalledAddon != aAddon) {
+ return;
+ }
+ AddonManager.removeAddonListener(listener);
+ deferred.resolve();
+ }
+ };
+ AddonManager.addAddonListener(listener);
+ aAddon.uninstall();
+
+ return deferred.promise;
+}
+
+function getTabActorForUrl(aClient, aUrl) {
+ let deferred = promise.defer();
+
+ aClient.listTabs(aResponse => {
+ let tabActor = aResponse.tabs.filter(aGrip => aGrip.url == aUrl).pop();
+ deferred.resolve(tabActor);
+ });
+
+ return deferred.promise;
+}
+
+function getAddonActorForId(aClient, aAddonId) {
+ info("Get addon actor for ID: " + aAddonId);
+ let deferred = promise.defer();
+
+ aClient.listAddons(aResponse => {
+ let addonActor = aResponse.addons.filter(aGrip => aGrip.id == aAddonId).pop();
+ info("got addon actor for ID: " + aAddonId);
+ deferred.resolve(addonActor);
+ });
+
+ return deferred.promise;
+}
+
+function attachTabActorForUrl(aClient, aUrl) {
+ let deferred = promise.defer();
+
+ getTabActorForUrl(aClient, aUrl).then(aGrip => {
+ aClient.attachTab(aGrip.actor, aResponse => {
+ deferred.resolve([aGrip, aResponse]);
+ });
+ });
+
+ return deferred.promise;
+}
+
+function attachThreadActorForUrl(aClient, aUrl) {
+ let deferred = promise.defer();
+
+ attachTabActorForUrl(aClient, aUrl).then(([aGrip, aResponse]) => {
+ aClient.attachThread(aResponse.threadActor, (aResponse, aThreadClient) => {
+ aThreadClient.resume(aResponse => {
+ deferred.resolve(aThreadClient);
+ });
+ });
+ });
+
+ return deferred.promise;
+}
+
+function once(aTarget, aEventName, aUseCapture = false) {
+ info("Waiting for event: '" + aEventName + "' on " + aTarget + ".");
+
+ let deferred = promise.defer();
+
+ for (let [add, remove] of [
+ ["addEventListener", "removeEventListener"],
+ ["addListener", "removeListener"],
+ ["on", "off"]
+ ]) {
+ if ((add in aTarget) && (remove in aTarget)) {
+ aTarget[add](aEventName, function onEvent(...aArgs) {
+ aTarget[remove](aEventName, onEvent, aUseCapture);
+ deferred.resolve.apply(deferred, aArgs);
+ }, aUseCapture);
+ break;
+ }
+ }
+
+ return deferred.promise;
+}
+
+function waitForTick() {
+ let deferred = promise.defer();
+ executeSoon(deferred.resolve);
+ return deferred.promise;
+}
+
+function waitForTime(aDelay) {
+ let deferred = promise.defer();
+ setTimeout(deferred.resolve, aDelay);
+ return deferred.promise;
+}
+
+function waitForSourceLoaded(aPanel, aUrl) {
+ let { Sources } = aPanel.panelWin.DebuggerView;
+ let isLoaded = Sources.items.some(item =>
+ item.attachment.source.url === aUrl);
+ if (isLoaded) {
+ info("The correct source has been loaded.");
+ return promise.resolve(null);
+ } else {
+ return waitForDebuggerEvents(aPanel, aPanel.panelWin.EVENTS.NEW_SOURCE).then(() => {
+ // Wait for it to be loaded in the UI and appear into Sources.items.
+ return waitForTick();
+ }).then(() => {
+ return waitForSourceLoaded(aPanel, aUrl);
+ });
+ }
+
+}
+
+function waitForSourceShown(aPanel, aUrl) {
+ return waitForDebuggerEvents(aPanel, aPanel.panelWin.EVENTS.SOURCE_SHOWN).then(aSource => {
+ let sourceUrl = aSource.url || aSource.introductionUrl;
+ info("Source shown: " + sourceUrl);
+
+ if (!sourceUrl.includes(aUrl)) {
+ return waitForSourceShown(aPanel, aUrl);
+ } else {
+ ok(true, "The correct source has been shown.");
+ }
+ });
+}
+
+function waitForEditorLocationSet(aPanel) {
+ return waitForDebuggerEvents(aPanel, aPanel.panelWin.EVENTS.EDITOR_LOCATION_SET);
+}
+
+function ensureSourceIs(aPanel, aUrlOrSource, aWaitFlag = false) {
+ let sources = aPanel.panelWin.DebuggerView.Sources;
+
+ if (sources.selectedValue === aUrlOrSource ||
+ (sources.selectedItem &&
+ sources.selectedItem.attachment.source.url.includes(aUrlOrSource))) {
+ ok(true, "Expected source is shown: " + aUrlOrSource);
+ return promise.resolve(null);
+ }
+ if (aWaitFlag) {
+ return waitForSourceShown(aPanel, aUrlOrSource);
+ }
+ ok(false, "Expected source was not already shown: " + aUrlOrSource);
+ return promise.reject(null);
+}
+
+function waitForCaretUpdated(aPanel, aLine, aCol = 1) {
+ return waitForEditorEvents(aPanel, "cursorActivity").then(() => {
+ let cursor = aPanel.panelWin.DebuggerView.editor.getCursor();
+ info("Caret updated: " + (cursor.line + 1) + ", " + (cursor.ch + 1));
+
+ if (!isCaretPos(aPanel, aLine, aCol)) {
+ return waitForCaretUpdated(aPanel, aLine, aCol);
+ } else {
+ ok(true, "The correct caret position has been set.");
+ }
+ });
+}
+
+function ensureCaretAt(aPanel, aLine, aCol = 1, aWaitFlag = false) {
+ if (isCaretPos(aPanel, aLine, aCol)) {
+ ok(true, "Expected caret position is set: " + aLine + "," + aCol);
+ return promise.resolve(null);
+ }
+ if (aWaitFlag) {
+ return waitForCaretUpdated(aPanel, aLine, aCol);
+ }
+ ok(false, "Expected caret position was not already set: " + aLine + "," + aCol);
+ return promise.reject(null);
+}
+
+function isCaretPos(aPanel, aLine, aCol = 1) {
+ let editor = aPanel.panelWin.DebuggerView.editor;
+ let cursor = editor.getCursor();
+
+ // Source editor starts counting line and column numbers from 0.
+ info("Current editor caret position: " + (cursor.line + 1) + ", " + (cursor.ch + 1));
+ return cursor.line == (aLine - 1) && cursor.ch == (aCol - 1);
+}
+
+function isDebugPos(aPanel, aLine) {
+ let editor = aPanel.panelWin.DebuggerView.editor;
+ let location = editor.getDebugLocation();
+
+ // Source editor starts counting line and column numbers from 0.
+ info("Current editor debug position: " + (location + 1));
+ return location != null && editor.hasLineClass(aLine - 1, "debug-line");
+}
+
+function isEditorSel(aPanel, [start, end]) {
+ let editor = aPanel.panelWin.DebuggerView.editor;
+ let range = {
+ start: editor.getOffset(editor.getCursor("start")),
+ end: editor.getOffset(editor.getCursor())
+ };
+
+ // Source editor starts counting line and column numbers from 0.
+ info("Current editor selection: " + (range.start + 1) + ", " + (range.end + 1));
+ return range.start == (start - 1) && range.end == (end - 1);
+}
+
+function waitForSourceAndCaret(aPanel, aUrl, aLine, aCol) {
+ return promise.all([
+ waitForSourceShown(aPanel, aUrl),
+ waitForCaretUpdated(aPanel, aLine, aCol)
+ ]);
+}
+
+function waitForCaretAndScopes(aPanel, aLine, aCol) {
+ return promise.all([
+ waitForCaretUpdated(aPanel, aLine, aCol),
+ waitForDebuggerEvents(aPanel, aPanel.panelWin.EVENTS.FETCHED_SCOPES)
+ ]);
+}
+
+function waitForSourceAndCaretAndScopes(aPanel, aUrl, aLine, aCol) {
+ return promise.all([
+ waitForSourceAndCaret(aPanel, aUrl, aLine, aCol),
+ waitForDebuggerEvents(aPanel, aPanel.panelWin.EVENTS.FETCHED_SCOPES)
+ ]);
+}
+
+function waitForDebuggerEvents(aPanel, aEventName, aEventRepeat = 1) {
+ info("Waiting for debugger event: '" + aEventName + "' to fire: " + aEventRepeat + " time(s).");
+
+ let deferred = promise.defer();
+ let panelWin = aPanel.panelWin;
+ let count = 0;
+
+ panelWin.on(aEventName, function onEvent(aEventName, ...aArgs) {
+ info("Debugger event '" + aEventName + "' fired: " + (++count) + " time(s).");
+
+ if (count == aEventRepeat) {
+ ok(true, "Enough '" + aEventName + "' panel events have been fired.");
+ panelWin.off(aEventName, onEvent);
+ deferred.resolve.apply(deferred, aArgs);
+ }
+ });
+
+ return deferred.promise;
+}
+
+function waitForEditorEvents(aPanel, aEventName, aEventRepeat = 1) {
+ info("Waiting for editor event: '" + aEventName + "' to fire: " + aEventRepeat + " time(s).");
+
+ let deferred = promise.defer();
+ let editor = aPanel.panelWin.DebuggerView.editor;
+ let count = 0;
+
+ editor.on(aEventName, function onEvent(...aArgs) {
+ info("Editor event '" + aEventName + "' fired: " + (++count) + " time(s).");
+
+ if (count == aEventRepeat) {
+ ok(true, "Enough '" + aEventName + "' editor events have been fired.");
+ editor.off(aEventName, onEvent);
+ deferred.resolve.apply(deferred, aArgs);
+ }
+ });
+
+ return deferred.promise;
+}
+
+function waitForThreadEvents(aPanel, aEventName, aEventRepeat = 1) {
+ info("Waiting for thread event: '" + aEventName + "' to fire: " + aEventRepeat + " time(s).");
+
+ let deferred = promise.defer();
+ let thread = aPanel.panelWin.gThreadClient;
+ let count = 0;
+
+ thread.addListener(aEventName, function onEvent(aEventName, ...aArgs) {
+ info("Thread event '" + aEventName + "' fired: " + (++count) + " time(s).");
+
+ if (count == aEventRepeat) {
+ ok(true, "Enough '" + aEventName + "' thread events have been fired.");
+ thread.removeListener(aEventName, onEvent);
+ deferred.resolve.apply(deferred, aArgs);
+ }
+ });
+
+ return deferred.promise;
+}
+
+function waitForClientEvents(aPanel, aEventName, aEventRepeat = 1) {
+ info("Waiting for client event: '" + aEventName + "' to fire: " + aEventRepeat + " time(s).");
+
+ let deferred = promise.defer();
+ let client = aPanel.panelWin.gClient;
+ let count = 0;
+
+ client.addListener(aEventName, function onEvent(aEventName, ...aArgs) {
+ info("Thread event '" + aEventName + "' fired: " + (++count) + " time(s).");
+
+ if (count == aEventRepeat) {
+ ok(true, "Enough '" + aEventName + "' thread events have been fired.");
+ client.removeListener(aEventName, onEvent);
+ deferred.resolve.apply(deferred, aArgs);
+ }
+ });
+
+ return deferred.promise;
+}
+
+function ensureThreadClientState(aPanel, aState) {
+ let thread = aPanel.panelWin.gThreadClient;
+ let state = thread.state;
+
+ info("Thread is: '" + state + "'.");
+
+ if (state == aState) {
+ return promise.resolve(null);
+ } else {
+ return waitForThreadEvents(aPanel, aState);
+ }
+}
+
+function reload(aPanel, aUrl) {
+ let activeTab = aPanel.panelWin.DebuggerController._target.activeTab;
+ aUrl ? activeTab.navigateTo(aUrl) : activeTab.reload();
+}
+
+function navigateActiveTabTo(aPanel, aUrl, aWaitForEventName, aEventRepeat) {
+ let finished = waitForDebuggerEvents(aPanel, aWaitForEventName, aEventRepeat);
+ reload(aPanel, aUrl);
+ return finished;
+}
+
+function navigateActiveTabInHistory(aPanel, aDirection, aWaitForEventName, aEventRepeat) {
+ let finished = waitForDebuggerEvents(aPanel, aWaitForEventName, aEventRepeat);
+ content.history[aDirection]();
+ return finished;
+}
+
+function reloadActiveTab(aPanel, aWaitForEventName, aEventRepeat) {
+ return navigateActiveTabTo(aPanel, null, aWaitForEventName, aEventRepeat);
+}
+
+function clearText(aElement) {
+ info("Clearing text...");
+ aElement.focus();
+ aElement.value = "";
+}
+
+function setText(aElement, aText) {
+ clearText(aElement);
+ info("Setting text: " + aText);
+ aElement.value = aText;
+}
+
+function typeText(aElement, aText) {
+ info("Typing text: " + aText);
+ aElement.focus();
+ EventUtils.sendString(aText, aElement.ownerDocument.defaultView);
+}
+
+function backspaceText(aElement, aTimes) {
+ info("Pressing backspace " + aTimes + " times.");
+ for (let i = 0; i < aTimes; i++) {
+ aElement.focus();
+ EventUtils.sendKey("BACK_SPACE", aElement.ownerDocument.defaultView);
+ }
+}
+
+function getTab(aTarget, aWindow) {
+ if (aTarget instanceof XULElement) {
+ return promise.resolve(aTarget);
+ } else {
+ return addTab(aTarget, aWindow);
+ }
+}
+
+function getSources(aClient) {
+ info("Getting sources.");
+
+ let deferred = promise.defer();
+
+ aClient.getSources((packet) => {
+ deferred.resolve(packet.sources);
+ });
+
+ return deferred.promise;
+}
+
+/**
+ * Optionaly open a new tab and then open the debugger panel.
+ * The returned promise resolves only one the panel is fully set.
+
+ * @param {String|xul:tab} urlOrTab
+ * If a string, consider it as the url of the tab to open before opening the
+ * debugger panel.
+ * Otherwise, if a <xul:tab>, do nothing, but open the debugger panel against
+ * the given tab.
+ * @param {Object} options
+ * Set of optional arguments:
+ * - {String} source
+ * If given, assert the default loaded source once the debugger is loaded.
+ * This string can be partial to only match a part of the source name.
+ * If null, do not expect any source and skip SOURCE_SHOWN wait.
+ * - {Number} line
+ * If given, wait for the caret to be set on a precise line
+ *
+ * @return {Promise}
+ * Resolves once debugger panel is fully set according to the given options.
+ */
+let initDebugger = Task.async(function*(urlOrTab, options) {
+ let { window, source, line } = options || {};
+ info("Initializing a debugger panel.");
+
+ let tab, url;
+ if (urlOrTab instanceof XULElement) {
+ // `urlOrTab` Is a Tab.
+ tab = urlOrTab;
+ } else {
+ // `urlOrTab` is an url. Open an empty tab first in order to load the page
+ // only once the panel is ready. That to be able to safely catch the
+ // SOURCE_SHOWN event.
+ tab = yield addTab("about:blank", window);
+ url = urlOrTab;
+ }
+ info("Debugee tab added successfully: " + urlOrTab);
+
+ let debuggee = tab.linkedBrowser.contentWindow.wrappedJSObject;
+ let target = TargetFactory.forTab(tab);
+
+ let toolbox = yield gDevTools.showToolbox(target, "jsdebugger");
+ info("Debugger panel shown successfully.");
+
+ let debuggerPanel = toolbox.getCurrentPanel();
+ let panelWin = debuggerPanel.panelWin;
+ let { Sources } = panelWin.DebuggerView;
+
+ prepareDebugger(debuggerPanel);
+
+ if (url && url != "about:blank") {
+ let onCaretUpdated;
+ if (line) {
+ onCaretUpdated = waitForCaretUpdated(debuggerPanel, line);
+ }
+ if (source === null) {
+ // When there is no source in the document, we shouldn't wait for
+ // SOURCE_SHOWN event
+ yield reload(debuggerPanel, url);
+ } else {
+ yield navigateActiveTabTo(debuggerPanel,
+ url,
+ panelWin.EVENTS.SOURCE_SHOWN);
+ }
+ if (source) {
+ let isSelected = Sources.selectedItem.attachment.source.url === source;
+ if (!isSelected) {
+ // Ensure that the source is loaded first before trying to select it
+ yield waitForSourceLoaded(debuggerPanel, source);
+ // Select the js file.
+ let onSource = waitForSourceAndCaret(debuggerPanel, source, line ? line : 1);
+ Sources.selectedValue = getSourceActor(Sources, source);
+ yield onSource;
+ }
+ }
+ yield onCaretUpdated;
+ }
+
+ return [tab, debuggee, debuggerPanel, window];
+});
+
+// Creates an add-on debugger for a given add-on. The returned AddonDebugger
+// object must be destroyed before finishing the test
+function initAddonDebugger(aAddonId) {
+ let addonDebugger = new AddonDebugger();
+ return addonDebugger.init(aAddonId).then(() => addonDebugger);
+}
+
+function AddonDebugger() {
+ this._onMessage = this._onMessage.bind(this);
+ this._onConsoleAPICall = this._onConsoleAPICall.bind(this);
+ EventEmitter.decorate(this);
+}
+
+AddonDebugger.prototype = {
+ init: Task.async(function* (aAddonId) {
+ info("Initializing an addon debugger panel.");
+
+ if (!DebuggerServer.initialized) {
+ DebuggerServer.init();
+ DebuggerServer.addBrowserActors();
+ }
+ DebuggerServer.allowChromeProcess = true;
+
+ this.frame = document.createElement("iframe");
+ this.frame.setAttribute("height", 400);
+ document.documentElement.appendChild(this.frame);
+ window.addEventListener("message", this._onMessage);
+
+ let transport = DebuggerServer.connectPipe();
+ this.client = new DebuggerClient(transport);
+
+ yield this.client.connect();
+
+ let addonActor = yield getAddonActorForId(this.client, aAddonId);
+
+ let targetOptions = {
+ form: addonActor,
+ client: this.client,
+ chrome: true,
+ isTabActor: false
+ };
+
+ let toolboxOptions = {
+ customIframe: this.frame
+ };
+
+ this.target = TargetFactory.forTab(targetOptions);
+ let toolbox = yield gDevTools.showToolbox(this.target, "jsdebugger", Toolbox.HostType.CUSTOM, toolboxOptions);
+
+ info("Addon debugger panel shown successfully.");
+
+ this.debuggerPanel = toolbox.getCurrentPanel();
+ yield waitForSourceShown(this.debuggerPanel, "");
+
+ prepareDebugger(this.debuggerPanel);
+ yield this._attachConsole();
+ }),
+
+ destroy: Task.async(function* () {
+ yield this.client.close();
+ yield this.debuggerPanel._toolbox.destroy();
+ this.frame.remove();
+ window.removeEventListener("message", this._onMessage);
+ }),
+
+ _attachConsole: function () {
+ let deferred = promise.defer();
+ this.client.attachConsole(this.target.form.consoleActor, ["ConsoleAPI"], (aResponse, aWebConsoleClient) => {
+ if (aResponse.error) {
+ deferred.reject(aResponse);
+ }
+ else {
+ this.webConsole = aWebConsoleClient;
+ this.client.addListener("consoleAPICall", this._onConsoleAPICall);
+ deferred.resolve();
+ }
+ });
+ return deferred.promise;
+ },
+
+ _onConsoleAPICall: function (aType, aPacket) {
+ if (aPacket.from != this.webConsole.actor)
+ return;
+ this.emit("console", aPacket.message);
+ },
+
+ /**
+ * Returns a list of the groups and sources in the UI. The returned array
+ * contains objects for each group with properties name and sources. The
+ * sources property contains an array with objects for each source for that
+ * group with properties label and url.
+ */
+ getSourceGroups: Task.async(function* () {
+ let debuggerWin = this.debuggerPanel.panelWin;
+ let sources = yield getSources(debuggerWin.gThreadClient);
+ ok(sources.length, "retrieved sources");
+
+ // groups will be the return value, groupmap and the maps we put in it will
+ // be used as quick lookups to add the url information in below
+ let groups = [];
+ let groupmap = new Map();
+
+ let uigroups = this.debuggerPanel.panelWin.document.querySelectorAll(".side-menu-widget-group");
+ for (let g of uigroups) {
+ let name = g.querySelector(".side-menu-widget-group-title .name").value;
+ let group = {
+ name: name,
+ sources: []
+ };
+ groups.push(group);
+ let labelmap = new Map();
+ groupmap.set(name, labelmap);
+
+ for (let l of g.querySelectorAll(".dbg-source-item")) {
+ let source = {
+ label: l.value,
+ url: null
+ };
+
+ labelmap.set(l.value, source);
+ group.sources.push(source);
+ }
+ }
+
+ for (let source of sources) {
+ let { label, group } = debuggerWin.DebuggerView.Sources.getItemByValue(source.actor).attachment;
+
+ if (!groupmap.has(group)) {
+ ok(false, "Saw a source group not in the UI: " + group);
+ continue;
+ }
+
+ if (!groupmap.get(group).has(label)) {
+ ok(false, "Saw a source label not in the UI: " + label);
+ continue;
+ }
+
+ groupmap.get(group).get(label).url = source.url.split(" -> ").pop();
+ }
+
+ return groups;
+ }),
+
+ _onMessage: function (event) {
+ if (typeof(event.data) !== "string") {
+ return;
+ }
+ let json = JSON.parse(event.data);
+ switch (json.name) {
+ case "toolbox-title":
+ this.title = json.data.value;
+ break;
+ }
+ }
+};
+
+function initChromeDebugger(aOnClose) {
+ info("Initializing a chrome debugger process.");
+
+ let deferred = promise.defer();
+
+ // Wait for the toolbox process to start...
+ BrowserToolboxProcess.init(aOnClose, (aEvent, aProcess) => {
+ info("Browser toolbox process started successfully.");
+
+ prepareDebugger(aProcess);
+ deferred.resolve(aProcess);
+ });
+
+ return deferred.promise;
+}
+
+function prepareDebugger(aDebugger) {
+ if ("target" in aDebugger) {
+ let view = aDebugger.panelWin.DebuggerView;
+ view.Variables.lazyEmpty = false;
+ view.Variables.lazySearch = false;
+ view.Filtering.FilteredSources._autoSelectFirstItem = true;
+ view.Filtering.FilteredFunctions._autoSelectFirstItem = true;
+ } else {
+ // Nothing to do here yet.
+ }
+}
+
+function teardown(aPanel, aFlags = {}) {
+ info("Destroying the specified debugger.");
+
+ let toolbox = aPanel._toolbox;
+ let tab = aPanel.target.tab;
+ let debuggerRootActorDisconnected = once(window, "Debugger:Shutdown");
+ let debuggerPanelDestroyed = once(aPanel, "destroyed");
+ let devtoolsToolboxDestroyed = toolbox.destroy();
+
+ return promise.all([
+ debuggerRootActorDisconnected,
+ debuggerPanelDestroyed,
+ devtoolsToolboxDestroyed
+ ]).then(() => aFlags.noTabRemoval ? null : removeTab(tab));
+}
+
+function closeDebuggerAndFinish(aPanel, aFlags = {}) {
+ let thread = aPanel.panelWin.gThreadClient;
+ if (thread.state == "paused" && !aFlags.whilePaused) {
+ ok(false, "You should use 'resumeDebuggerThenCloseAndFinish' instead, " +
+ "unless you're absolutely sure about what you're doing.");
+ }
+ return teardown(aPanel, aFlags).then(finish);
+}
+
+function resumeDebuggerThenCloseAndFinish(aPanel, aFlags = {}) {
+ let deferred = promise.defer();
+ let thread = aPanel.panelWin.gThreadClient;
+ thread.resume(() => closeDebuggerAndFinish(aPanel, aFlags).then(deferred.resolve));
+ return deferred.promise;
+}
+
+// Blackboxing helpers
+
+function getBlackBoxButton(aPanel) {
+ return aPanel.panelWin.document.getElementById("black-box");
+}
+
+/**
+ * Returns the node that has the black-boxed class applied to it.
+ */
+function getSelectedSourceElement(aPanel) {
+ return aPanel.panelWin.DebuggerView.Sources.selectedItem.prebuiltNode;
+}
+
+function toggleBlackBoxing(aPanel, aSourceActor = null) {
+ function clickBlackBoxButton() {
+ getBlackBoxButton(aPanel).click();
+ }
+
+ const blackBoxChanged = waitForDispatch(
+ aPanel,
+ aPanel.panelWin.constants.BLACKBOX
+ ).then(() => {
+ return aSourceActor ?
+ getSource(aPanel, aSourceActor) :
+ getSelectedSource(aPanel);
+ });
+
+ if (aSourceActor) {
+ aPanel.panelWin.DebuggerView.Sources.selectedValue = aSourceActor;
+ ensureSourceIs(aPanel, aSourceActor, true).then(clickBlackBoxButton);
+ } else {
+ clickBlackBoxButton();
+ }
+
+ return blackBoxChanged;
+}
+
+function selectSourceAndGetBlackBoxButton(aPanel, aUrl) {
+ function returnBlackboxButton() {
+ return getBlackBoxButton(aPanel);
+ }
+
+ let sources = aPanel.panelWin.DebuggerView.Sources;
+ sources.selectedValue = getSourceActor(sources, aUrl);
+ return ensureSourceIs(aPanel, aUrl, true).then(returnBlackboxButton);
+}
+
+// Variables view inspection popup helpers
+
+function openVarPopup(aPanel, aCoords, aWaitForFetchedProperties) {
+ let events = aPanel.panelWin.EVENTS;
+ let editor = aPanel.panelWin.DebuggerView.editor;
+ let bubble = aPanel.panelWin.DebuggerView.VariableBubble;
+ let tooltip = bubble._tooltip.panel;
+
+ let popupShown = once(tooltip, "popupshown");
+ let fetchedProperties = aWaitForFetchedProperties
+ ? waitForDebuggerEvents(aPanel, events.FETCHED_BUBBLE_PROPERTIES)
+ : promise.resolve(null);
+ let updatedFrame = waitForDebuggerEvents(aPanel, events.FETCHED_SCOPES);
+
+ let { left, top } = editor.getCoordsFromPosition(aCoords);
+ bubble._findIdentifier(left, top);
+ return promise.all([popupShown, fetchedProperties, updatedFrame]).then(waitForTick);
+}
+
+// Simulates the mouse hovering a variable in the debugger
+// Takes in account the position of the cursor in the text, if the text is
+// selected and if a button is currently pushed (aButtonPushed > 0).
+// The function returns a promise which returns true if the popup opened or
+// false if it didn't
+function intendOpenVarPopup(aPanel, aPosition, aButtonPushed) {
+ let bubble = aPanel.panelWin.DebuggerView.VariableBubble;
+ let editor = aPanel.panelWin.DebuggerView.editor;
+ let tooltip = bubble._tooltip;
+
+ let { left, top } = editor.getCoordsFromPosition(aPosition);
+
+ const eventDescriptor = {
+ clientX: left,
+ clientY: top,
+ buttons: aButtonPushed
+ };
+
+ bubble._onMouseMove(eventDescriptor);
+
+ const deferred = promise.defer();
+ window.setTimeout(
+ function () {
+ if (tooltip.isEmpty()) {
+ deferred.resolve(false);
+ } else {
+ deferred.resolve(true);
+ }
+ },
+ bubble.TOOLTIP_SHOW_DELAY + 1000
+ );
+
+ return deferred.promise;
+}
+
+function hideVarPopup(aPanel) {
+ let bubble = aPanel.panelWin.DebuggerView.VariableBubble;
+ let tooltip = bubble._tooltip.panel;
+
+ let popupHiding = once(tooltip, "popuphiding");
+ bubble.hideContents();
+ return popupHiding.then(waitForTick);
+}
+
+function hideVarPopupByScrollingEditor(aPanel) {
+ let editor = aPanel.panelWin.DebuggerView.editor;
+ let bubble = aPanel.panelWin.DebuggerView.VariableBubble;
+ let tooltip = bubble._tooltip.panel;
+
+ let popupHiding = once(tooltip, "popuphiding");
+ editor.setFirstVisibleLine(0);
+ return popupHiding.then(waitForTick);
+}
+
+function reopenVarPopup(...aArgs) {
+ return hideVarPopup.apply(this, aArgs).then(() => openVarPopup.apply(this, aArgs));
+}
+
+function attachAddonActorForId(aClient, aAddonId) {
+ let deferred = promise.defer();
+
+ getAddonActorForId(aClient, aAddonId).then(aGrip => {
+ aClient.attachAddon(aGrip.actor, aResponse => {
+ deferred.resolve([aGrip, aResponse]);
+ });
+ });
+
+ return deferred.promise;
+}
+
+function doResume(aPanel) {
+ const threadClient = aPanel.panelWin.gThreadClient;
+ return threadClient.resume();
+}
+
+function doInterrupt(aPanel) {
+ const threadClient = aPanel.panelWin.gThreadClient;
+ return threadClient.interrupt();
+}
+
+function pushPrefs(...aPrefs) {
+ let deferred = promise.defer();
+ SpecialPowers.pushPrefEnv({"set": aPrefs}, deferred.resolve);
+ return deferred.promise;
+}
+
+function popPrefs() {
+ let deferred = promise.defer();
+ SpecialPowers.popPrefEnv(deferred.resolve);
+ return deferred.promise;
+}
+
+// Source helpers
+
+function getSelectedSource(panel) {
+ const win = panel.panelWin;
+ return win.queries.getSelectedSource(win.DebuggerController.getState());
+}
+
+function getSource(panel, actor) {
+ const win = panel.panelWin;
+ return win.queries.getSource(win.DebuggerController.getState(), actor);
+}
+
+function getSelectedSourceURL(aSources) {
+ return (aSources.selectedItem &&
+ aSources.selectedItem.attachment.source.url);
+}
+
+function getSourceURL(aSources, aActor) {
+ let item = aSources.getItemByValue(aActor);
+ return item && item.attachment.source.url;
+}
+
+function getSourceActor(aSources, aURL) {
+ let item = aSources.getItemForAttachment(a => a.source && a.source.url === aURL);
+ return item && item.value;
+}
+
+function getSourceForm(aSources, aURL) {
+ let item = aSources.getItemByValue(getSourceActor(aSources, aURL));
+ return item.attachment.source;
+}
+
+var nextId = 0;
+
+function jsonrpc(tab, method, params) {
+ return new Promise(function (resolve, reject) {
+ let currentId = nextId++;
+ let messageManager = tab.linkedBrowser.messageManager;
+ messageManager.sendAsyncMessage("jsonrpc", {
+ method: method,
+ params: params,
+ id: currentId
+ });
+ messageManager.addMessageListener("jsonrpc", function listener(res) {
+ const { data: { result, error, id } } = res;
+ if (id !== currentId) {
+ return;
+ }
+
+ messageManager.removeMessageListener("jsonrpc", listener);
+ if (error != null) {
+ reject(error);
+ }
+
+ resolve(result);
+ });
+ });
+}
+
+function callInTab(tab, name) {
+ info("Calling function with name '" + name + "' in tab.");
+
+ return jsonrpc(tab, "call", [name, Array.prototype.slice.call(arguments, 2)]);
+}
+
+function evalInTab(tab, string) {
+ info("Evalling string in tab.");
+
+ return jsonrpc(tab, "_eval", [string]);
+}
+
+function createWorkerInTab(tab, url) {
+ info("Creating worker with url '" + url + "' in tab.");
+
+ return jsonrpc(tab, "createWorker", [url]);
+}
+
+function terminateWorkerInTab(tab, url) {
+ info("Terminating worker with url '" + url + "' in tab.");
+
+ return jsonrpc(tab, "terminateWorker", [url]);
+}
+
+function postMessageToWorkerInTab(tab, url, message) {
+ info("Posting message to worker with url '" + url + "' in tab.");
+
+ return jsonrpc(tab, "postMessageToWorker", [url, message]);
+}
+
+function generateMouseClickInTab(tab, path) {
+ info("Generating mouse click in tab.");
+
+ return jsonrpc(tab, "generateMouseClick", [path]);
+}
+
+function connect(client) {
+ info("Connecting client.");
+ return client.connect();
+}
+
+function close(client) {
+ info("Waiting for client to close.\n");
+ return client.close();
+}
+
+function listTabs(client) {
+ info("Listing tabs.");
+ return client.listTabs();
+}
+
+function findTab(tabs, url) {
+ info("Finding tab with url '" + url + "'.");
+ for (let tab of tabs) {
+ if (tab.url === url) {
+ return tab;
+ }
+ }
+ return null;
+}
+
+function attachTab(client, tab) {
+ info("Attaching to tab with url '" + tab.url + "'.");
+ return new Promise(function (resolve) {
+ client.attachTab(tab.actor, function (response, tabClient) {
+ resolve([response, tabClient]);
+ });
+ });
+}
+
+function listWorkers(tabClient) {
+ info("Listing workers.");
+ return new Promise(function (resolve) {
+ tabClient.listWorkers(function (response) {
+ resolve(response);
+ });
+ });
+}
+
+function findWorker(workers, url) {
+ info("Finding worker with url '" + url + "'.");
+ for (let worker of workers) {
+ if (worker.url === url) {
+ return worker;
+ }
+ }
+ return null;
+}
+
+function attachWorker(tabClient, worker) {
+ info("Attaching to worker with url '" + worker.url + "'.");
+ return new Promise(function (resolve, reject) {
+ tabClient.attachWorker(worker.actor, function (response, workerClient) {
+ resolve([response, workerClient]);
+ });
+ });
+}
+
+function waitForWorkerListChanged(tabClient) {
+ info("Waiting for worker list to change.");
+ return new Promise(function (resolve) {
+ tabClient.addListener("workerListChanged", function listener() {
+ tabClient.removeListener("workerListChanged", listener);
+ resolve();
+ });
+ });
+}
+
+function attachThread(workerClient, options) {
+ info("Attaching to thread.");
+ return new Promise(function (resolve, reject) {
+ workerClient.attachThread(options, function (response, threadClient) {
+ resolve([response, threadClient]);
+ });
+ });
+}
+
+function waitForWorkerClose(workerClient) {
+ info("Waiting for worker to close.");
+ return new Promise(function (resolve) {
+ workerClient.addOneTimeListener("close", function () {
+ info("Worker did close.");
+ resolve();
+ });
+ });
+}
+
+function resume(threadClient) {
+ info("Resuming thread.");
+ return threadClient.resume();
+}
+
+function findSource(sources, url) {
+ info("Finding source with url '" + url + "'.\n");
+ for (let source of sources) {
+ if (source.url === url) {
+ return source;
+ }
+ }
+ return null;
+}
+
+function waitForEvent(client, type, predicate) {
+ return new Promise(function (resolve) {
+ function listener(type, packet) {
+ if (!predicate(packet)) {
+ return;
+ }
+ client.removeListener(listener);
+ resolve(packet);
+ }
+
+ if (predicate) {
+ client.addListener(type, listener);
+ } else {
+ client.addOneTimeListener(type, function (type, packet) {
+ resolve(packet);
+ });
+ }
+ });
+}
+
+function waitForPause(threadClient) {
+ info("Waiting for pause.\n");
+ return waitForEvent(threadClient, "paused");
+}
+
+function setBreakpoint(sourceClient, location) {
+ info("Setting breakpoint.\n");
+ return sourceClient.setBreakpoint(location);
+}
+
+function source(sourceClient) {
+ info("Getting source.\n");
+ return sourceClient.source();
+}
+
+// Return a promise with a reference to jsterm, opening the split
+// console if necessary. This cleans up the split console pref so
+// it won't pollute other tests.
+function getSplitConsole(toolbox, win) {
+ registerCleanupFunction(() => {
+ Services.prefs.clearUserPref("devtools.toolbox.splitconsoleEnabled");
+ });
+
+ if (!win) {
+ win = toolbox.win;
+ }
+
+ if (!toolbox.splitConsole) {
+ EventUtils.synthesizeKey("VK_ESCAPE", {}, win);
+ }
+
+ return new Promise(resolve => {
+ toolbox.getPanelWhenReady("webconsole").then(() => {
+ ok(toolbox.splitConsole, "Split console is shown.");
+ let jsterm = toolbox.getPanel("webconsole").hud.jsterm;
+ resolve(jsterm);
+ });
+ });
+}
+
+// navigation
+
+function waitForNavigation(gPanel) {
+ const target = gPanel.panelWin.gTarget;
+ const deferred = promise.defer();
+ target.once("navigate", () => {
+ deferred.resolve();
+ });
+ info("Waiting for navigation...");
+ return deferred.promise;
+}
+
+// actions
+
+function bindActionCreators(panel) {
+ const win = panel.panelWin;
+ const dispatch = win.DebuggerController.dispatch;
+ const { bindActionCreators } = win.require("devtools/client/shared/vendor/redux");
+ return bindActionCreators(win.actions, dispatch);
+}
+
+// Wait until an action of `type` is dispatched. This is different
+// then `_afterDispatchDone` because it doesn't wait for async actions
+// to be done/errored. Use this if you want to listen for the "start"
+// action of an async operation (somewhat rare).
+function waitForNextDispatch(store, type) {
+ return new Promise(resolve => {
+ store.dispatch({
+ // Normally we would use `services.WAIT_UNTIL`, but use the
+ // internal name here so tests aren't forced to always pass it
+ // in
+ type: "@@service/waitUntil",
+ predicate: action => action.type === type,
+ run: (dispatch, getState, action) => {
+ resolve(action);
+ }
+ });
+ });
+}
+
+// Wait until an action of `type` is dispatched. If it's part of an
+// async operation, wait until the `status` field is "done" or "error"
+function _afterDispatchDone(store, type) {
+ return new Promise(resolve => {
+ store.dispatch({
+ // Normally we would use `services.WAIT_UNTIL`, but use the
+ // internal name here so tests aren't forced to always pass it
+ // in
+ type: "@@service/waitUntil",
+ predicate: action => {
+ if (action.type === type) {
+ return action.status ?
+ (action.status === "done" || action.status === "error") :
+ true;
+ }
+ },
+ run: (dispatch, getState, action) => {
+ resolve(action);
+ }
+ });
+ });
+}
+
+function waitForDispatch(panel, type, eventRepeat = 1) {
+ const controller = panel.panelWin.DebuggerController;
+ const actionType = panel.panelWin.constants[type];
+ let count = 0;
+
+ return Task.spawn(function* () {
+ info("Waiting for " + type + " to dispatch " + eventRepeat + " time(s)");
+ while (count < eventRepeat) {
+ yield _afterDispatchDone(controller, actionType);
+ count++;
+ info(type + " dispatched " + count + " time(s)");
+ }
+ });
+}
+
+function* initWorkerDebugger(TAB_URL, WORKER_URL) {
+ if (!DebuggerServer.initialized) {
+ DebuggerServer.init();
+ DebuggerServer.addBrowserActors();
+ }
+
+ let client = new DebuggerClient(DebuggerServer.connectPipe());
+ yield connect(client);
+
+ let tab = yield addTab(TAB_URL);
+ let { tabs } = yield listTabs(client);
+ let [, tabClient] = yield attachTab(client, findTab(tabs, TAB_URL));
+
+ yield createWorkerInTab(tab, WORKER_URL);
+
+ let { workers } = yield listWorkers(tabClient);
+ let [, workerClient] = yield attachWorker(tabClient,
+ findWorker(workers, WORKER_URL));
+
+ let toolbox = yield gDevTools.showToolbox(TargetFactory.forWorker(workerClient),
+ "jsdebugger",
+ Toolbox.HostType.WINDOW);
+
+ let debuggerPanel = toolbox.getCurrentPanel();
+ let gDebugger = debuggerPanel.panelWin;
+
+ return {client, tab, tabClient, workerClient, toolbox, gDebugger};
+}
+
diff --git a/devtools/client/debugger/test/mochitest/sjs_post-page.sjs b/devtools/client/debugger/test/mochitest/sjs_post-page.sjs
new file mode 100644
index 000000000..06f7c60d0
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/sjs_post-page.sjs
@@ -0,0 +1,16 @@
+/* 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/. */
+
+const CC = Components.Constructor;
+const BinaryInputStream = CC("@mozilla.org/binaryinputstream;1",
+ "nsIBinaryInputStream",
+ "setInputStream");
+
+function handleRequest(request, response)
+{
+ let method = request.method;
+ let body = "<script>\"" + method + "\";</script>";
+ body += "<form method=\"POST\"><input type=\"submit\"></form>";
+ response.bodyOutputStream.write(body, body.length);
+}
diff --git a/devtools/client/debugger/test/mochitest/sjs_random-javascript.sjs b/devtools/client/debugger/test/mochitest/sjs_random-javascript.sjs
new file mode 100644
index 000000000..3e0ea8e53
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/sjs_random-javascript.sjs
@@ -0,0 +1,11 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+function handleRequest(request, response) {
+ response.setStatusLine(request.httpVersion, 200, "OK");
+ response.setHeader("Content-Type", "application/javascript; charset=utf-8", false);
+ response.write([
+ "window.setInterval(function bacon() {",
+ " var x = '" + Math.random() + "';",
+ "}, 0);"].join("\n"));
+}
diff --git a/devtools/client/debugger/test/mochitest/testactors.js b/devtools/client/debugger/test/mochitest/testactors.js
new file mode 100644
index 000000000..f7583b615
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/testactors.js
@@ -0,0 +1,33 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+function TestActor1(aConnection, aTab)
+{
+ this.conn = aConnection;
+ this.tab = aTab;
+}
+
+TestActor1.prototype = {
+ actorPrefix: "test_one",
+
+ grip: function TA1_grip() {
+ return { actor: this.actorID,
+ test: "TestActor1" };
+ },
+
+ onPing: function TA1_onPing() {
+ return { pong: "pong" };
+ }
+};
+
+TestActor1.prototype.requestTypes = {
+ "ping": TestActor1.prototype.onPing
+};
+
+DebuggerServer.removeTabActor(TestActor1);
+DebuggerServer.removeGlobalActor(TestActor1);
+
+DebuggerServer.addTabActor(TestActor1, "testTabActor1");
+DebuggerServer.addGlobalActor(TestActor1, "testGlobalActor1");
diff --git a/devtools/client/debugger/utils.js b/devtools/client/debugger/utils.js
new file mode 100644
index 000000000..e2d3fbebe
--- /dev/null
+++ b/devtools/client/debugger/utils.js
@@ -0,0 +1,378 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* 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/. */
+/* globals document, window */
+/* import-globals-from ./debugger-controller.js */
+"use strict";
+
+// Maps known URLs to friendly source group names and put them at the
+// bottom of source list.
+var KNOWN_SOURCE_GROUPS = {
+ "Add-on SDK": "resource://gre/modules/commonjs/",
+};
+
+KNOWN_SOURCE_GROUPS[L10N.getStr("anonymousSourcesLabel")] = "anonymous";
+
+var XULUtils = {
+ /**
+ * Create <command> elements within `commandset` with event handlers
+ * bound to the `command` event
+ *
+ * @param commandset HTML Element
+ * A <commandset> element
+ * @param commands Object
+ * An object where keys specify <command> ids and values
+ * specify event handlers to be bound on the `command` event
+ */
+ addCommands: function (commandset, commands) {
+ Object.keys(commands).forEach(name => {
+ let node = document.createElement("command");
+ node.id = name;
+ // XXX bug 371900: the command element must have an oncommand
+ // attribute as a string set by `setAttribute` for keys to use it
+ node.setAttribute("oncommand", " ");
+ node.addEventListener("command", commands[name]);
+ commandset.appendChild(node);
+ });
+ }
+};
+
+// Used to detect minification for automatic pretty printing
+const SAMPLE_SIZE = 50; // no of lines
+const INDENT_COUNT_THRESHOLD = 5; // percentage
+const CHARACTER_LIMIT = 250; // line character limit
+
+/**
+ * Utility functions for handling sources.
+ */
+var SourceUtils = {
+ _labelsCache: new Map(), // Can't use WeakMaps because keys are strings.
+ _groupsCache: new Map(),
+ _minifiedCache: new Map(),
+
+ /**
+ * Returns true if the specified url and/or content type are specific to
+ * javascript files.
+ *
+ * @return boolean
+ * True if the source is likely javascript.
+ */
+ isJavaScript: function (aUrl, aContentType = "") {
+ return (aUrl && /\.jsm?$/.test(this.trimUrlQuery(aUrl))) ||
+ aContentType.includes("javascript");
+ },
+
+ /**
+ * Determines if the source text is minified by using
+ * the percentage indented of a subset of lines
+ *
+ * @return object
+ * A promise that resolves to true if source text is minified.
+ */
+ isMinified: function (key, text) {
+ if (this._minifiedCache.has(key)) {
+ return this._minifiedCache.get(key);
+ }
+
+ let isMinified;
+ let lineEndIndex = 0;
+ let lineStartIndex = 0;
+ let lines = 0;
+ let indentCount = 0;
+ let overCharLimit = false;
+
+ // Strip comments.
+ text = text.replace(/\/\*[\S\s]*?\*\/|\/\/(.+|\n)/g, "");
+
+ while (lines++ < SAMPLE_SIZE) {
+ lineEndIndex = text.indexOf("\n", lineStartIndex);
+ if (lineEndIndex == -1) {
+ break;
+ }
+ if (/^\s+/.test(text.slice(lineStartIndex, lineEndIndex))) {
+ indentCount++;
+ }
+ // For files with no indents but are not minified.
+ if ((lineEndIndex - lineStartIndex) > CHARACTER_LIMIT) {
+ overCharLimit = true;
+ break;
+ }
+ lineStartIndex = lineEndIndex + 1;
+ }
+
+ isMinified =
+ ((indentCount / lines) * 100) < INDENT_COUNT_THRESHOLD || overCharLimit;
+
+ this._minifiedCache.set(key, isMinified);
+ return isMinified;
+ },
+
+ /**
+ * Clears the labels, groups and minify cache, populated by methods like
+ * SourceUtils.getSourceLabel or Source Utils.getSourceGroup.
+ * This should be done every time the content location changes.
+ */
+ clearCache: function () {
+ this._labelsCache.clear();
+ this._groupsCache.clear();
+ this._minifiedCache.clear();
+ },
+
+ /**
+ * Gets a unique, simplified label from a source url.
+ *
+ * @param string aUrl
+ * The source url.
+ * @return string
+ * The simplified label.
+ */
+ getSourceLabel: function (aUrl) {
+ let cachedLabel = this._labelsCache.get(aUrl);
+ if (cachedLabel) {
+ return cachedLabel;
+ }
+
+ let sourceLabel = null;
+
+ for (let name of Object.keys(KNOWN_SOURCE_GROUPS)) {
+ if (aUrl.startsWith(KNOWN_SOURCE_GROUPS[name])) {
+ sourceLabel = aUrl.substring(KNOWN_SOURCE_GROUPS[name].length);
+ }
+ }
+
+ if (!sourceLabel) {
+ sourceLabel = this.trimUrl(aUrl);
+ }
+
+ let unicodeLabel = NetworkHelper.convertToUnicode(unescape(sourceLabel));
+ this._labelsCache.set(aUrl, unicodeLabel);
+ return unicodeLabel;
+ },
+
+ /**
+ * Gets as much information as possible about the hostname and directory paths
+ * of an url to create a short url group identifier.
+ *
+ * @param string aUrl
+ * The source url.
+ * @return string
+ * The simplified group.
+ */
+ getSourceGroup: function (aUrl) {
+ let cachedGroup = this._groupsCache.get(aUrl);
+ if (cachedGroup) {
+ return cachedGroup;
+ }
+
+ try {
+ // Use an nsIURL to parse all the url path parts.
+ var uri = Services.io.newURI(aUrl, null, null).QueryInterface(Ci.nsIURL);
+ } catch (e) {
+ // This doesn't look like a url, or nsIURL can't handle it.
+ return "";
+ }
+
+ let groupLabel = uri.prePath;
+
+ for (let name of Object.keys(KNOWN_SOURCE_GROUPS)) {
+ if (aUrl.startsWith(KNOWN_SOURCE_GROUPS[name])) {
+ groupLabel = name;
+ }
+ }
+
+ let unicodeLabel = NetworkHelper.convertToUnicode(unescape(groupLabel));
+ this._groupsCache.set(aUrl, unicodeLabel);
+ return unicodeLabel;
+ },
+
+ /**
+ * Trims the url by shortening it if it exceeds a certain length, adding an
+ * ellipsis at the end.
+ *
+ * @param string aUrl
+ * The source url.
+ * @param number aLength [optional]
+ * The expected source url length.
+ * @param number aSection [optional]
+ * The section to trim. Supported values: "start", "center", "end"
+ * @return string
+ * The shortened url.
+ */
+ trimUrlLength: function (aUrl, aLength, aSection) {
+ aLength = aLength || SOURCE_URL_DEFAULT_MAX_LENGTH;
+ aSection = aSection || "end";
+
+ if (aUrl.length > aLength) {
+ switch (aSection) {
+ case "start":
+ return ELLIPSIS + aUrl.slice(-aLength);
+ break;
+ case "center":
+ return aUrl.substr(0, aLength / 2 - 1) + ELLIPSIS + aUrl.slice(-aLength / 2 + 1);
+ break;
+ case "end":
+ return aUrl.substr(0, aLength) + ELLIPSIS;
+ break;
+ }
+ }
+ return aUrl;
+ },
+
+ /**
+ * Trims the query part or reference identifier of a url string, if necessary.
+ *
+ * @param string aUrl
+ * The source url.
+ * @return string
+ * The shortened url.
+ */
+ trimUrlQuery: function (aUrl) {
+ let length = aUrl.length;
+ let q1 = aUrl.indexOf("?");
+ let q2 = aUrl.indexOf("&");
+ let q3 = aUrl.indexOf("#");
+ let q = Math.min(q1 != -1 ? q1 : length,
+ q2 != -1 ? q2 : length,
+ q3 != -1 ? q3 : length);
+
+ return aUrl.slice(0, q);
+ },
+
+ /**
+ * Trims as much as possible from a url, while keeping the label unique
+ * in the sources container.
+ *
+ * @param string | nsIURL aUrl
+ * The source url.
+ * @param string aLabel [optional]
+ * The resulting label at each step.
+ * @param number aSeq [optional]
+ * The current iteration step.
+ * @return string
+ * The resulting label at the final step.
+ */
+ trimUrl: function (aUrl, aLabel, aSeq) {
+ if (!(aUrl instanceof Ci.nsIURL)) {
+ try {
+ // Use an nsIURL to parse all the url path parts.
+ aUrl = Services.io.newURI(aUrl, null, null).QueryInterface(Ci.nsIURL);
+ } catch (e) {
+ // This doesn't look like a url, or nsIURL can't handle it.
+ return aUrl;
+ }
+ }
+ if (!aSeq) {
+ let name = aUrl.fileName;
+ if (name) {
+ // This is a regular file url, get only the file name (contains the
+ // base name and extension if available).
+
+ // If this url contains an invalid query, unfortunately nsIURL thinks
+ // it's part of the file extension. It must be removed.
+ aLabel = aUrl.fileName.replace(/\&.*/, "");
+ } else {
+ // This is not a file url, hence there is no base name, nor extension.
+ // Proceed using other available information.
+ aLabel = "";
+ }
+ aSeq = 1;
+ }
+
+ // If we have a label and it doesn't only contain a query...
+ if (aLabel && aLabel.indexOf("?") != 0) {
+ // A page may contain multiple requests to the same url but with different
+ // queries. It is *not* redundant to show each one.
+ if (!DebuggerView.Sources.getItemForAttachment(e => e.label == aLabel)) {
+ return aLabel;
+ }
+ }
+
+ // Append the url query.
+ if (aSeq == 1) {
+ let query = aUrl.query;
+ if (query) {
+ return this.trimUrl(aUrl, aLabel + "?" + query, aSeq + 1);
+ }
+ aSeq++;
+ }
+ // Append the url reference.
+ if (aSeq == 2) {
+ let ref = aUrl.ref;
+ if (ref) {
+ return this.trimUrl(aUrl, aLabel + "#" + aUrl.ref, aSeq + 1);
+ }
+ aSeq++;
+ }
+ // Prepend the url directory.
+ if (aSeq == 3) {
+ let dir = aUrl.directory;
+ if (dir) {
+ return this.trimUrl(aUrl, dir.replace(/^\//, "") + aLabel, aSeq + 1);
+ }
+ aSeq++;
+ }
+ // Prepend the hostname and port number.
+ if (aSeq == 4) {
+ let host;
+ try {
+ // Bug 1261860: jar: URLs throw when accessing `hostPost`
+ host = aUrl.hostPort;
+ } catch (e) {}
+ if (host) {
+ return this.trimUrl(aUrl, host + "/" + aLabel, aSeq + 1);
+ }
+ aSeq++;
+ }
+ // Use the whole url spec but ignoring the reference.
+ if (aSeq == 5) {
+ return this.trimUrl(aUrl, aUrl.specIgnoringRef, aSeq + 1);
+ }
+ // Give up.
+ return aUrl.spec;
+ },
+
+ parseSource: function (aDebuggerView, aParser) {
+ let editor = aDebuggerView.editor;
+
+ let contents = editor.getText();
+ let location = aDebuggerView.Sources.selectedValue;
+ let parsedSource = aParser.get(contents, location);
+
+ return parsedSource;
+ },
+
+ findIdentifier: function (aEditor, parsedSource, x, y) {
+ let editor = aEditor;
+
+ // Calculate the editor's line and column at the current x and y coords.
+ let hoveredPos = editor.getPositionFromCoords({ left: x, top: y });
+ let hoveredOffset = editor.getOffset(hoveredPos);
+ let hoveredLine = hoveredPos.line;
+ let hoveredColumn = hoveredPos.ch;
+
+ let scriptInfo = parsedSource.getScriptInfo(hoveredOffset);
+
+ // If the script length is negative, we're not hovering JS source code.
+ if (scriptInfo.length == -1) {
+ return;
+ }
+
+ // Using the script offset, determine the actual line and column inside the
+ // script, to use when finding identifiers.
+ let scriptStart = editor.getPosition(scriptInfo.start);
+ let scriptLineOffset = scriptStart.line;
+ let scriptColumnOffset = (hoveredLine == scriptStart.line ? scriptStart.ch : 0);
+
+ let scriptLine = hoveredLine - scriptLineOffset;
+ let scriptColumn = hoveredColumn - scriptColumnOffset;
+ let identifierInfo = parsedSource.getIdentifierAt({
+ line: scriptLine + 1,
+ column: scriptColumn,
+ scriptIndex: scriptInfo.index
+ });
+
+ return identifierInfo;
+ }
+};
diff --git a/devtools/client/debugger/views/filter-view.js b/devtools/client/debugger/views/filter-view.js
new file mode 100644
index 000000000..460b1201c
--- /dev/null
+++ b/devtools/client/debugger/views/filter-view.js
@@ -0,0 +1,925 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* 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/. */
+/* import-globals-from ../debugger-controller.js */
+/* import-globals-from ../debugger-view.js */
+/* import-globals-from ../utils.js */
+/* globals document, window */
+"use strict";
+
+/**
+ * Functions handling the filtering UI.
+ */
+function FilterView(DebuggerController, DebuggerView) {
+ dumpn("FilterView was instantiated");
+
+ this.Parser = DebuggerController.Parser;
+
+ this.DebuggerView = DebuggerView;
+ this.FilteredSources = new FilteredSourcesView(DebuggerView);
+ this.FilteredFunctions = new FilteredFunctionsView(DebuggerController.SourceScripts,
+ DebuggerController.Parser,
+ DebuggerView);
+
+ this._onClick = this._onClick.bind(this);
+ this._onInput = this._onInput.bind(this);
+ this._onKeyPress = this._onKeyPress.bind(this);
+ this._onBlur = this._onBlur.bind(this);
+}
+
+FilterView.prototype = {
+ /**
+ * Initialization function, called when the debugger is started.
+ */
+ initialize: function () {
+ dumpn("Initializing the FilterView");
+
+ this._searchbox = document.getElementById("searchbox");
+ this._searchboxHelpPanel = document.getElementById("searchbox-help-panel");
+ this._filterLabel = document.getElementById("filter-label");
+ this._globalOperatorButton = document.getElementById("global-operator-button");
+ this._globalOperatorLabel = document.getElementById("global-operator-label");
+ this._functionOperatorButton = document.getElementById("function-operator-button");
+ this._functionOperatorLabel = document.getElementById("function-operator-label");
+ this._tokenOperatorButton = document.getElementById("token-operator-button");
+ this._tokenOperatorLabel = document.getElementById("token-operator-label");
+ this._lineOperatorButton = document.getElementById("line-operator-button");
+ this._lineOperatorLabel = document.getElementById("line-operator-label");
+ this._variableOperatorButton = document.getElementById("variable-operator-button");
+ this._variableOperatorLabel = document.getElementById("variable-operator-label");
+
+ this._fileSearchKey = ShortcutUtils.prettifyShortcut(document.getElementById("fileSearchKey"));
+ this._globalSearchKey = ShortcutUtils.prettifyShortcut(document.getElementById("globalSearchKey"));
+ this._filteredFunctionsKey = ShortcutUtils.prettifyShortcut(document.getElementById("functionSearchKey"));
+ this._tokenSearchKey = ShortcutUtils.prettifyShortcut(document.getElementById("tokenSearchKey"));
+ this._lineSearchKey = ShortcutUtils.prettifyShortcut(document.getElementById("lineSearchKey"));
+ this._variableSearchKey = ShortcutUtils.prettifyShortcut(document.getElementById("variableSearchKey"));
+
+ this._searchbox.addEventListener("click", this._onClick, false);
+ this._searchbox.addEventListener("select", this._onInput, false);
+ this._searchbox.addEventListener("input", this._onInput, false);
+ this._searchbox.addEventListener("keypress", this._onKeyPress, false);
+ this._searchbox.addEventListener("blur", this._onBlur, false);
+
+ let placeholder = L10N.getFormatStr("emptySearchText", this._fileSearchKey);
+ this._searchbox.setAttribute("placeholder", placeholder);
+
+ this._globalOperatorButton.setAttribute("label", SEARCH_GLOBAL_FLAG);
+ this._functionOperatorButton.setAttribute("label", SEARCH_FUNCTION_FLAG);
+ this._tokenOperatorButton.setAttribute("label", SEARCH_TOKEN_FLAG);
+ this._lineOperatorButton.setAttribute("label", SEARCH_LINE_FLAG);
+ this._variableOperatorButton.setAttribute("label", SEARCH_VARIABLE_FLAG);
+
+ this._filterLabel.setAttribute("value",
+ L10N.getFormatStr("searchPanelFilter", this._fileSearchKey));
+ this._globalOperatorLabel.setAttribute("value",
+ L10N.getFormatStr("searchPanelGlobal", this._globalSearchKey));
+ this._functionOperatorLabel.setAttribute("value",
+ L10N.getFormatStr("searchPanelFunction", this._filteredFunctionsKey));
+ this._tokenOperatorLabel.setAttribute("value",
+ L10N.getFormatStr("searchPanelToken", this._tokenSearchKey));
+ this._lineOperatorLabel.setAttribute("value",
+ L10N.getFormatStr("searchPanelGoToLine", this._lineSearchKey));
+ this._variableOperatorLabel.setAttribute("value",
+ L10N.getFormatStr("searchPanelVariable", this._variableSearchKey));
+
+ this.FilteredSources.initialize();
+ this.FilteredFunctions.initialize();
+
+ this._addCommands();
+ },
+
+ /**
+ * Destruction function, called when the debugger is closed.
+ */
+ destroy: function () {
+ dumpn("Destroying the FilterView");
+
+ this._searchbox.removeEventListener("click", this._onClick, false);
+ this._searchbox.removeEventListener("select", this._onInput, false);
+ this._searchbox.removeEventListener("input", this._onInput, false);
+ this._searchbox.removeEventListener("keypress", this._onKeyPress, false);
+ this._searchbox.removeEventListener("blur", this._onBlur, false);
+
+ this.FilteredSources.destroy();
+ this.FilteredFunctions.destroy();
+ },
+
+ /**
+ * Add commands that XUL can fire.
+ */
+ _addCommands: function () {
+ XULUtils.addCommands(document.getElementById("debuggerCommands"), {
+ fileSearchCommand: () => this._doFileSearch(),
+ globalSearchCommand: () => this._doGlobalSearch(),
+ functionSearchCommand: () => this._doFunctionSearch(),
+ tokenSearchCommand: () => this._doTokenSearch(),
+ lineSearchCommand: () => this._doLineSearch(),
+ variableSearchCommand: () => this._doVariableSearch(),
+ variablesFocusCommand: () => this._doVariablesFocus()
+ });
+ },
+
+ /**
+ * Gets the entered operator and arguments in the searchbox.
+ * @return array
+ */
+ get searchData() {
+ let operator = "", args = [];
+
+ let rawValue = this._searchbox.value;
+ let rawLength = rawValue.length;
+ let globalFlagIndex = rawValue.indexOf(SEARCH_GLOBAL_FLAG);
+ let functionFlagIndex = rawValue.indexOf(SEARCH_FUNCTION_FLAG);
+ let variableFlagIndex = rawValue.indexOf(SEARCH_VARIABLE_FLAG);
+ let tokenFlagIndex = rawValue.lastIndexOf(SEARCH_TOKEN_FLAG);
+ let lineFlagIndex = rawValue.lastIndexOf(SEARCH_LINE_FLAG);
+
+ // This is not a global, function or variable search, allow file/line flags.
+ if (globalFlagIndex != 0 && functionFlagIndex != 0 && variableFlagIndex != 0) {
+ // Token search has precedence over line search.
+ if (tokenFlagIndex != -1) {
+ operator = SEARCH_TOKEN_FLAG;
+ args.push(rawValue.slice(0, tokenFlagIndex)); // file
+ args.push(rawValue.substr(tokenFlagIndex + 1, rawLength)); // token
+ } else if (lineFlagIndex != -1) {
+ operator = SEARCH_LINE_FLAG;
+ args.push(rawValue.slice(0, lineFlagIndex)); // file
+ args.push(+rawValue.substr(lineFlagIndex + 1, rawLength) || 0); // line
+ } else {
+ args.push(rawValue);
+ }
+ }
+ // Global searches dissalow the use of file or line flags.
+ else if (globalFlagIndex == 0) {
+ operator = SEARCH_GLOBAL_FLAG;
+ args.push(rawValue.slice(1));
+ }
+ // Function searches dissalow the use of file or line flags.
+ else if (functionFlagIndex == 0) {
+ operator = SEARCH_FUNCTION_FLAG;
+ args.push(rawValue.slice(1));
+ }
+ // Variable searches dissalow the use of file or line flags.
+ else if (variableFlagIndex == 0) {
+ operator = SEARCH_VARIABLE_FLAG;
+ args.push(rawValue.slice(1));
+ }
+
+ return [operator, args];
+ },
+
+ /**
+ * Returns the current search operator.
+ * @return string
+ */
+ get searchOperator() {
+ return this.searchData[0];
+ },
+
+ /**
+ * Returns the current search arguments.
+ * @return array
+ */
+ get searchArguments() {
+ return this.searchData[1];
+ },
+
+ /**
+ * Clears the text from the searchbox and any changed views.
+ */
+ clearSearch: function () {
+ this._searchbox.value = "";
+ this.clearViews();
+
+ this.FilteredSources.clearView();
+ this.FilteredFunctions.clearView();
+ },
+
+ /**
+ * Clears all the views that may pop up when searching.
+ */
+ clearViews: function () {
+ this.DebuggerView.GlobalSearch.clearView();
+ this.FilteredSources.clearView();
+ this.FilteredFunctions.clearView();
+ this._searchboxHelpPanel.hidePopup();
+ },
+
+ /**
+ * Performs a line search if necessary.
+ * (Jump to lines in the currently visible source).
+ *
+ * @param number aLine
+ * The source line number to jump to.
+ */
+ _performLineSearch: function (aLine) {
+ // Make sure we're actually searching for a valid line.
+ if (aLine) {
+ this.DebuggerView.editor.setCursor({ line: aLine - 1, ch: 0 }, "center");
+ }
+ },
+
+ /**
+ * Performs a token search if necessary.
+ * (Search for tokens in the currently visible source).
+ *
+ * @param string aToken
+ * The source token to find.
+ */
+ _performTokenSearch: function (aToken) {
+ // Make sure we're actually searching for a valid token.
+ if (!aToken) {
+ return;
+ }
+ this.DebuggerView.editor.find(aToken);
+ },
+
+ /**
+ * The click listener for the search container.
+ */
+ _onClick: function () {
+ // If there's some text in the searchbox, displaying a panel would
+ // interfere with double/triple click default behaviors.
+ if (!this._searchbox.value) {
+ this._searchboxHelpPanel.openPopup(this._searchbox);
+ }
+ },
+
+ /**
+ * The input listener for the search container.
+ */
+ _onInput: function () {
+ this.clearViews();
+
+ // Make sure we're actually searching for something.
+ if (!this._searchbox.value) {
+ return;
+ }
+
+ // Perform the required search based on the specified operator.
+ switch (this.searchOperator) {
+ case SEARCH_GLOBAL_FLAG:
+ // Schedule a global search for when the user stops typing.
+ this.DebuggerView.GlobalSearch.scheduleSearch(this.searchArguments[0]);
+ break;
+ case SEARCH_FUNCTION_FLAG:
+ // Schedule a function search for when the user stops typing.
+ this.FilteredFunctions.scheduleSearch(this.searchArguments[0]);
+ break;
+ case SEARCH_VARIABLE_FLAG:
+ // Schedule a variable search for when the user stops typing.
+ this.DebuggerView.Variables.scheduleSearch(this.searchArguments[0]);
+ break;
+ case SEARCH_TOKEN_FLAG:
+ // Schedule a file+token search for when the user stops typing.
+ this.FilteredSources.scheduleSearch(this.searchArguments[0]);
+ this._performTokenSearch(this.searchArguments[1]);
+ break;
+ case SEARCH_LINE_FLAG:
+ // Schedule a file+line search for when the user stops typing.
+ this.FilteredSources.scheduleSearch(this.searchArguments[0]);
+ this._performLineSearch(this.searchArguments[1]);
+ break;
+ default:
+ // Schedule a file only search for when the user stops typing.
+ this.FilteredSources.scheduleSearch(this.searchArguments[0]);
+ break;
+ }
+ },
+
+ /**
+ * The key press listener for the search container.
+ */
+ _onKeyPress: function (e) {
+ // This attribute is not implemented in Gecko at this time, see bug 680830.
+ e.char = String.fromCharCode(e.charCode);
+
+ // Perform the required action based on the specified operator.
+ let [operator, args] = this.searchData;
+ let isGlobalSearch = operator == SEARCH_GLOBAL_FLAG;
+ let isFunctionSearch = operator == SEARCH_FUNCTION_FLAG;
+ let isVariableSearch = operator == SEARCH_VARIABLE_FLAG;
+ let isTokenSearch = operator == SEARCH_TOKEN_FLAG;
+ let isLineSearch = operator == SEARCH_LINE_FLAG;
+ let isFileOnlySearch = !operator && args.length == 1;
+
+ // Depending on the pressed keys, determine to correct action to perform.
+ let actionToPerform;
+
+ // Meta+G and Ctrl+N focus next matches.
+ if ((e.char == "g" && e.metaKey) || e.char == "n" && e.ctrlKey) {
+ actionToPerform = "selectNext";
+ }
+ // Meta+Shift+G and Ctrl+P focus previous matches.
+ else if ((e.char == "G" && e.metaKey) || e.char == "p" && e.ctrlKey) {
+ actionToPerform = "selectPrev";
+ }
+ // Return, enter, down and up keys focus next or previous matches, while
+ // the escape key switches focus from the search container.
+ else switch (e.keyCode) {
+ case KeyCodes.DOM_VK_RETURN:
+ var isReturnKey = true;
+ // If the shift key is pressed, focus on the previous result
+ actionToPerform = e.shiftKey ? "selectPrev" : "selectNext";
+ break;
+ case KeyCodes.DOM_VK_DOWN:
+ actionToPerform = "selectNext";
+ break;
+ case KeyCodes.DOM_VK_UP:
+ actionToPerform = "selectPrev";
+ break;
+ }
+
+ // If there's no action to perform, or no operator, file line or token
+ // were specified, then this is either a broken or empty search.
+ if (!actionToPerform || (!operator && !args.length)) {
+ this.DebuggerView.editor.dropSelection();
+ return;
+ }
+
+ e.preventDefault();
+ e.stopPropagation();
+
+ // Jump to the next/previous entry in the global search, or perform
+ // a new global search immediately
+ if (isGlobalSearch) {
+ let targetView = this.DebuggerView.GlobalSearch;
+ if (!isReturnKey) {
+ targetView[actionToPerform]();
+ } else if (targetView.hidden) {
+ targetView.scheduleSearch(args[0], 0);
+ }
+ return;
+ }
+
+ // Jump to the next/previous entry in the function search, perform
+ // a new function search immediately, or clear it.
+ if (isFunctionSearch) {
+ let targetView = this.FilteredFunctions;
+ if (!isReturnKey) {
+ targetView[actionToPerform]();
+ } else if (targetView.hidden) {
+ targetView.scheduleSearch(args[0], 0);
+ } else {
+ if (!targetView.selectedItem) {
+ targetView.selectedIndex = 0;
+ }
+ this.clearSearch();
+ }
+ return;
+ }
+
+ // Perform a new variable search immediately.
+ if (isVariableSearch) {
+ let targetView = this.DebuggerView.Variables;
+ if (isReturnKey) {
+ targetView.scheduleSearch(args[0], 0);
+ }
+ return;
+ }
+
+ // Jump to the next/previous entry in the file search, perform
+ // a new file search immediately, or clear it.
+ if (isFileOnlySearch) {
+ let targetView = this.FilteredSources;
+ if (!isReturnKey) {
+ targetView[actionToPerform]();
+ } else if (targetView.hidden) {
+ targetView.scheduleSearch(args[0], 0);
+ } else {
+ if (!targetView.selectedItem) {
+ targetView.selectedIndex = 0;
+ }
+ this.clearSearch();
+ }
+ return;
+ }
+
+ // Jump to the next/previous instance of the currently searched token.
+ if (isTokenSearch) {
+ let methods = { selectNext: "findNext", selectPrev: "findPrev" };
+ this.DebuggerView.editor[methods[actionToPerform]]();
+ return;
+ }
+
+ // Increment/decrement the currently searched caret line.
+ if (isLineSearch) {
+ let [, line] = args;
+ let amounts = { selectNext: 1, selectPrev: -1 };
+
+ // Modify the line number and jump to it.
+ line += !isReturnKey ? amounts[actionToPerform] : 0;
+ let lineCount = this.DebuggerView.editor.lineCount();
+ let lineTarget = line < 1 ? 1 : line > lineCount ? lineCount : line;
+ this._doSearch(SEARCH_LINE_FLAG, lineTarget);
+ return;
+ }
+ },
+
+ /**
+ * The blur listener for the search container.
+ */
+ _onBlur: function () {
+ this.clearViews();
+ },
+
+ /**
+ * Called when a filtering key sequence was pressed.
+ *
+ * @param string aOperator
+ * The operator to use for filtering.
+ */
+ _doSearch: function (aOperator = "", aText = "") {
+ this._searchbox.focus();
+ this._searchbox.value = ""; // Need to clear value beforehand. Bug 779738.
+
+ if (aText) {
+ this._searchbox.value = aOperator + aText;
+ return;
+ }
+ if (this.DebuggerView.editor.somethingSelected()) {
+ this._searchbox.value = aOperator + this.DebuggerView.editor.getSelection();
+ return;
+ }
+
+ let content = this.DebuggerView.editor.getText();
+ if (content.length < this.DebuggerView.LARGE_FILE_SIZE &&
+ SEARCH_AUTOFILL.indexOf(aOperator) != -1) {
+ let cursor = this.DebuggerView.editor.getCursor();
+ let location = this.DebuggerView.Sources.selectedItem.attachment.source.url;
+ let source = this.Parser.get(content, location);
+ let identifier = source.getIdentifierAt({ line: cursor.line + 1, column: cursor.ch });
+
+ if (identifier && identifier.name) {
+ this._searchbox.value = aOperator + identifier.name;
+ this._searchbox.select();
+ this._searchbox.selectionStart += aOperator.length;
+ return;
+ }
+ }
+ this._searchbox.value = aOperator;
+ },
+
+ /**
+ * Called when the source location filter key sequence was pressed.
+ */
+ _doFileSearch: function () {
+ this._doSearch();
+ this._searchboxHelpPanel.openPopup(this._searchbox);
+ },
+
+ /**
+ * Called when the global search filter key sequence was pressed.
+ */
+ _doGlobalSearch: function () {
+ this._doSearch(SEARCH_GLOBAL_FLAG);
+ this._searchboxHelpPanel.hidePopup();
+ },
+
+ /**
+ * Called when the source function filter key sequence was pressed.
+ */
+ _doFunctionSearch: function () {
+ this._doSearch(SEARCH_FUNCTION_FLAG);
+ this._searchboxHelpPanel.hidePopup();
+ },
+
+ /**
+ * Called when the source token filter key sequence was pressed.
+ */
+ _doTokenSearch: function () {
+ this._doSearch(SEARCH_TOKEN_FLAG);
+ this._searchboxHelpPanel.hidePopup();
+ },
+
+ /**
+ * Called when the source line filter key sequence was pressed.
+ */
+ _doLineSearch: function () {
+ this._doSearch(SEARCH_LINE_FLAG);
+ this._searchboxHelpPanel.hidePopup();
+ },
+
+ /**
+ * Called when the variable search filter key sequence was pressed.
+ */
+ _doVariableSearch: function () {
+ this._doSearch(SEARCH_VARIABLE_FLAG);
+ this._searchboxHelpPanel.hidePopup();
+ },
+
+ /**
+ * Called when the variables focus key sequence was pressed.
+ */
+ _doVariablesFocus: function () {
+ this.DebuggerView.showInstrumentsPane();
+ this.DebuggerView.Variables.focusFirstVisibleItem();
+ },
+
+ _searchbox: null,
+ _searchboxHelpPanel: null,
+ _globalOperatorButton: null,
+ _globalOperatorLabel: null,
+ _functionOperatorButton: null,
+ _functionOperatorLabel: null,
+ _tokenOperatorButton: null,
+ _tokenOperatorLabel: null,
+ _lineOperatorButton: null,
+ _lineOperatorLabel: null,
+ _variableOperatorButton: null,
+ _variableOperatorLabel: null,
+ _fileSearchKey: "",
+ _globalSearchKey: "",
+ _filteredFunctionsKey: "",
+ _tokenSearchKey: "",
+ _lineSearchKey: "",
+ _variableSearchKey: "",
+};
+
+/**
+ * Functions handling the filtered sources UI.
+ */
+function FilteredSourcesView(DebuggerView) {
+ dumpn("FilteredSourcesView was instantiated");
+
+ this.DebuggerView = DebuggerView;
+
+ this._onClick = this._onClick.bind(this);
+ this._onSelect = this._onSelect.bind(this);
+}
+
+FilteredSourcesView.prototype = Heritage.extend(ResultsPanelContainer.prototype, {
+ /**
+ * Initialization function, called when the debugger is started.
+ */
+ initialize: function () {
+ dumpn("Initializing the FilteredSourcesView");
+
+ this.anchor = document.getElementById("searchbox");
+ this.widget.addEventListener("select", this._onSelect, false);
+ this.widget.addEventListener("click", this._onClick, false);
+ },
+
+ /**
+ * Destruction function, called when the debugger is closed.
+ */
+ destroy: function () {
+ dumpn("Destroying the FilteredSourcesView");
+
+ this.widget.removeEventListener("select", this._onSelect, false);
+ this.widget.removeEventListener("click", this._onClick, false);
+ this.anchor = null;
+ },
+
+ /**
+ * Schedules searching for a source.
+ *
+ * @param string aToken
+ * The function to search for.
+ * @param number aWait
+ * The amount of milliseconds to wait until draining.
+ */
+ scheduleSearch: function (aToken, aWait) {
+ // The amount of time to wait for the requests to settle.
+ let maxDelay = FILE_SEARCH_ACTION_MAX_DELAY;
+ let delay = aWait === undefined ? maxDelay / aToken.length : aWait;
+
+ // Allow requests to settle down first.
+ setNamedTimeout("sources-search", delay, () => this._doSearch(aToken));
+ },
+
+ /**
+ * Finds file matches in all the displayed sources.
+ *
+ * @param string aToken
+ * The string to search for.
+ */
+ _doSearch: function (aToken, aStore = []) {
+ // Don't continue filtering if the searched token is an empty string.
+ // In contrast with function searching, in this case we don't want to
+ // show a list of all the files when no search token was supplied.
+ if (!aToken) {
+ return;
+ }
+
+ for (let item of this.DebuggerView.Sources.items) {
+ let lowerCaseLabel = item.attachment.label.toLowerCase();
+ let lowerCaseToken = aToken.toLowerCase();
+ if (lowerCaseLabel.match(lowerCaseToken)) {
+ aStore.push(item);
+ }
+
+ // Once the maximum allowed number of results is reached, proceed
+ // with building the UI immediately.
+ if (aStore.length >= RESULTS_PANEL_MAX_RESULTS) {
+ this._syncView(aStore);
+ return;
+ }
+ }
+
+ // Couldn't reach the maximum allowed number of results, but that's ok,
+ // continue building the UI.
+ this._syncView(aStore);
+ },
+
+ /**
+ * Updates the list of sources displayed in this container.
+ *
+ * @param array aSearchResults
+ * The results array, containing search details for each source.
+ */
+ _syncView: function (aSearchResults) {
+ // If there are no matches found, keep the popup hidden and avoid
+ // creating the view.
+ if (!aSearchResults.length) {
+ window.emit(EVENTS.FILE_SEARCH_MATCH_NOT_FOUND);
+ return;
+ }
+
+ for (let item of aSearchResults) {
+ let url = item.attachment.source.url;
+
+ if (url) {
+ // Create the element node for the location item.
+ let itemView = this._createItemView(
+ SourceUtils.trimUrlLength(item.attachment.label),
+ SourceUtils.trimUrlLength(url, 0, "start")
+ );
+
+ // Append a location item to this container for each match.
+ this.push([itemView], {
+ index: -1, /* specifies on which position should the item be appended */
+ attachment: {
+ url: url
+ }
+ });
+ }
+ }
+
+ // There's at least one item displayed in this container. Don't select it
+ // automatically if not forced (by tests) or in tandem with an
+ // operator.
+ if (this._autoSelectFirstItem || this.DebuggerView.Filtering.searchOperator) {
+ this.selectedIndex = 0;
+ }
+ this.hidden = false;
+
+ // Signal that file search matches were found and displayed.
+ window.emit(EVENTS.FILE_SEARCH_MATCH_FOUND);
+ },
+
+ /**
+ * The click listener for this container.
+ */
+ _onClick: function (e) {
+ let locationItem = this.getItemForElement(e.target);
+ if (locationItem) {
+ this.selectedItem = locationItem;
+ this.DebuggerView.Filtering.clearSearch();
+ }
+ },
+
+ /**
+ * The select listener for this container.
+ *
+ * @param object aItem
+ * The item associated with the element to select.
+ */
+ _onSelect: function ({ detail: locationItem }) {
+ if (locationItem) {
+ let source = queries.getSourceByURL(DebuggerController.getState(),
+ locationItem.attachment.url);
+ this.DebuggerView.setEditorLocation(source.actor, undefined, {
+ noCaret: true,
+ noDebug: true
+ });
+ }
+ }
+});
+
+/**
+ * Functions handling the function search UI.
+ */
+function FilteredFunctionsView(SourceScripts, Parser, DebuggerView) {
+ dumpn("FilteredFunctionsView was instantiated");
+
+ this.SourceScripts = SourceScripts;
+ this.Parser = Parser;
+ this.DebuggerView = DebuggerView;
+
+ this._onClick = this._onClick.bind(this);
+ this._onSelect = this._onSelect.bind(this);
+}
+
+FilteredFunctionsView.prototype = Heritage.extend(ResultsPanelContainer.prototype, {
+ /**
+ * Initialization function, called when the debugger is started.
+ */
+ initialize: function () {
+ dumpn("Initializing the FilteredFunctionsView");
+
+ this.anchor = document.getElementById("searchbox");
+ this.widget.addEventListener("select", this._onSelect, false);
+ this.widget.addEventListener("click", this._onClick, false);
+ },
+
+ /**
+ * Destruction function, called when the debugger is closed.
+ */
+ destroy: function () {
+ dumpn("Destroying the FilteredFunctionsView");
+
+ this.widget.removeEventListener("select", this._onSelect, false);
+ this.widget.removeEventListener("click", this._onClick, false);
+ this.anchor = null;
+ },
+
+ /**
+ * Schedules searching for a function in all of the sources.
+ *
+ * @param string aToken
+ * The function to search for.
+ * @param number aWait
+ * The amount of milliseconds to wait until draining.
+ */
+ scheduleSearch: function (aToken, aWait) {
+ // The amount of time to wait for the requests to settle.
+ let maxDelay = FUNCTION_SEARCH_ACTION_MAX_DELAY;
+ let delay = aWait === undefined ? maxDelay / aToken.length : aWait;
+
+ // Allow requests to settle down first.
+ setNamedTimeout("function-search", delay, () => {
+ // Start fetching as many sources as possible, then perform the search.
+ let actors = this.DebuggerView.Sources.values;
+ let sourcesFetched = DebuggerController.dispatch(actions.getTextForSources(actors));
+ sourcesFetched.then(aSources => this._doSearch(aToken, aSources));
+ });
+ },
+
+ /**
+ * Finds function matches in all the sources stored in the cache, and groups
+ * them by location and line number.
+ *
+ * @param string aToken
+ * The string to search for.
+ * @param array aSources
+ * An array of [url, text] tuples for each source.
+ */
+ _doSearch: function (aToken, aSources, aStore = []) {
+ // Continue parsing even if the searched token is an empty string, to
+ // cache the syntax tree nodes generated by the reflection API.
+
+ // Make sure the currently displayed source is parsed first. Once the
+ // maximum allowed number of results are found, parsing will be halted.
+ let currentActor = this.DebuggerView.Sources.selectedValue;
+ let currentSource = aSources.filter(([actor]) => actor == currentActor)[0];
+ aSources.splice(aSources.indexOf(currentSource), 1);
+ aSources.unshift(currentSource);
+
+ // If not searching for a specific function, only parse the displayed source,
+ // which is now the first item in the sources array.
+ if (!aToken) {
+ aSources.splice(1);
+ }
+
+ for (let [actor, contents] of aSources) {
+ let item = this.DebuggerView.Sources.getItemByValue(actor);
+ let url = item.attachment.source.url;
+ if (!url) {
+ continue;
+ }
+
+ let parsedSource = this.Parser.get(contents, url);
+ let sourceResults = parsedSource.getNamedFunctionDefinitions(aToken);
+
+ for (let scriptResult of sourceResults) {
+ for (let parseResult of scriptResult) {
+ aStore.push({
+ sourceUrl: scriptResult.sourceUrl,
+ scriptOffset: scriptResult.scriptOffset,
+ functionName: parseResult.functionName,
+ functionLocation: parseResult.functionLocation,
+ inferredName: parseResult.inferredName,
+ inferredChain: parseResult.inferredChain,
+ inferredLocation: parseResult.inferredLocation
+ });
+
+ // Once the maximum allowed number of results is reached, proceed
+ // with building the UI immediately.
+ if (aStore.length >= RESULTS_PANEL_MAX_RESULTS) {
+ this._syncView(aStore);
+ return;
+ }
+ }
+ }
+ }
+
+ // Couldn't reach the maximum allowed number of results, but that's ok,
+ // continue building the UI.
+ this._syncView(aStore);
+ },
+
+ /**
+ * Updates the list of functions displayed in this container.
+ *
+ * @param array aSearchResults
+ * The results array, containing search details for each source.
+ */
+ _syncView: function (aSearchResults) {
+ // If there are no matches found, keep the popup hidden and avoid
+ // creating the view.
+ if (!aSearchResults.length) {
+ window.emit(EVENTS.FUNCTION_SEARCH_MATCH_NOT_FOUND);
+ return;
+ }
+
+ for (let item of aSearchResults) {
+ // Some function expressions don't necessarily have a name, but the
+ // parser provides us with an inferred name from an enclosing
+ // VariableDeclarator, AssignmentExpression, ObjectExpression node.
+ if (item.functionName && item.inferredName &&
+ item.functionName != item.inferredName) {
+ let s = " " + L10N.getStr("functionSearchSeparatorLabel") + " ";
+ item.displayedName = item.inferredName + s + item.functionName;
+ }
+ // The function doesn't have an explicit name, but it could be inferred.
+ else if (item.inferredName) {
+ item.displayedName = item.inferredName;
+ }
+ // The function only has an explicit name.
+ else {
+ item.displayedName = item.functionName;
+ }
+
+ // Some function expressions have unexpected bounds, since they may not
+ // necessarily have an associated name defining them.
+ if (item.inferredLocation) {
+ item.actualLocation = item.inferredLocation;
+ } else {
+ item.actualLocation = item.functionLocation;
+ }
+
+ // Create the element node for the function item.
+ let itemView = this._createItemView(
+ SourceUtils.trimUrlLength(item.displayedName + "()"),
+ SourceUtils.trimUrlLength(item.sourceUrl, 0, "start"),
+ (item.inferredChain || []).join(".")
+ );
+
+ // Append a function item to this container for each match.
+ this.push([itemView], {
+ index: -1, /* specifies on which position should the item be appended */
+ attachment: item
+ });
+ }
+
+ // There's at least one item displayed in this container. Don't select it
+ // automatically if not forced (by tests).
+ if (this._autoSelectFirstItem) {
+ this.selectedIndex = 0;
+ }
+ this.hidden = false;
+
+ // Signal that function search matches were found and displayed.
+ window.emit(EVENTS.FUNCTION_SEARCH_MATCH_FOUND);
+ },
+
+ /**
+ * The click listener for this container.
+ */
+ _onClick: function (e) {
+ let functionItem = this.getItemForElement(e.target);
+ if (functionItem) {
+ this.selectedItem = functionItem;
+ this.DebuggerView.Filtering.clearSearch();
+ }
+ },
+
+ /**
+ * The select listener for this container.
+ */
+ _onSelect: function ({ detail: functionItem }) {
+ if (functionItem) {
+ let sourceUrl = functionItem.attachment.sourceUrl;
+ let actor = queries.getSourceByURL(DebuggerController.getState(), sourceUrl).actor;
+ let scriptOffset = functionItem.attachment.scriptOffset;
+ let actualLocation = functionItem.attachment.actualLocation;
+
+ this.DebuggerView.setEditorLocation(actor, actualLocation.start.line, {
+ charOffset: scriptOffset,
+ columnOffset: actualLocation.start.column,
+ align: "center",
+ noDebug: true
+ });
+ }
+ },
+
+ _searchTimeout: null,
+ _searchFunction: null,
+ _searchedToken: ""
+});
+
+DebuggerView.Filtering = new FilterView(DebuggerController, DebuggerView);
diff --git a/devtools/client/debugger/views/global-search-view.js b/devtools/client/debugger/views/global-search-view.js
new file mode 100644
index 000000000..c6a627971
--- /dev/null
+++ b/devtools/client/debugger/views/global-search-view.js
@@ -0,0 +1,756 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* 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/. */
+/* import-globals-from ../debugger-controller.js */
+/* import-globals-from ../debugger-view.js */
+/* import-globals-from ../utils.js */
+/* globals document, window */
+"use strict";
+
+/**
+ * Functions handling the global search UI.
+ */
+function GlobalSearchView(DebuggerController, DebuggerView) {
+ dumpn("GlobalSearchView was instantiated");
+
+ this.SourceScripts = DebuggerController.SourceScripts;
+ this.DebuggerView = DebuggerView;
+
+ this._onHeaderClick = this._onHeaderClick.bind(this);
+ this._onLineClick = this._onLineClick.bind(this);
+ this._onMatchClick = this._onMatchClick.bind(this);
+}
+
+GlobalSearchView.prototype = Heritage.extend(WidgetMethods, {
+ /**
+ * Initialization function, called when the debugger is started.
+ */
+ initialize: function () {
+ dumpn("Initializing the GlobalSearchView");
+
+ this.widget = new SimpleListWidget(document.getElementById("globalsearch"));
+ this._splitter = document.querySelector("#globalsearch + .devtools-horizontal-splitter");
+
+ this.emptyText = L10N.getStr("noMatchingStringsText");
+ },
+
+ /**
+ * Destruction function, called when the debugger is closed.
+ */
+ destroy: function () {
+ dumpn("Destroying the GlobalSearchView");
+ },
+
+ /**
+ * Sets the results container hidden or visible. It's hidden by default.
+ * @param boolean aFlag
+ */
+ set hidden(aFlag) {
+ this.widget.setAttribute("hidden", aFlag);
+ this._splitter.setAttribute("hidden", aFlag);
+ },
+
+ /**
+ * Gets the visibility state of the global search container.
+ * @return boolean
+ */
+ get hidden() {
+ return this.widget.getAttribute("hidden") == "true" ||
+ this._splitter.getAttribute("hidden") == "true";
+ },
+
+ /**
+ * Hides and removes all items from this search container.
+ */
+ clearView: function () {
+ this.hidden = true;
+ this.empty();
+ },
+
+ /**
+ * Selects the next found item in this container.
+ * Does not change the currently focused node.
+ */
+ selectNext: function () {
+ let totalLineResults = LineResults.size();
+ if (!totalLineResults) {
+ return;
+ }
+ if (++this._currentlyFocusedMatch >= totalLineResults) {
+ this._currentlyFocusedMatch = 0;
+ }
+ this._onMatchClick({
+ target: LineResults.getElementAtIndex(this._currentlyFocusedMatch)
+ });
+ },
+
+ /**
+ * Selects the previously found item in this container.
+ * Does not change the currently focused node.
+ */
+ selectPrev: function () {
+ let totalLineResults = LineResults.size();
+ if (!totalLineResults) {
+ return;
+ }
+ if (--this._currentlyFocusedMatch < 0) {
+ this._currentlyFocusedMatch = totalLineResults - 1;
+ }
+ this._onMatchClick({
+ target: LineResults.getElementAtIndex(this._currentlyFocusedMatch)
+ });
+ },
+
+ /**
+ * Schedules searching for a string in all of the sources.
+ *
+ * @param string aToken
+ * The string to search for.
+ * @param number aWait
+ * The amount of milliseconds to wait until draining.
+ */
+ scheduleSearch: function (aToken, aWait) {
+ // The amount of time to wait for the requests to settle.
+ let maxDelay = GLOBAL_SEARCH_ACTION_MAX_DELAY;
+ let delay = aWait === undefined ? maxDelay / aToken.length : aWait;
+
+ // Allow requests to settle down first.
+ setNamedTimeout("global-search", delay, () => {
+ // Start fetching as many sources as possible, then perform the search.
+ let actors = this.DebuggerView.Sources.values;
+ let sourcesFetched = DebuggerController.dispatch(actions.getTextForSources(actors));
+ sourcesFetched.then(aSources => this._doSearch(aToken, aSources));
+ });
+ },
+
+ /**
+ * Finds string matches in all the sources stored in the controller's cache,
+ * and groups them by url and line number.
+ *
+ * @param string aToken
+ * The string to search for.
+ * @param array aSources
+ * An array of [url, text] tuples for each source.
+ */
+ _doSearch: function (aToken, aSources) {
+ // Don't continue filtering if the searched token is an empty string.
+ if (!aToken) {
+ this.clearView();
+ return;
+ }
+
+ // Search is not case sensitive, prepare the actual searched token.
+ let lowerCaseToken = aToken.toLowerCase();
+ let tokenLength = aToken.length;
+
+ // Create a Map containing search details for each source.
+ let globalResults = new GlobalResults();
+
+ // Search for the specified token in each source's text.
+ for (let [actor, text] of aSources) {
+ let item = this.DebuggerView.Sources.getItemByValue(actor);
+ let url = item.attachment.source.url;
+ if (!url) {
+ continue;
+ }
+
+ // Verify that the search token is found anywhere in the source.
+ if (!text.toLowerCase().includes(lowerCaseToken)) {
+ continue;
+ }
+ // ...and if so, create a Map containing search details for each line.
+ let sourceResults = new SourceResults(actor,
+ globalResults,
+ this.DebuggerView.Sources);
+
+ // Search for the specified token in each line's text.
+ text.split("\n").forEach((aString, aLine) => {
+ // Search is not case sensitive, prepare the actual searched line.
+ let lowerCaseLine = aString.toLowerCase();
+
+ // Verify that the search token is found anywhere in this line.
+ if (!lowerCaseLine.includes(lowerCaseToken)) {
+ return;
+ }
+ // ...and if so, create a Map containing search details for each word.
+ let lineResults = new LineResults(aLine, sourceResults);
+
+ // Search for the specified token this line's text.
+ lowerCaseLine.split(lowerCaseToken).reduce((aPrev, aCurr, aIndex, aArray) => {
+ let prevLength = aPrev.length;
+ let currLength = aCurr.length;
+
+ // Everything before the token is unmatched.
+ let unmatched = aString.substr(prevLength, currLength);
+ lineResults.add(unmatched);
+
+ // The lowered-case line was split by the lowered-case token. So,
+ // get the actual matched text from the original line's text.
+ if (aIndex != aArray.length - 1) {
+ let matched = aString.substr(prevLength + currLength, tokenLength);
+ let range = { start: prevLength + currLength, length: matched.length };
+ lineResults.add(matched, range, true);
+ }
+
+ // Continue with the next sub-region in this line's text.
+ return aPrev + aToken + aCurr;
+ }, "");
+
+ if (lineResults.matchCount) {
+ sourceResults.add(lineResults);
+ }
+ });
+
+ if (sourceResults.matchCount) {
+ globalResults.add(sourceResults);
+ }
+ }
+
+ // Rebuild the results, then signal if there are any matches.
+ if (globalResults.matchCount) {
+ this.hidden = false;
+ this._currentlyFocusedMatch = -1;
+ this._createGlobalResultsUI(globalResults);
+ window.emit(EVENTS.GLOBAL_SEARCH_MATCH_FOUND);
+ } else {
+ window.emit(EVENTS.GLOBAL_SEARCH_MATCH_NOT_FOUND);
+ }
+ },
+
+ /**
+ * Creates global search results entries and adds them to this container.
+ *
+ * @param GlobalResults aGlobalResults
+ * An object containing all source results, grouped by source location.
+ */
+ _createGlobalResultsUI: function (aGlobalResults) {
+ let i = 0;
+
+ for (let sourceResults of aGlobalResults) {
+ if (i++ == 0) {
+ this._createSourceResultsUI(sourceResults);
+ } else {
+ // Dispatch subsequent document manipulation operations, to avoid
+ // blocking the main thread when a large number of search results
+ // is found, thus giving the impression of faster searching.
+ Services.tm.currentThread.dispatch({ run:
+ this._createSourceResultsUI.bind(this, sourceResults)
+ }, 0);
+ }
+ }
+ },
+
+ /**
+ * Creates source search results entries and adds them to this container.
+ *
+ * @param SourceResults aSourceResults
+ * An object containing all the matched lines for a specific source.
+ */
+ _createSourceResultsUI: function (aSourceResults) {
+ // Create the element node for the source results item.
+ let container = document.createElement("hbox");
+ aSourceResults.createView(container, {
+ onHeaderClick: this._onHeaderClick,
+ onLineClick: this._onLineClick,
+ onMatchClick: this._onMatchClick
+ });
+
+ // Append a source results item to this container.
+ let item = this.push([container], {
+ index: -1, /* specifies on which position should the item be appended */
+ attachment: {
+ sourceResults: aSourceResults
+ }
+ });
+ },
+
+ /**
+ * The click listener for a results header.
+ */
+ _onHeaderClick: function (e) {
+ let sourceResultsItem = SourceResults.getItemForElement(e.target);
+ sourceResultsItem.instance.toggle(e);
+ },
+
+ /**
+ * The click listener for a results line.
+ */
+ _onLineClick: function (e) {
+ let lineResultsItem = LineResults.getItemForElement(e.target);
+ this._onMatchClick({ target: lineResultsItem.firstMatch });
+ },
+
+ /**
+ * The click listener for a result match.
+ */
+ _onMatchClick: function (e) {
+ if (e instanceof Event) {
+ e.preventDefault();
+ e.stopPropagation();
+ }
+
+ let target = e.target;
+ let sourceResultsItem = SourceResults.getItemForElement(target);
+ let lineResultsItem = LineResults.getItemForElement(target);
+
+ sourceResultsItem.instance.expand();
+ this._currentlyFocusedMatch = LineResults.indexOfElement(target);
+ this._scrollMatchIntoViewIfNeeded(target);
+ this._bounceMatch(target);
+
+ let actor = sourceResultsItem.instance.actor;
+ let line = lineResultsItem.instance.line;
+
+ this.DebuggerView.setEditorLocation(actor, line + 1, { noDebug: true });
+
+ let range = lineResultsItem.lineData.range;
+ let cursor = this.DebuggerView.editor.getOffset({ line: line, ch: 0 });
+ let [ anchor, head ] = this.DebuggerView.editor.getPosition(
+ cursor + range.start,
+ cursor + range.start + range.length
+ );
+
+ this.DebuggerView.editor.setSelection(anchor, head);
+ },
+
+ /**
+ * Scrolls a match into view if not already visible.
+ *
+ * @param nsIDOMNode aMatch
+ * The match to scroll into view.
+ */
+ _scrollMatchIntoViewIfNeeded: function (aMatch) {
+ this.widget.ensureElementIsVisible(aMatch);
+ },
+
+ /**
+ * Starts a bounce animation for a match.
+ *
+ * @param nsIDOMNode aMatch
+ * The match to start a bounce animation for.
+ */
+ _bounceMatch: function (aMatch) {
+ Services.tm.currentThread.dispatch({ run: () => {
+ aMatch.addEventListener("transitionend", function onEvent() {
+ aMatch.removeEventListener("transitionend", onEvent);
+ aMatch.removeAttribute("focused");
+ });
+ aMatch.setAttribute("focused", "");
+ }}, 0);
+ aMatch.setAttribute("focusing", "");
+ },
+
+ _splitter: null,
+ _currentlyFocusedMatch: -1,
+ _forceExpandResults: false
+});
+
+DebuggerView.GlobalSearch = new GlobalSearchView(DebuggerController, DebuggerView);
+
+/**
+ * An object containing all source results, grouped by source location.
+ * Iterable via "for (let [location, sourceResults] of globalResults) { }".
+ */
+function GlobalResults() {
+ this._store = [];
+ SourceResults._itemsByElement = new Map();
+ LineResults._itemsByElement = new Map();
+}
+
+GlobalResults.prototype = {
+ /**
+ * Adds source results to this store.
+ *
+ * @param SourceResults aSourceResults
+ * An object containing search results for a specific source.
+ */
+ add: function (aSourceResults) {
+ this._store.push(aSourceResults);
+ },
+
+ /**
+ * Gets the number of source results in this store.
+ */
+ get matchCount() {
+ return this._store.length;
+ }
+};
+
+/**
+ * An object containing all the matched lines for a specific source.
+ * Iterable via "for (let [lineNumber, lineResults] of sourceResults) { }".
+ *
+ * @param string aActor
+ * The target source actor id.
+ * @param GlobalResults aGlobalResults
+ * An object containing all source results, grouped by source location.
+ */
+function SourceResults(aActor, aGlobalResults, sourcesView) {
+ let item = sourcesView.getItemByValue(aActor);
+ this.actor = aActor;
+ this.label = item.attachment.source.url;
+ this._globalResults = aGlobalResults;
+ this._store = [];
+}
+
+SourceResults.prototype = {
+ /**
+ * Adds line results to this store.
+ *
+ * @param LineResults aLineResults
+ * An object containing search results for a specific line.
+ */
+ add: function (aLineResults) {
+ this._store.push(aLineResults);
+ },
+
+ /**
+ * Gets the number of line results in this store.
+ */
+ get matchCount() {
+ return this._store.length;
+ },
+
+ /**
+ * Expands the element, showing all the added details.
+ */
+ expand: function () {
+ this._resultsContainer.removeAttribute("hidden");
+ this._arrow.setAttribute("open", "");
+ },
+
+ /**
+ * Collapses the element, hiding all the added details.
+ */
+ collapse: function () {
+ this._resultsContainer.setAttribute("hidden", "true");
+ this._arrow.removeAttribute("open");
+ },
+
+ /**
+ * Toggles between the element collapse/expand state.
+ */
+ toggle: function (e) {
+ this.expanded ^= 1;
+ },
+
+ /**
+ * Gets this element's expanded state.
+ * @return boolean
+ */
+ get expanded() {
+ return this._resultsContainer.getAttribute("hidden") != "true" &&
+ this._arrow.hasAttribute("open");
+ },
+
+ /**
+ * Sets this element's expanded state.
+ * @param boolean aFlag
+ */
+ set expanded(aFlag) {
+ this[aFlag ? "expand" : "collapse"]();
+ },
+
+ /**
+ * Gets the element associated with this item.
+ * @return nsIDOMNode
+ */
+ get target() {
+ return this._target;
+ },
+
+ /**
+ * Customization function for creating this item's UI.
+ *
+ * @param nsIDOMNode aElementNode
+ * The element associated with the displayed item.
+ * @param object aCallbacks
+ * An object containing all the necessary callback functions:
+ * - onHeaderClick
+ * - onMatchClick
+ */
+ createView: function (aElementNode, aCallbacks) {
+ this._target = aElementNode;
+
+ let arrow = this._arrow = document.createElement("box");
+ arrow.className = "arrow";
+
+ let locationNode = document.createElement("label");
+ locationNode.className = "plain dbg-results-header-location";
+ locationNode.setAttribute("value", this.label);
+
+ let matchCountNode = document.createElement("label");
+ matchCountNode.className = "plain dbg-results-header-match-count";
+ matchCountNode.setAttribute("value", "(" + this.matchCount + ")");
+
+ let resultsHeader = this._resultsHeader = document.createElement("hbox");
+ resultsHeader.className = "dbg-results-header";
+ resultsHeader.setAttribute("align", "center");
+ resultsHeader.appendChild(arrow);
+ resultsHeader.appendChild(locationNode);
+ resultsHeader.appendChild(matchCountNode);
+ resultsHeader.addEventListener("click", aCallbacks.onHeaderClick, false);
+
+ let resultsContainer = this._resultsContainer = document.createElement("vbox");
+ resultsContainer.className = "dbg-results-container";
+ resultsContainer.setAttribute("hidden", "true");
+
+ // Create lines search results entries and add them to this container.
+ // Afterwards, if the number of matches is reasonable, expand this
+ // container automatically.
+ for (let lineResults of this._store) {
+ lineResults.createView(resultsContainer, aCallbacks);
+ }
+ if (this.matchCount < GLOBAL_SEARCH_EXPAND_MAX_RESULTS) {
+ this.expand();
+ }
+
+ let resultsBox = document.createElement("vbox");
+ resultsBox.setAttribute("flex", "1");
+ resultsBox.appendChild(resultsHeader);
+ resultsBox.appendChild(resultsContainer);
+
+ aElementNode.id = "source-results-" + this.actor;
+ aElementNode.className = "dbg-source-results";
+ aElementNode.appendChild(resultsBox);
+
+ SourceResults._itemsByElement.set(aElementNode, { instance: this });
+ },
+
+ actor: "",
+ _globalResults: null,
+ _store: null,
+ _target: null,
+ _arrow: null,
+ _resultsHeader: null,
+ _resultsContainer: null
+};
+
+/**
+ * An object containing all the matches for a specific line.
+ * Iterable via "for (let chunk of lineResults) { }".
+ *
+ * @param number aLine
+ * The target line in the source.
+ * @param SourceResults aSourceResults
+ * An object containing all the matched lines for a specific source.
+ */
+function LineResults(aLine, aSourceResults) {
+ this.line = aLine;
+ this._sourceResults = aSourceResults;
+ this._store = [];
+ this._matchCount = 0;
+}
+
+LineResults.prototype = {
+ /**
+ * Adds string details to this store.
+ *
+ * @param string aString
+ * The text contents chunk in the line.
+ * @param object aRange
+ * An object containing the { start, length } of the chunk.
+ * @param boolean aMatchFlag
+ * True if the chunk is a matched string, false if just text content.
+ */
+ add: function (aString, aRange, aMatchFlag) {
+ this._store.push({ string: aString, range: aRange, match: !!aMatchFlag });
+ this._matchCount += aMatchFlag ? 1 : 0;
+ },
+
+ /**
+ * Gets the number of word results in this store.
+ */
+ get matchCount() {
+ return this._matchCount;
+ },
+
+ /**
+ * Gets the element associated with this item.
+ * @return nsIDOMNode
+ */
+ get target() {
+ return this._target;
+ },
+
+ /**
+ * Customization function for creating this item's UI.
+ *
+ * @param nsIDOMNode aElementNode
+ * The element associated with the displayed item.
+ * @param object aCallbacks
+ * An object containing all the necessary callback functions:
+ * - onMatchClick
+ * - onLineClick
+ */
+ createView: function (aElementNode, aCallbacks) {
+ this._target = aElementNode;
+
+ let lineNumberNode = document.createElement("label");
+ lineNumberNode.className = "plain dbg-results-line-number";
+ lineNumberNode.classList.add("devtools-monospace");
+ lineNumberNode.setAttribute("value", this.line + 1);
+
+ let lineContentsNode = document.createElement("hbox");
+ lineContentsNode.className = "dbg-results-line-contents";
+ lineContentsNode.classList.add("devtools-monospace");
+ lineContentsNode.setAttribute("flex", "1");
+
+ let lineString = "";
+ let lineLength = 0;
+ let firstMatch = null;
+
+ for (let lineChunk of this._store) {
+ let { string, range, match } = lineChunk;
+ lineString = string.substr(0, GLOBAL_SEARCH_LINE_MAX_LENGTH - lineLength);
+ lineLength += string.length;
+
+ let lineChunkNode = document.createElement("label");
+ lineChunkNode.className = "plain dbg-results-line-contents-string";
+ lineChunkNode.setAttribute("value", lineString);
+ lineChunkNode.setAttribute("match", match);
+ lineContentsNode.appendChild(lineChunkNode);
+
+ if (match) {
+ this._entangleMatch(lineChunkNode, lineChunk);
+ lineChunkNode.addEventListener("click", aCallbacks.onMatchClick, false);
+ firstMatch = firstMatch || lineChunkNode;
+ }
+ if (lineLength >= GLOBAL_SEARCH_LINE_MAX_LENGTH) {
+ lineContentsNode.appendChild(this._ellipsis.cloneNode(true));
+ break;
+ }
+ }
+
+ this._entangleLine(lineContentsNode, firstMatch);
+ lineContentsNode.addEventListener("click", aCallbacks.onLineClick, false);
+
+ let searchResult = document.createElement("hbox");
+ searchResult.className = "dbg-search-result";
+ searchResult.appendChild(lineNumberNode);
+ searchResult.appendChild(lineContentsNode);
+
+ aElementNode.appendChild(searchResult);
+ },
+
+ /**
+ * Handles a match while creating the view.
+ * @param nsIDOMNode aNode
+ * @param object aMatchChunk
+ */
+ _entangleMatch: function (aNode, aMatchChunk) {
+ LineResults._itemsByElement.set(aNode, {
+ instance: this,
+ lineData: aMatchChunk
+ });
+ },
+
+ /**
+ * Handles a line while creating the view.
+ * @param nsIDOMNode aNode
+ * @param nsIDOMNode aFirstMatch
+ */
+ _entangleLine: function (aNode, aFirstMatch) {
+ LineResults._itemsByElement.set(aNode, {
+ instance: this,
+ firstMatch: aFirstMatch,
+ ignored: true
+ });
+ },
+
+ /**
+ * An nsIDOMNode label with an ellipsis value.
+ */
+ _ellipsis: (function () {
+ let label = document.createElement("label");
+ label.className = "plain dbg-results-line-contents-string";
+ label.setAttribute("value", ELLIPSIS);
+ return label;
+ })(),
+
+ line: 0,
+ _sourceResults: null,
+ _store: null,
+ _target: null
+};
+
+/**
+ * A generator-iterator over the global, source or line results.
+ */
+GlobalResults.prototype[Symbol.iterator] =
+SourceResults.prototype[Symbol.iterator] =
+LineResults.prototype[Symbol.iterator] = function* () {
+ yield* this._store;
+};
+
+/**
+ * Gets the item associated with the specified element.
+ *
+ * @param nsIDOMNode aElement
+ * The element used to identify the item.
+ * @return object
+ * The matched item, or null if nothing is found.
+ */
+SourceResults.getItemForElement =
+LineResults.getItemForElement = function (aElement) {
+ return WidgetMethods.getItemForElement.call(this, aElement, { noSiblings: true });
+};
+
+/**
+ * Gets the element associated with a particular item at a specified index.
+ *
+ * @param number aIndex
+ * The index used to identify the item.
+ * @return nsIDOMNode
+ * The matched element, or null if nothing is found.
+ */
+SourceResults.getElementAtIndex =
+LineResults.getElementAtIndex = function (aIndex) {
+ for (let [element, item] of this._itemsByElement) {
+ if (!item.ignored && !aIndex--) {
+ return element;
+ }
+ }
+ return null;
+};
+
+/**
+ * Gets the index of an item associated with the specified element.
+ *
+ * @param nsIDOMNode aElement
+ * The element to get the index for.
+ * @return number
+ * The index of the matched element, or -1 if nothing is found.
+ */
+SourceResults.indexOfElement =
+LineResults.indexOfElement = function (aElement) {
+ let count = 0;
+ for (let [element, item] of this._itemsByElement) {
+ if (element == aElement) {
+ return count;
+ }
+ if (!item.ignored) {
+ count++;
+ }
+ }
+ return -1;
+};
+
+/**
+ * Gets the number of cached items associated with a specified element.
+ *
+ * @return number
+ * The number of key/value pairs in the corresponding map.
+ */
+SourceResults.size =
+LineResults.size = function () {
+ let count = 0;
+ for (let [, item] of this._itemsByElement) {
+ if (!item.ignored) {
+ count++;
+ }
+ }
+ return count;
+};
diff --git a/devtools/client/debugger/views/options-view.js b/devtools/client/debugger/views/options-view.js
new file mode 100644
index 000000000..2fb5b0600
--- /dev/null
+++ b/devtools/client/debugger/views/options-view.js
@@ -0,0 +1,215 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* 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/. */
+/* import-globals-from ../debugger-controller.js */
+/* import-globals-from ../debugger-view.js */
+/* import-globals-from ../utils.js */
+/* globals document, window */
+"use strict";
+
+// A time interval sufficient for the options popup panel to finish hiding
+// itself.
+const POPUP_HIDDEN_DELAY = 100; // ms
+
+/**
+ * Functions handling the options UI.
+ */
+function OptionsView(DebuggerController, DebuggerView) {
+ dumpn("OptionsView was instantiated");
+
+ this.DebuggerController = DebuggerController;
+ this.DebuggerView = DebuggerView;
+
+ this._toggleAutoPrettyPrint = this._toggleAutoPrettyPrint.bind(this);
+ this._togglePauseOnExceptions = this._togglePauseOnExceptions.bind(this);
+ this._toggleIgnoreCaughtExceptions = this._toggleIgnoreCaughtExceptions.bind(this);
+ this._toggleShowPanesOnStartup = this._toggleShowPanesOnStartup.bind(this);
+ this._toggleShowVariablesOnlyEnum = this._toggleShowVariablesOnlyEnum.bind(this);
+ this._toggleShowVariablesFilterBox = this._toggleShowVariablesFilterBox.bind(this);
+ this._toggleShowOriginalSource = this._toggleShowOriginalSource.bind(this);
+ this._toggleAutoBlackBox = this._toggleAutoBlackBox.bind(this);
+}
+
+OptionsView.prototype = {
+ /**
+ * Initialization function, called when the debugger is started.
+ */
+ initialize: function () {
+ dumpn("Initializing the OptionsView");
+
+ this._button = document.getElementById("debugger-options");
+ this._autoPrettyPrint = document.getElementById("auto-pretty-print");
+ this._pauseOnExceptionsItem = document.getElementById("pause-on-exceptions");
+ this._ignoreCaughtExceptionsItem = document.getElementById("ignore-caught-exceptions");
+ this._showPanesOnStartupItem = document.getElementById("show-panes-on-startup");
+ this._showVariablesOnlyEnumItem = document.getElementById("show-vars-only-enum");
+ this._showVariablesFilterBoxItem = document.getElementById("show-vars-filter-box");
+ this._showOriginalSourceItem = document.getElementById("show-original-source");
+ this._autoBlackBoxItem = document.getElementById("auto-black-box");
+
+ this._autoPrettyPrint.setAttribute("checked", Prefs.autoPrettyPrint);
+ this._pauseOnExceptionsItem.setAttribute("checked", Prefs.pauseOnExceptions);
+ this._ignoreCaughtExceptionsItem.setAttribute("checked", Prefs.ignoreCaughtExceptions);
+ this._showPanesOnStartupItem.setAttribute("checked", Prefs.panesVisibleOnStartup);
+ this._showVariablesOnlyEnumItem.setAttribute("checked", Prefs.variablesOnlyEnumVisible);
+ this._showVariablesFilterBoxItem.setAttribute("checked", Prefs.variablesSearchboxVisible);
+ this._showOriginalSourceItem.setAttribute("checked", Prefs.sourceMapsEnabled);
+ this._autoBlackBoxItem.setAttribute("checked", Prefs.autoBlackBox);
+
+ this._addCommands();
+ },
+
+ /**
+ * Destruction function, called when the debugger is closed.
+ */
+ destroy: function () {
+ dumpn("Destroying the OptionsView");
+ // Nothing to do here yet.
+ },
+
+ /**
+ * Add commands that XUL can fire.
+ */
+ _addCommands: function () {
+ XULUtils.addCommands(document.getElementById("debuggerCommands"), {
+ toggleAutoPrettyPrint: () => this._toggleAutoPrettyPrint(),
+ togglePauseOnExceptions: () => this._togglePauseOnExceptions(),
+ toggleIgnoreCaughtExceptions: () => this._toggleIgnoreCaughtExceptions(),
+ toggleShowPanesOnStartup: () => this._toggleShowPanesOnStartup(),
+ toggleShowOnlyEnum: () => this._toggleShowVariablesOnlyEnum(),
+ toggleShowVariablesFilterBox: () => this._toggleShowVariablesFilterBox(),
+ toggleShowOriginalSource: () => this._toggleShowOriginalSource(),
+ toggleAutoBlackBox: () => this._toggleAutoBlackBox()
+ });
+ },
+
+ /**
+ * Listener handling the 'gear menu' popup showing event.
+ */
+ _onPopupShowing: function () {
+ this._button.setAttribute("open", "true");
+ window.emit(EVENTS.OPTIONS_POPUP_SHOWING);
+ },
+
+ /**
+ * Listener handling the 'gear menu' popup hiding event.
+ */
+ _onPopupHiding: function () {
+ this._button.removeAttribute("open");
+ },
+
+ /**
+ * Listener handling the 'gear menu' popup hidden event.
+ */
+ _onPopupHidden: function () {
+ window.emit(EVENTS.OPTIONS_POPUP_HIDDEN);
+ },
+
+ /**
+ * Listener handling the 'auto pretty print' menuitem command.
+ */
+ _toggleAutoPrettyPrint: function () {
+ Prefs.autoPrettyPrint =
+ this._autoPrettyPrint.getAttribute("checked") == "true";
+ },
+
+ /**
+ * Listener handling the 'pause on exceptions' menuitem command.
+ */
+ _togglePauseOnExceptions: function () {
+ Prefs.pauseOnExceptions =
+ this._pauseOnExceptionsItem.getAttribute("checked") == "true";
+
+ this.DebuggerController.activeThread.pauseOnExceptions(
+ Prefs.pauseOnExceptions,
+ Prefs.ignoreCaughtExceptions);
+ },
+
+ _toggleIgnoreCaughtExceptions: function () {
+ Prefs.ignoreCaughtExceptions =
+ this._ignoreCaughtExceptionsItem.getAttribute("checked") == "true";
+
+ this.DebuggerController.activeThread.pauseOnExceptions(
+ Prefs.pauseOnExceptions,
+ Prefs.ignoreCaughtExceptions);
+ },
+
+ /**
+ * Listener handling the 'show panes on startup' menuitem command.
+ */
+ _toggleShowPanesOnStartup: function () {
+ Prefs.panesVisibleOnStartup =
+ this._showPanesOnStartupItem.getAttribute("checked") == "true";
+ },
+
+ /**
+ * Listener handling the 'show non-enumerables' menuitem command.
+ */
+ _toggleShowVariablesOnlyEnum: function () {
+ let pref = Prefs.variablesOnlyEnumVisible =
+ this._showVariablesOnlyEnumItem.getAttribute("checked") == "true";
+
+ this.DebuggerView.Variables.onlyEnumVisible = pref;
+ },
+
+ /**
+ * Listener handling the 'show variables searchbox' menuitem command.
+ */
+ _toggleShowVariablesFilterBox: function () {
+ let pref = Prefs.variablesSearchboxVisible =
+ this._showVariablesFilterBoxItem.getAttribute("checked") == "true";
+
+ this.DebuggerView.Variables.searchEnabled = pref;
+ },
+
+ /**
+ * Listener handling the 'show original source' menuitem command.
+ */
+ _toggleShowOriginalSource: function () {
+ let pref = Prefs.sourceMapsEnabled =
+ this._showOriginalSourceItem.getAttribute("checked") == "true";
+
+ // Don't block the UI while reconfiguring the server.
+ window.once(EVENTS.OPTIONS_POPUP_HIDDEN, () => {
+ // The popup panel needs more time to hide after triggering onpopuphidden.
+ window.setTimeout(() => {
+ this.DebuggerController.reconfigureThread({
+ useSourceMaps: pref,
+ autoBlackBox: Prefs.autoBlackBox
+ });
+ }, POPUP_HIDDEN_DELAY);
+ });
+ },
+
+ /**
+ * Listener handling the 'automatically black box minified sources' menuitem
+ * command.
+ */
+ _toggleAutoBlackBox: function () {
+ let pref = Prefs.autoBlackBox =
+ this._autoBlackBoxItem.getAttribute("checked") == "true";
+
+ // Don't block the UI while reconfiguring the server.
+ window.once(EVENTS.OPTIONS_POPUP_HIDDEN, () => {
+ // The popup panel needs more time to hide after triggering onpopuphidden.
+ window.setTimeout(() => {
+ this.DebuggerController.reconfigureThread({
+ useSourceMaps: Prefs.sourceMapsEnabled,
+ autoBlackBox: pref
+ });
+ }, POPUP_HIDDEN_DELAY);
+ });
+ },
+
+ _button: null,
+ _pauseOnExceptionsItem: null,
+ _showPanesOnStartupItem: null,
+ _showVariablesOnlyEnumItem: null,
+ _showVariablesFilterBoxItem: null,
+ _showOriginalSourceItem: null,
+ _autoBlackBoxItem: null
+};
+
+DebuggerView.Options = new OptionsView(DebuggerController, DebuggerView);
diff --git a/devtools/client/debugger/views/stack-frames-classic-view.js b/devtools/client/debugger/views/stack-frames-classic-view.js
new file mode 100644
index 000000000..df1b93088
--- /dev/null
+++ b/devtools/client/debugger/views/stack-frames-classic-view.js
@@ -0,0 +1,141 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* 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/. */
+/* import-globals-from ../debugger-controller.js */
+/* import-globals-from ../debugger-view.js */
+/* import-globals-from ../utils.js */
+/* globals document */
+"use strict";
+
+/*
+ * Functions handling the stackframes classic list UI.
+ * Controlled by the DebuggerView.StackFrames isntance.
+ */
+function StackFramesClassicListView(DebuggerController, DebuggerView) {
+ dumpn("StackFramesClassicListView was instantiated");
+
+ this.DebuggerView = DebuggerView;
+ this._onSelect = this._onSelect.bind(this);
+}
+
+StackFramesClassicListView.prototype = Heritage.extend(WidgetMethods, {
+ /**
+ * Initialization function, called when the debugger is started.
+ */
+ initialize: function () {
+ dumpn("Initializing the StackFramesClassicListView");
+
+ this.widget = new SideMenuWidget(document.getElementById("callstack-list"));
+ this.widget.addEventListener("select", this._onSelect, false);
+
+ this.emptyText = L10N.getStr("noStackFramesText");
+ this.autoFocusOnFirstItem = false;
+ this.autoFocusOnSelection = false;
+
+ // This view's contents are also mirrored in a different container.
+ this._mirror = this.DebuggerView.StackFrames;
+ },
+
+ /**
+ * Destruction function, called when the debugger is closed.
+ */
+ destroy: function () {
+ dumpn("Destroying the StackFramesClassicListView");
+
+ this.widget.removeEventListener("select", this._onSelect, false);
+ },
+
+ /**
+ * Adds a frame in this stackframes container.
+ *
+ * @param string aTitle
+ * The frame title (function name).
+ * @param string aUrl
+ * The frame source url.
+ * @param string aLine
+ * The frame line number.
+ * @param number aDepth
+ * The frame depth in the stack.
+ */
+ addFrame: function (aTitle, aUrl, aLine, aDepth) {
+ // Create the element node for the stack frame item.
+ let frameView = this._createFrameView.apply(this, arguments);
+
+ // Append a stack frame item to this container.
+ this.push([frameView], {
+ attachment: {
+ depth: aDepth
+ }
+ });
+ },
+
+ /**
+ * Customization function for creating an item's UI.
+ *
+ * @param string aTitle
+ * The frame title to be displayed in the list.
+ * @param string aUrl
+ * The frame source url.
+ * @param string aLine
+ * The frame line number.
+ * @param number aDepth
+ * The frame depth in the stack.
+ * @return nsIDOMNode
+ * The stack frame view.
+ */
+ _createFrameView: function (aTitle, aUrl, aLine, aDepth) {
+ let container = document.createElement("hbox");
+ container.id = "classic-stackframe-" + aDepth;
+ container.className = "dbg-classic-stackframe";
+ container.setAttribute("flex", "1");
+
+ let frameTitleNode = document.createElement("label");
+ frameTitleNode.className = "plain dbg-classic-stackframe-title";
+ frameTitleNode.setAttribute("value", aTitle);
+ frameTitleNode.setAttribute("crop", "center");
+
+ let frameDetailsNode = document.createElement("hbox");
+ frameDetailsNode.className = "plain dbg-classic-stackframe-details";
+
+ let frameUrlNode = document.createElement("label");
+ frameUrlNode.className = "plain dbg-classic-stackframe-details-url";
+ frameUrlNode.setAttribute("value", SourceUtils.getSourceLabel(aUrl));
+ frameUrlNode.setAttribute("crop", "center");
+ frameDetailsNode.appendChild(frameUrlNode);
+
+ let frameDetailsSeparator = document.createElement("label");
+ frameDetailsSeparator.className = "plain dbg-classic-stackframe-details-sep";
+ frameDetailsSeparator.setAttribute("value", SEARCH_LINE_FLAG);
+ frameDetailsNode.appendChild(frameDetailsSeparator);
+
+ let frameLineNode = document.createElement("label");
+ frameLineNode.className = "plain dbg-classic-stackframe-details-line";
+ frameLineNode.setAttribute("value", aLine);
+ frameDetailsNode.appendChild(frameLineNode);
+
+ container.appendChild(frameTitleNode);
+ container.appendChild(frameDetailsNode);
+
+ return container;
+ },
+
+ /**
+ * The select listener for the stackframes container.
+ */
+ _onSelect: function (e) {
+ let stackframeItem = this.selectedItem;
+ if (stackframeItem) {
+ // The container is not empty and an actual item was selected.
+ // Mirror the selected item in the breadcrumbs list.
+ let depth = stackframeItem.attachment.depth;
+ this._mirror.selectedItem = e => e.attachment.depth == depth;
+ }
+ },
+
+ _mirror: null
+});
+
+DebuggerView.StackFramesClassicList = new StackFramesClassicListView(DebuggerController,
+ DebuggerView);
diff --git a/devtools/client/debugger/views/stack-frames-view.js b/devtools/client/debugger/views/stack-frames-view.js
new file mode 100644
index 000000000..244f97b3d
--- /dev/null
+++ b/devtools/client/debugger/views/stack-frames-view.js
@@ -0,0 +1,283 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* 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/. */
+/* import-globals-from ../debugger-controller.js */
+/* import-globals-from ../debugger-view.js */
+/* import-globals-from ../utils.js */
+/* globals document, window */
+"use strict";
+
+/**
+ * Functions handling the stackframes UI.
+ */
+function StackFramesView(DebuggerController, DebuggerView) {
+ dumpn("StackFramesView was instantiated");
+
+ this.StackFrames = DebuggerController.StackFrames;
+ this.DebuggerView = DebuggerView;
+
+ this._onStackframeRemoved = this._onStackframeRemoved.bind(this);
+ this._onSelect = this._onSelect.bind(this);
+ this._onScroll = this._onScroll.bind(this);
+ this._afterScroll = this._afterScroll.bind(this);
+ this._getStackAsString = this._getStackAsString.bind(this);
+}
+
+StackFramesView.prototype = Heritage.extend(WidgetMethods, {
+ /**
+ * Initialization function, called when the debugger is started.
+ */
+ initialize: function () {
+ dumpn("Initializing the StackFramesView");
+
+ this._popupset = document.getElementById("debuggerPopupset");
+
+ this.widget = new BreadcrumbsWidget(document.getElementById("stackframes"));
+ this.widget.addEventListener("select", this._onSelect, false);
+ this.widget.addEventListener("scroll", this._onScroll, true);
+ this.widget.setAttribute("context", "stackFramesContextMenu");
+ window.addEventListener("resize", this._onScroll, true);
+
+ this.autoFocusOnFirstItem = false;
+ this.autoFocusOnSelection = false;
+
+ // This view's contents are also mirrored in a different container.
+ this._mirror = this.DebuggerView.StackFramesClassicList;
+ },
+
+ /**
+ * Destruction function, called when the debugger is closed.
+ */
+ destroy: function () {
+ dumpn("Destroying the StackFramesView");
+
+ this.widget.removeEventListener("select", this._onSelect, false);
+ this.widget.removeEventListener("scroll", this._onScroll, true);
+ window.removeEventListener("resize", this._onScroll, true);
+ },
+
+ /**
+ * Adds a frame in this stackframes container.
+ *
+ * @param string aTitle
+ * The frame title (function name).
+ * @param string aUrl
+ * The frame source url.
+ * @param string aLine
+ * The frame line number.
+ * @param number aDepth
+ * The frame depth in the stack.
+ * @param boolean aIsBlackBoxed
+ * Whether or not the frame is black boxed.
+ */
+ addFrame: function (aFrame, aLine, aColumn, aDepth, aIsBlackBoxed) {
+ let { source } = aFrame;
+
+ // The source may not exist in the source listing yet because it's
+ // an unnamed eval source, which we hide, so we need to add it
+ if (!DebuggerView.Sources.getItemByValue(source.actor)) {
+ DebuggerView.Sources.addSource(source, { force: true });
+ }
+
+ let location = DebuggerView.Sources.getDisplayURL(source);
+ let title = StackFrameUtils.getFrameTitle(aFrame);
+
+ // Blackboxed stack frames are collapsed into a single entry in
+ // the view. By convention, only the first frame is displayed.
+ if (aIsBlackBoxed) {
+ if (this._prevBlackBoxedUrl == location) {
+ return;
+ }
+ this._prevBlackBoxedUrl = location;
+ } else {
+ this._prevBlackBoxedUrl = null;
+ }
+
+ // Create the element node for the stack frame item.
+ let frameView = this._createFrameView(
+ title, location, aLine, aDepth, aIsBlackBoxed
+ );
+
+ // Append a stack frame item to this container.
+ this.push([frameView], {
+ index: 0, /* specifies on which position should the item be appended */
+ attachment: {
+ title: title,
+ url: location,
+ line: aLine,
+ depth: aDepth,
+ column: aColumn
+ },
+ // Make sure that when the stack frame item is removed, the corresponding
+ // mirrored item in the classic list is also removed.
+ finalize: this._onStackframeRemoved
+ });
+
+ // Mirror this newly inserted item inside the "Call Stack" tab.
+ this._mirror.addFrame(title, location, aLine, aDepth);
+ },
+
+ _getStackAsString: function () {
+ return [...this].map(frameItem => {
+ const { attachment: { title, url, line, column }} = frameItem;
+ return title + "@" + url + ":" + line + ":" + column;
+ }).join("\n");
+ },
+
+ addCopyContextMenu: function () {
+ let menupopup = document.createElement("menupopup");
+ let menuitem = document.createElement("menuitem");
+
+ menupopup.id = "stackFramesContextMenu";
+ menuitem.id = "copyStackMenuItem";
+
+ menuitem.setAttribute("label", "Copy");
+ menuitem.addEventListener("command", () => {
+ let stack = this._getStackAsString();
+ clipboardHelper.copyString(stack);
+ }, false);
+ menupopup.appendChild(menuitem);
+ this._popupset.appendChild(menupopup);
+ },
+
+ /**
+ * Selects the frame at the specified depth in this container.
+ * @param number aDepth
+ */
+ set selectedDepth(aDepth) {
+ this.selectedItem = aItem => aItem.attachment.depth == aDepth;
+ },
+
+ /**
+ * Gets the currently selected stack frame's depth in this container.
+ * This will essentially be the opposite of |selectedIndex|, which deals
+ * with the position in the view, where the last item added is actually
+ * the bottommost, not topmost.
+ * @return number
+ */
+ get selectedDepth() {
+ return this.selectedItem.attachment.depth;
+ },
+
+ /**
+ * Specifies if the active thread has more frames that need to be loaded.
+ */
+ dirty: false,
+
+ /**
+ * Customization function for creating an item's UI.
+ *
+ * @param string aTitle
+ * The frame title to be displayed in the list.
+ * @param string aUrl
+ * The frame source url.
+ * @param string aLine
+ * The frame line number.
+ * @param number aDepth
+ * The frame depth in the stack.
+ * @param boolean aIsBlackBoxed
+ * Whether or not the frame is black boxed.
+ * @return nsIDOMNode
+ * The stack frame view.
+ */
+ _createFrameView: function (aTitle, aUrl, aLine, aDepth, aIsBlackBoxed) {
+ let container = document.createElement("hbox");
+ container.id = "stackframe-" + aDepth;
+ container.className = "dbg-stackframe";
+
+ let frameDetails = SourceUtils.trimUrlLength(
+ SourceUtils.getSourceLabel(aUrl),
+ STACK_FRAMES_SOURCE_URL_MAX_LENGTH,
+ STACK_FRAMES_SOURCE_URL_TRIM_SECTION);
+
+ if (aIsBlackBoxed) {
+ container.classList.add("dbg-stackframe-black-boxed");
+ } else {
+ let frameTitleNode = document.createElement("label");
+ frameTitleNode.className = "plain dbg-stackframe-title breadcrumbs-widget-item-tag";
+ frameTitleNode.setAttribute("value", aTitle);
+ container.appendChild(frameTitleNode);
+
+ frameDetails += SEARCH_LINE_FLAG + aLine;
+ }
+
+ let frameDetailsNode = document.createElement("label");
+ frameDetailsNode.className = "plain dbg-stackframe-details breadcrumbs-widget-item-id";
+ frameDetailsNode.setAttribute("value", frameDetails);
+ container.appendChild(frameDetailsNode);
+
+ return container;
+ },
+
+ /**
+ * Function called each time a stack frame item is removed.
+ *
+ * @param object aItem
+ * The corresponding item.
+ */
+ _onStackframeRemoved: function (aItem) {
+ dumpn("Finalizing stackframe item: " + aItem.stringify());
+
+ // Remove the mirrored item in the classic list.
+ let depth = aItem.attachment.depth;
+ this._mirror.remove(this._mirror.getItemForAttachment(e => e.depth == depth));
+
+ // Forget the previously blackboxed stack frame url.
+ this._prevBlackBoxedUrl = null;
+ },
+
+ /**
+ * The select listener for the stackframes container.
+ */
+ _onSelect: function (e) {
+ let stackframeItem = this.selectedItem;
+ if (stackframeItem) {
+ // The container is not empty and an actual item was selected.
+ let depth = stackframeItem.attachment.depth;
+
+ // Mirror the selected item in the classic list.
+ this.suppressSelectionEvents = true;
+ this._mirror.selectedItem = e => e.attachment.depth == depth;
+ this.suppressSelectionEvents = false;
+
+ DebuggerController.StackFrames.selectFrame(depth);
+ }
+ },
+
+ /**
+ * The scroll listener for the stackframes container.
+ */
+ _onScroll: function () {
+ // Update the stackframes container only if we have to.
+ if (!this.dirty) {
+ return;
+ }
+ // Allow requests to settle down first.
+ setNamedTimeout("stack-scroll", STACK_FRAMES_SCROLL_DELAY, this._afterScroll);
+ },
+
+ /**
+ * Requests the addition of more frames from the controller.
+ */
+ _afterScroll: function () {
+ let scrollPosition = this.widget.getAttribute("scrollPosition");
+ let scrollWidth = this.widget.getAttribute("scrollWidth");
+
+ // If the stackframes container scrolled almost to the end, with only
+ // 1/10 of a breadcrumb remaining, load more content.
+ if (scrollPosition - scrollWidth / 10 < 1) {
+ this.ensureIndexIsVisible(CALL_STACK_PAGE_SIZE - 1);
+ this.dirty = false;
+
+ // Loads more stack frames from the debugger server cache.
+ DebuggerController.StackFrames.addMoreFrames();
+ }
+ },
+
+ _mirror: null,
+ _prevBlackBoxedUrl: null
+});
+
+DebuggerView.StackFrames = new StackFramesView(DebuggerController, DebuggerView);
diff --git a/devtools/client/debugger/views/toolbar-view.js b/devtools/client/debugger/views/toolbar-view.js
new file mode 100644
index 000000000..d76275a71
--- /dev/null
+++ b/devtools/client/debugger/views/toolbar-view.js
@@ -0,0 +1,287 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* 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/. */
+/* import-globals-from ../debugger-controller.js */
+/* import-globals-from ../debugger-view.js */
+/* import-globals-from ../utils.js */
+/* globals document */
+"use strict";
+
+/**
+ * Functions handling the toolbar view: close button, expand/collapse button,
+ * pause/resume and stepping buttons etc.
+ */
+function ToolbarView(DebuggerController, DebuggerView) {
+ dumpn("ToolbarView was instantiated");
+
+ this.StackFrames = DebuggerController.StackFrames;
+ this.ThreadState = DebuggerController.ThreadState;
+ this.DebuggerController = DebuggerController;
+ this.DebuggerView = DebuggerView;
+
+ this._onTogglePanesActivated = this._onTogglePanesActivated.bind(this);
+ this._onTogglePanesPressed = this._onTogglePanesPressed.bind(this);
+ this._onResumePressed = this._onResumePressed.bind(this);
+ this._onStepOverPressed = this._onStepOverPressed.bind(this);
+ this._onStepInPressed = this._onStepInPressed.bind(this);
+ this._onStepOutPressed = this._onStepOutPressed.bind(this);
+}
+
+ToolbarView.prototype = {
+ get activeThread() {
+ return this.DebuggerController.activeThread;
+ },
+
+ get resumptionWarnFunc() {
+ return this.DebuggerController._ensureResumptionOrder;
+ },
+
+ /**
+ * Initialization function, called when the debugger is started.
+ */
+ initialize: function () {
+ dumpn("Initializing the ToolbarView");
+
+ this._instrumentsPaneToggleButton = document.getElementById("instruments-pane-toggle");
+ this._resumeButton = document.getElementById("resume");
+ this._stepOverButton = document.getElementById("step-over");
+ this._stepInButton = document.getElementById("step-in");
+ this._stepOutButton = document.getElementById("step-out");
+ this._resumeOrderTooltip = new Tooltip(document);
+ this._resumeOrderTooltip.defaultPosition = TOOLBAR_ORDER_POPUP_POSITION;
+
+ let resumeKey = ShortcutUtils.prettifyShortcut(document.getElementById("resumeKey"));
+ let stepOverKey = ShortcutUtils.prettifyShortcut(document.getElementById("stepOverKey"));
+ let stepInKey = ShortcutUtils.prettifyShortcut(document.getElementById("stepInKey"));
+ let stepOutKey = ShortcutUtils.prettifyShortcut(document.getElementById("stepOutKey"));
+ this._resumeTooltip = L10N.getFormatStr("resumeButtonTooltip", resumeKey);
+ this._pauseTooltip = L10N.getFormatStr("pauseButtonTooltip", resumeKey);
+ this._pausePendingTooltip = L10N.getStr("pausePendingButtonTooltip");
+ this._stepOverTooltip = L10N.getFormatStr("stepOverTooltip", stepOverKey);
+ this._stepInTooltip = L10N.getFormatStr("stepInTooltip", stepInKey);
+ this._stepOutTooltip = L10N.getFormatStr("stepOutTooltip", stepOutKey);
+
+ this._instrumentsPaneToggleButton.addEventListener("mousedown",
+ this._onTogglePanesActivated, false);
+ this._instrumentsPaneToggleButton.addEventListener("keydown",
+ this._onTogglePanesPressed, false);
+ this._resumeButton.addEventListener("mousedown", this._onResumePressed, false);
+ this._stepOverButton.addEventListener("mousedown", this._onStepOverPressed, false);
+ this._stepInButton.addEventListener("mousedown", this._onStepInPressed, false);
+ this._stepOutButton.addEventListener("mousedown", this._onStepOutPressed, false);
+
+ this._stepOverButton.setAttribute("tooltiptext", this._stepOverTooltip);
+ this._stepInButton.setAttribute("tooltiptext", this._stepInTooltip);
+ this._stepOutButton.setAttribute("tooltiptext", this._stepOutTooltip);
+ this._toggleButtonsState({ enabled: false });
+
+ this._addCommands();
+ },
+
+ /**
+ * Destruction function, called when the debugger is closed.
+ */
+ destroy: function () {
+ dumpn("Destroying the ToolbarView");
+
+ this._instrumentsPaneToggleButton.removeEventListener("mousedown",
+ this._onTogglePanesActivated, false);
+ this._instrumentsPaneToggleButton.removeEventListener("keydown",
+ this._onTogglePanesPressed, false);
+ this._resumeButton.removeEventListener("mousedown", this._onResumePressed, false);
+ this._stepOverButton.removeEventListener("mousedown", this._onStepOverPressed, false);
+ this._stepInButton.removeEventListener("mousedown", this._onStepInPressed, false);
+ this._stepOutButton.removeEventListener("mousedown", this._onStepOutPressed, false);
+ },
+
+ /**
+ * Add commands that XUL can fire.
+ */
+ _addCommands: function () {
+ XULUtils.addCommands(document.getElementById("debuggerCommands"), {
+ resumeCommand: this.getCommandHandler("resumeCommand"),
+ stepOverCommand: this.getCommandHandler("stepOverCommand"),
+ stepInCommand: this.getCommandHandler("stepInCommand"),
+ stepOutCommand: this.getCommandHandler("stepOutCommand")
+ });
+ },
+
+ /**
+ * Retrieve the callback associated with the provided debugger command.
+ *
+ * @param {String} command
+ * The debugger command id.
+ * @return {Function} the corresponding callback.
+ */
+ getCommandHandler: function (command) {
+ switch (command) {
+ case "resumeCommand":
+ return () => this._onResumePressed();
+ case "stepOverCommand":
+ return () => this._onStepOverPressed();
+ case "stepInCommand":
+ return () => this._onStepInPressed();
+ case "stepOutCommand":
+ return () => this._onStepOutPressed();
+ default:
+ return () => {};
+ }
+ },
+
+ /**
+ * Display a warning when trying to resume a debuggee while another is paused.
+ * Debuggees must be unpaused in a Last-In-First-Out order.
+ *
+ * @param string aPausedUrl
+ * The URL of the last paused debuggee.
+ */
+ showResumeWarning: function (aPausedUrl) {
+ let label = L10N.getFormatStr("resumptionOrderPanelTitle", aPausedUrl);
+ let defaultStyle = "default-tooltip-simple-text-colors";
+ this._resumeOrderTooltip.setTextContent({ messages: [label] });
+ this._resumeOrderTooltip.show(this._resumeButton);
+ },
+
+ /**
+ * Sets the resume button state based on the debugger active thread.
+ *
+ * @param string aState
+ * Either "paused", "attached", or "breakOnNext".
+ * @param boolean hasLocation
+ * True if we are paused at a specific JS location
+ */
+ toggleResumeButtonState: function (aState, hasLocation) {
+ // Intermidiate state after pressing the pause button and waiting
+ // for the next script execution to happen.
+ if (aState == "breakOnNext") {
+ this._resumeButton.setAttribute("break-on-next", "true");
+ this._resumeButton.disabled = true;
+ this._resumeButton.setAttribute("tooltiptext", this._pausePendingTooltip);
+ return;
+ }
+
+ this._resumeButton.removeAttribute("break-on-next");
+ this._resumeButton.disabled = false;
+
+ // If we're paused, check and show a resume label on the button.
+ if (aState == "paused") {
+ this._resumeButton.setAttribute("checked", "true");
+ this._resumeButton.setAttribute("tooltiptext", this._resumeTooltip);
+
+ // Only enable the stepping buttons if we are paused at a
+ // specific location. After bug 789430, we'll always be paused
+ // at a location, but currently you can pause the entire engine
+ // at any point without knowing the location.
+ if (hasLocation) {
+ this._toggleButtonsState({ enabled: true });
+ }
+ }
+ // If we're attached, do the opposite.
+ else if (aState == "attached") {
+ this._resumeButton.removeAttribute("checked");
+ this._resumeButton.setAttribute("tooltiptext", this._pauseTooltip);
+ this._toggleButtonsState({ enabled: false });
+ }
+ },
+
+ _toggleButtonsState: function ({ enabled }) {
+ const buttons = [
+ this._stepOutButton,
+ this._stepInButton,
+ this._stepOverButton
+ ];
+ for (let button of buttons) {
+ button.disabled = !enabled;
+ }
+ },
+
+ /**
+ * Listener handling the toggle button space and return key event.
+ */
+ _onTogglePanesPressed: function (event) {
+ if (ViewHelpers.isSpaceOrReturn(event)) {
+ this._onTogglePanesActivated();
+ }
+ },
+
+ /**
+ * Listener handling the toggle button click event.
+ */
+ _onTogglePanesActivated: function() {
+ DebuggerView.toggleInstrumentsPane({
+ visible: DebuggerView.instrumentsPaneHidden,
+ animated: true,
+ delayed: true
+ });
+ },
+
+ /**
+ * Listener handling the pause/resume button click event.
+ */
+ _onResumePressed: function () {
+ if (this.StackFrames._currentFrameDescription != FRAME_TYPE.NORMAL ||
+ this._resumeButton.disabled) {
+ return;
+ }
+
+ if (this.activeThread.paused) {
+ this.StackFrames.currentFrameDepth = -1;
+ this.activeThread.resume(this.resumptionWarnFunc);
+ } else {
+ this.ThreadState.interruptedByResumeButton = true;
+ this.toggleResumeButtonState("breakOnNext");
+ this.activeThread.breakOnNext();
+ }
+ },
+
+ /**
+ * Listener handling the step over button click event.
+ */
+ _onStepOverPressed: function () {
+ if (this.activeThread.paused && !this._stepOverButton.disabled) {
+ this.StackFrames.currentFrameDepth = -1;
+ this.activeThread.stepOver(this.resumptionWarnFunc);
+ }
+ },
+
+ /**
+ * Listener handling the step in button click event.
+ */
+ _onStepInPressed: function () {
+ if (this.StackFrames._currentFrameDescription != FRAME_TYPE.NORMAL ||
+ this._stepInButton.disabled) {
+ return;
+ }
+
+ if (this.activeThread.paused) {
+ this.StackFrames.currentFrameDepth = -1;
+ this.activeThread.stepIn(this.resumptionWarnFunc);
+ }
+ },
+
+ /**
+ * Listener handling the step out button click event.
+ */
+ _onStepOutPressed: function () {
+ if (this.activeThread.paused && !this._stepOutButton.disabled) {
+ this.StackFrames.currentFrameDepth = -1;
+ this.activeThread.stepOut(this.resumptionWarnFunc);
+ }
+ },
+
+ _instrumentsPaneToggleButton: null,
+ _resumeButton: null,
+ _stepOverButton: null,
+ _stepInButton: null,
+ _stepOutButton: null,
+ _resumeOrderTooltip: null,
+ _resumeTooltip: "",
+ _pauseTooltip: "",
+ _stepOverTooltip: "",
+ _stepInTooltip: "",
+ _stepOutTooltip: ""
+};
+
+DebuggerView.Toolbar = new ToolbarView(DebuggerController, DebuggerView);
diff --git a/devtools/client/debugger/views/variable-bubble-view.js b/devtools/client/debugger/views/variable-bubble-view.js
new file mode 100644
index 000000000..3ac2f971e
--- /dev/null
+++ b/devtools/client/debugger/views/variable-bubble-view.js
@@ -0,0 +1,321 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* 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/. */
+/* import-globals-from ../debugger-controller.js */
+/* import-globals-from ../debugger-view.js */
+/* import-globals-from ../utils.js */
+/* globals document, window */
+"use strict";
+
+const {setTooltipVariableContent} = require("devtools/client/shared/widgets/tooltip/VariableContentHelper");
+
+/**
+ * Functions handling the variables bubble UI.
+ */
+function VariableBubbleView(DebuggerController, DebuggerView) {
+ dumpn("VariableBubbleView was instantiated");
+
+ this.StackFrames = DebuggerController.StackFrames;
+ this.Parser = DebuggerController.Parser;
+ this.DebuggerView = DebuggerView;
+
+ this._onMouseMove = this._onMouseMove.bind(this);
+ this._onMouseOut = this._onMouseOut.bind(this);
+ this._onPopupHiding = this._onPopupHiding.bind(this);
+}
+
+VariableBubbleView.prototype = {
+ /**
+ * Delay before showing the variables bubble tooltip when hovering a valid
+ * target.
+ */
+ TOOLTIP_SHOW_DELAY: 750,
+
+ /**
+ * Tooltip position for the variables bubble tooltip.
+ */
+ TOOLTIP_POSITION: "topcenter bottomleft",
+
+ /**
+ * Initialization function, called when the debugger is started.
+ */
+ initialize: function () {
+ dumpn("Initializing the VariableBubbleView");
+
+ this._toolbox = DebuggerController._toolbox;
+ this._editorContainer = document.getElementById("editor");
+ this._editorContainer.addEventListener("mousemove", this._onMouseMove, false);
+ this._editorContainer.addEventListener("mouseout", this._onMouseOut, false);
+
+ this._tooltip = new Tooltip(document, {
+ closeOnEvents: [{
+ emitter: this._toolbox,
+ event: "select"
+ }, {
+ emitter: this._editorContainer,
+ event: "scroll",
+ useCapture: true
+ }, {
+ emitter: document,
+ event: "keydown"
+ }]
+ });
+ this._tooltip.defaultPosition = this.TOOLTIP_POSITION;
+ this._tooltip.panel.addEventListener("popuphiding", this._onPopupHiding);
+ },
+
+ /**
+ * Destruction function, called when the debugger is closed.
+ */
+ destroy: function () {
+ dumpn("Destroying the VariableBubbleView");
+
+ this._tooltip.panel.removeEventListener("popuphiding", this._onPopupHiding);
+ this._editorContainer.removeEventListener("mousemove", this._onMouseMove, false);
+ this._editorContainer.removeEventListener("mouseout", this._onMouseOut, false);
+ },
+
+ /**
+ * Specifies whether literals can be (redundantly) inspected in a popup.
+ * This behavior is deprecated, but still tested in a few places.
+ */
+ _ignoreLiterals: true,
+
+ /**
+ * Searches for an identifier underneath the specified position in the
+ * source editor, and if found, opens a VariablesView inspection popup.
+ *
+ * @param number x, y
+ * The left/top coordinates where to look for an identifier.
+ */
+ _findIdentifier: function (x, y) {
+ let editor = this.DebuggerView.editor;
+
+ // Calculate the editor's line and column at the current x and y coords.
+ let hoveredPos = editor.getPositionFromCoords({ left: x, top: y });
+ let hoveredOffset = editor.getOffset(hoveredPos);
+ let hoveredLine = hoveredPos.line;
+ let hoveredColumn = hoveredPos.ch;
+
+ // A source contains multiple scripts. Find the start index of the script
+ // containing the specified offset relative to its parent source.
+ let contents = editor.getText();
+ let location = this.DebuggerView.Sources.selectedValue;
+ let parsedSource = this.Parser.get(contents, location);
+ let scriptInfo = parsedSource.getScriptInfo(hoveredOffset);
+
+ // If the script length is negative, we're not hovering JS source code.
+ if (scriptInfo.length == -1) {
+ return;
+ }
+
+ // Using the script offset, determine the actual line and column inside the
+ // script, to use when finding identifiers.
+ let scriptStart = editor.getPosition(scriptInfo.start);
+ let scriptLineOffset = scriptStart.line;
+ let scriptColumnOffset = (hoveredLine == scriptStart.line ? scriptStart.ch : 0);
+
+ let scriptLine = hoveredLine - scriptLineOffset;
+ let scriptColumn = hoveredColumn - scriptColumnOffset;
+ let identifierInfo = parsedSource.getIdentifierAt({
+ line: scriptLine + 1,
+ column: scriptColumn,
+ scriptIndex: scriptInfo.index,
+ ignoreLiterals: this._ignoreLiterals
+ });
+
+ // If the info is null, we're not hovering any identifier.
+ if (!identifierInfo) {
+ return;
+ }
+
+ // Transform the line and column relative to the parsed script back
+ // to the context of the parent source.
+ let { start: identifierStart, end: identifierEnd } = identifierInfo.location;
+ let identifierCoords = {
+ line: identifierStart.line + scriptLineOffset,
+ column: identifierStart.column + scriptColumnOffset,
+ length: identifierEnd.column - identifierStart.column
+ };
+
+ // Evaluate the identifier in the current stack frame and show the
+ // results in a VariablesView inspection popup.
+ this.StackFrames.evaluate(identifierInfo.evalString)
+ .then(frameFinished => {
+ if ("return" in frameFinished) {
+ this.showContents({
+ coords: identifierCoords,
+ evalPrefix: identifierInfo.evalString,
+ objectActor: frameFinished.return
+ });
+ } else {
+ let msg = "Evaluation has thrown for: " + identifierInfo.evalString;
+ console.warn(msg);
+ dumpn(msg);
+ }
+ })
+ .then(null, err => {
+ let msg = "Couldn't evaluate: " + err.message;
+ console.error(msg);
+ dumpn(msg);
+ });
+ },
+
+ /**
+ * Shows an inspection popup for a specified object actor grip.
+ *
+ * @param string object
+ * An object containing the following properties:
+ * - coords: the inspected identifier coordinates in the editor,
+ * containing the { line, column, length } properties.
+ * - evalPrefix: a prefix for the variables view evaluation macros.
+ * - objectActor: the value grip for the object actor.
+ */
+ showContents: function ({ coords, evalPrefix, objectActor }) {
+ let editor = this.DebuggerView.editor;
+ let { line, column, length } = coords;
+
+ // Highlight the function found at the mouse position.
+ this._markedText = editor.markText(
+ { line: line - 1, ch: column },
+ { line: line - 1, ch: column + length });
+
+ // If the grip represents a primitive value, use a more lightweight
+ // machinery to display it.
+ if (VariablesView.isPrimitive({ value: objectActor })) {
+ let className = VariablesView.getClass(objectActor);
+ let textContent = VariablesView.getString(objectActor);
+ this._tooltip.setTextContent({
+ messages: [textContent],
+ messagesClass: className,
+ containerClass: "plain"
+ }, [{
+ label: L10N.getStr("addWatchExpressionButton"),
+ className: "dbg-expression-button",
+ command: () => {
+ this.DebuggerView.VariableBubble.hideContents();
+ this.DebuggerView.WatchExpressions.addExpression(evalPrefix, true);
+ }
+ }]);
+ } else {
+ setTooltipVariableContent(this._tooltip, objectActor, {
+ searchPlaceholder: L10N.getStr("emptyPropertiesFilterText"),
+ searchEnabled: Prefs.variablesSearchboxVisible,
+ eval: (variable, value) => {
+ let string = variable.evaluationMacro(variable, value);
+ this.StackFrames.evaluate(string);
+ this.DebuggerView.VariableBubble.hideContents();
+ }
+ }, {
+ getEnvironmentClient: aObject => gThreadClient.environment(aObject),
+ getObjectClient: aObject => gThreadClient.pauseGrip(aObject),
+ simpleValueEvalMacro: this._getSimpleValueEvalMacro(evalPrefix),
+ getterOrSetterEvalMacro: this._getGetterOrSetterEvalMacro(evalPrefix),
+ overrideValueEvalMacro: this._getOverrideValueEvalMacro(evalPrefix)
+ }, {
+ fetched: (aEvent, aType) => {
+ if (aType == "properties") {
+ window.emit(EVENTS.FETCHED_BUBBLE_PROPERTIES);
+ }
+ }
+ }, [{
+ label: L10N.getStr("addWatchExpressionButton"),
+ className: "dbg-expression-button",
+ command: () => {
+ this.DebuggerView.VariableBubble.hideContents();
+ this.DebuggerView.WatchExpressions.addExpression(evalPrefix, true);
+ }
+ }], this._toolbox);
+ }
+
+ this._tooltip.show(this._markedText.anchor);
+ },
+
+ /**
+ * Hides the inspection popup.
+ */
+ hideContents: function () {
+ clearNamedTimeout("editor-mouse-move");
+ this._tooltip.hide();
+ },
+
+ /**
+ * Checks whether the inspection popup is shown.
+ *
+ * @return boolean
+ * True if the panel is shown or showing, false otherwise.
+ */
+ contentsShown: function () {
+ return this._tooltip.isShown();
+ },
+
+ /**
+ * Functions for getting customized variables view evaluation macros.
+ *
+ * @param string aPrefix
+ * See the corresponding VariablesView.* functions.
+ */
+ _getSimpleValueEvalMacro: function (aPrefix) {
+ return (item, string) =>
+ VariablesView.simpleValueEvalMacro(item, string, aPrefix);
+ },
+ _getGetterOrSetterEvalMacro: function (aPrefix) {
+ return (item, string) =>
+ VariablesView.getterOrSetterEvalMacro(item, string, aPrefix);
+ },
+ _getOverrideValueEvalMacro: function (aPrefix) {
+ return (item, string) =>
+ VariablesView.overrideValueEvalMacro(item, string, aPrefix);
+ },
+
+ /**
+ * The mousemove listener for the source editor.
+ */
+ _onMouseMove: function (e) {
+ // Prevent the variable inspection popup from showing when the thread client
+ // is not paused, or while a popup is already visible, or when the user tries
+ // to select text in the editor.
+ let isResumed = gThreadClient && gThreadClient.state != "paused";
+ let isSelecting = this.DebuggerView.editor.somethingSelected() && e.buttons > 0;
+ let isPopupVisible = !this._tooltip.isHidden();
+ if (isResumed || isSelecting || isPopupVisible) {
+ clearNamedTimeout("editor-mouse-move");
+ return;
+ }
+ // Allow events to settle down first. If the mouse hovers over
+ // a certain point in the editor long enough, try showing a variable bubble.
+ setNamedTimeout("editor-mouse-move",
+ this.TOOLTIP_SHOW_DELAY, () => this._findIdentifier(e.clientX, e.clientY));
+ },
+
+ /**
+ * The mouseout listener for the source editor container node.
+ */
+ _onMouseOut: function () {
+ clearNamedTimeout("editor-mouse-move");
+ },
+
+ /**
+ * Listener handling the popup hiding event.
+ */
+ _onPopupHiding: function ({ target }) {
+ if (this._tooltip.panel != target) {
+ return;
+ }
+ if (this._markedText) {
+ this._markedText.clear();
+ this._markedText = null;
+ }
+ if (!this._tooltip.isEmpty()) {
+ this._tooltip.empty();
+ }
+ },
+
+ _editorContainer: null,
+ _markedText: null,
+ _tooltip: null
+};
+
+DebuggerView.VariableBubble = new VariableBubbleView(DebuggerController, DebuggerView);
diff --git a/devtools/client/debugger/views/watch-expressions-view.js b/devtools/client/debugger/views/watch-expressions-view.js
new file mode 100644
index 000000000..59d3ad5a0
--- /dev/null
+++ b/devtools/client/debugger/views/watch-expressions-view.js
@@ -0,0 +1,303 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* 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/. */
+/* import-globals-from ../debugger-controller.js */
+/* import-globals-from ../debugger-view.js */
+/* import-globals-from ../utils.js */
+/* globals document */
+"use strict";
+
+const {KeyCodes} = require("devtools/client/shared/keycodes");
+
+/**
+ * Functions handling the watch expressions UI.
+ */
+function WatchExpressionsView(DebuggerController, DebuggerView) {
+ dumpn("WatchExpressionsView was instantiated");
+
+ this.StackFrames = DebuggerController.StackFrames;
+ this.DebuggerView = DebuggerView;
+
+ this.switchExpression = this.switchExpression.bind(this);
+ this.deleteExpression = this.deleteExpression.bind(this);
+ this._createItemView = this._createItemView.bind(this);
+ this._onClick = this._onClick.bind(this);
+ this._onClose = this._onClose.bind(this);
+ this._onBlur = this._onBlur.bind(this);
+ this._onKeyPress = this._onKeyPress.bind(this);
+}
+
+WatchExpressionsView.prototype = Heritage.extend(WidgetMethods, {
+ /**
+ * Initialization function, called when the debugger is started.
+ */
+ initialize: function () {
+ dumpn("Initializing the WatchExpressionsView");
+
+ this.widget = new SimpleListWidget(document.getElementById("expressions"));
+ this.widget.setAttribute("context", "debuggerWatchExpressionsContextMenu");
+ this.widget.addEventListener("click", this._onClick, false);
+
+ this.headerText = L10N.getStr("addWatchExpressionText");
+ this._addCommands();
+ },
+
+ /**
+ * Destruction function, called when the debugger is closed.
+ */
+ destroy: function () {
+ dumpn("Destroying the WatchExpressionsView");
+
+ this.widget.removeEventListener("click", this._onClick, false);
+ },
+
+ /**
+ * Add commands that XUL can fire.
+ */
+ _addCommands: function () {
+ XULUtils.addCommands(document.getElementById("debuggerCommands"), {
+ addWatchExpressionCommand: () => this._onCmdAddExpression(),
+ removeAllWatchExpressionsCommand: () => this._onCmdRemoveAllExpressions()
+ });
+ },
+
+ /**
+ * Adds a watch expression in this container.
+ *
+ * @param string aExpression [optional]
+ * An optional initial watch expression text.
+ * @param boolean aSkipUserInput [optional]
+ * Pass true to avoid waiting for additional user input
+ * on the watch expression.
+ */
+ addExpression: function (aExpression = "", aSkipUserInput = false) {
+ // Watch expressions are UI elements which benefit from visible panes.
+ this.DebuggerView.showInstrumentsPane();
+
+ // Create the element node for the watch expression item.
+ let itemView = this._createItemView(aExpression);
+
+ // Append a watch expression item to this container.
+ let expressionItem = this.push([itemView.container], {
+ index: 0, /* specifies on which position should the item be appended */
+ attachment: {
+ view: itemView,
+ initialExpression: aExpression,
+ currentExpression: "",
+ }
+ });
+
+ // Automatically focus the new watch expression input
+ // if additional user input is desired.
+ if (!aSkipUserInput) {
+ expressionItem.attachment.view.inputNode.select();
+ expressionItem.attachment.view.inputNode.focus();
+ this.DebuggerView.Variables.parentNode.scrollTop = 0;
+ }
+ // Otherwise, add and evaluate the new watch expression immediately.
+ else {
+ this.toggleContents(false);
+ this._onBlur({ target: expressionItem.attachment.view.inputNode });
+ }
+ },
+
+ /**
+ * Changes the watch expression corresponding to the specified variable item.
+ * This function is called whenever a watch expression's code is edited in
+ * the variables view container.
+ *
+ * @param Variable aVar
+ * The variable representing the watch expression evaluation.
+ * @param string aExpression
+ * The new watch expression text.
+ */
+ switchExpression: function (aVar, aExpression) {
+ let expressionItem =
+ [...this].filter(i => i.attachment.currentExpression == aVar.name)[0];
+
+ // Remove the watch expression if it's going to be empty or a duplicate.
+ if (!aExpression || this.getAllStrings().indexOf(aExpression) != -1) {
+ this.deleteExpression(aVar);
+ return;
+ }
+
+ // Save the watch expression code string.
+ expressionItem.attachment.currentExpression = aExpression;
+ expressionItem.attachment.view.inputNode.value = aExpression;
+
+ // Synchronize with the controller's watch expressions store.
+ this.StackFrames.syncWatchExpressions();
+ },
+
+ /**
+ * Removes the watch expression corresponding to the specified variable item.
+ * This function is called whenever a watch expression's value is edited in
+ * the variables view container.
+ *
+ * @param Variable aVar
+ * The variable representing the watch expression evaluation.
+ */
+ deleteExpression: function (aVar) {
+ let expressionItem =
+ [...this].filter(i => i.attachment.currentExpression == aVar.name)[0];
+
+ // Remove the watch expression.
+ this.remove(expressionItem);
+
+ // Synchronize with the controller's watch expressions store.
+ this.StackFrames.syncWatchExpressions();
+ },
+
+ /**
+ * Gets the watch expression code string for an item in this container.
+ *
+ * @param number aIndex
+ * The index used to identify the watch expression.
+ * @return string
+ * The watch expression code string.
+ */
+ getString: function (aIndex) {
+ return this.getItemAtIndex(aIndex).attachment.currentExpression;
+ },
+
+ /**
+ * Gets the watch expressions code strings for all items in this container.
+ *
+ * @return array
+ * The watch expressions code strings.
+ */
+ getAllStrings: function () {
+ return this.items.map(e => e.attachment.currentExpression);
+ },
+
+ /**
+ * Customization function for creating an item's UI.
+ *
+ * @param string aExpression
+ * The watch expression string.
+ */
+ _createItemView: function (aExpression) {
+ let container = document.createElement("hbox");
+ container.className = "list-widget-item dbg-expression";
+ container.setAttribute("align", "center");
+
+ let arrowNode = document.createElement("hbox");
+ arrowNode.className = "dbg-expression-arrow";
+
+ let inputNode = document.createElement("textbox");
+ inputNode.className = "plain dbg-expression-input devtools-monospace";
+ inputNode.setAttribute("value", aExpression);
+ inputNode.setAttribute("flex", "1");
+
+ let closeNode = document.createElement("toolbarbutton");
+ closeNode.className = "plain variables-view-delete";
+
+ closeNode.addEventListener("click", this._onClose, false);
+ inputNode.addEventListener("blur", this._onBlur, false);
+ inputNode.addEventListener("keypress", this._onKeyPress, false);
+
+ container.appendChild(arrowNode);
+ container.appendChild(inputNode);
+ container.appendChild(closeNode);
+
+ return {
+ container: container,
+ arrowNode: arrowNode,
+ inputNode: inputNode,
+ closeNode: closeNode
+ };
+ },
+
+ /**
+ * Called when the add watch expression key sequence was pressed.
+ */
+ _onCmdAddExpression: function (aText) {
+ // Only add a new expression if there's no pending input.
+ if (this.getAllStrings().indexOf("") == -1) {
+ this.addExpression(aText || this.DebuggerView.editor.getSelection());
+ }
+ },
+
+ /**
+ * Called when the remove all watch expressions key sequence was pressed.
+ */
+ _onCmdRemoveAllExpressions: function () {
+ // Empty the view of all the watch expressions and clear the cache.
+ this.empty();
+
+ // Synchronize with the controller's watch expressions store.
+ this.StackFrames.syncWatchExpressions();
+ },
+
+ /**
+ * The click listener for this container.
+ */
+ _onClick: function (e) {
+ if (e.button != 0) {
+ // Only allow left-click to trigger this event.
+ return;
+ }
+ let expressionItem = this.getItemForElement(e.target);
+ if (!expressionItem) {
+ // The container is empty or we didn't click on an actual item.
+ this.addExpression();
+ }
+ },
+
+ /**
+ * The click listener for a watch expression's close button.
+ */
+ _onClose: function (e) {
+ // Remove the watch expression.
+ this.remove(this.getItemForElement(e.target));
+
+ // Synchronize with the controller's watch expressions store.
+ this.StackFrames.syncWatchExpressions();
+
+ // Prevent clicking the expression element itself.
+ e.preventDefault();
+ e.stopPropagation();
+ },
+
+ /**
+ * The blur listener for a watch expression's textbox.
+ */
+ _onBlur: function ({ target: textbox }) {
+ let expressionItem = this.getItemForElement(textbox);
+ let oldExpression = expressionItem.attachment.currentExpression;
+ let newExpression = textbox.value.trim();
+
+ // Remove the watch expression if it's empty.
+ if (!newExpression) {
+ this.remove(expressionItem);
+ }
+ // Remove the watch expression if it's a duplicate.
+ else if (!oldExpression && this.getAllStrings().indexOf(newExpression) != -1) {
+ this.remove(expressionItem);
+ }
+ // Expression is eligible.
+ else {
+ expressionItem.attachment.currentExpression = newExpression;
+ }
+
+ // Synchronize with the controller's watch expressions store.
+ this.StackFrames.syncWatchExpressions();
+ },
+
+ /**
+ * The keypress listener for a watch expression's textbox.
+ */
+ _onKeyPress: function (e) {
+ switch (e.keyCode) {
+ case KeyCodes.DOM_VK_RETURN:
+ case KeyCodes.DOM_VK_ESCAPE:
+ e.stopPropagation();
+ this.DebuggerView.editor.focus();
+ }
+ }
+});
+
+DebuggerView.WatchExpressions = new WatchExpressionsView(DebuggerController,
+ DebuggerView);
diff --git a/devtools/client/debugger/views/workers-view.js b/devtools/client/debugger/views/workers-view.js
new file mode 100644
index 000000000..0dc8dc3a5
--- /dev/null
+++ b/devtools/client/debugger/views/workers-view.js
@@ -0,0 +1,55 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* 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/. */
+/* import-globals-from ../debugger-controller.js */
+/* import-globals-from ../debugger-view.js */
+/* import-globals-from ../utils.js */
+/* globals document */
+"use strict";
+
+function WorkersView() {
+ this._onWorkerSelect = this._onWorkerSelect.bind(this);
+}
+
+WorkersView.prototype = Heritage.extend(WidgetMethods, {
+ initialize: function () {
+ if (!Prefs.workersEnabled) {
+ return;
+ }
+
+ document.getElementById("workers-pane").removeAttribute("hidden");
+ document.getElementById("workers-splitter").removeAttribute("hidden");
+
+ this.widget = new SideMenuWidget(document.getElementById("workers"), {
+ showArrows: true,
+ });
+ this.emptyText = L10N.getStr("noWorkersText");
+ this.widget.addEventListener("select", this._onWorkerSelect, false);
+ },
+
+ addWorker: function (workerForm) {
+ let element = document.createElement("label");
+ element.className = "plain dbg-worker-item";
+ element.setAttribute("value", workerForm.url);
+ element.setAttribute("flex", "1");
+
+ this.push([element, workerForm.actor], {
+ attachment: workerForm
+ });
+ },
+
+ removeWorker: function (workerForm) {
+ this.remove(this.getItemByValue(workerForm.actor));
+ },
+
+ _onWorkerSelect: function () {
+ if (this.selectedItem !== null) {
+ DebuggerController.Workers._onWorkerSelect(this.selectedItem.attachment);
+ this.selectedItem = null;
+ }
+ }
+});
+
+DebuggerView.Workers = new WorkersView();