summaryrefslogtreecommitdiffstats
path: root/devtools/client/debugger/test
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/test
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/test')
-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
420 files changed, 34908 insertions, 0 deletions
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");